/* * Copyright 2010-2015 Institut Pasteur. * * This file is part of Icy. * * Icy is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Icy is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Icy. If not, see <http://www.gnu.org/licenses/>. */ package icy.undo; import icy.resource.ResourceUtil; import icy.resource.icon.IcyIcon; import icy.util.StringUtil; import java.awt.Image; import javax.swing.UIManager; import javax.swing.undo.CannotRedoException; import javax.swing.undo.CannotUndoException; import javax.swing.undo.UndoableEdit; /** * Abstract Icy {@link UndoableEdit} class. * * @author Stephane */ public abstract class AbstractIcyUndoableEdit implements IcyUndoableEdit { protected static final IcyIcon DEFAULT_ICON = new IcyIcon(ResourceUtil.ICON_LIGHTING, 16); /** * Source of the UndoableEdit */ protected Object source; /** * Defaults to true; becomes false if this edit is undone, true * again if it is redone. */ protected boolean hasBeenDone; /** * True if this edit has not received <code>die</code>; defaults * to <code>true</code>. */ protected boolean alive; /** * Used to recognize the edit in the undo manager panel */ protected IcyIcon icon; /** * Representation name in the history panel */ protected String presentationName; /** * Mergeable property of this edit */ protected boolean mergeable; /** * Creates an <code>UndoableAction</code> which defaults <code>hasBeenDone</code> and * <code>alive</code> to <code>true</code>. */ public AbstractIcyUndoableEdit(Object source, String name, Image icon) { super(); // this.source = new WeakReference<Object>(source); this.source = source; hasBeenDone = true; alive = true; if (icon != null) this.icon = new IcyIcon(icon, 16); else this.icon = DEFAULT_ICON; presentationName = name; // by default collapse operation is supported mergeable = true; } /** * Creates an <code>UndoableAction</code> which defaults <code>hasBeenDone</code> and * <code>alive</code> to <code>true</code>. */ public AbstractIcyUndoableEdit(Object source, String name) { this(source, name, null); } /** * Creates an <code>UndoableAction</code> which defaults <code>hasBeenDone</code> and * <code>alive</code> to <code>true</code>. */ public AbstractIcyUndoableEdit(Object source, Image icon) { this(source, "", icon); } /** * Creates an <code>UndoableAction</code> which defaults <code>hasBeenDone</code> and * <code>alive</code> to <code>true</code>. */ public AbstractIcyUndoableEdit(Object source) { this(source, "", null); } @Override public Object getSource() { return source; } @Override public IcyIcon getIcon() { return icon; } /** * Sets <code>alive</code> to false. Note that this is a one way operation; dead edits cannot be * resurrected.<br> * Sending <code>undo</code> or <code>redo</code> to a dead edit results in an exception being * thrown. * <p> * Typically an edit is killed when it is consolidated by another edit's <code>addEdit</code> or * <code>replaceEdit</code> method, or when it is dequeued from an <code>UndoManager</code>. */ @Override public void die() { alive = false; // remove source reference source = null; } /** * Throws <code>CannotUndoException</code> if <code>canUndo</code> returns <code>false</code>. * Sets <code>hasBeenDone</code> to <code>false</code>. Subclasses should override to undo the * operation represented by this edit. Override should begin with * a call to super. * * @exception CannotUndoException * if <code>canUndo</code> returns <code>false</code> * @see #canUndo */ @Override public void undo() throws CannotUndoException { if (!canUndo()) throw new CannotUndoException(); hasBeenDone = false; } @Override public boolean canUndo() { return alive && hasBeenDone; } /** * Throws <code>CannotRedoException</code> if <code>canRedo</code> returns false. Sets * <code>hasBeenDone</code> to <code>true</code>. * Subclasses should override to redo the operation represented by * this edit. Override should begin with a call to super. * * @exception CannotRedoException * if <code>canRedo</code> returns <code>false</code> * @see #canRedo */ @Override public void redo() throws CannotRedoException { if (!canRedo()) throw new CannotRedoException(); hasBeenDone = true; } @Override public boolean canRedo() { return alive && !hasBeenDone; } /* * This default implementation returns false. */ @Override public boolean addEdit(UndoableEdit anEdit) { return false; } /* * This default implementation returns false. */ @Override public boolean replaceEdit(UndoableEdit anEdit) { return false; } /* * This default implementation returns true. */ @Override final public boolean isSignificant() { // should always returns true for easier UndoManager manipulation return true; } @Override public boolean isMergeable() { return mergeable; } public void setMergeable(boolean value) { mergeable = value; } /** * This default implementation returns "". Used by <code>getUndoPresentationName</code> and * <code>getRedoPresentationName</code> to * construct the strings they return. Subclasses should override to * return an appropriate description of the operation this edit * represents. * * @return the empty string "" * @see #getUndoPresentationName * @see #getRedoPresentationName */ @Override public String getPresentationName() { return presentationName; } /** * Retrieves the value from the defaults table with key * <code>AbstractUndoableEdit.undoText</code> and returns * that value followed by a space, followed by <code>getPresentationName</code>. * If <code>getPresentationName</code> returns "", * then the defaults value is returned alone. * * @return the value from the defaults table with key <code>AbstractUndoableEdit.undoText</code> * , followed * by a space, followed by <code>getPresentationName</code> unless * <code>getPresentationName</code> is "" in which * case, the defaults value is returned alone. * @see #getPresentationName */ @Override public String getUndoPresentationName() { String name = getPresentationName(); if (!StringUtil.isEmpty(name)) name = UIManager.getString("AbstractUndoableEdit.undoText") + " " + name; else name = UIManager.getString("AbstractUndoableEdit.undoText"); return name; } /** * Retrieves the value from the defaults table with key * <code>AbstractUndoableEdit.redoText</code> and returns * that value followed by a space, followed by <code>getPresentationName</code>. * If <code>getPresentationName</code> returns "", * then the defaults value is returned alone. * * @return the value from the defaults table with key <code>AbstractUndoableEdit.redoText</code> * , followed * by a space, followed by <code>getPresentationName</code> unless * <code>getPresentationName</code> is "" in which * case, the defaults value is returned alone. * @see #getPresentationName */ @Override public String getRedoPresentationName() { String name = getPresentationName(); if (!StringUtil.isEmpty(name)) name = UIManager.getString("AbstractUndoableEdit.redoText") + " " + name; else name = UIManager.getString("AbstractUndoableEdit.redoText"); return name; } /** * Returns a string that displays and identifies this * object's properties. * * @return a String representation of this object */ @Override public String toString() { return super.toString() + " hasBeenDone: " + hasBeenDone + " alive: " + alive; } // /** // * Update live state of the edit.<br> // * For instance, an edit attached to a Plugin should probably die when the plugin is // closed.<br> // * Returns false if the edit should die, true otherwise. // */ // public abstract boolean updateLiveState(); // { // // no more reference on source --> die // if (getSource() == null) // die(); // } }