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

Last change on this file since 14206 was 13832, checked in by Don-vip, 6 years ago

fix #5603, see #5256, see #15240 - add first/last buttons for geoimage dialog. Fixed shortcuts. Switch to SVG icons taken from https://commons.wikimedia.org/wiki/GNOME_Desktop_icons#Navigation

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