source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/CommandStackDialog.java@ 6070

Last change on this file since 6070 was 6070, checked in by stoecker, 11 years ago

see #8853 remove tabs, trailing spaces, windows line ends, strange characters

  • Property svn:eol-style set to native
File size: 17.5 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.gui.dialogs;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Component;
7import java.awt.Dimension;
8import java.awt.GridBagLayout;
9import java.awt.event.ActionEvent;
10import java.awt.event.KeyEvent;
11import java.awt.event.MouseEvent;
12import java.util.ArrayList;
13import java.util.Arrays;
14import java.util.LinkedHashSet;
15import java.util.List;
16import java.util.Set;
17
18import javax.swing.AbstractAction;
19import javax.swing.Box;
20import javax.swing.JComponent;
21import javax.swing.JLabel;
22import javax.swing.JPanel;
23import javax.swing.JPopupMenu;
24import javax.swing.JScrollPane;
25import javax.swing.JSeparator;
26import javax.swing.JTree;
27import javax.swing.event.TreeModelEvent;
28import javax.swing.event.TreeModelListener;
29import javax.swing.event.TreeSelectionEvent;
30import javax.swing.event.TreeSelectionListener;
31import javax.swing.tree.DefaultMutableTreeNode;
32import javax.swing.tree.DefaultTreeCellRenderer;
33import javax.swing.tree.DefaultTreeModel;
34import javax.swing.tree.TreePath;
35import javax.swing.tree.TreeSelectionModel;
36
37import org.openstreetmap.josm.Main;
38import org.openstreetmap.josm.actions.AutoScaleAction;
39import org.openstreetmap.josm.command.Command;
40import org.openstreetmap.josm.command.PseudoCommand;
41import org.openstreetmap.josm.data.osm.OsmPrimitive;
42import org.openstreetmap.josm.gui.MapFrame;
43import org.openstreetmap.josm.gui.SideButton;
44import org.openstreetmap.josm.gui.layer.OsmDataLayer;
45import org.openstreetmap.josm.gui.layer.OsmDataLayer.CommandQueueListener;
46import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
47import org.openstreetmap.josm.tools.FilteredCollection;
48import org.openstreetmap.josm.tools.GBC;
49import org.openstreetmap.josm.tools.ImageProvider;
50import org.openstreetmap.josm.tools.InputMapUtils;
51import org.openstreetmap.josm.tools.Predicate;
52import org.openstreetmap.josm.tools.Shortcut;
53
54public class CommandStackDialog extends ToggleDialog implements CommandQueueListener {
55
56 private DefaultTreeModel undoTreeModel = new DefaultTreeModel(new DefaultMutableTreeNode());
57 private DefaultTreeModel redoTreeModel = new DefaultTreeModel(new DefaultMutableTreeNode());
58
59 private JTree undoTree = new JTree(undoTreeModel);
60 private JTree redoTree = new JTree(redoTreeModel);
61
62 private UndoRedoSelectionListener undoSelectionListener;
63 private UndoRedoSelectionListener redoSelectionListener;
64
65 private JScrollPane scrollPane;
66 private JSeparator separator = new JSeparator();
67 // only visible, if separator is the top most component
68 private Component spacer = Box.createRigidArea(new Dimension(0, 3));
69
70 // last operation is remembered to select the next undo/redo entry in the list
71 // after undo/redo command
72 private UndoRedoType lastOperation = UndoRedoType.UNDO;
73
74 // Actions for context menu and Enter key
75 private SelectAction selectAction = new SelectAction();
76 private SelectAndZoomAction selectAndZoomAction = new SelectAndZoomAction();
77
78 public CommandStackDialog(final MapFrame mapFrame) {
79 super(tr("Command Stack"), "commandstack", tr("Open a list of all commands (undo buffer)."),
80 Shortcut.registerShortcut("subwindow:commandstack", tr("Toggle: {0}",
81 tr("Command Stack")), KeyEvent.VK_O, Shortcut.ALT_SHIFT), 100, true);
82 undoTree.addMouseListener(new MouseEventHandler());
83 undoTree.setRootVisible(false);
84 undoTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
85 undoTree.setShowsRootHandles(true);
86 undoTree.expandRow(0);
87 undoTree.setCellRenderer(new CommandCellRenderer());
88 undoSelectionListener = new UndoRedoSelectionListener(undoTree);
89 undoTree.getSelectionModel().addTreeSelectionListener(undoSelectionListener);
90 InputMapUtils.unassignCtrlShiftUpDown(undoTree, JComponent.WHEN_FOCUSED);
91
92 redoTree.addMouseListener(new MouseEventHandler());
93 redoTree.setRootVisible(false);
94 redoTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
95 redoTree.setShowsRootHandles(true);
96 redoTree.expandRow(0);
97 redoTree.setCellRenderer(new CommandCellRenderer());
98 redoSelectionListener = new UndoRedoSelectionListener(redoTree);
99 redoTree.getSelectionModel().addTreeSelectionListener(redoSelectionListener);
100
101 JPanel treesPanel = new JPanel(new GridBagLayout());
102
103 treesPanel.add(spacer, GBC.eol());
104 spacer.setVisible(false);
105 treesPanel.add(undoTree, GBC.eol().fill(GBC.HORIZONTAL));
106 separator.setVisible(false);
107 treesPanel.add(separator, GBC.eol().fill(GBC.HORIZONTAL));
108 treesPanel.add(redoTree, GBC.eol().fill(GBC.HORIZONTAL));
109 treesPanel.add(Box.createRigidArea(new Dimension(0, 0)), GBC.std().weight(0, 1));
110 treesPanel.setBackground(redoTree.getBackground());
111
112 wireUpdateEnabledStateUpdater(selectAction, undoTree);
113 wireUpdateEnabledStateUpdater(selectAction, redoTree);
114
115 UndoRedoAction undoAction = new UndoRedoAction(UndoRedoType.UNDO);
116 wireUpdateEnabledStateUpdater(undoAction, undoTree);
117
118 UndoRedoAction redoAction = new UndoRedoAction(UndoRedoType.REDO);
119 wireUpdateEnabledStateUpdater(redoAction, redoTree);
120
121 scrollPane = (JScrollPane)createLayout(treesPanel, true, Arrays.asList(new SideButton[] {
122 new SideButton(selectAction),
123 new SideButton(undoAction),
124 new SideButton(redoAction)
125 }));
126
127 InputMapUtils.addEnterAction(undoTree, selectAndZoomAction);
128 InputMapUtils.addEnterAction(redoTree, selectAndZoomAction);
129 }
130
131 private static class CommandCellRenderer extends DefaultTreeCellRenderer {
132 @Override public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
133 super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
134 DefaultMutableTreeNode v = (DefaultMutableTreeNode)value;
135 if (v.getUserObject() instanceof JLabel) {
136 JLabel l = (JLabel)v.getUserObject();
137 setIcon(l.getIcon());
138 setText(l.getText());
139 }
140 return this;
141 }
142 }
143
144 /**
145 * Selection listener for undo and redo area.
146 * If one is clicked, takes away the selection from the other, so
147 * it behaves as if it was one component.
148 */
149 private class UndoRedoSelectionListener implements TreeSelectionListener {
150 private JTree source;
151
152 public UndoRedoSelectionListener(JTree source) {
153 this.source = source;
154 }
155
156 public void valueChanged(TreeSelectionEvent e) {
157 if (source == undoTree) {
158 redoTree.getSelectionModel().removeTreeSelectionListener(redoSelectionListener);
159 redoTree.clearSelection();
160 redoTree.getSelectionModel().addTreeSelectionListener(redoSelectionListener);
161 }
162 if (source == redoTree) {
163 undoTree.getSelectionModel().removeTreeSelectionListener(undoSelectionListener);
164 undoTree.clearSelection();
165 undoTree.getSelectionModel().addTreeSelectionListener(undoSelectionListener);
166 }
167 }
168 }
169
170 /**
171 * Interface to provide a callback for enabled state update.
172 */
173 protected interface IEnabledStateUpdating {
174 void updateEnabledState();
175 }
176
177 /**
178 * Wires updater for enabled state to the events.
179 */
180 protected void wireUpdateEnabledStateUpdater(final IEnabledStateUpdating updater, JTree tree) {
181 addShowNotifyListener(updater);
182
183 tree.addTreeSelectionListener(new TreeSelectionListener() {
184 public void valueChanged(TreeSelectionEvent e) {
185 updater.updateEnabledState();
186 }
187 });
188
189 tree.getModel().addTreeModelListener(new TreeModelListener() {
190 public void treeNodesChanged(TreeModelEvent e) {
191 updater.updateEnabledState();
192 }
193
194 public void treeNodesInserted(TreeModelEvent e) {
195 updater.updateEnabledState();
196 }
197
198 public void treeNodesRemoved(TreeModelEvent e) {
199 updater.updateEnabledState();
200 }
201
202 public void treeStructureChanged(TreeModelEvent e) {
203 updater.updateEnabledState();
204 }
205 });
206 }
207
208 @Override
209 public void showNotify() {
210 buildTrees();
211 for (IEnabledStateUpdating listener : showNotifyListener) {
212 listener.updateEnabledState();
213 }
214 Main.main.undoRedo.addCommandQueueListener(this);
215 }
216
217 /**
218 * Simple listener setup to update the button enabled state when the side dialog shows.
219 */
220 Set<IEnabledStateUpdating> showNotifyListener = new LinkedHashSet<IEnabledStateUpdating>();
221
222 private void addShowNotifyListener(IEnabledStateUpdating listener) {
223 showNotifyListener.add(listener);
224 }
225
226 @Override
227 public void hideNotify() {
228 undoTreeModel.setRoot(new DefaultMutableTreeNode());
229 redoTreeModel.setRoot(new DefaultMutableTreeNode());
230 Main.main.undoRedo.removeCommandQueueListener(this);
231 }
232
233 /**
234 * Build the trees of undo and redo commands (initially or when
235 * they have changed).
236 */
237 private void buildTrees() {
238 setTitle(tr("Command Stack"));
239 if (Main.map == null || Main.map.mapView == null || Main.map.mapView.getEditLayer() == null)
240 return;
241
242 List<Command> undoCommands = Main.main.undoRedo.commands;
243 DefaultMutableTreeNode undoRoot = new DefaultMutableTreeNode();
244 for (int i=0; i<undoCommands.size(); ++i) {
245 undoRoot.add(getNodeForCommand(undoCommands.get(i), i));
246 }
247 undoTreeModel.setRoot(undoRoot);
248
249 List<Command> redoCommands = Main.main.undoRedo.redoCommands;
250 DefaultMutableTreeNode redoRoot = new DefaultMutableTreeNode();
251 for (int i=0; i<redoCommands.size(); ++i) {
252 redoRoot.add(getNodeForCommand(redoCommands.get(i), i));
253 }
254 redoTreeModel.setRoot(redoRoot);
255 if (redoTreeModel.getChildCount(redoRoot) > 0) {
256 redoTree.scrollRowToVisible(0);
257 scrollPane.getHorizontalScrollBar().setValue(0);
258 }
259
260 separator.setVisible(!undoCommands.isEmpty() || !redoCommands.isEmpty());
261 spacer.setVisible(undoCommands.isEmpty() && !redoCommands.isEmpty());
262
263 // if one tree is empty, move selection to the other
264 switch (lastOperation) {
265 case UNDO:
266 if (undoCommands.isEmpty()) {
267 lastOperation = UndoRedoType.REDO;
268 }
269 break;
270 case REDO:
271 if (redoCommands.isEmpty()) {
272 lastOperation = UndoRedoType.UNDO;
273 }
274 break;
275 }
276
277 // select the next command to undo/redo
278 switch (lastOperation) {
279 case UNDO:
280 undoTree.setSelectionRow(undoTree.getRowCount()-1);
281 break;
282 case REDO:
283 redoTree.setSelectionRow(0);
284 break;
285 }
286
287 undoTree.scrollRowToVisible(undoTreeModel.getChildCount(undoRoot)-1);
288 scrollPane.getHorizontalScrollBar().setValue(0);
289 }
290
291 /**
292 * Wraps a command in a CommandListMutableTreeNode.
293 * Recursively adds child commands.
294 */
295 protected CommandListMutableTreeNode getNodeForCommand(PseudoCommand c, int idx) {
296 CommandListMutableTreeNode node = new CommandListMutableTreeNode(c, idx);
297 if (c.getChildren() != null) {
298 List<PseudoCommand> children = new ArrayList<PseudoCommand>(c.getChildren());
299 for (int i=0; i<children.size(); ++i) {
300 node.add(getNodeForCommand(children.get(i), i));
301 }
302 }
303 return node;
304 }
305
306 /**
307 * Return primitives that are affected by some command
308 * @param path GUI elements
309 * @return collection of affected primitives, onluy usable ones
310 */
311 protected static FilteredCollection<OsmPrimitive> getAffectedPrimitives(TreePath path) {
312 PseudoCommand c = ((CommandListMutableTreeNode) path.getLastPathComponent()).getCommand();
313 final OsmDataLayer currentLayer = Main.map.mapView.getEditLayer();
314 FilteredCollection<OsmPrimitive> prims = new FilteredCollection<OsmPrimitive>(
315 c.getParticipatingPrimitives(),
316 new Predicate<OsmPrimitive>(){
317 public boolean evaluate(OsmPrimitive o) {
318 OsmPrimitive p = currentLayer.data.getPrimitiveById(o);
319 return p != null && p.isUsable();
320 }
321 }
322 );
323 return prims;
324 }
325
326 public void commandChanged(int queueSize, int redoSize) {
327 if (!isVisible())
328 return;
329 buildTrees();
330 }
331
332 public class SelectAction extends AbstractAction implements IEnabledStateUpdating {
333
334 public SelectAction() {
335 super();
336 putValue(NAME,tr("Select"));
337 putValue(SHORT_DESCRIPTION, tr("Selects the objects that take part in this command (unless currently deleted)"));
338 putValue(SMALL_ICON, ImageProvider.get("dialogs","select"));
339
340 }
341
342 @Override
343 public void actionPerformed(ActionEvent e) {
344 TreePath path;
345 undoTree.getSelectionPath();
346 if (!undoTree.isSelectionEmpty()) {
347 path = undoTree.getSelectionPath();
348 } else if (!redoTree.isSelectionEmpty()) {
349 path = redoTree.getSelectionPath();
350 } else
351 throw new IllegalStateException();
352
353 if (Main.map == null || Main.map.mapView == null || Main.map.mapView.getEditLayer() == null) return;
354 Main.map.mapView.getEditLayer().data.setSelected( getAffectedPrimitives(path));
355 }
356
357 @Override
358 public void updateEnabledState() {
359 setEnabled(!undoTree.isSelectionEmpty() || !redoTree.isSelectionEmpty());
360 }
361
362 }
363
364 public class SelectAndZoomAction extends SelectAction {
365 public SelectAndZoomAction() {
366 super();
367 putValue(NAME,tr("Select and zoom"));
368 putValue(SHORT_DESCRIPTION, tr("Selects the objects that take part in this command (unless currently deleted), then and zooms to it"));
369 putValue(SMALL_ICON, ImageProvider.get("dialogs/autoscale","selection"));
370 }
371
372 @Override
373 public void actionPerformed(ActionEvent e) {
374 super.actionPerformed(e);
375 if (Main.map == null || Main.map.mapView == null || Main.map.mapView.getEditLayer() == null) return;
376 AutoScaleAction.autoScale("selection");
377 }
378 }
379
380 /**
381 * undo / redo switch to reduce duplicate code
382 */
383 protected enum UndoRedoType {UNDO, REDO}
384
385 /**
386 * Action to undo or redo all commands up to (and including) the seleced item.
387 */
388 protected class UndoRedoAction extends AbstractAction implements IEnabledStateUpdating {
389 private UndoRedoType type;
390 private JTree tree;
391
392 /**
393 * constructor
394 * @param type decide whether it is an undo action or a redo action
395 */
396 public UndoRedoAction(UndoRedoType type) {
397 super();
398 this.type = type;
399 switch (type) {
400 case UNDO:
401 tree = undoTree;
402 putValue(NAME,tr("Undo"));
403 putValue(SHORT_DESCRIPTION, tr("Undo the selected and all later commands"));
404 putValue(SMALL_ICON, ImageProvider.get("undo"));
405 break;
406 case REDO:
407 tree = redoTree;
408 putValue(NAME,tr("Redo"));
409 putValue(SHORT_DESCRIPTION, tr("Redo the selected and all earlier commands"));
410 putValue(SMALL_ICON, ImageProvider.get("redo"));
411 break;
412 }
413 }
414
415 public void actionPerformed(ActionEvent e) {
416 lastOperation = type;
417 TreePath path = tree.getSelectionPath();
418
419 // we can only undo top level commands
420 if (path.getPathCount() != 2)
421 throw new IllegalStateException();
422
423 int idx = ((CommandListMutableTreeNode) path.getLastPathComponent()).getIndex();
424
425 // calculate the number of commands to undo/redo; then do it
426 switch (type) {
427 case UNDO:
428 int numUndo = ((DefaultMutableTreeNode) undoTreeModel.getRoot()).getChildCount() - idx;
429 Main.main.undoRedo.undo(numUndo);
430 break;
431 case REDO:
432 int numRedo = idx+1;
433 Main.main.undoRedo.redo(numRedo);
434 break;
435 }
436 Main.map.repaint();
437 }
438
439 public void updateEnabledState() {
440 // do not allow execution if nothing is selected or a sub command was selected
441 setEnabled(!tree.isSelectionEmpty() && tree.getSelectionPath().getPathCount()==2);
442 }
443 }
444
445 class MouseEventHandler extends PopupMenuLauncher {
446
447 public MouseEventHandler() {
448 super(new CommandStackPopup());
449 }
450
451 @Override
452 public void mouseClicked(MouseEvent evt) {
453 if (isDoubleClick(evt)) {
454 selectAndZoomAction.actionPerformed(null);
455 }
456 }
457 }
458
459 private class CommandStackPopup extends JPopupMenu {
460 public CommandStackPopup(){
461 add(selectAction);
462 add(selectAndZoomAction);
463 }
464 }
465}
Note: See TracBrowser for help on using the repository browser.