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