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.GraphicsEnvironment; 012import java.awt.GridBagLayout; 013import java.awt.Rectangle; 014import java.awt.event.ActionEvent; 015import java.awt.event.KeyEvent; 016import java.awt.event.MouseWheelEvent; 017import java.awt.event.MouseWheelListener; 018import java.util.ArrayList; 019import java.util.Collection; 020import java.util.HashMap; 021import java.util.List; 022import java.util.Map; 023import java.util.concurrent.CopyOnWriteArrayList; 024 025import javax.swing.AbstractAction; 026import javax.swing.AbstractButton; 027import javax.swing.Action; 028import javax.swing.BorderFactory; 029import javax.swing.BoxLayout; 030import javax.swing.ButtonGroup; 031import javax.swing.ImageIcon; 032import javax.swing.JButton; 033import javax.swing.JCheckBoxMenuItem; 034import javax.swing.JComponent; 035import javax.swing.JPanel; 036import javax.swing.JPopupMenu; 037import javax.swing.JSplitPane; 038import javax.swing.JToolBar; 039import javax.swing.KeyStroke; 040import javax.swing.SwingUtilities; 041import javax.swing.border.Border; 042import javax.swing.event.PopupMenuEvent; 043import javax.swing.event.PopupMenuListener; 044import javax.swing.plaf.basic.BasicSplitPaneDivider; 045import javax.swing.plaf.basic.BasicSplitPaneUI; 046 047import org.openstreetmap.josm.Main; 048import org.openstreetmap.josm.actions.LassoModeAction; 049import org.openstreetmap.josm.actions.mapmode.DeleteAction; 050import org.openstreetmap.josm.actions.mapmode.DrawAction; 051import org.openstreetmap.josm.actions.mapmode.ExtrudeAction; 052import org.openstreetmap.josm.actions.mapmode.ImproveWayAccuracyAction; 053import org.openstreetmap.josm.actions.mapmode.MapMode; 054import org.openstreetmap.josm.actions.mapmode.ParallelWayAction; 055import org.openstreetmap.josm.actions.mapmode.SelectAction; 056import org.openstreetmap.josm.actions.mapmode.ZoomAction; 057import org.openstreetmap.josm.data.Preferences; 058import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent; 059import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener; 060import org.openstreetmap.josm.data.ViewportData; 061import org.openstreetmap.josm.gui.MapView.LayerChangeListener; 062import org.openstreetmap.josm.gui.dialogs.ChangesetDialog; 063import org.openstreetmap.josm.gui.dialogs.CommandStackDialog; 064import org.openstreetmap.josm.gui.dialogs.ConflictDialog; 065import org.openstreetmap.josm.gui.dialogs.DialogsPanel; 066import org.openstreetmap.josm.gui.dialogs.FilterDialog; 067import org.openstreetmap.josm.gui.dialogs.LayerListDialog; 068import org.openstreetmap.josm.gui.dialogs.MapPaintDialog; 069import org.openstreetmap.josm.gui.dialogs.MinimapDialog; 070import org.openstreetmap.josm.gui.dialogs.NotesDialog; 071import org.openstreetmap.josm.gui.dialogs.RelationListDialog; 072import org.openstreetmap.josm.gui.dialogs.SelectionListDialog; 073import org.openstreetmap.josm.gui.dialogs.ToggleDialog; 074import org.openstreetmap.josm.gui.dialogs.UserListDialog; 075import org.openstreetmap.josm.gui.dialogs.ValidatorDialog; 076import org.openstreetmap.josm.gui.dialogs.properties.PropertiesDialog; 077import org.openstreetmap.josm.gui.layer.Layer; 078import org.openstreetmap.josm.gui.layer.LayerManager; 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, 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 The content pane used to register shortcuts in its 187 * {@link javax.swing.InputMap} and {@link javax.swing.ActionMap} 188 * @param viewportData the initial viewport of the map. Can be null, then 189 * the viewport is derived from the layer data. 190 */ 191 public MapFrame(JPanel contentPane, ViewportData viewportData) { 192 setSize(400, 400); 193 setLayout(new BorderLayout()); 194 195 mapView = new MapView(Main.getLayerManager(), contentPane, viewportData); 196 if (!GraphicsEnvironment.isHeadless()) { 197 new FileDrop(mapView); 198 } 199 200 splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true); 201 202 leftPanel = new JPanel(new GridBagLayout()); 203 leftPanel.add(mapView, GBC.std().fill()); 204 splitPane.setLeftComponent(leftPanel); 205 206 dialogsPanel = new DialogsPanel(splitPane); 207 splitPane.setRightComponent(dialogsPanel); 208 209 /** 210 * All additional space goes to the mapView 211 */ 212 splitPane.setResizeWeight(1.0); 213 214 /** 215 * Some beautifications. 216 */ 217 splitPane.setDividerSize(5); 218 splitPane.setBorder(null); 219 splitPane.setUI(new BasicSplitPaneUI() { 220 @Override 221 public BasicSplitPaneDivider createDefaultDivider() { 222 return new BasicSplitPaneDivider(this) { 223 @Override 224 public void setBorder(Border b) { 225 // Do nothing 226 } 227 }; 228 } 229 }); 230 231 // JSplitPane supports F6 and F8 shortcuts by default, but we need them for Audio actions 232 splitPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_F6, 0), new Object()); 233 splitPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_F8, 0), new Object()); 234 235 add(splitPane, BorderLayout.CENTER); 236 237 dialogsPanel.setLayout(new BoxLayout(dialogsPanel, BoxLayout.Y_AXIS)); 238 dialogsPanel.setPreferredSize(new Dimension(Main.pref.getInteger("toggleDialogs.width", DEF_TOGGLE_DLG_WIDTH), 0)); 239 dialogsPanel.setMinimumSize(new Dimension(24, 0)); 240 mapView.setMinimumSize(new Dimension(10, 0)); 241 242 // toolBarActions, map mode buttons 243 mapModeSelect = new SelectAction(this); 244 mapModeSelectLasso = new LassoModeAction(); 245 mapModeDraw = new DrawAction(this); 246 mapModeZoom = new ZoomAction(this); 247 248 addMapMode(new IconToggleButton(mapModeSelect)); 249 addMapMode(new IconToggleButton(mapModeSelectLasso, true)); 250 addMapMode(new IconToggleButton(mapModeDraw)); 251 addMapMode(new IconToggleButton(mapModeZoom, true)); 252 addMapMode(new IconToggleButton(new DeleteAction(this), true)); 253 addMapMode(new IconToggleButton(new ParallelWayAction(this), true)); 254 addMapMode(new IconToggleButton(new ExtrudeAction(this), true)); 255 addMapMode(new IconToggleButton(new ImproveWayAccuracyAction(Main.map), false)); 256 toolBarActionsGroup.setSelected(allMapModeButtons.get(0).getModel(), true); 257 toolBarActions.setFloatable(false); 258 259 // toolBarToggles, toggle dialog buttons 260 LayerListDialog.createInstance(this); 261 propertiesDialog = new PropertiesDialog(); 262 selectionListDialog = new SelectionListDialog(); 263 relationListDialog = new RelationListDialog(); 264 conflictDialog = new ConflictDialog(); 265 validatorDialog = new ValidatorDialog(); 266 filterDialog = new FilterDialog(); 267 mapPaintDialog = new MapPaintDialog(); 268 noteDialog = new NotesDialog(); 269 270 addToggleDialog(LayerListDialog.getInstance()); 271 addToggleDialog(propertiesDialog); 272 addToggleDialog(selectionListDialog); 273 addToggleDialog(relationListDialog); 274 addToggleDialog(new MinimapDialog()); 275 addToggleDialog(new CommandStackDialog()); 276 addToggleDialog(new UserListDialog()); 277 addToggleDialog(conflictDialog); 278 addToggleDialog(validatorDialog); 279 addToggleDialog(filterDialog); 280 addToggleDialog(new ChangesetDialog(), true); 281 addToggleDialog(mapPaintDialog); 282 addToggleDialog(noteDialog); 283 toolBarToggle.setFloatable(false); 284 285 // status line below the map 286 statusLine = new MapStatus(this); 287 MapView.addLayerChangeListener(this); 288 289 boolean unregisterTab = Shortcut.findShortcut(KeyEvent.VK_TAB, 0) != null; 290 if (unregisterTab) { 291 for (JComponent c: allDialogButtons) { 292 c.setFocusTraversalKeysEnabled(false); 293 } 294 for (JComponent c: allMapModeButtons) { 295 c.setFocusTraversalKeysEnabled(false); 296 } 297 } 298 299 if (Main.pref.getBoolean("debug.advanced-keypress-detector.enable", true)) { 300 keyDetector.register(); 301 } 302 } 303 304 public boolean selectSelectTool(boolean onlyIfModeless) { 305 if (onlyIfModeless && !Main.pref.getBoolean("modeless", false)) 306 return false; 307 308 return selectMapMode(mapModeSelect); 309 } 310 311 public boolean selectDrawTool(boolean onlyIfModeless) { 312 if (onlyIfModeless && !Main.pref.getBoolean("modeless", false)) 313 return false; 314 315 return selectMapMode(mapModeDraw); 316 } 317 318 public boolean selectZoomTool(boolean onlyIfModeless) { 319 if (onlyIfModeless && !Main.pref.getBoolean("modeless", false)) 320 return false; 321 322 return selectMapMode(mapModeZoom); 323 } 324 325 /** 326 * Called as some kind of destructor when the last layer has been removed. 327 * Delegates the call to all Destroyables within this component (e.g. MapModes) 328 */ 329 @Override 330 public void destroy() { 331 MapView.removeLayerChangeListener(this); 332 dialogsPanel.destroy(); 333 Main.pref.removePreferenceChangeListener(sidetoolbarPreferencesChangedListener); 334 for (int i = 0; i < toolBarActions.getComponentCount(); ++i) { 335 if (toolBarActions.getComponent(i) instanceof Destroyable) { 336 ((Destroyable) toolBarActions.getComponent(i)).destroy(); 337 } 338 } 339 for (int i = 0; i < toolBarToggle.getComponentCount(); ++i) { 340 if (toolBarToggle.getComponent(i) instanceof Destroyable) { 341 ((Destroyable) toolBarToggle.getComponent(i)).destroy(); 342 } 343 } 344 345 statusLine.destroy(); 346 mapView.destroy(); 347 keyDetector.unregister(); 348 } 349 350 public Action getDefaultButtonAction() { 351 return ((AbstractButton) toolBarActions.getComponent(0)).getAction(); 352 } 353 354 /** 355 * Open all ToggleDialogs that have their preferences property set. Close all others. 356 */ 357 public void initializeDialogsPane() { 358 dialogsPanel.initialize(allDialogs); 359 } 360 361 public IconToggleButton addToggleDialog(final ToggleDialog dlg) { 362 return addToggleDialog(dlg, false); 363 } 364 365 /** 366 * Call this to add new toggle dialogs to the left button-list 367 * @param dlg The toggle dialog. It must not be in the list already. 368 * @param isExpert {@code true} if it's reserved to expert mode 369 * @return button allowing to toggle the dialog 370 */ 371 public IconToggleButton addToggleDialog(final ToggleDialog dlg, boolean isExpert) { 372 final IconToggleButton button = new IconToggleButton(dlg.getToggleAction(), isExpert); 373 button.setShowHideButtonListener(dlg); 374 button.setInheritsPopupMenu(true); 375 dlg.setButton(button); 376 toolBarToggle.add(button); 377 allDialogs.add(dlg); 378 allDialogButtons.add(button); 379 button.applyButtonHiddenPreferences(); 380 if (dialogsPanel.initialized) { 381 dialogsPanel.add(dlg); 382 } 383 return button; 384 } 385 386 public void addMapMode(IconToggleButton b) { 387 if (b.getAction() instanceof MapMode) { 388 mapModes.add((MapMode) b.getAction()); 389 } else 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.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 svp.addMouseWheelListener(new MouseWheelListener() { 499 @Override 500 public void mouseWheelMoved(MouseWheelEvent e) { 501 svp.scroll(0, e.getUnitsToScroll() * 5); 502 } 503 }); 504 sideToolBar = svp; 505 } 506 sideToolBar.setVisible(Main.pref.getBoolean("sidetoolbar.visible", true)); 507 sidetoolbarPreferencesChangedListener = new Preferences.PreferenceChangedListener() { 508 @Override 509 public void preferenceChanged(PreferenceChangeEvent e) { 510 if ("sidetoolbar.visible".equals(e.getKey())) { 511 sideToolBar.setVisible(Main.pref.getBoolean("sidetoolbar.visible")); 512 } 513 } 514 }; 515 Main.pref.addPreferenceChangeListener(sidetoolbarPreferencesChangedListener); 516 517 /** 518 * sideToolBar: add it to the panel 519 */ 520 panel.add(sideToolBar, BorderLayout.WEST); 521 522 /** 523 * statusLine: add to panel 524 */ 525 if (statusLine != null && Main.pref.getBoolean("statusline.visible", true)) { 526 panel.add(statusLine, BorderLayout.SOUTH); 527 } 528 } 529 530 private final class SideToolbarPopupMenu extends JPopupMenu { 531 private static final int staticMenuEntryCount = 2; 532 private final JCheckBoxMenuItem doNotHide = new JCheckBoxMenuItem(new AbstractAction(tr("Do not hide toolbar")) { 533 @Override 534 public void actionPerformed(ActionEvent e) { 535 boolean sel = ((JCheckBoxMenuItem) e.getSource()).getState(); 536 Main.pref.put("sidetoolbar.always-visible", sel); 537 } 538 }); 539 { 540 addPopupMenuListener(new PopupMenuListener() { 541 @Override 542 public void popupMenuWillBecomeVisible(PopupMenuEvent e) { 543 final Object src = ((JPopupMenu) e.getSource()).getInvoker(); 544 if (src instanceof IconToggleButton) { 545 insert(new Separator(), 0); 546 insert(new AbstractAction() { 547 { 548 putValue(NAME, tr("Hide this button")); 549 putValue(SHORT_DESCRIPTION, tr("Click the arrow at the bottom to show it again.")); 550 } 551 552 @Override 553 public void actionPerformed(ActionEvent e) { 554 ((IconToggleButton) src).setButtonHidden(true); 555 validateToolBarsVisibility(); 556 } 557 }, 0); 558 } 559 doNotHide.setSelected(Main.pref.getBoolean("sidetoolbar.always-visible", true)); 560 } 561 562 @Override 563 public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { 564 while (getComponentCount() > staticMenuEntryCount) { 565 remove(0); 566 } 567 } 568 569 @Override 570 public void popupMenuCanceled(PopupMenuEvent e) { 571 // Do nothing 572 } 573 }); 574 575 add(new AbstractAction(tr("Hide edit toolbar")) { 576 @Override 577 public void actionPerformed(ActionEvent e) { 578 Main.pref.put("sidetoolbar.visible", false); 579 } 580 }); 581 add(doNotHide); 582 } 583 } 584 585 class ListAllButtonsAction extends AbstractAction { 586 587 private JButton button; 588 private final transient Collection<? extends HideableButton> buttons; 589 590 ListAllButtonsAction(Collection<? extends HideableButton> buttons) { 591 this.buttons = buttons; 592 } 593 594 public void setButton(JButton button) { 595 this.button = button; 596 final ImageIcon icon = ImageProvider.get("audio-fwd"); 597 putValue(SMALL_ICON, icon); 598 button.setPreferredSize(new Dimension(icon.getIconWidth(), icon.getIconHeight() + 64)); 599 } 600 601 @Override 602 public void actionPerformed(ActionEvent e) { 603 JPopupMenu menu = new JPopupMenu(); 604 for (HideableButton b : buttons) { 605 final HideableButton t = b; 606 menu.add(new JCheckBoxMenuItem(new AbstractAction() { 607 { 608 putValue(NAME, t.getActionName()); 609 putValue(SMALL_ICON, t.getIcon()); 610 putValue(SELECTED_KEY, t.isButtonVisible()); 611 putValue(SHORT_DESCRIPTION, tr("Hide or show this toggle button")); 612 } 613 614 @Override 615 public void actionPerformed(ActionEvent e) { 616 if ((Boolean) getValue(SELECTED_KEY)) { 617 t.showButton(); 618 } else { 619 t.hideButton(); 620 } 621 validateToolBarsVisibility(); 622 } 623 })); 624 } 625 if (button != null) { 626 Rectangle bounds = button.getBounds(); 627 menu.show(button, bounds.x + bounds.width, 0); 628 } 629 } 630 } 631 632 public void validateToolBarsVisibility() { 633 for (IconToggleButton b : allDialogButtons) { 634 b.applyButtonHiddenPreferences(); 635 } 636 toolBarToggle.repaint(); 637 for (IconToggleButton b : allMapModeButtons) { 638 b.applyButtonHiddenPreferences(); 639 } 640 toolBarActions.repaint(); 641 } 642 643 /** 644 * Replies the instance of a toggle dialog of type <code>type</code> managed by this map frame 645 * 646 * @param <T> toggle dialog type 647 * @param type the class of the toggle dialog, i.e. UserListDialog.class 648 * @return the instance of a toggle dialog of type <code>type</code> managed by this 649 * map frame; null, if no such dialog exists 650 * 651 */ 652 public <T> T getToggleDialog(Class<T> type) { 653 return dialogsPanel.getToggleDialog(type); 654 } 655 656 public void setDialogsPanelVisible(boolean visible) { 657 rememberToggleDialogWidth(); 658 dialogsPanel.setVisible(visible); 659 splitPane.setDividerLocation(visible ? splitPane.getWidth()-Main.pref.getInteger("toggleDialogs.width", DEF_TOGGLE_DLG_WIDTH) : 0); 660 splitPane.setDividerSize(visible ? 5 : 0); 661 } 662 663 /** 664 * Remember the current width of the (possibly resized) toggle dialog area 665 */ 666 public void rememberToggleDialogWidth() { 667 if (dialogsPanel.isVisible()) { 668 Main.pref.putInteger("toggleDialogs.width", splitPane.getWidth()-splitPane.getDividerLocation()); 669 } 670 } 671 672 /** 673 * Remove panel from top of MapView by class 674 * @param type type of panel 675 */ 676 public void removeTopPanel(Class<?> type) { 677 int n = leftPanel.getComponentCount(); 678 for (int i = 0; i < n; i++) { 679 Component c = leftPanel.getComponent(i); 680 if (type.isInstance(c)) { 681 leftPanel.remove(i); 682 leftPanel.doLayout(); 683 return; 684 } 685 } 686 } 687 688 /** 689 * Find panel on top of MapView by class 690 * @param <T> type 691 * @param type type of panel 692 * @return found panel 693 */ 694 public <T> T getTopPanel(Class<T> type) { 695 int n = leftPanel.getComponentCount(); 696 for (int i = 0; i < n; i++) { 697 Component c = leftPanel.getComponent(i); 698 if (type.isInstance(c)) 699 return type.cast(c); 700 } 701 return null; 702 } 703 704 /** 705 * Add component {@code c} on top of MapView 706 * @param c component 707 */ 708 public void addTopPanel(Component c) { 709 leftPanel.add(c, GBC.eol().fill(GBC.HORIZONTAL), leftPanel.getComponentCount()-1); 710 leftPanel.doLayout(); 711 c.doLayout(); 712 } 713 714 /** 715 * Interface to notify listeners of the change of the mapMode. 716 */ 717 public interface MapModeChangeListener { 718 /** 719 * Trigerred when map mode changes. 720 * @param oldMapMode old map mode 721 * @param newMapMode new map mode 722 */ 723 void mapModeChange(MapMode oldMapMode, MapMode newMapMode); 724 } 725 726 /** 727 * the mapMode listeners 728 */ 729 private static final CopyOnWriteArrayList<MapModeChangeListener> mapModeChangeListeners = new CopyOnWriteArrayList<>(); 730 731 private transient PreferenceChangedListener sidetoolbarPreferencesChangedListener; 732 /** 733 * Adds a mapMode change listener 734 * 735 * @param listener the listener. Ignored if null or already registered. 736 */ 737 public static void addMapModeChangeListener(MapModeChangeListener listener) { 738 if (listener != null) { 739 mapModeChangeListeners.addIfAbsent(listener); 740 } 741 } 742 743 /** 744 * Removes a mapMode change listener 745 * 746 * @param listener the listener. Ignored if null or already registered. 747 */ 748 public static void removeMapModeChangeListener(MapModeChangeListener listener) { 749 mapModeChangeListeners.remove(listener); 750 } 751 752 protected static void fireMapModeChanged(MapMode oldMapMode, MapMode newMapMode) { 753 for (MapModeChangeListener l : mapModeChangeListeners) { 754 l.mapModeChange(oldMapMode, newMapMode); 755 } 756 } 757 758 @Override 759 public void activeLayerChange(Layer oldLayer, Layer newLayer) { 760 boolean modeChanged = false; 761 if (mapMode == null || !mapMode.layerIsSupported(newLayer)) { 762 MapMode newMapMode = getLastMapMode(newLayer); 763 modeChanged = newMapMode != mapMode; 764 if (newMapMode != null) { 765 // it would be nice to select first supported mode when layer is first selected, 766 // but it don't work well with for example editgpx layer 767 selectMapMode(newMapMode, newLayer); 768 } else if (mapMode != null) { 769 mapMode.exitMode(); // if new mode is null - simply exit from previous mode 770 } 771 } 772 // if this is really a change (and not the first active layer) 773 if (oldLayer != null) { 774 if (!modeChanged && mapMode != null) { 775 // Let mapmodes know about new active layer 776 mapMode.exitMode(); 777 mapMode.enterMode(); 778 } 779 // invalidate repaint cache 780 mapView.preferenceChanged(null); 781 } 782 783 // After all listeners notice new layer, some buttons will be disabled/enabled 784 // and possibly need to be hidden/shown. 785 SwingUtilities.invokeLater(new Runnable() { 786 @Override public void run() { 787 validateToolBarsVisibility(); 788 } 789 }); 790 } 791 792 private MapMode getLastMapMode(Layer newLayer) { 793 MapMode mode = lastMapMode.get(newLayer); 794 if (mode == null) { 795 // if no action is selected - try to select default action 796 Action defaultMode = getDefaultButtonAction(); 797 if (defaultMode instanceof MapMode && ((MapMode) defaultMode).layerIsSupported(newLayer)) { 798 mode = (MapMode) defaultMode; 799 } 800 } 801 return mode; 802 } 803 804 @Override 805 public void layerAdded(Layer newLayer) { 806 // Do nothing 807 } 808 809 @Override 810 public void layerRemoved(Layer oldLayer) { 811 lastMapMode.remove(oldLayer); 812 } 813}