001    // License: GPL. See LICENSE file for details.
002    package org.openstreetmap.josm.gui;
003    
004    import static org.openstreetmap.josm.tools.I18n.tr;
005    
006    import java.awt.BorderLayout;
007    import java.awt.Component;
008    import java.awt.Container;
009    import java.awt.Dimension;
010    import java.awt.Font;
011    import java.awt.GridBagLayout;
012    import java.awt.Rectangle;
013    import java.awt.event.ActionEvent;
014    import java.awt.event.KeyEvent;
015    import java.awt.event.MouseWheelEvent;
016    import java.awt.event.MouseWheelListener;
017    import java.util.ArrayList;
018    import java.util.Collection;
019    import java.util.HashMap;
020    import java.util.List;
021    import java.util.Map;
022    import java.util.concurrent.CopyOnWriteArrayList;
023    
024    import javax.swing.AbstractAction;
025    import javax.swing.AbstractButton;
026    import javax.swing.Action;
027    import javax.swing.BoxLayout;
028    import javax.swing.ButtonGroup;
029    import javax.swing.JButton;
030    import javax.swing.JCheckBoxMenuItem;
031    import javax.swing.JComponent;
032    import javax.swing.JPanel;
033    import javax.swing.JPopupMenu;
034    import javax.swing.JSplitPane;
035    import javax.swing.JToolBar;
036    import javax.swing.KeyStroke;
037    import javax.swing.SwingUtilities;
038    import javax.swing.border.Border;
039    import javax.swing.plaf.basic.BasicSplitPaneDivider;
040    import javax.swing.plaf.basic.BasicSplitPaneUI;
041    
042    import org.openstreetmap.josm.Main;
043    import org.openstreetmap.josm.actions.LassoModeAction;
044    import org.openstreetmap.josm.actions.mapmode.DeleteAction;
045    import org.openstreetmap.josm.actions.mapmode.DrawAction;
046    import org.openstreetmap.josm.actions.mapmode.ExtrudeAction;
047    import org.openstreetmap.josm.actions.mapmode.ImproveWayAccuracyAction;
048    import org.openstreetmap.josm.actions.mapmode.MapMode;
049    import org.openstreetmap.josm.actions.mapmode.ParallelWayAction;
050    import org.openstreetmap.josm.actions.mapmode.SelectAction;
051    import org.openstreetmap.josm.actions.mapmode.ZoomAction;
052    import org.openstreetmap.josm.data.Preferences;
053    import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent;
054    import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener;
055    import org.openstreetmap.josm.gui.MapView.LayerChangeListener;
056    import org.openstreetmap.josm.gui.dialogs.ChangesetDialog;
057    import org.openstreetmap.josm.gui.dialogs.CommandStackDialog;
058    import org.openstreetmap.josm.gui.dialogs.ConflictDialog;
059    import org.openstreetmap.josm.gui.dialogs.DialogsPanel;
060    import org.openstreetmap.josm.gui.dialogs.FilterDialog;
061    import org.openstreetmap.josm.gui.dialogs.HistoryDialog;
062    import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
063    import org.openstreetmap.josm.gui.dialogs.MapPaintDialog;
064    import org.openstreetmap.josm.gui.dialogs.RelationListDialog;
065    import org.openstreetmap.josm.gui.dialogs.SelectionListDialog;
066    import org.openstreetmap.josm.gui.dialogs.ToggleDialog;
067    import org.openstreetmap.josm.gui.dialogs.UserListDialog;
068    import org.openstreetmap.josm.gui.dialogs.ValidatorDialog;
069    import org.openstreetmap.josm.gui.dialogs.properties.PropertiesDialog;
070    import org.openstreetmap.josm.gui.layer.Layer;
071    import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
072    import org.openstreetmap.josm.tools.Destroyable;
073    import org.openstreetmap.josm.tools.GBC;
074    
075    /**
076     * One Map frame with one dataset behind. This is the container gui class whose
077     * display can be set to the different views.
078     *
079     * @author imi
080     */
081    public class MapFrame extends JPanel implements Destroyable, LayerChangeListener {
082    
083        /**
084         * The current mode, this frame operates.
085         */
086        public MapMode mapMode;
087    
088        private final List<MapMode> mapModes = new ArrayList<MapMode>();
089        /**
090         * The view control displayed.
091         */
092        public MapView mapView;
093        /**
094         * The toolbar with the action icons. To add new toggle dialog actions, use addToggleDialog
095         * instead of adding directly to this list. To add a new mode use addMapMode.
096         */
097        private JToolBar toolBarActions = new JToolBar(JToolBar.VERTICAL);
098        private JToolBar toolBarToggle = new JToolBar(JToolBar.VERTICAL);
099        /**
100         * The status line below the map
101         */
102        public MapStatus statusLine;
103    
104        // Toggle dialogs
105        public ConflictDialog conflictDialog;
106        public FilterDialog filterDialog;
107        public RelationListDialog relationListDialog;
108        public ValidatorDialog validatorDialog;
109        public SelectionListDialog selectionListDialog;
110        public PropertiesDialog propertiesDialog;
111    
112        // Map modes
113        public final SelectAction mapModeSelect;
114        private final MapMode mapModeDraw;
115        private final MapMode mapModeZoom;
116    
117        /**
118         * The panel list of all toggle dialog icons. To add new toggle dialog actions, use addToggleDialog
119         * instead of adding directly to this list.
120         */
121        private List<ToggleDialog> allDialogs = new ArrayList<ToggleDialog>();
122        private final JPanel leftPanel;
123        private final DialogsPanel dialogsPanel;
124    
125        public final ButtonGroup toolGroup = new ButtonGroup();
126    
127        private List<IconToggleButton> allDialogButtons = new ArrayList<IconToggleButton>();
128        private List<IconToggleButton> allMapModeButtons = new ArrayList<IconToggleButton>();
129    
130        private final ListAllButtonsAction listAllDialogsAction = new ListAllButtonsAction(allDialogButtons);
131        private final ListAllButtonsAction listAllMapModesAction = new ListAllButtonsAction(allMapModeButtons);
132        private final JButton listAllToggleDialogsButton = new JButton(listAllDialogsAction);
133        private final JButton listAllMapModesButton = new JButton(listAllMapModesAction);
134        {
135            listAllDialogsAction.setButton(listAllToggleDialogsButton);
136            listAllMapModesAction.setButton(listAllMapModesButton);
137        }
138    
139        /**
140         * Default width of the toggle dialog area.
141         */
142        public static final int DEF_TOGGLE_DLG_WIDTH = 330;
143    
144        private final Map<Layer, MapMode> lastMapMode = new HashMap<Layer, MapMode>();
145    
146        public MapFrame(JPanel contentPane) {
147            setSize(400,400);
148            setLayout(new BorderLayout());
149    
150    
151            mapView = new MapView(contentPane);
152    
153            new FileDrop(mapView);
154    
155            leftPanel = new JPanel();
156            leftPanel.setLayout(new GridBagLayout());
157    
158            leftPanel.add(mapView, GBC.std().fill());
159    
160            // toolbar
161            toolBarActions.setFloatable(false);
162            addMapMode(new IconToggleButton(mapModeSelect = new SelectAction(this)));
163            addMapMode(new IconToggleButton(new LassoModeAction(), true));
164            addMapMode(new IconToggleButton(mapModeDraw = new DrawAction(this)));
165            addMapMode(new IconToggleButton(mapModeZoom = new ZoomAction(this)));
166            addMapMode(new IconToggleButton(new DeleteAction(this), true));
167            addMapMode(new IconToggleButton(new ParallelWayAction(this), true));
168            addMapMode(new IconToggleButton(new ExtrudeAction(this), true));
169            addMapMode(new IconToggleButton(new ImproveWayAccuracyAction(Main.map), true));
170    
171            toolGroup.setSelected(((AbstractButton)toolBarActions.getComponent(0)).getModel(), true);
172    
173            JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true);
174            dialogsPanel = new DialogsPanel(splitPane);
175            splitPane.setLeftComponent(leftPanel);
176            splitPane.setRightComponent(dialogsPanel);
177    
178            /**
179             * All additional space goes to the mapView
180             */
181            splitPane.setResizeWeight(1.0);
182    
183            /**
184             * Some beautifications.
185             */
186            splitPane.setDividerSize(5);
187            splitPane.setBorder(null);
188            splitPane.setUI(new BasicSplitPaneUI() {
189                @Override
190                public BasicSplitPaneDivider createDefaultDivider() {
191                    return new BasicSplitPaneDivider(this) {
192                        @Override
193                        public void setBorder(Border b) {
194                        }
195                    };
196                }
197            });
198    
199            // JSplitPane supports F6 and F8 shortcuts by default, but we need them for Audio actions
200            splitPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_F6, 0), new Object());
201            splitPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_F8, 0), new Object());
202    
203            add(splitPane, BorderLayout.CENTER);
204    
205            dialogsPanel.setLayout(new BoxLayout(dialogsPanel, BoxLayout.Y_AXIS));
206            dialogsPanel.setPreferredSize(new Dimension(Main.pref.getInteger("toggleDialogs.width",DEF_TOGGLE_DLG_WIDTH), 0));
207    
208            dialogsPanel.setMinimumSize(new Dimension(24, 0));
209            mapView.setMinimumSize(new Dimension(10,0));
210    
211            toolBarToggle.setFloatable(false);
212            LayerListDialog.createInstance(this);
213            addToggleDialog(LayerListDialog.getInstance());
214            addToggleDialog(propertiesDialog = new PropertiesDialog(this));
215            addToggleDialog(selectionListDialog = new SelectionListDialog());
216            addToggleDialog(relationListDialog = new RelationListDialog());
217            addToggleDialog(new CommandStackDialog(this));
218            addToggleDialog(new UserListDialog());
219            addToggleDialog(new HistoryDialog(), true);
220            addToggleDialog(conflictDialog = new ConflictDialog());
221            addToggleDialog(validatorDialog = new ValidatorDialog());
222            addToggleDialog(filterDialog = new FilterDialog());
223            addToggleDialog(new ChangesetDialog(this), true);
224            addToggleDialog(new MapPaintDialog());
225    
226            // status line below the map
227            statusLine = new MapStatus(this);
228            MapView.addLayerChangeListener(this);
229        }
230    
231        public boolean selectSelectTool(boolean onlyIfModeless) {
232            if(onlyIfModeless && !Main.pref.getBoolean("modeless", false))
233                return false;
234    
235            return selectMapMode(mapModeSelect);
236        }
237    
238        public boolean selectDrawTool(boolean onlyIfModeless) {
239            if(onlyIfModeless && !Main.pref.getBoolean("modeless", false))
240                return false;
241    
242            return selectMapMode(mapModeDraw);
243        }
244    
245        public boolean selectZoomTool(boolean onlyIfModeless) {
246            if(onlyIfModeless && !Main.pref.getBoolean("modeless", false))
247                return false;
248    
249            return selectMapMode(mapModeZoom);
250        }
251    
252        /**
253         * Called as some kind of destructor when the last layer has been removed.
254         * Delegates the call to all Destroyables within this component (e.g. MapModes)
255         */
256        public void destroy() {
257            MapView.removeLayerChangeListener(this);
258            dialogsPanel.destroy();
259            Main.pref.removePreferenceChangeListener(sidetoolbarPreferencesChangedListener);
260            for (int i = 0; i < toolBarActions.getComponentCount(); ++i) {
261                if (toolBarActions.getComponent(i) instanceof Destroyable) {
262                    ((Destroyable)toolBarActions.getComponent(i)).destroy();
263                }
264            }
265            for (int i = 0; i < toolBarToggle.getComponentCount(); ++i) {
266                if (toolBarToggle.getComponent(i) instanceof Destroyable) {
267                    ((Destroyable)toolBarToggle.getComponent(i)).destroy();
268                }
269            }
270    
271            // MapFrame gets destroyed when the last layer is removed, but the status line background
272            // thread that collects the information doesn't get destroyed automatically.
273            if(statusLine.thread != null) {
274                try {
275                    statusLine.thread.interrupt();
276                } catch (Exception e) {
277                    e.printStackTrace();
278                }
279            }
280            mapView.destroy();
281        }
282    
283        public Action getDefaultButtonAction() {
284            return ((AbstractButton)toolBarActions.getComponent(0)).getAction();
285        }
286    
287        /**
288         * Open all ToggleDialogs that have their preferences property set. Close all others.
289         */
290        public void initializeDialogsPane() {
291            dialogsPanel.initialize(allDialogs);
292        }
293    
294        public IconToggleButton addToggleDialog(final ToggleDialog dlg) {
295            return addToggleDialog(dlg, false);
296        }
297    
298        /**
299         * Call this to add new toggle dialogs to the left button-list
300         * @param dlg The toggle dialog. It must not be in the list already.
301         */
302        public IconToggleButton addToggleDialog(final ToggleDialog dlg, boolean isExpert) {
303            final IconToggleButton button = new IconToggleButton(dlg.getToggleAction(), isExpert);
304            button.setShowHideButtonListener(dlg);
305            addHideContextMenu(button);
306            dlg.setButton(button);
307            toolBarToggle.add(button);
308            allDialogs.add(dlg);
309            allDialogButtons.add(button);
310            button.applyButtonHiddenPreferences();
311            if (dialogsPanel.initialized) {
312                dialogsPanel.add(dlg);
313            }
314            return button;
315        }
316    
317    
318    
319        public void addMapMode(IconToggleButton b) {
320            toolBarActions.add(b);
321            toolGroup.add(b);
322            allMapModeButtons.add(b);
323            if (b.getAction() instanceof MapMode) {
324                mapModes.add((MapMode) b.getAction());
325            } else
326                throw new IllegalArgumentException("MapMode action must be subclass of MapMode");
327            addHideContextMenu(b);
328            b.applyButtonHiddenPreferences();
329        }
330    
331        /**
332         * Fires an property changed event "visible".
333         */
334        @Override public void setVisible(boolean aFlag) {
335            boolean old = isVisible();
336            super.setVisible(aFlag);
337            if (old != aFlag) {
338                firePropertyChange("visible", old, aFlag);
339            }
340        }
341    
342        /**
343         * Change the operating map mode for the view. Will call unregister on the
344         * old MapMode and register on the new one. Now this function also verifies
345         * if new map mode is correct mode for current layer and does not change mode
346         * in such cases.
347         * @param mapMode   The new mode to set.
348         * @return
349         */
350        public boolean selectMapMode(MapMode newMapMode) {
351            return selectMapMode(newMapMode, mapView.getActiveLayer());
352        }
353    
354        /**
355         * Another version of the selectMapMode for changing layer action.
356         * Pass newly selected layer to this method.
357         * @param newMapMode
358         * @param newLayer
359         * @return True if mode is really selected
360         */
361        public boolean selectMapMode(MapMode newMapMode, Layer newLayer) {
362            if (newMapMode == null || !newMapMode.layerIsSupported(newLayer))
363                return false;
364    
365            MapMode oldMapMode = this.mapMode;
366            if (newMapMode == oldMapMode)
367                return true;
368            if (oldMapMode != null) {
369                oldMapMode.exitMode();
370            }
371            this.mapMode = newMapMode;
372            newMapMode.enterMode();
373            lastMapMode.put(newLayer, newMapMode);
374            fireMapModeChanged(oldMapMode, newMapMode);
375            return true;
376        }
377    
378        /**
379         * Fill the given panel by adding all necessary components to the different
380         * locations.
381         *
382         * @param panel The container to fill. Must have an BorderLayout.
383         */
384        public void fillPanel(Container panel) {
385            panel.add(this, BorderLayout.CENTER);
386            JToolBar jb = new JToolBar(JToolBar.VERTICAL);
387            jb.setFloatable(false);
388            toolBarActions.setAlignmentX(0.5f);
389            jb.add(toolBarActions);
390            listAllMapModesButton.setAlignmentX(0.5f);
391            listAllMapModesButton.setBorder(null);
392            listAllMapModesButton.setFont(listAllMapModesButton.getFont().deriveFont(Font.PLAIN));
393            jb.add(listAllMapModesButton);
394    
395            if(Main.pref.getBoolean("sidetoolbar.togglevisible", true)) {
396                jb.addSeparator(new Dimension(0,18));
397                toolBarToggle.setAlignmentX(0.5f);
398                jb.add(toolBarToggle);
399                listAllToggleDialogsButton.setAlignmentX(0.5f);
400                listAllToggleDialogsButton.setBorder(null);
401                listAllToggleDialogsButton.setFont(listAllToggleDialogsButton.getFont().deriveFont(Font.PLAIN));
402                jb.add(listAllToggleDialogsButton);
403            }
404    
405            final Component toToggle;
406            if (Main.pref.getBoolean("sidetoolbar.scrollable", true)) {
407                final ScrollViewport svp = new ScrollViewport(jb, ScrollViewport.VERTICAL_DIRECTION);
408                toToggle = svp;
409                panel.add(svp, BorderLayout.WEST);
410                jb.addMouseWheelListener(new MouseWheelListener() {
411    
412                    public void mouseWheelMoved(MouseWheelEvent e) {
413                        svp.scroll(0, e.getUnitsToScroll() * 5);
414                    }
415                });
416            } else {
417                toToggle = jb;
418                panel.add(jb, BorderLayout.WEST);
419            }
420            toToggle.setVisible(Main.pref.getBoolean("sidetoolbar.visible", true));
421    
422            jb.addMouseListener(new PopupMenuLauncher(new JPopupMenu() {
423    
424                {
425                    add(new AbstractAction(tr("Hide edit toolbar")) {
426    
427                        @Override
428                        public void actionPerformed(ActionEvent e) {
429                            Main.pref.put("sidetoolbar.visible", false);
430                        }
431                    });
432                }
433            }));
434    
435            sidetoolbarPreferencesChangedListener = new Preferences.PreferenceChangedListener() {
436    
437                @Override
438                public void preferenceChanged(PreferenceChangeEvent e) {
439                    if ("sidetoolbar.visible".equals(e.getKey())) {
440                        toToggle.setVisible(Main.pref.getBoolean("sidetoolbar.visible"));
441                    }
442                }
443            };
444            Main.pref.addPreferenceChangeListener(sidetoolbarPreferencesChangedListener);
445    
446            if (statusLine != null && Main.pref.getBoolean("statusline.visible", true)) {
447                panel.add(statusLine, BorderLayout.SOUTH);
448            }
449        }
450    
451        private void addHideContextMenu(final IconToggleButton b) {
452            //context menu
453            b.addMouseListener(new PopupMenuLauncher(new JPopupMenu() {
454                {
455                    add(new AbstractAction() {
456                        {
457                            putValue(NAME, tr("Hide this button"));
458                            putValue(SHORT_DESCRIPTION, tr("Click the arrow at the bottom to show it again."));
459                        }
460                        @Override
461                        public void actionPerformed(ActionEvent e) {
462                            b.setButtonHidden(true);
463                            validateToolBarsVisibility();
464                        }
465                    });
466                }
467            }));
468        }
469    
470        class ListAllButtonsAction extends AbstractAction {
471    
472            private JButton button;
473            private Collection<? extends HideableButton> buttons;
474    
475    
476            public ListAllButtonsAction(Collection<? extends HideableButton> buttons) {
477                this.buttons = buttons;
478                putValue(NAME, ">>");
479            }
480    
481            public void setButton(JButton button) {
482                this.button =  button;
483            }
484    
485            @Override
486            public void actionPerformed(ActionEvent e) {
487                JPopupMenu menu = new JPopupMenu();
488                for (HideableButton b : buttons) {
489                    final HideableButton t = b;
490                    menu.add(new JCheckBoxMenuItem(new AbstractAction() {
491                        {
492                            putValue(NAME, t.getActionName());
493                            putValue(SMALL_ICON, t.getIcon());
494                            putValue(SELECTED_KEY, t.isButtonVisible());
495                            putValue(SHORT_DESCRIPTION, tr("Hide or show this toggle button"));
496                        }
497                        @Override
498                        public void actionPerformed(ActionEvent e) {
499                            if ((Boolean) getValue(SELECTED_KEY)) {
500                                t.showButton();
501                            } else {
502                                t.hideButton();
503                            }
504                            validateToolBarsVisibility();
505                        }
506                    }));
507                }
508                Rectangle bounds = button.getBounds();
509                menu.show(button, bounds.x + bounds.width, 0);
510            }
511        }
512    
513        public void validateToolBarsVisibility() {
514            for (IconToggleButton b : allDialogButtons) {
515                b.applyButtonHiddenPreferences();
516            }
517            toolBarToggle.repaint();
518            for (IconToggleButton b : allMapModeButtons) {
519                b.applyButtonHiddenPreferences();
520            }
521            toolBarActions.repaint();
522        }
523    
524        /**
525         * Replies the instance of a toggle dialog of type <code>type</code> managed by this
526         * map frame
527         *
528         * @param <T>
529         * @param type the class of the toggle dialog, i.e. UserListDialog.class
530         * @return the instance of a toggle dialog of type <code>type</code> managed by this
531         * map frame; null, if no such dialog exists
532         *
533         */
534        public <T> T getToggleDialog(Class<T> type) {
535            return dialogsPanel.getToggleDialog(type);
536        }
537    
538        /**
539         * Remember the current width of the (possibly resized) toggle dialog area
540         */
541        public void rememberToggleDialogWidth() {
542            Main.pref.putInteger("toggleDialogs.width", dialogsPanel.getWidth());
543        }
544    
545        /*
546         * Remove panel from top of MapView by class
547         */
548        public void removeTopPanel(Class<?> type) {
549            int n = leftPanel.getComponentCount();
550            for (int i=0; i<n; i++) {
551                Component c = leftPanel.getComponent(i);
552                if (type.isInstance(c)) {
553                    leftPanel.remove(i);
554                    leftPanel.doLayout();
555                    //                repaint();
556                    return;
557                }
558            }
559        }
560    
561        /*
562         * Find panel on top of MapView by class
563         */
564        public <T> T getTopPanel(Class<T> type) {
565            int n = leftPanel.getComponentCount();
566            for (int i=0; i<n; i++) {
567                Component c = leftPanel.getComponent(i);
568                if (type.isInstance(c))
569                    return type.cast(c);
570            }
571            return null;
572        }
573    
574        /**
575         * Add component @param c on top of MapView
576         */
577        public void addTopPanel(Component c) {
578            leftPanel.add(c, GBC.eol().fill(GBC.HORIZONTAL), leftPanel.getComponentCount()-1);
579            leftPanel.doLayout();
580            c.doLayout();
581        }
582    
583        /**
584         * Interface to notify listeners of the change of the mapMode.
585         */
586        public interface MapModeChangeListener {
587            void mapModeChange(MapMode oldMapMode, MapMode newMapMode);
588        }
589    
590        /**
591         * the mapMode listeners
592         */
593        private static final CopyOnWriteArrayList<MapModeChangeListener> mapModeChangeListeners = new CopyOnWriteArrayList<MapModeChangeListener>();
594    
595        private PreferenceChangedListener sidetoolbarPreferencesChangedListener;
596        /**
597         * Adds a mapMode change listener
598         *
599         * @param listener the listener. Ignored if null or already registered.
600         */
601        public static void addMapModeChangeListener(MapModeChangeListener listener) {
602            if (listener != null) {
603                mapModeChangeListeners.addIfAbsent(listener);
604            }
605        }
606        /**
607         * Removes a mapMode change listener
608         *
609         * @param listener the listener. Ignored if null or already registered.
610         */
611        public static void removeMapModeChangeListener(MapModeChangeListener listener) {
612            mapModeChangeListeners.remove(listener);
613        }
614    
615        protected static void fireMapModeChanged(MapMode oldMapMode, MapMode newMapMode) {
616            for (MapModeChangeListener l : mapModeChangeListeners) {
617                l.mapModeChange(oldMapMode, newMapMode);
618            }
619        }
620    
621        @Override
622        public void activeLayerChange(Layer oldLayer, Layer newLayer) {
623            boolean modeChanged = false;
624            if (mapMode == null || !mapMode.layerIsSupported(newLayer)) {
625                MapMode newMapMode = getLastMapMode(newLayer);
626                modeChanged = newMapMode != mapMode;
627                if (newMapMode != null) {
628                    selectMapMode(newMapMode, newLayer); // it would be nice to select first supported mode when layer is first selected, but it don't work well with for example editgpx layer
629                } else if (mapMode != null) {
630                    mapMode.exitMode(); // if new mode is null - simply exit from previous mode
631                }
632            }
633            // if this is really a change (and not the first active layer)
634            if (oldLayer != null) {
635                if (!modeChanged && mapMode != null) {
636                    // Let mapmodes know about new active layer
637                    mapMode.exitMode();
638                    mapMode.enterMode();
639                }
640                // invalidate repaint cache
641                Main.map.mapView.preferenceChanged(null);
642            }
643    
644            // After all listeners notice new layer, some buttons will be disabled/enabled
645            // and possibly need to be hidden/shown.
646            SwingUtilities.invokeLater(new Runnable() {
647                public void run() {
648                    validateToolBarsVisibility();
649                }
650            });
651        }
652    
653    
654        private MapMode getLastMapMode(Layer newLayer) {
655            MapMode mode = lastMapMode.get(newLayer);
656            if (mode == null) {
657                // if no action is selected - try to select default action
658                Action defaultMode = getDefaultButtonAction();
659                if (defaultMode instanceof MapMode && ((MapMode)defaultMode).layerIsSupported(newLayer)) {
660                    mode = (MapMode) defaultMode;
661                }
662            }
663            return mode;
664        }
665    
666        @Override
667        public void layerAdded(Layer newLayer) { }
668    
669        @Override
670        public void layerRemoved(Layer oldLayer) {
671            lastMapMode.remove(oldLayer);
672        }
673    }