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

Last change on this file since 3715 was 3669, checked in by bastiK, 13 years ago

add validator plugin to josm core. Original author: Francisco R. Santos (frsantos); major contributions by bilbo, daeron, delta_foxtrot, imi, jttt, jrreid, gabriel, guggis, pieren, rrankin, skela, stoecker, stotz and others

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