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