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