Changeset 3262 in josm for trunk/src/org/openstreetmap/josm/gui/dialogs/CommandStackDialog.java
- Timestamp:
- 2010-05-18T23:43:52+02:00 (14 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/gui/dialogs/CommandStackDialog.java
r2224 r3262 6 6 import java.awt.BorderLayout; 7 7 import java.awt.Component; 8 import java.awt.Dimension; 9 import java.awt.GridBagLayout; 10 import java.awt.Point; 11 import java.awt.event.ActionEvent; 8 12 import java.awt.event.KeyEvent; 9 import java.util.Collection; 10 13 import java.awt.event.MouseEvent; 14 15 import java.util.ArrayList; 16 import java.util.LinkedHashSet; 17 import java.util.List; 18 import java.util.Set; 19 20 import javax.swing.AbstractAction; 21 import javax.swing.Box; 11 22 import javax.swing.JLabel; 23 import javax.swing.JPanel; 24 import javax.swing.JPopupMenu; 12 25 import javax.swing.JScrollPane; 26 import javax.swing.JSeparator; 13 27 import javax.swing.JTree; 28 import javax.swing.event.TreeModelEvent; 29 import javax.swing.event.TreeModelListener; 30 import javax.swing.event.TreeSelectionEvent; 31 import javax.swing.event.TreeSelectionListener; 14 32 import javax.swing.tree.DefaultMutableTreeNode; 15 33 import javax.swing.tree.DefaultTreeCellRenderer; 16 34 import javax.swing.tree.DefaultTreeModel; 35 import javax.swing.tree.TreePath; 36 import javax.swing.tree.TreeSelectionModel; 17 37 18 38 import org.openstreetmap.josm.Main; 19 39 import org.openstreetmap.josm.command.Command; 40 import org.openstreetmap.josm.command.PseudoCommand; 41 import org.openstreetmap.josm.data.osm.DatasetCollection; 42 import org.openstreetmap.josm.data.osm.OsmPrimitive; 20 43 import org.openstreetmap.josm.gui.MapFrame; 44 import org.openstreetmap.josm.gui.SideButton; 45 import org.openstreetmap.josm.gui.layer.OsmDataLayer; 21 46 import org.openstreetmap.josm.gui.layer.OsmDataLayer.CommandQueueListener; 47 import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher; 48 import org.openstreetmap.josm.tools.GBC; 49 import org.openstreetmap.josm.tools.ImageProvider; 50 import org.openstreetmap.josm.tools.Predicate; 22 51 import org.openstreetmap.josm.tools.Shortcut; 23 52 24 53 public class CommandStackDialog extends ToggleDialog implements CommandQueueListener { 25 54 26 private DefaultTreeModel treeModel = new DefaultTreeModel(new DefaultMutableTreeNode()); 27 private JTree tree = new JTree(treeModel); 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; 28 72 29 73 public CommandStackDialog(final MapFrame mapFrame) { … … 32 76 Main.main.undoRedo.listenerCommands.add(this); 33 77 34 tree.setRootVisible(false); 35 tree.setShowsRootHandles(true); 36 tree.expandRow(0); 37 tree.setCellRenderer(new DefaultTreeCellRenderer(){ 38 @Override public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) { 39 super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus); 40 DefaultMutableTreeNode v = (DefaultMutableTreeNode)value; 41 if (v.getUserObject() instanceof JLabel) { 42 JLabel l = (JLabel)v.getUserObject(); 43 setIcon(l.getIcon()); 44 setText(l.getText()); 45 } 46 return this; 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(); 47 188 } 48 189 }); 49 tree.setVisibleRowCount(8); 50 add(new JScrollPane(tree), BorderLayout.CENTER); 51 } 52 53 @Override public void setVisible(boolean v) { 54 if (v) { 55 buildList(); 56 } else if (tree != null) { 57 treeModel.setRoot(new DefaultMutableTreeNode()); 58 } 59 super.setVisible(v); 60 } 61 62 private void buildList() { 63 if(Main.main.undoRedo.commands.size() != 0) { 64 setTitle(tr("Command Stack: {0}", Main.main.undoRedo.commands.size())); 65 } else { 66 setTitle(tr("Command Stack")); 67 } 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")); 68 243 if (Main.map == null || Main.map.mapView == null || Main.map.mapView.getEditLayer() == null) 69 244 return; 70 Collection<Command> commands = Main.main.undoRedo.commands; 71 DefaultMutableTreeNode root = new DefaultMutableTreeNode(); 72 for (Command c : commands) { 73 root.add(c.description()); 74 } 75 treeModel.setRoot(root); 76 tree.scrollRowToVisible(treeModel.getChildCount(root)-1); 77 } 78 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 79 310 public void commandChanged(int queueSize, int redoSize) { 80 311 if (!isVisible()) 81 312 return; 82 treeModel.setRoot(new DefaultMutableTreeNode()); 83 buildList(); 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 } 84 455 } 85 456 }
Note:
See TracChangeset
for help on using the changeset viewer.