source: josm/trunk/src/org/openstreetmap/josm/gui/MapFrame.java@ 4567

Last change on this file since 4567 was 4567, checked in by stoecker, 13 years ago

fix #6875 - patch by Larry0ua - Data Layer can be changed after selecting another layer

  • Property svn:eol-style set to native
File size: 18.5 KB
Line 
1// License: GPL. See LICENSE file for details.
2package org.openstreetmap.josm.gui;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.BorderLayout;
7import java.awt.Container;
8import java.awt.Dimension;
9import java.awt.Font;
10import java.awt.Rectangle;
11import java.awt.event.ActionEvent;
12import java.awt.event.KeyEvent;
13import java.awt.event.MouseWheelEvent;
14import java.awt.event.MouseWheelListener;
15import java.util.ArrayList;
16import java.util.HashMap;
17import java.util.List;
18import java.util.Map;
19import java.util.concurrent.CopyOnWriteArrayList;
20
21import javax.swing.AbstractAction;
22import javax.swing.AbstractButton;
23import javax.swing.Action;
24import javax.swing.BoxLayout;
25import javax.swing.ButtonGroup;
26import javax.swing.JButton;
27import javax.swing.JCheckBoxMenuItem;
28import javax.swing.JComponent;
29import javax.swing.JPanel;
30import javax.swing.JPopupMenu;
31import javax.swing.JSplitPane;
32import javax.swing.JToolBar;
33import javax.swing.KeyStroke;
34import javax.swing.border.Border;
35import javax.swing.plaf.basic.BasicSplitPaneDivider;
36import javax.swing.plaf.basic.BasicSplitPaneUI;
37
38import org.openstreetmap.josm.Main;
39import org.openstreetmap.josm.actions.mapmode.DeleteAction;
40import org.openstreetmap.josm.actions.mapmode.DrawAction;
41import org.openstreetmap.josm.actions.mapmode.ExtrudeAction;
42import org.openstreetmap.josm.actions.mapmode.MapMode;
43import org.openstreetmap.josm.actions.mapmode.ParallelWayAction;
44import org.openstreetmap.josm.actions.mapmode.SelectAction;
45import org.openstreetmap.josm.actions.mapmode.ZoomAction;
46import org.openstreetmap.josm.gui.MapView.LayerChangeListener;
47import org.openstreetmap.josm.gui.dialogs.ChangesetDialog;
48import org.openstreetmap.josm.gui.dialogs.CommandStackDialog;
49import org.openstreetmap.josm.gui.dialogs.ConflictDialog;
50import org.openstreetmap.josm.gui.dialogs.DialogsPanel;
51import org.openstreetmap.josm.gui.dialogs.FilterDialog;
52import org.openstreetmap.josm.gui.dialogs.HistoryDialog;
53import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
54import org.openstreetmap.josm.gui.dialogs.MapPaintDialog;
55import org.openstreetmap.josm.gui.dialogs.RelationListDialog;
56import org.openstreetmap.josm.gui.dialogs.SelectionListDialog;
57import org.openstreetmap.josm.gui.dialogs.ToggleDialog;
58import org.openstreetmap.josm.gui.dialogs.UserListDialog;
59import org.openstreetmap.josm.gui.dialogs.ValidatorDialog;
60import org.openstreetmap.josm.gui.dialogs.properties.PropertiesDialog;
61import org.openstreetmap.josm.gui.layer.Layer;
62import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
63import org.openstreetmap.josm.tools.Destroyable;
64
65/**
66 * One Map frame with one dataset behind. This is the container gui class whose
67 * display can be set to the different views.
68 *
69 * @author imi
70 */
71public class MapFrame extends JPanel implements Destroyable, LayerChangeListener {
72
73 /**
74 * The current mode, this frame operates.
75 */
76 public MapMode mapMode;
77
78 private final List<MapMode> mapModes = new ArrayList<MapMode>();
79 /**
80 * The view control displayed.
81 */
82 public MapView mapView;
83 /**
84 * The toolbar with the action icons. To add new toggle dialog actions, use addToggleDialog
85 * instead of adding directly to this list. To add a new mode use addMapMode.
86 */
87 private JToolBar toolBarActions = new JToolBar(JToolBar.VERTICAL);
88 private JToolBar toolBarToggle = new JToolBar(JToolBar.VERTICAL);
89 /**
90 * The status line below the map
91 */
92 public MapStatus statusLine;
93
94 public ConflictDialog conflictDialog;
95 public FilterDialog filterDialog;
96 public RelationListDialog relationListDialog;
97 public ValidatorDialog validatorDialog;
98 public SelectionListDialog selectionListDialog;
99 public PropertiesDialog propertiesDialog;
100
101 /**
102 * The panel list of all toggle dialog icons. To add new toggle dialog actions, use addToggleDialog
103 * instead of adding directly to this list.
104 */
105 private List<ToggleDialog> allDialogs = new ArrayList<ToggleDialog>();
106 private final DialogsPanel dialogsPanel;
107
108 public final ButtonGroup toolGroup = new ButtonGroup();
109
110 public final JButton otherButton = new JButton(new OtherButtonsAction());
111
112 /**
113 * Default width of the toggle dialog area.
114 */
115 public static final int DEF_TOGGLE_DLG_WIDTH = 330;
116
117 private final Map<Layer, MapMode> lastMapMode = new HashMap<Layer, MapMode>();
118
119 public MapFrame(JPanel contentPane) {
120 setSize(400,400);
121 setLayout(new BorderLayout());
122
123 mapView = new MapView(contentPane);
124
125 new FileDrop(mapView);
126
127 // show menu entry
128 Main.main.menu.viewMenu.setVisible(true);
129
130 // toolbar
131 toolBarActions.setFloatable(false);
132 addMapMode(new IconToggleButton(new SelectAction(this)));
133 addMapMode(new IconToggleButton(new DrawAction(this)));
134 addMapMode(new IconToggleButton(new ZoomAction(this)));
135 addMapMode(new IconToggleButton(new DeleteAction(this)));
136 addMapMode(new IconToggleButton(new ExtrudeAction(this)));
137 addMapMode(new IconToggleButton(new ParallelWayAction(this)));
138
139 toolGroup.setSelected(((AbstractButton)toolBarActions.getComponent(0)).getModel(), true);
140
141 JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true);
142 dialogsPanel = new DialogsPanel(splitPane);
143 splitPane.setLeftComponent(mapView);
144 splitPane.setRightComponent(dialogsPanel);
145
146 /**
147 * All additional space goes to the mapView
148 */
149 splitPane.setResizeWeight(1.0);
150
151 /**
152 * Some beautifications.
153 */
154 splitPane.setDividerSize(5);
155 splitPane.setBorder(null);
156 splitPane.setUI(new BasicSplitPaneUI() {
157 @Override
158 public BasicSplitPaneDivider createDefaultDivider() {
159 return new BasicSplitPaneDivider(this) {
160 @Override
161 public void setBorder(Border b) {
162 }
163 };
164 }
165 });
166
167 // JSplitPane supports F6 and F8 shortcuts by default, but we need them for Audio actions
168 splitPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_F6, 0), new Object());
169 splitPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_F8, 0), new Object());
170
171 add(splitPane, BorderLayout.CENTER);
172
173 dialogsPanel.setLayout(new BoxLayout(dialogsPanel, BoxLayout.Y_AXIS));
174 dialogsPanel.setPreferredSize(new Dimension(Main.pref.getInteger("toggleDialogs.width",DEF_TOGGLE_DLG_WIDTH), 0));
175
176 dialogsPanel.setMinimumSize(new Dimension(24, 0));
177 mapView.setMinimumSize(new Dimension(10,0));
178
179 toolBarToggle.setFloatable(false);
180 LayerListDialog.createInstance(this);
181 addToggleDialog(LayerListDialog.getInstance());
182 addToggleDialog(propertiesDialog = new PropertiesDialog(this));
183 addToggleDialog(selectionListDialog = new SelectionListDialog());
184 addToggleDialog(relationListDialog = new RelationListDialog());
185 addToggleDialog(new CommandStackDialog(this));
186 addToggleDialog(new UserListDialog());
187 addToggleDialog(new HistoryDialog());
188 addToggleDialog(conflictDialog = new ConflictDialog());
189 addToggleDialog(validatorDialog = new ValidatorDialog());
190 addToggleDialog(filterDialog = new FilterDialog());
191 addToggleDialog(new ChangesetDialog(this));
192 addToggleDialog(new MapPaintDialog());
193
194 // status line below the map
195 statusLine = new MapStatus(this);
196 MapView.addLayerChangeListener(this);
197 }
198
199 public void selectSelectTool(boolean onlyIfModeless) {
200 if(onlyIfModeless && !Main.pref.getBoolean("modeless", false))
201 return;
202
203 selectMapMode((MapMode)getDefaultButtonAction());
204 }
205
206 public void selectDrawTool(boolean onlyIfModeless) {
207 if(onlyIfModeless && !Main.pref.getBoolean("modeless", false))
208 return;
209
210 Action drawAction = ((AbstractButton)toolBarActions.getComponent(1)).getAction();
211 selectMapMode((MapMode)drawAction);
212 }
213
214 /**
215 * Called as some kind of destructor when the last layer has been removed.
216 * Delegates the call to all Destroyables within this component (e.g. MapModes)
217 */
218 public void destroy() {
219 MapView.removeLayerChangeListener(this);
220 dialogsPanel.destroy();
221 for (int i = 0; i < toolBarActions.getComponentCount(); ++i) {
222 if (toolBarActions.getComponent(i) instanceof Destroyable) {
223 ((Destroyable)toolBarActions.getComponent(i)).destroy();
224 }
225 }
226 for (int i = 0; i < toolBarToggle.getComponentCount(); ++i) {
227 if (toolBarToggle.getComponent(i) instanceof Destroyable) {
228 ((Destroyable)toolBarToggle.getComponent(i)).destroy();
229 }
230 }
231
232 // remove menu entries
233 Main.main.menu.viewMenu.setVisible(false);
234
235 // MapFrame gets destroyed when the last layer is removed, but the status line background
236 // thread that collects the information doesn't get destroyed automatically.
237 if(statusLine.thread != null) {
238 try {
239 statusLine.thread.interrupt();
240 } catch (Exception e) {
241 e.printStackTrace();
242 }
243 }
244 mapView.destroy();
245 }
246
247 public Action getDefaultButtonAction() {
248 return ((AbstractButton)toolBarActions.getComponent(0)).getAction();
249 }
250
251 /**
252 * Open all ToggleDialogs that have their preferences property set. Close all others.
253 */
254 public void initializeDialogsPane() {
255 dialogsPanel.initialize(allDialogs);
256 }
257
258 /**
259 * Call this to add new toggle dialogs to the left button-list
260 * @param dlg The toggle dialog. It must not be in the list already.
261 */
262 public IconToggleButton addToggleDialog(final ToggleDialog dlg) {
263 final IconToggleButton button = new IconToggleButton(dlg.getToggleAction());
264 button.addMouseListener(new PopupMenuLauncher(new JPopupMenu() {
265 {
266 add(new AbstractAction() {
267 {
268 putValue(NAME, tr("Hide this button"));
269 putValue(SHORT_DESCRIPTION, tr("Click the arrow at the bottom to show it again."));
270 }
271
272 @Override
273 public void actionPerformed(ActionEvent e) {
274 dlg.hideButton();
275 validateToolBarToggle();
276 }
277 });
278 }
279 }));
280 dlg.setButton(button);
281 if (button.isVisible()) {
282 toolBarToggle.add(button);
283 }
284 allDialogs.add(dlg);
285 if (dialogsPanel.initialized) {
286 dialogsPanel.add(dlg);
287 }
288 return button;
289 }
290
291 public void validateToolBarToggle() {
292 toolBarToggle.removeAll();
293 for (ToggleDialog dlg : allDialogs) {
294 if (dlg.getButton().isVisible()) {
295 toolBarToggle.add(dlg.getButton());
296 }
297 }
298 }
299
300 public void addMapMode(IconToggleButton b) {
301 toolBarActions.add(b);
302 toolGroup.add(b);
303 if (b.getAction() instanceof MapMode) {
304 mapModes.add((MapMode) b.getAction());
305 } else
306 throw new IllegalArgumentException("MapMode action must be subclass of MapMode");
307 }
308
309 /**
310 * Fires an property changed event "visible".
311 */
312 @Override public void setVisible(boolean aFlag) {
313 boolean old = isVisible();
314 super.setVisible(aFlag);
315 if (old != aFlag) {
316 firePropertyChange("visible", old, aFlag);
317 }
318 }
319
320 /**
321 * Change the operating map mode for the view. Will call unregister on the
322 * old MapMode and register on the new one. Now this function also verifies
323 * if new map mode is correct mode for current layer and does not change mode
324 * in such cases.
325 * @param mapMode The new mode to set.
326 */
327 public void selectMapMode(MapMode newMapMode) {
328 selectMapMode(newMapMode, mapView.getActiveLayer());
329 }
330
331 /**
332 * Another version of the selectMapMode for changing layer action.
333 * Pass newly selected layer to this method.
334 * @param newMapMode
335 * @param newLayer
336 */
337 public void selectMapMode(MapMode newMapMode, Layer newLayer) {
338 if (newMapMode == null || !newMapMode.layerIsSupported(newLayer))
339 return;
340
341 MapMode oldMapMode = this.mapMode;
342 if (newMapMode == oldMapMode)
343 return;
344 if (oldMapMode != null) {
345 oldMapMode.exitMode();
346 }
347 this.mapMode = newMapMode;
348 newMapMode.enterMode();
349 lastMapMode.put(newLayer, newMapMode);
350 fireMapModeChanged(oldMapMode, newMapMode);
351 }
352
353 /**
354 * Fill the given panel by adding all necessary components to the different
355 * locations.
356 *
357 * @param panel The container to fill. Must have an BorderLayout.
358 */
359 public void fillPanel(Container panel) {
360 panel.add(this, BorderLayout.CENTER);
361 JToolBar jb = new JToolBar(JToolBar.VERTICAL);
362 jb.setFloatable(false);
363 toolBarActions.setAlignmentX(0.5f);
364 jb.add(toolBarActions);
365
366 jb.addSeparator(new Dimension(0,18));
367 toolBarToggle.setAlignmentX(0.5f);
368 jb.add(toolBarToggle);
369 otherButton.setAlignmentX(0.5f);
370 otherButton.setBorder(null);
371 otherButton.setFont(otherButton.getFont().deriveFont(Font.PLAIN));
372 jb.add(otherButton);
373
374 if(Main.pref.getBoolean("sidetoolbar.visible", true))
375 {
376 if(Main.pref.getBoolean("sidetoolbar.scrollable", true)) {
377 final ScrollViewport svp = new ScrollViewport(jb, ScrollViewport.VERTICAL_DIRECTION);
378 panel.add(svp, BorderLayout.WEST);
379 jb.addMouseWheelListener(new MouseWheelListener() {
380 public void mouseWheelMoved(MouseWheelEvent e) {
381 svp.scroll(0,e.getUnitsToScroll() * 5);
382 }
383 });
384 } else {
385 panel.add(jb, BorderLayout.WEST);
386 }
387 }
388 if (statusLine != null && Main.pref.getBoolean("statusline.visible", true)) {
389 panel.add(statusLine, BorderLayout.SOUTH);
390 }
391 }
392
393 class OtherButtonsAction extends AbstractAction {
394
395 public OtherButtonsAction() {
396 putValue(NAME, ">>");
397 }
398
399 @Override
400 public void actionPerformed(ActionEvent e) {
401 JPopupMenu menu = new JPopupMenu();
402 for (final ToggleDialog t : allDialogs) {
403 menu.add(new JCheckBoxMenuItem(new AbstractAction() {
404 {
405 putValue(NAME, t.getToggleAction().getValue(NAME));
406 putValue(SMALL_ICON, t.getToggleAction().getValue(SMALL_ICON));
407 putValue(SELECTED_KEY, !t.isButtonHidden());
408 putValue(SHORT_DESCRIPTION, tr("Hide or show this toggle button"));
409 }
410 @Override
411 public void actionPerformed(ActionEvent e) {
412 if ((Boolean) getValue(SELECTED_KEY)) {
413 t.showButton();
414 validateToolBarToggle();
415 } else {
416 t.hideButton();
417 validateToolBarToggle();
418 }
419 }
420 }));
421 }
422 Rectangle bounds = otherButton.getBounds();
423 menu.show(otherButton, bounds.x+bounds.width, 0);
424 }
425 }
426
427 /**
428 * Replies the instance of a toggle dialog of type <code>type</code> managed by this
429 * map frame
430 *
431 * @param <T>
432 * @param type the class of the toggle dialog, i.e. UserListDialog.class
433 * @return the instance of a toggle dialog of type <code>type</code> managed by this
434 * map frame; null, if no such dialog exists
435 *
436 */
437 public <T> T getToggleDialog(Class<T> type) {
438 return dialogsPanel.getToggleDialog(type);
439 }
440
441 /**
442 * Returns the current width of the (possibly resized) toggle dialog area
443 */
444 public int getToggleDlgWidth() {
445 return dialogsPanel.getWidth();
446 }
447
448 /**
449 * Interface to notify listeners of the change of the mapMode.
450 */
451 public interface MapModeChangeListener {
452 void mapModeChange(MapMode oldMapMode, MapMode newMapMode);
453 }
454
455 /**
456 * the mapMode listeners
457 */
458 private static final CopyOnWriteArrayList<MapModeChangeListener> mapModeChangeListeners = new CopyOnWriteArrayList<MapModeChangeListener>();
459 /**
460 * Adds a mapMode change listener
461 *
462 * @param listener the listener. Ignored if null or already registered.
463 */
464 public static void addMapModeChangeListener(MapModeChangeListener listener) {
465 if (listener != null) {
466 mapModeChangeListeners.addIfAbsent(listener);
467 }
468 }
469 /**
470 * Removes a mapMode change listener
471 *
472 * @param listener the listener. Ignored if null or already registered.
473 */
474 public static void removeMapModeChangeListener(MapModeChangeListener listener) {
475 mapModeChangeListeners.remove(listener);
476 }
477
478 protected static void fireMapModeChanged(MapMode oldMapMode, MapMode newMapMode) {
479 for (MapModeChangeListener l : mapModeChangeListeners) {
480 l.mapModeChange(oldMapMode, newMapMode);
481 }
482 }
483
484 @Override
485 public void activeLayerChange(Layer oldLayer, Layer newLayer) {
486 boolean modeChanged = false;
487 if (mapMode == null || !mapMode.layerIsSupported(newLayer)) {
488 MapMode newMapMode = lastMapMode.get(newLayer);
489 modeChanged = newMapMode != mapMode;
490 if (newMapMode != null) {
491 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
492 } else {
493 mapMode.exitMode(); // if new mode is null - simply exit from previous mode
494 }
495 }
496 if (!modeChanged && mapMode != null) {
497 // Let mapmodes know about new active layer
498 mapMode.exitMode();
499 mapMode.enterMode();
500 }
501 // invalidate repaint cache
502 Main.map.mapView.preferenceChanged(null);
503 }
504
505 @Override
506 public void layerAdded(Layer newLayer) { }
507
508 @Override
509 public void layerRemoved(Layer oldLayer) {
510 lastMapMode.remove(oldLayer);
511 }
512}
Note: See TracBrowser for help on using the repository browser.