source: josm/trunk/src/org/openstreetmap/josm/gui/MapFrame.java@ 17379

Last change on this file since 17379 was 16987, checked in by simon04, 4 years ago

Logging: classify "Switching map mode from X to Y" as debug

  • Property svn:eol-style set to native
File size: 33.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.BorderLayout;
7import java.awt.Color;
8import java.awt.Component;
9import java.awt.Container;
10import java.awt.Dimension;
11import java.awt.GridBagLayout;
12import java.awt.Rectangle;
13import java.awt.event.ActionEvent;
14import java.awt.event.KeyEvent;
15import java.util.ArrayList;
16import java.util.Arrays;
17import java.util.Collection;
18import java.util.HashMap;
19import java.util.List;
20import java.util.Map;
21import java.util.Optional;
22import java.util.concurrent.CopyOnWriteArrayList;
23
24import javax.swing.AbstractAction;
25import javax.swing.AbstractButton;
26import javax.swing.Action;
27import javax.swing.BorderFactory;
28import javax.swing.BoxLayout;
29import javax.swing.ButtonGroup;
30import javax.swing.InputMap;
31import javax.swing.JButton;
32import javax.swing.JCheckBoxMenuItem;
33import javax.swing.JComponent;
34import javax.swing.JPanel;
35import javax.swing.JPopupMenu;
36import javax.swing.JSplitPane;
37import javax.swing.JToggleButton;
38import javax.swing.JToolBar;
39import javax.swing.KeyStroke;
40import javax.swing.SwingConstants;
41import javax.swing.border.Border;
42import javax.swing.event.PopupMenuEvent;
43import javax.swing.event.PopupMenuListener;
44import javax.swing.plaf.basic.BasicArrowButton;
45import javax.swing.plaf.basic.BasicSplitPaneDivider;
46import javax.swing.plaf.basic.BasicSplitPaneUI;
47
48import org.openstreetmap.josm.actions.ExpertToggleAction;
49import org.openstreetmap.josm.actions.mapmode.DeleteAction;
50import org.openstreetmap.josm.actions.mapmode.DrawAction;
51import org.openstreetmap.josm.actions.mapmode.ExtrudeAction;
52import org.openstreetmap.josm.actions.mapmode.ImproveWayAccuracyAction;
53import org.openstreetmap.josm.actions.mapmode.MapMode;
54import org.openstreetmap.josm.actions.mapmode.ParallelWayAction;
55import org.openstreetmap.josm.actions.mapmode.SelectAction;
56import org.openstreetmap.josm.actions.mapmode.SelectLassoAction;
57import org.openstreetmap.josm.actions.mapmode.ZoomAction;
58import org.openstreetmap.josm.data.ViewportData;
59import org.openstreetmap.josm.data.preferences.AbstractProperty;
60import org.openstreetmap.josm.data.preferences.BooleanProperty;
61import org.openstreetmap.josm.data.preferences.IntegerProperty;
62import org.openstreetmap.josm.gui.dialogs.ChangesetDialog;
63import org.openstreetmap.josm.gui.dialogs.CommandStackDialog;
64import org.openstreetmap.josm.gui.dialogs.ConflictDialog;
65import org.openstreetmap.josm.gui.dialogs.DialogsPanel;
66import org.openstreetmap.josm.gui.dialogs.FilterDialog;
67import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
68import org.openstreetmap.josm.gui.dialogs.MapPaintDialog;
69import org.openstreetmap.josm.gui.dialogs.MinimapDialog;
70import org.openstreetmap.josm.gui.dialogs.NotesDialog;
71import org.openstreetmap.josm.gui.dialogs.RelationListDialog;
72import org.openstreetmap.josm.gui.dialogs.SelectionListDialog;
73import org.openstreetmap.josm.gui.dialogs.ToggleDialog;
74import org.openstreetmap.josm.gui.dialogs.UserListDialog;
75import org.openstreetmap.josm.gui.dialogs.ValidatorDialog;
76import org.openstreetmap.josm.gui.dialogs.properties.PropertiesDialog;
77import org.openstreetmap.josm.gui.layer.Layer;
78import org.openstreetmap.josm.gui.layer.LayerManager.LayerAddEvent;
79import org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener;
80import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent;
81import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent;
82import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent;
83import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
84import org.openstreetmap.josm.gui.util.AdvancedKeyPressDetector;
85import org.openstreetmap.josm.spi.preferences.Config;
86import org.openstreetmap.josm.tools.Destroyable;
87import org.openstreetmap.josm.tools.GBC;
88import org.openstreetmap.josm.tools.ImageProvider;
89import org.openstreetmap.josm.tools.Logging;
90import org.openstreetmap.josm.tools.Shortcut;
91
92/**
93 * One Map frame with one dataset behind. This is the container gui class whose
94 * display can be set to the different views.
95 *
96 * @author imi
97 */
98public class MapFrame extends JPanel implements Destroyable, ActiveLayerChangeListener, LayerChangeListener {
99 /**
100 * Default width of the toggle dialog area.
101 */
102 public static final int DEF_TOGGLE_DLG_WIDTH = 330;
103
104 private static final IntegerProperty TOGGLE_DIALOGS_WIDTH = new IntegerProperty("toggleDialogs.width", DEF_TOGGLE_DLG_WIDTH);
105 /**
106 * Do not require to switch modes (potlatch style workflow) for drawing/selecting map modes.
107 * @since 12347
108 */
109 public static final BooleanProperty MODELESS = new BooleanProperty("modeless", false);
110 /**
111 * Whether the toolbar is visible
112 */
113 public static final BooleanProperty TOOLBAR_VISIBLE = new BooleanProperty("toolbar.visible", true);
114 /**
115 * Whether the side toolbar is visible
116 */
117 public static final BooleanProperty SIDE_TOOLBAR_VISIBLE = new BooleanProperty("sidetoolbar.visible", true);
118 /**
119 * The current mode, this frame operates.
120 */
121 public MapMode mapMode;
122
123 /**
124 * The view control displayed.
125 */
126 public final MapView mapView;
127
128 /**
129 * This object allows to detect key press and release events
130 */
131 public final transient AdvancedKeyPressDetector keyDetector = new AdvancedKeyPressDetector();
132
133 /**
134 * The toolbar with the action icons. To add new toggle dialog buttons,
135 * use addToggleDialog, to add a new map mode button use addMapMode.
136 */
137 private JComponent sideToolBar = new JToolBar(JToolBar.VERTICAL);
138 private final ButtonGroup toolBarActionsGroup = new ButtonGroup();
139 private final JToolBar toolBarActions = new JToolBar(JToolBar.VERTICAL);
140 private final JToolBar toolBarToggle = new JToolBar(JToolBar.VERTICAL);
141
142 private final List<ToggleDialog> allDialogs = new ArrayList<>();
143 private final List<IconToggleButton> allDialogButtons = new ArrayList<>();
144 /**
145 * All map mode buttons. Should only be read form the outside
146 */
147 public final List<IconToggleButton> allMapModeButtons = new ArrayList<>();
148
149 private final ListAllButtonsAction listAllDialogsAction = new ListAllButtonsAction(allDialogButtons);
150 private final ListAllButtonsAction listAllMapModesAction = new ListAllButtonsAction(allMapModeButtons);
151
152 // Toggle dialogs
153
154 /** Conflict dialog */
155 public final ConflictDialog conflictDialog;
156 /** Filter dialog */
157 public final FilterDialog filterDialog;
158 /** Relation list dialog */
159 public final RelationListDialog relationListDialog;
160 /** Validator dialog */
161 public final ValidatorDialog validatorDialog;
162 /** Selection list dialog */
163 public final SelectionListDialog selectionListDialog;
164 /** Properties dialog */
165 public final PropertiesDialog propertiesDialog;
166 /** Map paint dialog */
167 public final MapPaintDialog mapPaintDialog;
168 /** Notes dialog */
169 public final NotesDialog noteDialog;
170
171 // Map modes
172
173 /** Select mode */
174 public final SelectAction mapModeSelect;
175 /** Draw mode */
176 public final DrawAction mapModeDraw;
177 /** Zoom mode */
178 public final ZoomAction mapModeZoom;
179 /** Delete mode */
180 public final DeleteAction mapModeDelete;
181 /** Select Lasso mode */
182 public final SelectLassoAction mapModeSelectLasso;
183
184 private final transient Map<Layer, MapMode> lastMapMode = new HashMap<>();
185
186 /**
187 * The status line below the map
188 */
189 public MapStatus statusLine;
190
191 /**
192 * The split pane with the mapview (leftPanel) and toggle dialogs (dialogsPanel).
193 */
194 private final JSplitPane splitPane;
195 private final JPanel leftPanel;
196 private final DialogsPanel dialogsPanel;
197
198 /**
199 * Constructs a new {@code MapFrame}.
200 * @param viewportData the initial viewport of the map. Can be null, then
201 * the viewport is derived from the layer data.
202 * @since 11713
203 */
204 public MapFrame(ViewportData viewportData) {
205 setSize(400, 400);
206 setLayout(new BorderLayout());
207
208 mapView = new MapView(MainApplication.getLayerManager(), viewportData);
209
210 splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true);
211
212 leftPanel = new JPanel(new GridBagLayout());
213 leftPanel.add(mapView, GBC.std().fill());
214 splitPane.setLeftComponent(leftPanel);
215
216 dialogsPanel = new DialogsPanel(splitPane);
217 splitPane.setRightComponent(dialogsPanel);
218
219 /**
220 * All additional space goes to the mapView
221 */
222 splitPane.setResizeWeight(1.0);
223
224 /**
225 * Some beautifications.
226 */
227 splitPane.setDividerSize(5);
228 splitPane.setBorder(null);
229 splitPane.setUI(new NoBorderSplitPaneUI());
230
231 // JSplitPane supports F6, F8, Home and End shortcuts by default, but we need them for Audio and Image Mapping actions
232 InputMap splitInputMap = splitPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
233 splitInputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_F6, 0), new Object());
234 splitInputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_F8, 0), new Object());
235 splitInputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_HOME, 0), new Object());
236 splitInputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_END, 0), new Object());
237
238 add(splitPane, BorderLayout.CENTER);
239
240 dialogsPanel.setLayout(new BoxLayout(dialogsPanel, BoxLayout.Y_AXIS));
241 dialogsPanel.setPreferredSize(new Dimension(TOGGLE_DIALOGS_WIDTH.get(), 0));
242 dialogsPanel.setMinimumSize(new Dimension(24, 0));
243 mapView.setMinimumSize(new Dimension(10, 0));
244
245 // toolBarActions, map mode buttons
246 mapModeSelect = new SelectAction(this);
247 mapModeSelectLasso = new SelectLassoAction();
248 mapModeDraw = new DrawAction();
249 mapModeZoom = new ZoomAction(this);
250 mapModeDelete = new DeleteAction();
251
252 addMapMode(new IconToggleButton(mapModeSelect));
253 addMapMode(new IconToggleButton(mapModeSelectLasso, true));
254 addMapMode(new IconToggleButton(mapModeDraw));
255 addMapMode(new IconToggleButton(mapModeZoom, true));
256 addMapMode(new IconToggleButton(mapModeDelete, true));
257 addMapMode(new IconToggleButton(new ParallelWayAction(this), true));
258 addMapMode(new IconToggleButton(new ExtrudeAction(), true));
259 addMapMode(new IconToggleButton(new ImproveWayAccuracyAction(), false));
260 toolBarActionsGroup.setSelected(allMapModeButtons.get(0).getModel(), true);
261 toolBarActions.setFloatable(false);
262
263 // toolBarToggles, toggle dialog buttons
264 LayerListDialog.createInstance(mapView.getLayerManager());
265 propertiesDialog = new PropertiesDialog();
266 selectionListDialog = new SelectionListDialog();
267 relationListDialog = new RelationListDialog();
268 conflictDialog = new ConflictDialog();
269 validatorDialog = new ValidatorDialog();
270 filterDialog = new FilterDialog();
271 mapPaintDialog = new MapPaintDialog();
272 noteDialog = new NotesDialog();
273
274 addToggleDialog(LayerListDialog.getInstance());
275 addToggleDialog(propertiesDialog);
276 addToggleDialog(selectionListDialog);
277 addToggleDialog(relationListDialog);
278 addToggleDialog(new MinimapDialog());
279 addToggleDialog(new CommandStackDialog());
280 addToggleDialog(new UserListDialog());
281 addToggleDialog(conflictDialog);
282 addToggleDialog(validatorDialog);
283 addToggleDialog(filterDialog);
284 addToggleDialog(new ChangesetDialog(), true);
285 addToggleDialog(mapPaintDialog);
286 addToggleDialog(noteDialog);
287 toolBarToggle.setFloatable(false);
288
289 // status line below the map
290 statusLine = new MapStatus(this);
291 MainApplication.getLayerManager().addLayerChangeListener(this);
292 MainApplication.getLayerManager().addActiveLayerChangeListener(this);
293
294 boolean unregisterTab = Shortcut.findShortcut(KeyEvent.VK_TAB, 0).isPresent();
295 if (unregisterTab) {
296 for (JComponent c: allDialogButtons) {
297 c.setFocusTraversalKeysEnabled(false);
298 }
299 for (JComponent c: allMapModeButtons) {
300 c.setFocusTraversalKeysEnabled(false);
301 }
302 }
303
304 if (Config.getPref().getBoolean("debug.advanced-keypress-detector.enable", true)) {
305 keyDetector.register();
306 }
307 }
308
309 /**
310 * Enables the select tool
311 * @param onlyIfModeless Only enable if modeless mode is active
312 * @return <code>true</code> if it is selected
313 */
314 public boolean selectSelectTool(boolean onlyIfModeless) {
315 if (onlyIfModeless && !MODELESS.get())
316 return false;
317
318 return selectMapMode(mapModeSelect);
319 }
320
321 /**
322 * Enables the draw tool
323 * @param onlyIfModeless Only enable if modeless mode is active
324 * @return <code>true</code> if it is selected
325 */
326 public boolean selectDrawTool(boolean onlyIfModeless) {
327 if (onlyIfModeless && !MODELESS.get())
328 return false;
329
330 return selectMapMode(mapModeDraw);
331 }
332
333 /**
334 * Enables the zoom tool
335 * @param onlyIfModeless Only enable if modeless mode is active
336 * @return <code>true</code> if it is selected
337 */
338 public boolean selectZoomTool(boolean onlyIfModeless) {
339 if (onlyIfModeless && !MODELESS.get())
340 return false;
341
342 return selectMapMode(mapModeZoom);
343 }
344
345 /**
346 * Called as some kind of destructor when the last layer has been removed.
347 * Delegates the call to all Destroyables within this component (e.g. MapModes)
348 */
349 @Override
350 public void destroy() {
351 MainApplication.getLayerManager().removeLayerChangeListener(this);
352 MainApplication.getLayerManager().removeActiveLayerChangeListener(this);
353 MainApplication.getMenu().modeMenu.removeAll();
354 rememberToggleDialogWidth();
355 dialogsPanel.destroy();
356 SIDE_TOOLBAR_VISIBLE.removeListener(sidetoolbarPreferencesChangedListener);
357 for (int i = 0; i < toolBarActions.getComponentCount(); ++i) {
358 if (toolBarActions.getComponent(i) instanceof Destroyable) {
359 ((Destroyable) toolBarActions.getComponent(i)).destroy();
360 }
361 }
362 toolBarActions.removeAll();
363 for (int i = 0; i < toolBarToggle.getComponentCount(); ++i) {
364 if (toolBarToggle.getComponent(i) instanceof Destroyable) {
365 ((Destroyable) toolBarToggle.getComponent(i)).destroy();
366 }
367 }
368 toolBarToggle.removeAll();
369
370 statusLine.destroy();
371 mapView.destroy();
372 keyDetector.unregister();
373
374 allDialogs.clear();
375 allDialogButtons.clear();
376 allMapModeButtons.clear();
377 }
378
379 /**
380 * Gets the action of the default (first) map mode
381 * @return That action
382 */
383 public Action getDefaultButtonAction() {
384 return ((AbstractButton) toolBarActions.getComponent(0)).getAction();
385 }
386
387 /**
388 * Open all ToggleDialogs that have their preferences property set. Close all others.
389 */
390 public void initializeDialogsPane() {
391 dialogsPanel.initialize(allDialogs);
392 }
393
394 /**
395 * Adds a new toggle dialog to the left button list. It is displayed in expert and normal mode
396 * @param dlg The dialog
397 * @return The button
398 */
399 public IconToggleButton addToggleDialog(final ToggleDialog dlg) {
400 return addToggleDialog(dlg, false);
401 }
402
403 /**
404 * Call this to add new toggle dialogs to the left button-list
405 * @param dlg The toggle dialog. It must not be in the list already.
406 * @param isExpert {@code true} if it's reserved to expert mode
407 * @return button allowing to toggle the dialog
408 */
409 public IconToggleButton addToggleDialog(final ToggleDialog dlg, boolean isExpert) {
410 final IconToggleButton button = new IconToggleButton(dlg.getToggleAction(), isExpert);
411 button.setShowHideButtonListener(dlg);
412 button.setInheritsPopupMenu(true);
413 dlg.setButton(button);
414 toolBarToggle.add(button);
415 allDialogs.add(dlg);
416 allDialogButtons.add(button);
417 button.applyButtonHiddenPreferences();
418 if (dialogsPanel.initialized) {
419 dialogsPanel.add(dlg);
420 }
421 return button;
422 }
423
424 /**
425 * Call this to remove existing toggle dialog from the left button-list
426 * @param dlg The toggle dialog. It must be already in the list.
427 * @since 10851
428 */
429 public void removeToggleDialog(final ToggleDialog dlg) {
430 final JToggleButton button = dlg.getButton();
431 if (button != null) {
432 allDialogButtons.remove(button);
433 toolBarToggle.remove(button);
434 }
435 dialogsPanel.remove(dlg);
436 allDialogs.remove(dlg);
437 }
438
439 /**
440 * Adds a new map mode button
441 * @param b The map mode button with a {@link MapMode} action.
442 */
443 public void addMapMode(IconToggleButton b) {
444 if (!(b.getAction() instanceof MapMode))
445 throw new IllegalArgumentException("MapMode action must be subclass of MapMode");
446 MainMenu.add(MainApplication.getMenu().modeMenu, (MapMode) b.getAction());
447 allMapModeButtons.add(b);
448 toolBarActionsGroup.add(b);
449 toolBarActions.add(b);
450 b.applyButtonHiddenPreferences();
451 b.setInheritsPopupMenu(true);
452 }
453
454 /**
455 * Fires an property changed event "visible".
456 * @param aFlag {@code true} if display should be visible
457 */
458 @Override public void setVisible(boolean aFlag) {
459 boolean old = isVisible();
460 super.setVisible(aFlag);
461 if (old != aFlag) {
462 firePropertyChange("visible", old, aFlag);
463 }
464 }
465
466 /**
467 * Change the operating map mode for the view. Will call unregister on the
468 * old MapMode and register on the new one. Now this function also verifies
469 * if new map mode is correct mode for current layer and does not change mode
470 * in such cases.
471 * @param newMapMode The new mode to set.
472 * @return {@code true} if mode is really selected
473 */
474 public boolean selectMapMode(MapMode newMapMode) {
475 return selectMapMode(newMapMode, mapView.getLayerManager().getActiveLayer());
476 }
477
478 /**
479 * Another version of the selectMapMode for changing layer action.
480 * Pass newly selected layer to this method.
481 * @param newMapMode The new mode to set.
482 * @param newLayer newly selected layer
483 * @return {@code true} if mode is really selected
484 */
485 public boolean selectMapMode(MapMode newMapMode, Layer newLayer) {
486 MapMode oldMapMode = this.mapMode;
487 if (newMapMode == oldMapMode)
488 return true;
489 if (newMapMode == null || !newMapMode.layerIsSupported(newLayer)) {
490 newMapMode = null;
491 }
492 Logging.debug("Switching map mode from {0} to {1}",
493 Optional.ofNullable(oldMapMode).map(m -> m.getClass().getSimpleName()).orElse("(none)"),
494 Optional.ofNullable(newMapMode).map(m -> m.getClass().getSimpleName()).orElse("(none)"));
495
496 if (oldMapMode != null) {
497 MainApplication.getMenu().findMapModeMenuItem(oldMapMode).ifPresent(m -> m.setSelected(false));
498 oldMapMode.exitMode();
499 }
500 this.mapMode = newMapMode;
501 if (newMapMode != null) {
502 newMapMode.enterMode();
503 MainApplication.getMenu().findMapModeMenuItem(newMapMode).ifPresent(m -> m.setSelected(true));
504 }
505 lastMapMode.put(newLayer, newMapMode);
506 fireMapModeChanged(oldMapMode, newMapMode);
507 return newMapMode != null;
508 }
509
510 /**
511 * Fill the given panel by adding all necessary components to the different
512 * locations.
513 *
514 * @param panel The container to fill. Must have a BorderLayout.
515 */
516 public void fillPanel(Container panel) {
517 panel.add(this, BorderLayout.CENTER);
518
519 /**
520 * sideToolBar: add map modes icons
521 */
522 if (Config.getPref().getBoolean("sidetoolbar.mapmodes.visible", true)) {
523 toolBarActions.setAlignmentX(0.5f);
524 toolBarActions.setBorder(null);
525 toolBarActions.setInheritsPopupMenu(true);
526 sideToolBar.add(toolBarActions);
527 sideToolBar.add(listAllMapModesAction.createButton());
528 }
529
530 /**
531 * sideToolBar: add toggle dialogs icons
532 */
533 if (Config.getPref().getBoolean("sidetoolbar.toggledialogs.visible", true)) {
534 ((JToolBar) sideToolBar).addSeparator(new Dimension(0, 18));
535 toolBarToggle.setAlignmentX(0.5f);
536 toolBarToggle.setBorder(null);
537 toolBarToggle.setInheritsPopupMenu(true);
538 sideToolBar.add(toolBarToggle);
539 sideToolBar.add(listAllDialogsAction.createButton());
540 }
541
542 /**
543 * sideToolBar: add dynamic popup menu
544 */
545 sideToolBar.setComponentPopupMenu(new SideToolbarPopupMenu());
546 ((JToolBar) sideToolBar).setFloatable(false);
547 sideToolBar.setBorder(BorderFactory.createEmptyBorder(0, 1, 0, 1));
548
549 /**
550 * sideToolBar: decide scroll- and visibility
551 */
552 if (Config.getPref().getBoolean("sidetoolbar.scrollable", true)) {
553 final ScrollViewport svp = new ScrollViewport(sideToolBar, ScrollViewport.VERTICAL_DIRECTION);
554 sideToolBar = svp;
555 }
556 sideToolBar.setVisible(SIDE_TOOLBAR_VISIBLE.get());
557 sidetoolbarPreferencesChangedListener = e -> sideToolBar.setVisible(e.getProperty().get());
558 SIDE_TOOLBAR_VISIBLE.addListener(sidetoolbarPreferencesChangedListener);
559
560 /**
561 * sideToolBar: add it to the panel
562 */
563 panel.add(sideToolBar, BorderLayout.WEST);
564
565 /**
566 * statusLine: add to panel
567 */
568 if (statusLine != null && Config.getPref().getBoolean("statusline.visible", true)) {
569 panel.add(statusLine, BorderLayout.SOUTH);
570 }
571 }
572
573 static final class NoBorderSplitPaneUI extends BasicSplitPaneUI {
574 static final class NoBorderBasicSplitPaneDivider extends BasicSplitPaneDivider {
575 NoBorderBasicSplitPaneDivider(BasicSplitPaneUI ui) {
576 super(ui);
577 }
578
579 @Override
580 public void setBorder(Border b) {
581 // Do nothing
582 }
583 }
584
585 @Override
586 public BasicSplitPaneDivider createDefaultDivider() {
587 return new NoBorderBasicSplitPaneDivider(this);
588 }
589 }
590
591 private final class SideToolbarPopupMenu extends JPopupMenu {
592 private static final int staticMenuEntryCount = 2;
593 private final JCheckBoxMenuItem doNotHide = new JCheckBoxMenuItem(new AbstractAction(tr("Do not hide toolbar")) {
594 @Override
595 public void actionPerformed(ActionEvent e) {
596 boolean sel = ((JCheckBoxMenuItem) e.getSource()).getState();
597 Config.getPref().putBoolean("sidetoolbar.always-visible", sel);
598 }
599 });
600 {
601 addPopupMenuListener(new PopupMenuListener() {
602 @Override
603 public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
604 final Object src = ((JPopupMenu) e.getSource()).getInvoker();
605 if (src instanceof IconToggleButton) {
606 insert(new Separator(), 0);
607 insert(new AbstractAction() {
608 {
609 putValue(NAME, tr("Hide this button"));
610 putValue(SHORT_DESCRIPTION, tr("Click the arrow at the bottom to show it again."));
611 }
612
613 @Override
614 public void actionPerformed(ActionEvent e) {
615 ((IconToggleButton) src).setButtonHidden(true);
616 validateToolBarsVisibility();
617 }
618 }, 0);
619 }
620 doNotHide.setSelected(Config.getPref().getBoolean("sidetoolbar.always-visible", true));
621 }
622
623 @Override
624 public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
625 while (getComponentCount() > staticMenuEntryCount) {
626 remove(0);
627 }
628 }
629
630 @Override
631 public void popupMenuCanceled(PopupMenuEvent e) {
632 // Do nothing
633 }
634 });
635
636 add(new AbstractAction(tr("Hide edit toolbar")) {
637 @Override
638 public void actionPerformed(ActionEvent e) {
639 SIDE_TOOLBAR_VISIBLE.put(false);
640 }
641 });
642 add(doNotHide);
643 }
644 }
645
646 class ListAllButtonsAction extends AbstractAction {
647
648 private JButton button;
649 private final transient Collection<? extends HideableButton> buttons;
650
651 ListAllButtonsAction(Collection<? extends HideableButton> buttons) {
652 this.buttons = buttons;
653 }
654
655 JButton createButton() {
656 button = new BasicArrowButton(SwingConstants.EAST, null, null, Color.BLACK, null) {
657
658 @Override
659 public Dimension getMaximumSize() {
660 final Dimension dimension = ImageProvider.ImageSizes.TOOLBAR.getImageDimension();
661 dimension.width = Integer.MAX_VALUE;
662 return dimension;
663 }
664 };
665 button.setAction(this);
666 button.setAlignmentX(0.5f);
667 button.setInheritsPopupMenu(true);
668 return button;
669 }
670
671 @Override
672 public void actionPerformed(ActionEvent e) {
673 JPopupMenu menu = new JPopupMenu();
674 for (HideableButton b : buttons) {
675 if (!b.isExpert() || ExpertToggleAction.isExpert()) {
676 final HideableButton t = b;
677 menu.add(new JCheckBoxMenuItem(new AbstractAction() {
678 {
679 putValue(NAME, t.getActionName());
680 putValue(SMALL_ICON, t.getIcon());
681 putValue(SELECTED_KEY, t.isButtonVisible());
682 putValue(SHORT_DESCRIPTION, tr("Hide or show this toggle button"));
683 }
684
685 @Override
686 public void actionPerformed(ActionEvent e) {
687 if ((Boolean) getValue(SELECTED_KEY)) {
688 t.showButton();
689 } else {
690 t.hideButton();
691 }
692 validateToolBarsVisibility();
693 }
694 }));
695 }
696 }
697 if (button != null && button.isShowing()) {
698 Rectangle bounds = button.getBounds();
699 menu.show(button, bounds.x + bounds.width, 0);
700 }
701 }
702 }
703
704 /**
705 * Validate the visibility of all tool bars and hide the ones that should be hidden
706 */
707 public void validateToolBarsVisibility() {
708 for (IconToggleButton b : allDialogButtons) {
709 b.applyButtonHiddenPreferences();
710 }
711 toolBarToggle.repaint();
712 for (IconToggleButton b : allMapModeButtons) {
713 b.applyButtonHiddenPreferences();
714 }
715 toolBarActions.repaint();
716 }
717
718 /**
719 * Replies the instance of a toggle dialog of type <code>type</code> managed by this map frame
720 *
721 * @param <T> toggle dialog type
722 * @param type the class of the toggle dialog, i.e. UserListDialog.class
723 * @return the instance of a toggle dialog of type <code>type</code> managed by this
724 * map frame; null, if no such dialog exists
725 *
726 */
727 public <T extends ToggleDialog> T getToggleDialog(Class<T> type) {
728 return dialogsPanel.getToggleDialog(type);
729 }
730
731 /**
732 * Shows or hides the side dialog panel
733 * @param visible The new visibility
734 */
735 public void setDialogsPanelVisible(boolean visible) {
736 rememberToggleDialogWidth();
737 dialogsPanel.setVisible(visible);
738 splitPane.setDividerLocation(visible ? splitPane.getWidth() - TOGGLE_DIALOGS_WIDTH.get() : 0);
739 splitPane.setDividerSize(visible ? 5 : 0);
740 }
741
742 /**
743 * Remember the current width of the (possibly resized) toggle dialog area
744 */
745 public void rememberToggleDialogWidth() {
746 if (dialogsPanel.isVisible()) {
747 TOGGLE_DIALOGS_WIDTH.put(splitPane.getWidth() - splitPane.getDividerLocation() - splitPane.getDividerSize() - 1);
748 }
749 }
750
751 /**
752 * Remove panel from top of MapView by class
753 * @param type type of panel
754 */
755 public void removeTopPanel(Class<?> type) {
756 int n = leftPanel.getComponentCount();
757 for (int i = 0; i < n; i++) {
758 Component c = leftPanel.getComponent(i);
759 if (type.isInstance(c)) {
760 leftPanel.remove(i);
761 leftPanel.doLayout();
762 return;
763 }
764 }
765 }
766
767 /**
768 * Find panel on top of MapView by class
769 * @param <T> type
770 * @param type type of panel
771 * @return found panel
772 */
773 public <T> T getTopPanel(Class<T> type) {
774 return Arrays.stream(leftPanel.getComponents())
775 .filter(type::isInstance)
776 .findFirst().map(type::cast).orElse(null);
777 }
778
779 /**
780 * Add component {@code c} on top of MapView
781 * @param c component
782 */
783 public void addTopPanel(Component c) {
784 leftPanel.add(c, GBC.eol().fill(GBC.HORIZONTAL), leftPanel.getComponentCount()-1);
785 leftPanel.doLayout();
786 c.doLayout();
787 }
788
789 /**
790 * Interface to notify listeners of the change of the mapMode.
791 * @since 10600 (functional interface)
792 */
793 @FunctionalInterface
794 public interface MapModeChangeListener {
795 /**
796 * Trigerred when map mode changes.
797 * @param oldMapMode old map mode
798 * @param newMapMode new map mode
799 */
800 void mapModeChange(MapMode oldMapMode, MapMode newMapMode);
801 }
802
803 /**
804 * the mapMode listeners
805 */
806 private static final CopyOnWriteArrayList<MapModeChangeListener> mapModeChangeListeners = new CopyOnWriteArrayList<>();
807
808 private transient AbstractProperty.ValueChangeListener<Boolean> sidetoolbarPreferencesChangedListener;
809 /**
810 * Adds a mapMode change listener
811 *
812 * @param listener the listener. Ignored if null or already registered.
813 */
814 public static void addMapModeChangeListener(MapModeChangeListener listener) {
815 if (listener != null) {
816 mapModeChangeListeners.addIfAbsent(listener);
817 }
818 }
819
820 /**
821 * Removes a mapMode change listener
822 *
823 * @param listener the listener. Ignored if null or already registered.
824 */
825 public static void removeMapModeChangeListener(MapModeChangeListener listener) {
826 mapModeChangeListeners.remove(listener);
827 }
828
829 protected static void fireMapModeChanged(MapMode oldMapMode, MapMode newMapMode) {
830 for (MapModeChangeListener l : mapModeChangeListeners) {
831 l.mapModeChange(oldMapMode, newMapMode);
832 }
833 }
834
835 @Override
836 public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) {
837 boolean modeChanged = false;
838 Layer newLayer = e.getSource().getActiveLayer();
839 if (mapMode == null || !mapMode.layerIsSupported(newLayer)) {
840 MapMode newMapMode = getLastMapMode(newLayer);
841 modeChanged = newMapMode != mapMode;
842 if (newMapMode != null) {
843 // it would be nice to select first supported mode when layer is first selected,
844 // but it don't work well with for example editgpx layer
845 selectMapMode(newMapMode, newLayer);
846 } else if (mapMode != null) {
847 mapMode.exitMode(); // if new mode is null - simply exit from previous mode
848 mapMode = null;
849 }
850 }
851 // if this is really a change (and not the first active layer)
852 if (e.getPreviousActiveLayer() != null && !modeChanged && mapMode != null) {
853 // Let mapmodes know about new active layer
854 mapMode.exitMode();
855 mapMode.enterMode();
856 }
857
858 // After all listeners notice new layer, some buttons will be disabled/enabled
859 // and possibly need to be hidden/shown.
860 validateToolBarsVisibility();
861 }
862
863 private MapMode getLastMapMode(Layer newLayer) {
864 MapMode mode = lastMapMode.get(newLayer);
865 if (mode == null) {
866 // if no action is selected - try to select default action
867 Action defaultMode = getDefaultButtonAction();
868 if (defaultMode instanceof MapMode && ((MapMode) defaultMode).layerIsSupported(newLayer)) {
869 mode = (MapMode) defaultMode;
870 }
871 }
872 return mode;
873 }
874
875 @Override
876 public void layerAdded(LayerAddEvent e) {
877 // ignored
878 }
879
880 @Override
881 public void layerRemoving(LayerRemoveEvent e) {
882 lastMapMode.remove(e.getRemovedLayer());
883 }
884
885 @Override
886 public void layerOrderChanged(LayerOrderChangeEvent e) {
887 // ignored
888 }
889
890}
Note: See TracBrowser for help on using the repository browser.