001/* An action for moving an object up or down in its list.
002
003 Copyright (c) 2004-2016 The Regents of the University of California.
004 All rights reserved.
005 Permission is hereby granted, without written agreement and without
006 license or royalty fees, to use, copy, modify, and distribute this
007 software and its documentation for any purpose, provided that the above
008 copyright notice and the following two paragraphs appear in all copies
009 of this software.
010
011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
015 SUCH DAMAGE.
016
017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
022 ENHANCEMENTS, OR MODIFICATIONS.
023
024 PT_COPYRIGHT_VERSION_2
025 COPYRIGHTENDKEY
026
027 */
028package ptolemy.vergil.toolbox;
029
030import java.awt.Event;
031import java.awt.event.ActionEvent;
032import java.awt.event.KeyEvent;
033import java.util.Iterator;
034import java.util.LinkedList;
035import java.util.List;
036import java.util.ListIterator;
037
038import javax.swing.KeyStroke;
039
040import diva.gui.GUIUtilities;
041import ptolemy.kernel.undo.UndoAction;
042import ptolemy.kernel.undo.UndoStackAttribute;
043import ptolemy.kernel.util.ChangeRequest;
044import ptolemy.kernel.util.IllegalActionException;
045import ptolemy.kernel.util.InternalErrorException;
046import ptolemy.kernel.util.NamedObj;
047import ptolemy.util.MessageHandler;
048
049///////////////////////////////////////////////////////////////////
050//// MoveAction
051
052/**
053 An action to move an object up or down in its list.
054 This can be used, for example, to move icon elements towards
055 the foreground or to control the order in which attributes
056 or ports appear.
057
058 @author Edward A. Lee
059 @version $Id$
060 @since Ptolemy II 4.1
061 @Pt.ProposedRating Yellow (eal)
062 @Pt.AcceptedRating Red (johnr)
063 */
064@SuppressWarnings("serial")
065public class MoveAction extends FigureAction {
066    /** Construct a new action. The type of move is specified by
067     *  the public fields DOWN, TO_FIRST, TO_LAST, and UP.
068     *  @param description A description.
069     *  @param type Indicator of the type of move.
070     */
071    public MoveAction(String description, MoveType type) {
072        super(description);
073        _type = type;
074        // Add key bindings.
075        // FIXME: these bindings are also set in vergil.basic.BasicGraphFrame.
076
077        // Front and back are a little confusing here.  TO_FIRST means
078        // that the element is first in the moml file, which means it is
079        // drawn first and then obscured.
080
081        if (_type == TO_FIRST) {
082            putValue(GUIUtilities.ACCELERATOR_KEY,
083                    KeyStroke.getKeyStroke(KeyEvent.VK_B, Event.CTRL_MASK));
084        } else if (_type == TO_LAST) {
085            putValue(GUIUtilities.ACCELERATOR_KEY,
086                    KeyStroke.getKeyStroke(KeyEvent.VK_F, Event.CTRL_MASK));
087        }
088    }
089
090    ///////////////////////////////////////////////////////////////////
091    ////                         public variables                  ////
092
093    /** Indicator for move down. */
094    public static final MoveType DOWN = new MoveType();
095
096    /** Indicator for move to first. */
097    public static final MoveType TO_FIRST = new MoveType();
098
099    /** Indicator for move to last. */
100    public static final MoveType TO_LAST = new MoveType();
101
102    /** Indicator for move up. */
103    public static final MoveType UP = new MoveType();
104
105    ///////////////////////////////////////////////////////////////////
106    ////                         public methods                    ////
107
108    /** Perform the move action and register the undo action.
109     *  @param event The event.
110     */
111    @Override
112    public void actionPerformed(final ActionEvent event) {
113        // Determine which entity was selected for the look inside action.
114        super.actionPerformed(event);
115
116        final NamedObj target = getTarget();
117
118        if (target == null) {
119            return;
120        }
121
122        if (target.getDerivedLevel() < Integer.MAX_VALUE) {
123            MessageHandler.error(
124                    "Cannot change the position of " + target.getFullName()
125                            + " because the position is set by the class.");
126            return;
127        }
128
129        ChangeRequest request = new ChangeRequest(target, "Move towards last") {
130            @Override
131            protected void _execute() throws IllegalActionException {
132                // Static method takes a list, so we construct a
133                // list with one element.
134                LinkedList targets = new LinkedList();
135                targets.add(target);
136                move(targets, _type, target);
137            }
138        };
139
140        target.requestChange(request);
141    }
142
143    /** Move the objects in the specified list up or down in the list
144     *  of similar objects in their container, as specified by the move type.
145     *  If the type is TO_FIRST or UP, then
146     *  the objects in the specified list are processed in reverse order,
147     *  under the assumption that they will already be sorted into the order
148     *  in which they appear in the list of similar objects in their container.
149     *  This is factored out as a separate static method so
150     *  that it can be called in the redo action and so that it can be
151     *  used elsewhere.  The context is what is used to register an
152     *  undo action. It should be a common container, or if there is
153     *  only one target, then the target itself.
154     *  @param targets The list of objects to move.
155     *  @param type One of DOWN, TO_FIRST, TO_LAST, and UP.
156     *  @param context The context.
157     */
158    public static void move(final List targets, final MoveType type,
159            final NamedObj context) {
160        final int[] priorIndexes = new int[targets.size()];
161        boolean movedOne = false;
162
163        try {
164            if (type == TO_FIRST || type == UP) {
165                // Traverse the list in reverse order.
166                ListIterator targetIterator = targets
167                        .listIterator(targets.size());
168
169                for (int i = targets.size() - 1; i >= 0; i--) {
170                    NamedObj target = (NamedObj) targetIterator.previous();
171
172                    if (type == DOWN) {
173                        priorIndexes[i] = target.moveDown();
174                    } else if (type == TO_FIRST) {
175                        priorIndexes[i] = target.moveToFirst();
176                    } else if (type == TO_LAST) {
177                        priorIndexes[i] = target.moveToLast();
178                    } else {
179                        priorIndexes[i] = target.moveUp();
180                    }
181
182                    if (priorIndexes[i] >= 0) {
183                        movedOne = true;
184                    }
185                }
186            } else {
187                // Traverse the list in forward order.
188                Iterator targetIterator = targets.iterator();
189
190                for (int i = 0; i < targets.size(); i++) {
191                    NamedObj target;
192                    target = (NamedObj) targetIterator.next();
193
194                    if (type == DOWN) {
195                        priorIndexes[i] = target.moveDown();
196                    } else if (type == TO_FIRST) {
197                        priorIndexes[i] = target.moveToFirst();
198                    } else if (type == TO_LAST) {
199                        priorIndexes[i] = target.moveToLast();
200                    } else {
201                        priorIndexes[i] = target.moveUp();
202                    }
203
204                    if (priorIndexes[i] >= 0) {
205                        movedOne = true;
206                    }
207                }
208            }
209        } catch (IllegalActionException e) {
210            // This should only be thrown if the target
211            // has no container, which in theory is not
212            // possible.
213            throw new InternalErrorException(e);
214        }
215
216        if (!movedOne) {
217            // Do not generate any undo action if no move happened.
218            return;
219        }
220
221        UndoAction undoAction = new UndoAction() {
222            @Override
223            public void execute() {
224                try {
225                    // Undo has to reverse the order of the do.
226                    if (type == TO_FIRST || type == UP) {
227                        // Traverse the list in forward order.
228                        Iterator targetIterator = targets.iterator();
229
230                        for (int i = 0; i < targets.size(); i++) {
231                            NamedObj target = (NamedObj) targetIterator.next();
232                            target.moveToIndex(priorIndexes[i]);
233                        }
234                    } else {
235                        // Traverse the list in reverse order.
236                        ListIterator targetIterator = targets
237                                .listIterator(targets.size());
238
239                        for (int i = targets.size() - 1; i >= 0; i--) {
240                            NamedObj target = (NamedObj) targetIterator
241                                    .previous();
242                            target.moveToIndex(priorIndexes[i]);
243                        }
244                    }
245                } catch (IllegalActionException e) {
246                    // This should only be thrown if the target
247                    // has no container, which in theory is not
248                    // possible.
249                    throw new InternalErrorException(e);
250                }
251
252                // Create redo action.
253                UndoAction redoAction = new UndoAction() {
254                    @Override
255                    public void execute() {
256                        move(targets, type, context);
257                    }
258                };
259
260                UndoStackAttribute undoInfo = UndoStackAttribute
261                        .getUndoInfo(context);
262                undoInfo.push(redoAction);
263            }
264        };
265
266        UndoStackAttribute undoInfo = UndoStackAttribute.getUndoInfo(context);
267        undoInfo.push(undoAction);
268    }
269
270    ///////////////////////////////////////////////////////////////////
271    ////                         private variables                 ////
272
273    /** The type of move. */
274    private MoveType _type;
275
276    ///////////////////////////////////////////////////////////////////
277    ////                         inner classes                     ////
278
279    /** For a type-safe enumeration. */
280    private static class MoveType {
281    }
282}