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