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

Last change on this file since 8387 was 8387, checked in by Don-vip, 9 years ago

fix some Findbugs warnings

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