// --------------------------------------------------------------------
// Undo
// --------------------------------------------------------------------
/*

    This file is part of the extensible drawing editor Ipe.
    Copyright (C) 1993-2004  Otfried Cheong

    Ipe is free software; you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    As a special exception, you have permission to link Ipe with the
    CGAL library and distribute executables, as long as you follow the
    requirements of the Gnu General Public License in regard to all of
    the software in the executable aside from CGAL.

    Ipe 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 General Public
    License for more details.

    You should have received a copy of the GNU General Public License
    along with Ipe; if not, you can find it at
    "http://www.gnu.org/copyleft/gpl.html", or write to the Free
    Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*/

#include "ipeundo.h"

// --------------------------------------------------------------------

/*! \class IpeUndoItem
  \ingroup high
  \brief Abstract base class for items on the undo stack.
*/

// Implementation of pure virtual destructor.
IpeUndoItem::~IpeUndoItem()
{
  // nothing
}

// --------------------------------------------------------------------

/*! \class IpeUndoStack
  \ingroup high
  \brief An undo stack for Ipe.
*/

//! Create empty undo stack.
IpeUndoStack::IpeUndoStack()
{
  iCur = 0;
}

//! Destructor destroys all items on stack.
IpeUndoStack::~IpeUndoStack()
{
  for (uint i = 0; i < iStack.size(); ++i)
    delete iStack[i];
}

//! Clears the complete undo stack.
void IpeUndoStack::Clear()
{
  for (uint i = 0; i < iStack.size(); ++i)
    delete iStack[i];
  iStack.clear();
  iCur = 0;
}

//! Add an undo item to the stack.
/*! If the stack is not at the end, discard remaining items.
  Takes ownership of item. */
void IpeUndoStack::Add(IpeUndoItem *item)
{
  for (uint i = iCur; i < iStack.size(); ++i)
    delete iStack[i];
  iStack.resize(iCur);
  iStack.push_back(item);
  iCur = iStack.size();
}

//! Perform an undo operation.
int IpeUndoStack::Undo(IpeDocument *doc)
{
  assert(CanUndo());
  --iCur;
  return iStack[iCur]->Undo(doc);
}

//! Perform a redo operation.
int IpeUndoStack::Redo(IpeDocument *doc)
{
  assert(CanRedo());
  ++iCur;
  return iStack[iCur - 1]->Redo(doc);
}

//! Return text for possible undo operation.
IpeString IpeUndoStack::UndoText() const
{
  assert(CanUndo());
  return iStack[iCur - 1]->Text();
}

//! Return text for possible redo operation.
IpeString IpeUndoStack::RedoText() const
{
  assert(CanRedo());
  return iStack[iCur]->Text();
}

// --------------------------------------------------------------------

/*! \class IpeUndoPageEdit
  \ingroup high
  \brief Undo item for a modification to the current page.
*/

//! Takes ownership of page (a copy of the page before the modification).
IpeUndoPageEdit::IpeUndoPageEdit(int pno, IpePage *page, IpeString text)
{
  iPno = pno;
  iPage = page;
  iText = text;
}

IpeUndoPageEdit::~IpeUndoPageEdit()
{
  delete iPage;
}

IpeString IpeUndoPageEdit::Text() const
{
  return iText;
}

int IpeUndoPageEdit::Undo(IpeDocument *doc)
{
  IpePage *p = (*doc)[iPno];
  (*doc)[iPno] = iPage;
  iPage = p;
  return iPno;
}

// Undo swaps, so redo is identical.
int IpeUndoPageEdit::Redo(IpeDocument *doc)
{
  return Undo(doc);
}

// --------------------------------------------------------------------

/*! \class IpeUndoObjInsertion
  \ingroup high
  \brief Undo item for the insertion of a single object.
*/

IpeUndoObjInsertion::IpeUndoObjInsertion(int pno, IpeString text)
{
  iPno = pno;
  iText = text;
}

IpeString IpeUndoObjInsertion::Text() const
{
  return iText;
}

