001    // License: GPL. For details, see LICENSE file.
002    package org.openstreetmap.josm.actions;
003    
004    import static org.openstreetmap.josm.tools.I18n.tr;
005    
006    import java.awt.AWTEvent;
007    import java.awt.Cursor;
008    import java.awt.GridBagLayout;
009    import java.awt.Insets;
010    import java.awt.Toolkit;
011    import java.awt.event.AWTEventListener;
012    import java.awt.event.ActionEvent;
013    import java.awt.event.FocusEvent;
014    import java.awt.event.FocusListener;
015    import java.awt.event.KeyEvent;
016    import java.awt.event.MouseEvent;
017    import java.awt.event.MouseListener;
018    import java.awt.event.MouseMotionListener;
019    
020    import javax.swing.JLabel;
021    import javax.swing.JPanel;
022    import javax.swing.JTextField;
023    
024    import org.openstreetmap.josm.Main;
025    import org.openstreetmap.josm.actions.mapmode.MapMode;
026    import org.openstreetmap.josm.data.coor.EastNorth;
027    import org.openstreetmap.josm.data.imagery.OffsetBookmark;
028    import org.openstreetmap.josm.gui.ExtendedDialog;
029    import org.openstreetmap.josm.gui.JMultilineLabel;
030    import org.openstreetmap.josm.gui.layer.ImageryLayer;
031    import org.openstreetmap.josm.tools.GBC;
032    import org.openstreetmap.josm.tools.ImageProvider;
033    
034    
035    public class ImageryAdjustAction extends MapMode implements MouseListener, MouseMotionListener, AWTEventListener{
036        static ImageryOffsetDialog offsetDialog;
037        static Cursor cursor = ImageProvider.getCursor("normal", "move");
038    
039        double oldDx, oldDy;
040        boolean mouseDown;
041        EastNorth prevEastNorth;
042        private ImageryLayer layer;
043        private MapMode oldMapMode;
044    
045        public ImageryAdjustAction(ImageryLayer layer) {
046            super(tr("New offset"), "adjustimg",
047                    tr("Adjust the position of this imagery layer"), Main.map,
048                    cursor);
049            putValue("toolbar", false);
050            this.layer = layer;
051        }
052    
053        @Override public void enterMode() {
054            super.enterMode();
055            if (layer == null)
056                return;
057            if (!layer.isVisible()) {
058                layer.setVisible(true);
059            }
060            Main.map.mapView.addMouseListener(this);
061            Main.map.mapView.addMouseMotionListener(this);
062            oldDx = layer.getDx();
063            oldDy = layer.getDy();
064            try {
065                Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.KEY_EVENT_MASK);
066            } catch (SecurityException ex) {
067            }
068            offsetDialog = new ImageryOffsetDialog();
069            offsetDialog.setVisible(true);
070        }
071    
072        @Override public void exitMode() {
073            super.exitMode();
074            if (offsetDialog != null) {
075                layer.setOffset(oldDx, oldDy);
076                offsetDialog.setVisible(false);
077                offsetDialog = null;
078            }
079            try {
080                Toolkit.getDefaultToolkit().removeAWTEventListener(this);
081            } catch (SecurityException ex) {
082            }
083            Main.map.mapView.removeMouseListener(this);
084            Main.map.mapView.removeMouseMotionListener(this);
085        }
086    
087        @Override
088        public void eventDispatched(AWTEvent event) {
089            if (!(event instanceof KeyEvent)) return;
090            if (event.getID() != KeyEvent.KEY_PRESSED) return;
091            if (layer == null) return;
092            if (offsetDialog != null && offsetDialog.areFieldsInFocus()) return;
093            KeyEvent kev = (KeyEvent)event;
094            double dx = 0, dy = 0;
095            switch (kev.getKeyCode()) {
096            case KeyEvent.VK_UP : dy = +1; break;
097            case KeyEvent.VK_DOWN : dy = -1; break;
098            case KeyEvent.VK_LEFT : dx = -1; break;
099            case KeyEvent.VK_RIGHT : dx = +1; break;
100            }
101            if (dx != 0 || dy != 0) {
102                double ppd = layer.getPPD();
103                layer.displace(dx / ppd, dy / ppd);
104                if (offsetDialog != null) {
105                    offsetDialog.updateOffset();
106                }
107                kev.consume();
108                Main.map.repaint();
109            }
110        }
111    
112        @Override public void mousePressed(MouseEvent e) {
113            if (e.getButton() != MouseEvent.BUTTON1)
114                return;
115    
116            if (layer.isVisible()) {
117                prevEastNorth=Main.map.mapView.getEastNorth(e.getX(),e.getY());
118                Main.map.mapView.setNewCursor(Cursor.MOVE_CURSOR, this);
119            }
120        }
121    
122        @Override public void mouseDragged(MouseEvent e) {
123            if (layer == null || prevEastNorth == null) return;
124            EastNorth eastNorth =
125                Main.map.mapView.getEastNorth(e.getX(),e.getY());
126            double dx = layer.getDx()+eastNorth.east()-prevEastNorth.east();
127            double dy = layer.getDy()+eastNorth.north()-prevEastNorth.north();
128            layer.setOffset(dx, dy);
129            if (offsetDialog != null) {
130                offsetDialog.updateOffset();
131            }
132            Main.map.repaint();
133            prevEastNorth = eastNorth;
134        }
135    
136        @Override public void mouseReleased(MouseEvent e) {
137            Main.map.mapView.repaint();
138            Main.map.mapView.resetCursor(this);
139            prevEastNorth = null;
140        }
141    
142        @Override
143        public void actionPerformed(ActionEvent e) {
144            if (offsetDialog != null || layer == null || Main.map == null)
145                return;
146            oldMapMode = Main.map.mapMode;
147            layer.enableOffsetServer(false);
148            super.actionPerformed(e);
149        }
150    
151        class ImageryOffsetDialog extends ExtendedDialog implements FocusListener {
152            public final JTextField tOffset = new JTextField();
153            JTextField tBookmarkName = new JTextField();
154            private boolean ignoreListener;
155            public ImageryOffsetDialog() {
156                super(Main.parent,
157                        tr("Adjust imagery offset"),
158                        new String[] { tr("OK"),tr("Cancel") },
159                        false);
160                setButtonIcons(new String[] { "ok", "cancel" });
161                contentInsets = new Insets(10, 15, 5, 15);
162                JPanel pnl = new JPanel(new GridBagLayout());
163                pnl.add(new JMultilineLabel(tr("Use arrow keys or drag the imagery layer with mouse to adjust the imagery offset.\n" +
164                        "You can also enter east and north offset in the {0} coordinates.\n" +
165                        "If you want to save the offset as bookmark, enter the bookmark name below",Main.getProjection().toString())), GBC.eop());
166                pnl.add(new JLabel(tr("Offset: ")),GBC.std());
167                pnl.add(tOffset,GBC.eol().fill(GBC.HORIZONTAL).insets(0,0,0,5));
168                pnl.add(new JLabel(tr("Bookmark name: ")),GBC.std());
169                pnl.add(tBookmarkName,GBC.eol().fill(GBC.HORIZONTAL));
170                tOffset.setColumns(16);
171                updateOffsetIntl();
172                tOffset.addFocusListener(this);
173                setContent(pnl);
174                setupDialog();
175            }
176    
177            public boolean areFieldsInFocus() {
178                return tOffset.hasFocus();
179            }
180    
181            @Override
182            public void focusGained(FocusEvent e) {
183            }
184    
185            @Override
186            public void focusLost(FocusEvent e) {
187                if (ignoreListener) return;
188                String ostr = tOffset.getText();
189                int semicolon = ostr.indexOf(';');
190                if( semicolon >= 0 && semicolon + 1 < ostr.length() ) {
191                    try {
192                        // here we assume that Double.parseDouble() needs '.' as a decimal separator
193                        String easting = ostr.substring(0, semicolon).trim().replace(',', '.');
194                        String northing = ostr.substring(semicolon + 1).trim().replace(',', '.');
195                        double dx = Double.parseDouble(easting);
196                        double dy = Double.parseDouble(northing);
197                        layer.setOffset(dx, dy);
198                    } catch (NumberFormatException nfe) {
199                        // we repaint offset numbers in any case
200                    }
201                }
202                updateOffsetIntl();
203                if (Main.isDisplayingMapView()) {
204                    Main.map.repaint();
205                }
206            }
207    
208            public void updateOffset() {
209                ignoreListener = true;
210                updateOffsetIntl();
211                ignoreListener = false;
212            }
213    
214            public void updateOffsetIntl() {
215                // Support projections with very small numbers (e.g. 4326)
216                int precision = Main.getProjection().getDefaultZoomInPPD() >= 1.0 ? 2 : 7;
217                // US locale to force decimal separator to be '.'
218                tOffset.setText(new java.util.Formatter(java.util.Locale.US).format(
219                        "%1." + precision + "f; %1." + precision + "f",
220                        layer.getDx(), layer.getDy()).toString());
221            }
222    
223            private boolean confirmOverwriteBookmark() {
224                ExtendedDialog dialog = new ExtendedDialog(
225                        Main.parent,
226                        tr("Overwrite"),
227                        new String[] {tr("Overwrite"), tr("Cancel")}
228                ) {{
229                    contentInsets = new Insets(10, 15, 10, 15);
230                }};
231                dialog.setContent(tr("Offset bookmark already exists. Overwrite?"));
232                dialog.setButtonIcons(new String[] {"ok.png", "cancel.png"});
233                dialog.setupDialog();
234                dialog.setVisible(true);
235                return dialog.getValue() == 1;
236            }
237    
238            @Override
239            protected void buttonAction(int buttonIndex, ActionEvent evt) {
240                if (buttonIndex == 0 && tBookmarkName.getText() != null && !"".equals(tBookmarkName.getText()) &&
241                        OffsetBookmark.getBookmarkByName(layer, tBookmarkName.getText()) != null) {
242                    if (!confirmOverwriteBookmark()) return;
243                }
244                super.buttonAction(buttonIndex, evt);
245            }
246    
247            @Override
248            public void setVisible(boolean visible) {
249                super.setVisible(visible);
250                if (visible) return;
251                offsetDialog = null;
252                if (getValue() != 1) {
253                    layer.setOffset(oldDx, oldDy);
254                } else if (tBookmarkName.getText() != null && !"".equals(tBookmarkName.getText())) {
255                    OffsetBookmark.bookmarkOffset(tBookmarkName.getText(), layer);
256                }
257                Main.main.menu.imageryMenu.refreshOffsetMenu();
258                if (Main.map == null) return;
259                if (oldMapMode != null) {
260                    Main.map.selectMapMode(oldMapMode);
261                    oldMapMode = null;
262                } else {
263                    Main.map.selectSelectTool(false);
264                }
265            }
266        }
267    }