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