/* * Copyright (c) 2014 tabletoptool.com team. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/gpl.html * * Contributors: * rptools.com team - initial implementation * tabletoptool.com team - further development */ package com.t3.client.tool.drawing; import javax.swing.undo.AbstractUndoableEdit; import javax.swing.undo.CannotRedoException; import javax.swing.undo.CannotUndoException; import javax.swing.undo.UndoManager; import org.apache.log4j.Logger; import com.t3.client.TabletopTool; import com.t3.model.ModelChangeEvent; import com.t3.model.ModelChangeListener; import com.t3.model.Zone; import com.t3.model.drawing.Drawable; import com.t3.model.drawing.Pen; /** * /** This class controls the undo/redo behavior for drawables. (Rewritten by Azhrei) * <p> * This class is instantiated by the Zone constructor (and is not copied by the Zone copy constructor). It's purpose is * to record all changes to drawables for the zone, allowing for easy undo/redo functionality. In the future it would be * possible to add a listbox showing what edits are in the list and allowing individual edits to be deleted. This would * require replacement of the Swing UndoManager however, as that class does not support non-linear editing of the * UndoManager. * * @author jgorrell * @version $Revision: 5828 $ $Date: 2011-11-26 18:29:24 -0500 (Sat, 26 Nov 2011) $ $Author: azhrei_fje $ */ public class UndoPerZone implements ModelChangeListener { private static final Logger log = Logger.getLogger(UndoPerZone.class); /** * Swing's undo/redo support */ private final UndoManager manager; private Zone zone = null; public UndoPerZone(Zone z) { // All we need to do is register for the Zone.Event.DRAWABLE_REMOVED event so that we can // eliminate that drawable out of the undo list. As of SVN 5771, this is only used by the // CLEAR ALL DRAWINGS function. See ServerMethodHandler.clearAllDrawings() // Using an event handler is the right way to do this, but the DrawableUndoManager would // need to register with every zone as they are created. Tying into that process is too much // intrusion this late in the game. Instead, I'll modify the Zone class to invoke this class // whenever a drawable is removed. The coupling is too tight, but it'll work for 1.3. // This is weird. It seems that this constructor can be called via Reflection when a new Zone is being instantiated by the Hessian library. // That creation process causes null to be passed as the zone so we can't register ourselves as a modelChangeListener for that zone or // we get NPE errors. Instead, we need to let the Zone constructor instantiate use, then add us as a change listener. Seems a little weird // to me, but the whole TabletopTool as both server/client is weird anyway... zone = z; manager = new UndoManager(); } protected UndoPerZone(UndoPerZone upz) throws IllegalArgumentException { throw new IllegalArgumentException("copy constructor of UndoPerZone"); } private void checkZone() { if (zone == null && log.isDebugEnabled()) log.debug("zone == null (!)"); } /** * Add a drawable to the undo set. * * @param pen * The pen used to draw. * @param drawable * The drawable just drawn. */ public void addDrawable(Pen pen, Drawable drawable) { checkZone(); if (log.isDebugEnabled()) log.debug("drawable " + drawable + " being added to zone " + zone.getName()); manager.addEdit(new DrawableUndoableEdit(pen, drawable)); com.t3.client.AppActions.UNDO_PER_MAP.isAvailable(); com.t3.client.AppActions.REDO_PER_MAP.isAvailable(); } public boolean canUndo() { return manager.canUndo(); } public boolean canRedo() { return manager.canRedo(); } /** * Undo the last edit if one exists. */ public void undo() { checkZone(); if (!canUndo()) { if (log.isDebugEnabled()) log.debug("Can't undo from zone " + zone.getName()); return; } if (log.isDebugEnabled()) log.debug("Undoing last change on zone " + zone.getName()); manager.undo(); } /** * Redo the last undo if one exists. */ public void redo() { checkZone(); if (!canRedo()) { if (log.isDebugEnabled()) log.debug("Can't redo from zone " + zone.getName()); return; } if (log.isDebugEnabled()) log.debug("Redoing next change on zone " + zone.getName()); manager.redo(); } /** * Invoked when the user activates the "Clear All Drawings" menu option. Could also be used just before writing the * Zone out to persistent storage in order to keep the file size as small as possible (but leaving it there for * debugging might be nice). */ public void clear() { manager.discardAllEdits(); } /** * This shouldn't need to be used since all operations are handled internally. * * @return */ @Deprecated public UndoManager getUndoManager() { return manager; } /** * Class used to undo/redo drawables. The GM can undo/redo any drawables, but clients should be able to manipulate * only their own. * * @author jgorrell * @version $Revision: 5828 $ $Date: 2011-11-26 18:29:24 -0500 (Sat, 26 Nov 2011) $ $Author: azhrei_fje $ */ private class DrawableUndoableEdit extends AbstractUndoableEdit { private static final long serialVersionUID = -1373046215655231284L; /** * The pen used to modify the zone. */ private final Pen pen; /** * What has been drawn. */ private final Drawable drawable; /** * Create the undoable edit. * * @param aPen * The pen for drawing. * @param aDrawable * The drawable rendered. */ public DrawableUndoableEdit(Pen aPen, Drawable aDrawable) { pen = aPen; drawable = aDrawable; } /** * To undo, send the drawable id to the server's <code>undoDraw</code> command. * * @see javax.swing.undo.UndoableEdit#undo() */ @Override public void undo() throws CannotUndoException { super.undo(); // Tell the server to undo the drawable. TabletopTool.serverCommand().undoDraw(zone.getId(), drawable.getId()); } /** * @see javax.swing.undo.UndoableEdit#redo() */ @Override public void redo() throws CannotRedoException { super.redo(); // Render the drawable again, but don't add it to the undo manager. TabletopTool.serverCommand().draw(zone.getId(), pen, drawable); } } @Override public void modelChanged(ModelChangeEvent event) { log.debug("Inside the modelChanged() event"); } }