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

Last change on this file since 4339 was 4134, checked in by bastiK, 13 years ago

see #67 - new parallel way drawing mode (patch by Ole Jørgen Brønner)

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