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

Last change on this file since 3872 was 3855, checked in by bastiK, 13 years ago

Extended mappaint style dialog. No longer required to restart JOSM for changes in mappaint preferences.

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