001/*
002 * Copyright 2010-2015 Institut Pasteur.
003 * 
004 * This file is part of Icy.
005 * 
006 * Icy is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 * 
011 * Icy is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014 * GNU General Public License for more details.
015 * 
016 * You should have received a copy of the GNU General Public License
017 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
018 */
019package icy.undo;
020
021import icy.resource.ResourceUtil;
022import icy.resource.icon.IcyIcon;
023import icy.util.StringUtil;
024
025import java.awt.Image;
026
027import javax.swing.UIManager;
028import javax.swing.undo.CannotRedoException;
029import javax.swing.undo.CannotUndoException;
030import javax.swing.undo.UndoableEdit;
031
032/**
033 * Abstract Icy {@link UndoableEdit} class.
034 * 
035 * @author Stephane
036 */
037public abstract class AbstractIcyUndoableEdit implements IcyUndoableEdit
038{
039    protected static final IcyIcon DEFAULT_ICON = new IcyIcon(ResourceUtil.ICON_LIGHTING, 16);
040
041    /**
042     * Source of the UndoableEdit
043     */
044    protected Object source;
045
046    /**
047     * Defaults to true; becomes false if this edit is undone, true
048     * again if it is redone.
049     */
050    protected boolean hasBeenDone;
051
052    /**
053     * True if this edit has not received <code>die</code>; defaults
054     * to <code>true</code>.
055     */
056    protected boolean alive;
057
058    /**
059     * Used to recognize the edit in the undo manager panel
060     */
061    protected IcyIcon icon;
062
063    /**
064     * Representation name in the history panel
065     */
066    protected String presentationName;
067
068    /**
069     * Mergeable property of this edit
070     */
071    protected boolean mergeable;
072
073    /**
074     * Creates an <code>UndoableAction</code> which defaults <code>hasBeenDone</code> and
075     * <code>alive</code> to <code>true</code>.
076     */
077    public AbstractIcyUndoableEdit(Object source, String name, Image icon)
078    {
079        super();
080
081        // this.source = new WeakReference<Object>(source);
082        this.source = source;
083        hasBeenDone = true;
084        alive = true;
085
086        if (icon != null)
087            this.icon = new IcyIcon(icon, 16);
088        else
089            this.icon = DEFAULT_ICON;
090        presentationName = name;
091        // by default collapse operation is supported
092        mergeable = true;
093    }
094
095    /**
096     * Creates an <code>UndoableAction</code> which defaults <code>hasBeenDone</code> and
097     * <code>alive</code> to <code>true</code>.
098     */
099    public AbstractIcyUndoableEdit(Object source, String name)
100    {
101        this(source, name, null);
102    }
103
104    /**
105     * Creates an <code>UndoableAction</code> which defaults <code>hasBeenDone</code> and
106     * <code>alive</code> to <code>true</code>.
107     */
108    public AbstractIcyUndoableEdit(Object source, Image icon)
109    {
110        this(source, "", icon);
111    }
112
113    /**
114     * Creates an <code>UndoableAction</code> which defaults <code>hasBeenDone</code> and
115     * <code>alive</code> to <code>true</code>.
116     */
117    public AbstractIcyUndoableEdit(Object source)
118    {
119        this(source, "", null);
120    }
121
122    @Override
123    public Object getSource()
124    {
125        return source;
126    }
127
128    @Override
129    public IcyIcon getIcon()
130    {
131        return icon;
132    }
133
134    /**
135     * Sets <code>alive</code> to false. Note that this is a one way operation; dead edits cannot be
136     * resurrected.<br>
137     * Sending <code>undo</code> or <code>redo</code> to a dead edit results in an exception being
138     * thrown.
139     * <p>
140     * Typically an edit is killed when it is consolidated by another edit's <code>addEdit</code> or
141     * <code>replaceEdit</code> method, or when it is dequeued from an <code>UndoManager</code>.
142     */
143    @Override
144    public void die()
145    {
146        alive = false;
147
148        // remove source reference
149        source = null;
150    }
151
152    /**
153     * Throws <code>CannotUndoException</code> if <code>canUndo</code> returns <code>false</code>.
154     * Sets <code>hasBeenDone</code> to <code>false</code>. Subclasses should override to undo the
155     * operation represented by this edit. Override should begin with
156     * a call to super.
157     * 
158     * @exception CannotUndoException
159     *            if <code>canUndo</code> returns <code>false</code>
160     * @see #canUndo
161     */
162    @Override
163    public void undo() throws CannotUndoException
164    {
165        if (!canUndo())
166            throw new CannotUndoException();
167
168        hasBeenDone = false;
169    }
170
171    @Override
172    public boolean canUndo()
173    {
174        return alive && hasBeenDone;
175    }
176
177    /**
178     * Throws <code>CannotRedoException</code> if <code>canRedo</code> returns false. Sets
179     * <code>hasBeenDone</code> to <code>true</code>.
180     * Subclasses should override to redo the operation represented by
181     * this edit. Override should begin with a call to super.
182     * 
183     * @exception CannotRedoException
184     *            if <code>canRedo</code> returns <code>false</code>
185     * @see #canRedo
186     */
187    @Override
188    public void redo() throws CannotRedoException
189    {
190        if (!canRedo())
191            throw new CannotRedoException();
192
193        hasBeenDone = true;
194    }
195
196    @Override
197    public boolean canRedo()
198    {
199        return alive && !hasBeenDone;
200    }
201
202    /*
203     * This default implementation returns false.
204     */
205    @Override
206    public boolean addEdit(UndoableEdit anEdit)
207    {
208        return false;
209    }
210
211    /*
212     * This default implementation returns false.
213     */
214    @Override
215    public boolean replaceEdit(UndoableEdit anEdit)
216    {
217        return false;
218    }
219
220    /*
221     * This default implementation returns true.
222     */
223    @Override
224    final public boolean isSignificant()
225    {
226        // should always returns true for easier UndoManager manipulation
227        return true;
228    }
229
230    @Override
231    public boolean isMergeable()
232    {
233        return mergeable;
234    }
235
236    public void setMergeable(boolean value)
237    {
238        mergeable = value;
239    }
240
241    /**
242     * This default implementation returns "". Used by <code>getUndoPresentationName</code> and
243     * <code>getRedoPresentationName</code> to
244     * construct the strings they return. Subclasses should override to
245     * return an appropriate description of the operation this edit
246     * represents.
247     * 
248     * @return the empty string ""
249     * @see #getUndoPresentationName
250     * @see #getRedoPresentationName
251     */
252    @Override
253    public String getPresentationName()
254    {
255        return presentationName;
256    }
257
258    /**
259     * Retrieves the value from the defaults table with key
260     * <code>AbstractUndoableEdit.undoText</code> and returns
261     * that value followed by a space, followed by <code>getPresentationName</code>.
262     * If <code>getPresentationName</code> returns "",
263     * then the defaults value is returned alone.
264     * 
265     * @return the value from the defaults table with key <code>AbstractUndoableEdit.undoText</code>
266     *         , followed
267     *         by a space, followed by <code>getPresentationName</code> unless
268     *         <code>getPresentationName</code> is "" in which
269     *         case, the defaults value is returned alone.
270     * @see #getPresentationName
271     */
272    @Override
273    public String getUndoPresentationName()
274    {
275        String name = getPresentationName();
276
277        if (!StringUtil.isEmpty(name))
278            name = UIManager.getString("AbstractUndoableEdit.undoText") + " " + name;
279        else
280            name = UIManager.getString("AbstractUndoableEdit.undoText");
281
282        return name;
283    }
284
285    /**
286     * Retrieves the value from the defaults table with key
287     * <code>AbstractUndoableEdit.redoText</code> and returns
288     * that value followed by a space, followed by <code>getPresentationName</code>.
289     * If <code>getPresentationName</code> returns "",
290     * then the defaults value is returned alone.
291     * 
292     * @return the value from the defaults table with key <code>AbstractUndoableEdit.redoText</code>
293     *         , followed
294     *         by a space, followed by <code>getPresentationName</code> unless
295     *         <code>getPresentationName</code> is "" in which
296     *         case, the defaults value is returned alone.
297     * @see #getPresentationName
298     */
299    @Override
300    public String getRedoPresentationName()
301    {
302        String name = getPresentationName();
303
304        if (!StringUtil.isEmpty(name))
305            name = UIManager.getString("AbstractUndoableEdit.redoText") + " " + name;
306        else
307            name = UIManager.getString("AbstractUndoableEdit.redoText");
308
309        return name;
310    }
311
312    /**
313     * Returns a string that displays and identifies this
314     * object's properties.
315     * 
316     * @return a String representation of this object
317     */
318    @Override
319    public String toString()
320    {
321        return super.toString() + " hasBeenDone: " + hasBeenDone + " alive: " + alive;
322    }
323
324    // /**
325    // * Update live state of the edit.<br>
326    // * For instance, an edit attached to a Plugin should probably die when the plugin is
327    // closed.<br>
328    // * Returns false if the edit should die, true otherwise.
329    // */
330    // public abstract boolean updateLiveState();
331    // {
332    // // no more reference on source --> die
333    // if (getSource() == null)
334    // die();
335    // }
336}