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

Last change on this file since 12496 was 12391, checked in by michael2402, 7 years ago

See #14794: Documentation for the gui package

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