Ignore:
Timestamp:
2010-05-18T23:43:52+02:00 (14 years ago)
Author:
bastiK
Message:

extended command list dialog; added inspection panel

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/gui/dialogs/CommandStackDialog.java

    r2224 r3262  
    66import java.awt.BorderLayout;
    77import java.awt.Component;
     8import java.awt.Dimension;
     9import java.awt.GridBagLayout;
     10import java.awt.Point;
     11import java.awt.event.ActionEvent;
    812import java.awt.event.KeyEvent;
    9 import java.util.Collection;
    10 
     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;
    1122import javax.swing.JLabel;
     23import javax.swing.JPanel;
     24import javax.swing.JPopupMenu;
    1225import javax.swing.JScrollPane;
     26import javax.swing.JSeparator;
    1327import javax.swing.JTree;
     28import javax.swing.event.TreeModelEvent;
     29import javax.swing.event.TreeModelListener;
     30import javax.swing.event.TreeSelectionEvent;
     31import javax.swing.event.TreeSelectionListener;
    1432import javax.swing.tree.DefaultMutableTreeNode;
    1533import javax.swing.tree.DefaultTreeCellRenderer;
    1634import javax.swing.tree.DefaultTreeModel;
     35import javax.swing.tree.TreePath;
     36import javax.swing.tree.TreeSelectionModel;
    1737
    1838import org.openstreetmap.josm.Main;
    1939import 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;
    2043import org.openstreetmap.josm.gui.MapFrame;
     44import org.openstreetmap.josm.gui.SideButton;
     45import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    2146import 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;
    2251import org.openstreetmap.josm.tools.Shortcut;
    2352
    2453public class CommandStackDialog extends ToggleDialog implements CommandQueueListener {
    2554
    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;
    2872
    2973    public CommandStackDialog(final MapFrame mapFrame) {
     
    3276        Main.main.undoRedo.listenerCommands.add(this);
    3377
    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();
    47188            }
    48189        });
    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"));
    68243        if (Main.map == null || Main.map.mapView == null || Main.map.mapView.getEditLayer() == null)
    69244            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
    79310    public void commandChanged(int queueSize, int redoSize) {
    80311        if (!isVisible())
    81312            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        }
    84455    }
    85456}
Note: See TracChangeset for help on using the changeset viewer.