HaikuDepot: Base-classes for generic undo/redo support.

I plan to use these in the text view and editor framework.
This commit is contained in:
Stephan Aßmus 2015-02-07 23:12:02 +01:00
parent ee3b36afff
commit 651e8326ca
11 changed files with 715 additions and 1 deletions

View File

@ -4,7 +4,7 @@ UsePrivateHeaders interface shared package ;
# source directories
local sourceDirs =
model textview ui ui_generic
edits_generic model textview ui ui_generic
;
local sourceDir ;
@ -15,6 +15,13 @@ for sourceDir in $(sourceDirs) {
SEARCH_SOURCE += [ FDirName $(HAIKU_TOP) src servers package ] ;
local textDocumentSources =
# edits_generic
CompoundEdit.cpp
EditContext.cpp
EditManager.cpp
EditStack.cpp
UndoableEdit.cpp
# textview
Bullet.cpp
BulletData.cpp
CharacterStyle.cpp

View File

@ -0,0 +1,117 @@
/*
* Copyright 2006-2112, Stephan Aßmus <superstippi@gmx.de>
* Distributed under the terms of the MIT License.
*/
#include "CompoundEdit.h"
#include <stdio.h>
CompoundEdit::CompoundEdit(const char* name)
: UndoableEdit()
, fEdits()
, fName(name)
{
}
CompoundEdit::~CompoundEdit()
{
}
status_t
CompoundEdit::InitCheck()
{
return B_OK;
}
status_t
CompoundEdit::Perform(EditContext& context)
{
status_t status = B_OK;
int32 count = fEdits.CountItems();
int32 i = 0;
for (; i < count; i++) {
status = fEdits.ItemAtFast(i)->Perform(context);
if (status != B_OK)
break;
}
if (status != B_OK) {
// roll back
i--;
for (; i >= 0; i--) {
fEdits.ItemAtFast(i)->Undo(context);
}
}
return status;
}
status_t
CompoundEdit::Undo(EditContext& context)
{
status_t status = B_OK;
int32 count = fEdits.CountItems();
int32 i = count - 1;
for (; i >= 0; i--) {
status = fEdits.ItemAtFast(i)->Undo(context);
if (status != B_OK)
break;
}
if (status != B_OK) {
// roll back
i++;
for (; i < count; i++) {
fEdits.ItemAtFast(i)->Redo(context);
}
}
return status;
}
status_t
CompoundEdit::Redo(EditContext& context)
{
status_t status = B_OK;
int32 count = fEdits.CountItems();
int32 i = 0;
for (; i < count; i++) {
status = fEdits.ItemAtFast(i)->Redo(context);
if (status != B_OK)
break;
}
if (status != B_OK) {
// roll back
i--;
for (; i >= 0; i--) {
fEdits.ItemAtFast(i)->Undo(context);
}
}
return status;
}
void
CompoundEdit::GetName(BString& name)
{
name << fName;
}
bool
CompoundEdit::AppendEdit(const UndoableEditRef& edit)
{
return fEdits.Add(edit);
}

View File

@ -0,0 +1,36 @@
/*
* Copyright 2006-2112, Stephan Aßmus <superstippi@gmx.de>
* Distributed under the terms of the MIT License.
*/
#ifndef COMPOUND_EDIT_H
#define COMPOUND_EDIT_H
#include <String.h>
#include "List.h"
#include "UndoableEdit.h"
class CompoundEdit : public UndoableEdit {
public:
CompoundEdit(const char* name);
virtual ~CompoundEdit();
virtual status_t InitCheck();
virtual status_t Perform(EditContext& context);
virtual status_t Undo(EditContext& context);
virtual status_t Redo(EditContext& context);
virtual void GetName(BString& name);
bool AppendEdit(const UndoableEditRef& edit);
private:
typedef List<UndoableEditRef, false, 2> EditList;
EditList fEdits;
BString fName;
};
#endif // COMPOUND_EDIT_H

View File

@ -0,0 +1,18 @@
/*
* Copyright 2013 Stephan Aßmus <superstippi@gmx.de>
* Distributed under the terms of the MIT License.
*/
#include "EditContext.h"
#include <stdio.h>
EditContext::EditContext()
{
}
EditContext::~EditContext()
{
}

View File

@ -0,0 +1,26 @@
/*
* Copyright 2013 Stephan Aßmus <superstippi@gmx.de>
* Distributed under the terms of the MIT License.
*/
#ifndef EDIT_CONTEXT_H
#define EDIT_CONTEXT_H
#include <Referenceable.h>
/**
* EditContext is passed to UndoableEdits in Perform(), Undo(), and Redo().
* It provides a context in which the user performs these operations
* and can be used for example to control the selection or visible
* portion of the document to focus the user's attention on the
* elements affected by the UndoableEdit.
*/
class EditContext : public BReferenceable {
public:
EditContext();
virtual ~EditContext();
};
typedef BReference<EditContext> EditContextRef;
#endif // EDIT_CONTEXT_H

View File

@ -0,0 +1,244 @@
/*
* Copyright 2006-2012, Stephan Aßmus <superstippi@gmx.de>
* Distributed under the terms of the MIT License.
*/
#include "EditManager.h"
#include <stdio.h>
#include <string.h>
#include <Locker.h>
#include <String.h>
EditManager::Listener::~Listener()
{
}
EditManager::EditManager()
{
}
EditManager::~EditManager()
{
Clear();
}
status_t
EditManager::Perform(UndoableEdit* edit, EditContext& context)
{
if (edit == NULL)
return B_BAD_VALUE;
return Perform(UndoableEditRef(edit, true), context);
}
status_t
EditManager::Perform(const UndoableEditRef& edit, EditContext& context)
{
status_t ret = edit.Get() != NULL ? B_OK : B_BAD_VALUE;
if (ret == B_OK)
ret = edit->InitCheck();
if (ret == B_OK)
ret = edit->Perform(context);
if (ret == B_OK) {
ret = _AddEdit(edit);
if (ret != B_OK)
edit->Undo(context);
}
_NotifyListeners();
return ret;
}
status_t
EditManager::Undo(EditContext& context)
{
status_t status = B_ERROR;
if (!fUndoHistory.IsEmpty()) {
UndoableEditRef edit(fUndoHistory.Top());
fUndoHistory.Pop();
status = edit->Undo(context);
if (status == B_OK)
fRedoHistory.Push(edit);
else
fUndoHistory.Push(edit);
}
_NotifyListeners();
return status;
}
status_t
EditManager::Redo(EditContext& context)
{
status_t status = B_ERROR;
if (!fRedoHistory.IsEmpty()) {
UndoableEditRef edit(fRedoHistory.Top());
fRedoHistory.Pop();
status = edit->Redo(context);
if (status == B_OK)
fUndoHistory.Push(edit);
else
fRedoHistory.Push(edit);
}
_NotifyListeners();
return status;
}
bool
EditManager::GetUndoName(BString& name)
{
if (!fUndoHistory.IsEmpty()) {
name << " ";
fUndoHistory.Top()->GetName(name);
return true;
}
return false;
}
bool
EditManager::GetRedoName(BString& name)
{
if (!fRedoHistory.IsEmpty()) {
name << " ";
fRedoHistory.Top()->GetName(name);
return true;
}
return false;
}
void
EditManager::Clear()
{
while (!fUndoHistory.IsEmpty())
fUndoHistory.Pop();
while (!fRedoHistory.IsEmpty())
fRedoHistory.Pop();
_NotifyListeners();
}
void
EditManager::Save()
{
if (!fUndoHistory.IsEmpty())
fEditAtSave = fUndoHistory.Top();
_NotifyListeners();
}
bool
EditManager::IsSaved()
{
bool saved = fUndoHistory.IsEmpty();
if (fEditAtSave.Get() != NULL && !saved) {
if (fEditAtSave == fUndoHistory.Top())
saved = true;
}
return saved;
}
// #pragma mark -
bool
EditManager::AddListener(Listener* listener)
{
return fListeners.Add(listener);
}
void
EditManager::RemoveListener(Listener* listener)
{
fListeners.Remove(listener);
}
// #pragma mark -
status_t
EditManager::_AddEdit(const UndoableEditRef& edit)
{
status_t status = B_OK;
bool add = true;
if (!fUndoHistory.IsEmpty()) {
// Try to collapse edits to a single edit
// or remove this and the previous edit if
// they reverse each other
const UndoableEditRef& top = fUndoHistory.Top();
if (edit->UndoesPrevious(top.Get())) {
add = false;
fUndoHistory.Pop();
} else if (top->CombineWithNext(edit.Get())) {
add = false;
// After collapsing, the edit might
// have changed it's mind about InitCheck()
// (the commands reversed each other)
if (top->InitCheck() != B_OK) {
fUndoHistory.Pop();
}
} else if (edit->CombineWithPrevious(top.Get())) {
fUndoHistory.Pop();
// After collapsing, the edit might
// have changed it's mind about InitCheck()
// (the commands reversed each other)
if (edit->InitCheck() != B_OK) {
add = false;
}
}
}
if (add) {
if (!fUndoHistory.Push(edit))
status = B_NO_MEMORY;
}
if (status == B_OK) {
// The redo stack needs to be empty
// as soon as an edit was added (also in case of collapsing)
while (!fRedoHistory.IsEmpty()) {
fRedoHistory.Pop();
}
}
return status;
}
void
EditManager::_NotifyListeners()
{
int32 count = fListeners.CountItems();
if (count == 0)
return;
// Iterate a copy of the list, so we don't crash if listeners
// detach themselves while being notified.
ListenerList listenersCopy(fListeners);
if (listenersCopy.CountItems() != count)
return;
for (int32 i = 0; i < count; i++)
listenersCopy.ItemAtFast(i)->EditManagerChanged(this);
}

View File

@ -0,0 +1,60 @@
/*
* Copyright 2006-2012, Stephan Aßmus <superstippi@gmx.de>
* Distributed under the terms of the MIT License.
*/
#ifndef EDIT_MANAGER_H
#define EDIT_MANAGER_H
#include "EditStack.h"
#include "UndoableEdit.h"
class BString;
class EditContext;
class EditManager {
public:
class Listener {
public:
virtual ~Listener();
virtual void EditManagerChanged(
const EditManager* manager) = 0;
};
public:
EditManager();
virtual ~EditManager();
status_t Perform(UndoableEdit* edit,
EditContext& context);
status_t Perform(const UndoableEditRef& edit,
EditContext& context);
status_t Undo(EditContext& context);
status_t Redo(EditContext& context);
bool GetUndoName(BString& name);
bool GetRedoName(BString& name);
void Clear();
void Save();
bool IsSaved();
bool AddListener(Listener* listener);
void RemoveListener(Listener* listener);
private:
status_t _AddEdit(const UndoableEditRef& edit);
void _NotifyListeners();
private:
EditStack fUndoHistory;
EditStack fRedoHistory;
UndoableEditRef fEditAtSave;
typedef List<Listener*, true, 4> ListenerList;
ListenerList fListeners;
};
#endif // EDIT_MANAGER_H

View File

@ -0,0 +1,49 @@
/*
* Copyright 2012, Stephan Aßmus <superstippi@gmx.de>
* Distributed under the terms of the MIT License.
*/
#include "EditStack.h"
#include <stdio.h>
EditStack::EditStack()
: fEdits()
{
}
EditStack::~EditStack()
{
}
bool
EditStack::Push(const UndoableEditRef& edit)
{
return fEdits.Add(edit);
}
UndoableEditRef
EditStack::Pop()
{
UndoableEditRef edit(Top());
fEdits.Remove();
return edit;
}
const UndoableEditRef&
EditStack::Top() const
{
return fEdits.LastItem();
}
bool
EditStack::IsEmpty() const
{
return fEdits.CountItems() == 0;
}

View File

@ -0,0 +1,30 @@
/*
* Copyright 2012, Stephan Aßmus <superstippi@gmx.de>
* Distributed under the terms of the MIT License.
*/
#ifndef EDIT_STACK_H
#define EDIT_STACK_H
#include "List.h"
#include "UndoableEdit.h"
class EditStack {
public:
EditStack();
virtual ~EditStack();
bool Push(const UndoableEditRef& edit);
UndoableEditRef Pop();
const UndoableEditRef& Top() const;
bool IsEmpty() const;
private:
typedef List<UndoableEditRef, false> EditList;
EditList fEdits;
};
#endif // EDIT_STACK_H

View File

@ -0,0 +1,86 @@
/*
* Copyright 2006-2012, Stephan Aßmus <superstippi@gmx.de>
* Distributed under the terms of the MIT License.
*/
#include "UndoableEdit.h"
#include <stdio.h>
#include <OS.h>
#include <String.h>
//static int32 sInstanceCount = 0;
UndoableEdit::UndoableEdit()
:
fTimeStamp(system_time())
{
// sInstanceCount++;
// printf("UndoableEdits: %ld\n", sInstanceCount);
}
UndoableEdit::~UndoableEdit()
{
// sInstanceCount--;
// printf("UndoableEdits: %ld\n", sInstanceCount);
}
status_t
UndoableEdit::InitCheck()
{
return B_NO_INIT;
}
status_t
UndoableEdit::Perform(EditContext& context)
{
return B_ERROR;
}
status_t
UndoableEdit::Undo(EditContext& context)
{
return B_ERROR;
}
status_t
UndoableEdit::Redo(EditContext& context)
{
return Perform(context);
}
void
UndoableEdit::GetName(BString& name)
{
name << "Name of edit goes here.";
}
bool
UndoableEdit::UndoesPrevious(const UndoableEdit* previous)
{
return false;
}
bool
UndoableEdit::CombineWithNext(const UndoableEdit* next)
{
return false;
}
bool
UndoableEdit::CombineWithPrevious(const UndoableEdit* previous)
{
return false;
}

View File

@ -0,0 +1,41 @@
/*
* Copyright 2006-2012 Stephan Aßmus <superstippi@gmx.de>
* Distributed under the terms of the MIT License.
*/
#ifndef UNDOABLE_EDIT_H
#define UNDOABLE_EDIT_H
#include <Referenceable.h>
class BString;
class EditContext;
class UndoableEdit : public BReferenceable {
public:
UndoableEdit();
virtual ~UndoableEdit();
virtual status_t InitCheck();
virtual status_t Perform(EditContext& context);
virtual status_t Undo(EditContext& context);
virtual status_t Redo(EditContext& context);
virtual void GetName(BString& name);
virtual bool UndoesPrevious(const UndoableEdit* previous);
virtual bool CombineWithNext(const UndoableEdit* next);
virtual bool CombineWithPrevious(
const UndoableEdit* previous);
inline bigtime_t TimeStamp() const
{ return fTimeStamp; }
protected:
bigtime_t fTimeStamp;
};
typedef BReference<UndoableEdit> UndoableEditRef;
#endif // UNDOABLE_EDIT_H