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

Last change on this file since 12136 was 12079, checked in by michael2402, 7 years ago

ScrollViewport: Fix layout computation, natively support mouse wheel

The layout size width/height is set to the one of the inner component if scrolling in that direction is not enabled.
The ScrollViewport now supports the mouse wheel in both horizontal and vertical versions.

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