int IpeUndoObjInsertion::Undo(IpeDocument *doc)
{
  iObj.push_back((*doc)[iPno]->back());
  (*doc)[iPno]->pop_back();
  return iPno;
}

int IpeUndoObjInsertion::Redo(IpeDocument *doc)
{
  (*doc)[iPno]->push_back(iObj.back());
  iObj.clear();
  return iPno;
}

// --------------------------------------------------------------------

/*! \class IpeUndoPageIns
  \ingroup high
  \brief Undo item for the insertion of a page.
*/

IpeUndoPageIns::IpeUndoPageIns(int pno, IpeString text)
{
  iPno = pno;
  iPage = 0;
  iText = text;
}

IpeString IpeUndoPageIns::Text() const
{
  return iText;
}

int IpeUndoPageIns::Undo(IpeDocument *doc)
{
  assert(iPage == 0);
  iPage = (*doc)[iPno];
  doc->erase(doc->begin() + iPno);
  return iPno;
}

int IpeUndoPageIns::Redo(IpeDocument *doc)
{
  doc->insert(doc->begin() + iPno, iPage);
  iPage = 0;
  return iPno;
}

// --------------------------------------------------------------------

/*! \class IpeUndoPageDel
  \ingroup high
  \brief Undo item for the deletion of an empty page.
*/

IpeUndoPageDel::IpeUndoPageDel(int pno, IpePage *page, IpeString text)
{
  iPno = pno;
  iPage = page;
  iText = text;
}

IpeUndoPageDel::~IpeUndoPageDel()
{
  delete iPage;
}

IpeString IpeUndoPageDel::Text() const
{
  return iText;
}

int IpeUndoPageDel::Undo(IpeDocument *doc)
{
  doc->insert(doc->begin() + iPno, iPage);
  iPage = 0;
  return iPno;
}

int IpeUndoPageDel::Redo(IpeDocument *doc)
{
  assert(iPage == 0);
  iPage = (*doc)[iPno];
  doc->erase(doc->begin() + iPno);
  return iPno;
}

// --------------------------------------------------------------------

/*! \class IpeUndoViews
  \ingroup high
  \brief Undo item for change to views of a page.
*/

IpeUndoViews::IpeUndoViews(int pno, IpeViewSeq views, IpeString text)
{
  iPno = pno;
  iViews = views;
  iText = text;
}

IpeString IpeUndoViews::Text() const
{
  return iText;
}

int IpeUndoViews::Undo(IpeDocument *doc)
{
  IpeViewSeq views = (*doc)[iPno]->Views();
  (*doc)[iPno]->SetViews(iViews);
  iViews = views;
  return iPno;
}

// Undo swaps, so redo is identical.
int IpeUndoViews::Redo(IpeDocument *doc)
{
  return Undo(doc);
}

// --------------------------------------------------------------------

/*! \class IpeUndoObjectEdit
  \ingroup high
  \brief Undo item for change to a single object.
*/

IpeUndoObjectEdit::IpeUndoObjectEdit(int pno, IpePage::iterator it,
				     IpePage *page, const IpePgObject &obj,
				     IpeString text)
{
  IpePage::iterator it1 = page->begin();
  iObjNum = 0;
  while (it1 != it) {
    ++it1;
    ++iObjNum;
  }
  iPno = pno;
  iObject.push_back(obj);
  iText = text;
}

IpeString IpeUndoObjectEdit::Text() const
{
  return iText;
}

int IpeUndoObjectEdit::Undo(IpeDocument *doc)
{
  IpePage *page = (*doc)[iPno];
  IpePage::iterator it = page->begin();
  for (int i = 0; i < iObjNum; ++i)
    ++it;
  iObject.push_back(*it);
  *it = iObject.front();
  iObject.pop_front();
  return iPno;
}

// Undo swaps, so redo is identical.
int IpeUndoObjectEdit::Redo(IpeDocument *doc)
{
  return Undo(doc);
}

// --------------------------------------------------------------------
