/********************************************************************************
* *
* U n d o / R e d o - a b l e C o m m a n d *
* *
*********************************************************************************
* Copyright (C) 2000,2024 by Jeroen van der Zijp. All Rights Reserved. *
*********************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License as published by *
* the Free Software Foundation; either version 3 of the License, or *
* (at your option) any later version. *
* *
* This library 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 Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public License *
* along with this program. If not, see *
********************************************************************************/
#ifndef FXUNDOLIST_H
#define FXUNDOLIST_H
#ifndef FXOBJECT_H
#include "FXObject.h"
#endif
namespace FX {
// Declarations
class FXUndoList;
/**
* Base class for undoable commands records. Each undo records all
* the information necessary to undo, as well as redo, a given operation.
* Since commands are derived from FXObject, subclassed commands
* may be able to implement their behaviour by sending messages
* (like ID_SETINTVALUE, etc) as well as simple API calls to
* objects to be modified.
* Small, incremental undo commands may sometimes be consolidated
* into larger ones, by merging consecutive commands. A merge
* is effected by calling the mergeWith() API after first establishing
* that merging is possible with canMerge().
* When commands are merged, the incoming (new) command is deleted and
* not added to the undo list. In some cases, the incoming command may
* completely cancel the existing commands, causing both to be deleted.
* In that case the prior command will be removed from the undo list.
* To allow UndoList to manage memory, your subclassed Command should
* re-implement size() and return the amount of memory used by your
* undo command. This way, bookkeeping periodically pare down undo
* commands to keep memory usage in check.
*/
class FXAPI FXCommand : public FXObject {
FXDECLARE_ABSTRACT(FXCommand)
private:
FXival refs;
private:
FXCommand(const FXCommand& org);
FXCommand &operator=(const FXCommand&);
protected:
FXCommand():refs(0){}
public:
// Return reference count
FXival nrefs() const { return refs; }
// Increment reference count
void ref(){ ++refs; }
// Decrement reference count
void unref(){ if(--refs<=0) delete this; }
/**
* Undo this command.
*/
virtual void undo() = 0;
/**
* Redo this command.
*/
virtual void redo() = 0;
/**
* Return the size of the information in the undo record.
* The undo list may be periodically trimmed to limit memory
* usage to a certain limit.
* For proper accounting, value returned should include the
* size of the command record itself as well as any data linked
* from it.
*/
virtual FXuval size() const;
/**
* Name of the undo command to be shown on a button;
* for example, "Undo Delete".
*/
virtual FXString undoName() const;
/**
* Name of the redo command to be shown on a button;
* for example, "Redo Delete".
*/
virtual FXString redoName() const;
/**
* Return true if this command could possibly be merged with a
* previous undo command. This is useful to combine e.g. multiple
* consecutive single-character text changes into a single block change.
* The default implementation returns false.
*/
virtual FXbool canMerge() const;
/**
* Called by the undo system to try and merge the new incoming command
* with this command. When overriding this API, return:
*
* 0 if incoming command could not be merged into this command,
* 1 if incoming command could be merged into this command,
* 2 if incoming command completely cancels effect of this command.
*
* The default implementation returns 0.
*/
virtual FXuint mergeWith(FXCommand* command);
};
// A marked pointer to a command
typedef FXMarkedPtr FXCommandPtr;
// An array of marked command pointers
typedef FXArray FXCommandArray;
/**
* Group of undoable commands.
* Complicated manipulations may sometimes be more effectively
* implemented as a combination of multiple sub-commands.
* The CommandGroup implements this concept by invoking each
* the sub-commands undo() and redo() in the proper order.
* For example, a network editing program may decompose a
* "Delete Node" operation into a number of connection changes
* followed by a unconnected node removal operation.
* Note that some sub-commands may themselves be also CommandGroup
* items.
* You start a new CommandGroup by creating an instance of CommandGroup
* cg and calling undolist.begin(ch), and end it by calling undolist.end().
* In between, additional CommandGroup items may be created as well,
* keeping in mind that the inner CommandGroup must be closed with
* undolist.end() before adding to the outer CommandGroup.
* They are strictly nested.
*/
class FXAPI FXCommandGroup : public FXCommand {
FXDECLARE(FXCommandGroup)
friend class FXUndoList;
private:
FXCommandArray command;
FXCommandGroup *group;
private:
FXCommandGroup(const FXCommandGroup&);
FXCommandGroup &operator=(const FXCommandGroup&);
public:
/// Construct initially empty undo command group
FXCommandGroup():group(nullptr){ }
/// Return true if empty
FXbool empty(){ return command.no()==0; }
/// Undo whole command group
virtual void undo();
/// Redo whole command group
virtual void redo();
/// Return the size of the command group
virtual FXuval size() const;
/// Clear list
virtual void clear();
/// Delete undo command and sub-commands
virtual ~FXCommandGroup();
};
/**
* The UndoList class manages a list of undoable commands.
*
* When performing an undo, the document is regressed from its
* current state to an earlier state. Likewise, performing a
* redo, a document is advanced from an earlier state to a later
* state.
*
* A document state may be marked, i.e. a special designated
* state may be identified. Typically, a freshly loaded document
* is marked as "clean". Also, any time a document is saved back
* to disk, the latest state could be marked as "clean".
*
* You can go back to the marked state by invoking revert().
* The revert() API will call a sequence of undo's or redo's, depending
* on whether one need to go to older or newer state of the document.
*
* UndoList can directly receive SEL_UPDATE and SEL_COMMAND
* messages from widgets. For example, sending ID_UNDO command
* will invoke the undo() API and cause an undo operation.
*
*/
class FXAPI FXUndoList : public FXCommandGroup {
FXDECLARE(FXUndoList)
private:
FXuval space; // Total memory in the undo commands
FXint undocount; // Number of undo records
FXint redocount; // Number of redo records
FXint marker; // Marker value
FXbool markset; // Mark is set
FXbool alternate; // Keep alternate history
FXbool working; // Currently busy with undo or redo
private:
FXUndoList(const FXUndoList&);
FXUndoList &operator=(const FXUndoList&);
public:
long onCmdUndo(FXObject*,FXSelector,void*);
long onUpdUndo(FXObject*,FXSelector,void*);
long onCmdRedo(FXObject*,FXSelector,void*);
long onUpdRedo(FXObject*,FXSelector,void*);
long onCmdClear(FXObject*,FXSelector,void*);
long onUpdClear(FXObject*,FXSelector,void*);
long onCmdRevert(FXObject*,FXSelector,void*);
long onUpdRevert(FXObject*,FXSelector,void*);
long onCmdUndoAll(FXObject*,FXSelector,void*);
long onCmdRedoAll(FXObject*,FXSelector,void*);
long onUpdUndoCount(FXObject*,FXSelector,void*);
long onUpdRedoCount(FXObject*,FXSelector,void*);
long onCmdAltHistory(FXObject*,FXSelector,void*);
long onUpdAltHistory(FXObject*,FXSelector,void*);
long onCmdDumpStats(FXObject*,FXSelector,void*);
public:
enum{
ID_CLEAR=FXWindow::ID_LAST,
ID_REVERT,
ID_UNDO,
ID_REDO,
ID_UNDO_ALL,
ID_REDO_ALL,
ID_UNDO_COUNT,
ID_REDO_COUNT,
ID_ALT_HISTORY,
ID_DUMP_STATS,
ID_LAST
};
public:
/**
* Make new empty undo list, initially unmarked.
*/
FXUndoList();
/**
* Return true if currently inside undo or redo operation; this
* is useful to avoid generating another undo command while inside
* an undo operation.
*/
FXbool busy() const { return working; }
/**
* If alternate history mode is in effect, remember the alternate
* history, moving all the redo-commands into the undolist followed by
* their corresponding undo-commands, in reverse order.
* The sequence has a net zero effect on the document, as each undo is
* paired up with a corresponding redo.
* If alternate history is not in effect, simply delete the redo-
* commands.
* This is automatically invoked when a new undo command is added,
* and not typically called by the user directly.
*/
FXbool cut();
/**
* Add new command, executing it if desired. The new command will be merged
* with the previous command if merge is true and we're not at a marked position
* and the commands are mergeable. Otherwise the new command will be appended
* after the last undo command in the currently active undo group.
* If the new command is successfully merged, it will be deleted.
*/
FXbool add(FXCommand* cmd,FXbool doit=false,FXbool merge=true);
/**
* Begin undo command sub-group. This begins a new group of commands that
* are treated as a single command. Must eventually be followed by a
* matching end() after recording the sub-commands. The new sub-group
* will be appended to its parent group's undo list when end() is called.
*/
FXbool begin(FXCommandGroup *command);
/**
* End undo command sub-group. If the sub-group is still empty, it will
* be deleted; otherwise, the sub-group will be added as a new command
* into parent group.
* A matching begin() must have been called previously.
*/
FXbool end();
/**
* Abort the current command sub-group being compiled. All commands
* already added to the sub-groups undo list will be discarded.
* Intermediate command groups will be left intact.
*/
FXbool abort();
/**
* Undo last command. This will move the command to the redo list.
*/
virtual void undo();
/**
* Redo next command. This will move the command back to the undo list.
*/
virtual void redo();
/**
* Undo all commands.
*/
void undoAll();
/**
* Redo all commands.
*/
void redoAll();
/**
* Revert to marked.
*/
void revert();
/**
* Can we undo more commands.
*/
FXbool canUndo() const;
/**
* Can we redo more commands.
*/
FXbool canRedo() const;
/**
* Can revert to marked.
*/
FXbool canRevert() const;
/**
* Current top undo command.
*/
FXCommand* current() const;
/**
* Return name of the first undo command available; if no
* undo command available this will return the empty string.
*/
virtual FXString undoName() const;
/**
* Return name of the first redo command available; if no
* Redo command available this will return the empty string.
*/
virtual FXString redoName() const;
/// Size of undo information
virtual FXuval size() const;
/// Clear list
virtual void clear();
/// Number of undo records
FXint undoCount() const { return undocount; }
/// Number of redo records
FXint redoCount() const { return redocount; }
/**
* Trim undo list down, starting from the oldest commands, until
* no more than nc commands are left in the undo list.
* Call this periodically to prevent the undo-list from growing
* beyond a certain number of records.
*/
void trimCount(FXint nc);
/**
* Trim undo list down, starting from the oldest commands, until
* total memory used drops below size sz.
* Call this periodically to prevent the undo-list from growing
* beyond a certain maximum amount of memory.
*/
void trimSize(FXuval sz);
/**
* Trim undo list down, starting from the oldest commands,
* until reaching the marked ("clean" state) point.
* If no mark was set this does nothing.
*/
void trimMark();
/**
* Mark the current state of the undo list, which is initially unmarked.
* There can be only one active mark at any time. Call mark() at any
* time when you know your document to be "clean"; for example when you
* save the document to disk.
* If you don't need to undo past this marked point, consider calling
* trimMark() to delete all undo commands for states prior to the mark.
*/
void mark();
/**
* Unmark the marked state.
*/
void unmark();
/**
* Check if the current state was marked, if the application has returned
* to the previously marked state.
*/
FXbool marked() const;
/**
* Enable or disable alternate history mode.
* In alternate history mode, adding a new command after performing a
* number of undo's will remember the alternate history, and allow a
* sequence of undo's and redo's to navigate back through this alternate
* history.
*/
void setAlternateHistory(FXbool flag){ alternate=flag; }
/**
* Returns true if alternate history mode is in effect.
*/
FXbool getAlternateHistory() const { return alternate; }
/// Dump statistics
void dumpStats();
/// Destroy
virtual ~FXUndoList();
};
}
#endif