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

Last change on this file was 19384, checked in by GerdP, 12 months ago

fix #23851

  • move code to handle the width of the divider line

This seems to be the better solution as it also seems to work well after restart.

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