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