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

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

Sonar/Findbugs - Unused formal parameter

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