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