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

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

fix missing repaint after active layer change

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