using System; using System.Threading; namespace PaintDotNet.Measurement { /// /// A HistoryMemento is generally used to save part of the state of the Document /// so that an action that is yet to be performed can be undone at a later time. /// For example, if you are going to paint in a certain region, you first create a /// HistoryMemento that saves the contents of the area you are painting to. Then you /// paint. Then you push the history action on to the history stack. /// /// Using the HistoryMementoData class you can serialize your data to disk so that it /// doesn't fester in memory. There are important rules to follow here though: /// 1. Don't hold a reference to a Layer. Store a reference to the DocumentWorkspace and /// the layer's index instead, and access it via Workspace.Document.Layers[index]. /// 2. The exception to #1 is if you are deleting a layer. But you should use /// DeleteLayerHistoryMemento for that. If you need to delete a layer as part of a /// compound action, use CompoundHistoryMemento in conjunction with /// DeleteLayerHistoryMemento. /// 3. To generalize, avoid serializing something unless you're replacing or deleting it. /// (and by 'serializing' I mean 'putting it in your HistoryMementoData class') /// It is better to hold a 'navigation reference' as opposed to a real reference. /// An example of a 'navigation reference' is listed in #1, where we don't store a ref /// to the layer itself but we store the information needed to navigate to it. /// The reasoning for this is made clear if you consider the following case. Assume you /// are holding on to a layer reference ("private Layer theLayer;"). Next, assume that /// the layer is deleted. Then the deletion is undone. The new layer in memory is not /// the layer you have a reference to even though they hold the same data. Changes made /// to one do not show up in the other one. Put another way, history actions should /// store large objects and their locations "by value," and not "by reference." /// public abstract class HistoryMemento { private string name; public string Name { get { return this.name; } set { this.name = value; } } private ImageResource image; public ImageResource Image { get { return this.image; } set { this.image = value; } } protected int id; private static int nextId = 0; public int ID { get { return this.id; } set { this.id = value; } } private Guid seriesGuid = Guid.Empty; public Guid SeriesGuid { get { return this.seriesGuid; } set { this.seriesGuid = value; } } private PersistedObject historyMementoData = null; /// /// Gets or sets the HistoryMementoData associated with this HistoryMemento. /// /// /// Setting this property will immediately serialize the given object to disk. /// protected HistoryMementoData Data { get { if (historyMementoData == null) { return null; } else { return (HistoryMementoData)historyMementoData.Object; } } set { this.historyMementoData = new PersistedObject(value, false); } } /// /// Ensures that the memory held by the Data property is serialized to disk and /// freed from memory. /// public void Flush() { if (historyMementoData != null) { historyMementoData.Flush(); } OnFlush(); } protected virtual void OnFlush() { } /// /// This will perform the necessary work required to undo an action. /// Note that the returned HistoryMemento should have the same ID. /// /// /// Returns a HistoryMemento that can be used to redo the action. /// Note that this property should hold: undoAction = undoAction.PerformUndo().PerformUndo() /// protected abstract HistoryMemento OnUndo(); /// /// This method ensures that the returned HistoryMemento has the appropriate ID tag. /// /// Returns a HistoryMemento that can be used to redo the action. /// The ID of this HistoryMemento will be the same as the object that this /// method was called on. public HistoryMemento PerformUndo() { HistoryMemento ha = OnUndo(); ha.ID = this.ID; ha.SeriesGuid = this.SeriesGuid; return ha; } public HistoryMemento(string name, ImageResource image) { SystemLayer.Tracing.LogFeature("HM(" + GetType().Name + ")"); this.name = name; this.image = image; this.id = Interlocked.Increment(ref nextId); } } }