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

Last change on this file since 13660 was 12866, checked in by Don-vip, 7 years ago

SonarQube:

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