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

Last change on this file since 3262 was 3262, checked in by bastiK, 14 years ago

extended command list dialog; added inspection panel

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