root/src/apps/icon-o-matic/generic/command/CommandStack.cpp
/*
 * Copyright 2006-2007, Haiku.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Stephan Aßmus <superstippi@gmx.de>
 */

#include "CommandStack.h"

#include <stdio.h>
#include <string.h>

#include <Locker.h>
#include <String.h>

#include "Command.h"

// constructor
CommandStack::CommandStack()
        : BLocker("history"),
          Observable(),
          fSavedCommand(NULL)
{
}

// destructor
CommandStack::~CommandStack()
{
        Clear();
}

// Perform
status_t
CommandStack::Perform(Command* command)
{
        if (!Lock())
                return B_ERROR;

        status_t ret = command ? B_OK : B_BAD_VALUE;
        if (ret == B_OK)
                ret = command->InitCheck();

        if (ret == B_OK)
                ret = command->Perform();

        if (ret == B_OK)
                ret = _AddCommand(command);

        if (ret != B_OK) {
                // no one else feels responsible...
                delete command;
        }

        Unlock();
        return ret;
}

// Undo
status_t
CommandStack::Undo()
{
        if (!Lock())
                return B_ERROR;

        status_t status = B_ERROR;
        if (!fUndoHistory.empty()) {
                Command* command = fUndoHistory.top();
                fUndoHistory.pop();
                status = command->Undo();
                if (status == B_OK)
                        fRedoHistory.push(command);
                else
                        fUndoHistory.push(command);
        }
        Unlock();

        Notify();

        return status;
}

// Redo
status_t
CommandStack::Redo()
{
        if (!Lock())
                return B_ERROR;

        status_t status = B_ERROR;
        if (!fRedoHistory.empty()) {
                Command* command = fRedoHistory.top();
                fRedoHistory.pop();
                status = command->Redo();
                if (status == B_OK)
                        fUndoHistory.push(command);
                else
                        fRedoHistory.push(command);
        }
        Unlock();

        Notify();

        return status;
}

// UndoName
bool
CommandStack::GetUndoName(BString& name)
{
        bool success = false;
        if (Lock()) {
                if (!fUndoHistory.empty()) {
                        name << " ";
                        fUndoHistory.top()->GetName(name);
                        success = true;
                }
                Unlock();
        }
        return success;
}

// RedoName
bool
CommandStack::GetRedoName(BString& name)
{
        bool success = false;
        if (Lock()) {
                if (!fRedoHistory.empty()) {
                        name << " ";
                        fRedoHistory.top()->GetName(name);
                        success = true;
                }
                Unlock();
        }
        return success;
}

// Clear
void
CommandStack::Clear()
{
        if (Lock()) {
                while (!fUndoHistory.empty()) {
                        delete fUndoHistory.top();
                        fUndoHistory.pop();
                }
                while (!fRedoHistory.empty()) {
                        delete fRedoHistory.top();
                        fRedoHistory.pop();
                }
                Unlock();
        }

        Notify();
}

// Save
void
CommandStack::Save()
{
        if (Lock()) {
                if (!fUndoHistory.empty())
                        fSavedCommand = fUndoHistory.top();
                Unlock();
        }

        Notify();
}

// IsSaved
bool
CommandStack::IsSaved()
{
        bool saved = false;
        if (Lock()) {
                saved = fUndoHistory.empty();
                if (fSavedCommand && !saved) {
                        if (fSavedCommand == fUndoHistory.top())
                                saved = true;
                }
                Unlock();
        }
        return saved;
}

// #pragma mark -

// _AddCommand
status_t
CommandStack::_AddCommand(Command* command)
{
        status_t status = B_OK;

        bool add = true;
        if (!fUndoHistory.empty()) {
                // try to collapse commands to a single command
                // or remove this and the previous command if
                // they reverse each other
                if (Command* top = fUndoHistory.top()) {
                        if (command->UndoesPrevious(top)) {
                                add = false;
                                fUndoHistory.pop();
                                delete top;
                                delete command;
                        } else if (top->CombineWithNext(command)) {
                                add = false;
                                delete command;
                                // after collapsing, the command might
                                // have changed it's mind about InitCheck()
                                // (the commands reversed each other)
                                if (top->InitCheck() < B_OK) {
                                        fUndoHistory.pop();
                                        delete top;
                                }
                        } else if (command->CombineWithPrevious(top)) {
                                fUndoHistory.pop();
                                delete top;
                                // after collapsing, the command might
                                // have changed it's mind about InitCheck()
                                // (the commands reversed each other)
                                if (command->InitCheck() < B_OK) {
                                        delete command;
                                        add = false;
                                }
                        }
                }
        }
        if (add) {
                try {
                        fUndoHistory.push(command);
                } catch (...) {
                        status = B_ERROR;
                }
        }

        if (status == B_OK) {
                // the redo stack needs to be empty
                // as soon as a command was added (also in case of collapsing)
                while (!fRedoHistory.empty()) {
                        delete fRedoHistory.top();
                        fRedoHistory.pop();
                }
        }

        Notify();

        return status;
}