Logo Search packages:      
Sourcecode: tasks version File versions  Download package

koto-undo-manager.c

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 *  Authors: Iain Holmes <iain@gnome.org>
 *
 *  Copyright 2002 - 2006 Iain Holmes
 *
 *  This file is free software; you can redistribute it and/or
 *  modify it under the terms of version 2 of the GNU Library General Public
 *  License as published by the Free Software Foundation;
 *
 *  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
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public
 *  License along with this library; if not, write to the
 *  Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 *  Boston, MA 02111-1307, USA.
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <glib/gi18n.h>

#include "koto-undo-manager.h"

G_DEFINE_TYPE (KotoUndoManager, koto_undo_manager, G_TYPE_OBJECT);

#define GET_PRIVATE(o)  \
   (G_TYPE_INSTANCE_GET_PRIVATE ((o), KOTO_TYPE_UNDO_MANAGER, KotoUndoManagerPrivate))

enum {
      CHANGED,
      LAST_SIGNAL
};

static guint signals[LAST_SIGNAL];

struct _KotoUndoManagerPrivate {
      GList *contexts, *undo, *redo;
      
      KotoUndoContext *working;
};

struct _KotoUndoContext {
      char *name;
      int count;

      GList *undoables;
};

static void
context_free (KotoUndoContext *ctxt)
{
      GList *u;

      g_free (ctxt->name);
      for (u = ctxt->undoables; u; u = u->next) {
            KotoUndoable *undoable = u->data;
            
            koto_undoable_free (undoable);
      }
      
      g_list_free (ctxt->undoables);

      g_free (ctxt);
}

static KotoUndoContext *
context_new (const char *name)
{
      KotoUndoContext *ctxt;

      ctxt = g_new (KotoUndoContext, 1);

      ctxt->name = g_strdup (name);
      ctxt->count = 0;
      ctxt->undoables = NULL;

      return ctxt;
}

static void
context_undo (KotoUndoContext *ctxt)
{
      GList *p;

      for (p = ctxt->undoables; p; p = p->next) {
            KotoUndoable *u = p->data;

            if (u->undo) {
                  u->undo (u->closure);
            }
      }
}

static void
context_redo (KotoUndoContext *ctxt)
{
      GList *p;

      for (p = g_list_last (ctxt->undoables); p; p = p->prev) {
            KotoUndoable *u = p->data;

            if (u->redo) {
                  u->redo (u->closure);
            }
      }
}

static void
finalize (GObject *object)
{
      KotoUndoManager *manager;
      KotoUndoManagerPrivate *priv;
      GList *p;

      manager = KOTO_UNDO_MANAGER (object);
      priv = manager->priv;

      for (p = priv->contexts; p; p = p->next) {
            KotoUndoContext *ctxt = p->data;
            context_free (ctxt);
      }
      g_list_free (priv->contexts);

      G_OBJECT_CLASS (koto_undo_manager_parent_class)->finalize (object);
}

static void
koto_undo_manager_class_init (KotoUndoManagerClass *klass)
{
      GObjectClass *object_class;

      g_type_class_add_private (klass, sizeof (KotoUndoManagerPrivate));

      object_class = G_OBJECT_CLASS (klass);

      object_class->finalize = finalize;

      signals[CHANGED] = g_signal_new ("changed",
                               G_TYPE_FROM_CLASS (klass),
                               G_SIGNAL_RUN_FIRST |
                               G_SIGNAL_NO_RECURSE,
                               G_STRUCT_OFFSET (KotoUndoManagerClass, changed),
                               NULL, NULL,
                               g_cclosure_marshal_VOID__VOID,
                               G_TYPE_NONE, 0);

}

static void
koto_undo_manager_init (KotoUndoManager *manager)
{
      manager->priv = GET_PRIVATE (manager);
}

KotoUndoManager *
koto_undo_manager_new (void)
{
      KotoUndoManager *manager;

      manager = g_object_new (KOTO_TYPE_UNDO_MANAGER, NULL);
      return manager;
}

gboolean
koto_undo_manager_can_undo (KotoUndoManager *manager)
{
      return (manager->priv->undo != NULL);
}

gboolean
koto_undo_manager_can_redo (KotoUndoManager *manager)
{
      return (manager->priv->redo != NULL);
}

/* FIXME: Should these return copies of the names 
   so that they're threadsafe? */
const char *
koto_undo_manager_get_undo_name (KotoUndoManager *manager)
{
      if (manager->priv->undo) {
            KotoUndoContext *ctxt = manager->priv->undo->data;
            return ctxt->name;
      } else {
            return "";
      }
}

const char *
koto_undo_manager_get_redo_name (KotoUndoManager *manager)
{
      if (manager->priv->redo) {
            KotoUndoContext *ctxt = manager->priv->redo->data;
            return ctxt->name;
      } else {
            return "";
      }
}

KotoUndoContext *
koto_undo_manager_context_begin (KotoUndoManager *manager,
                           const char *name)
{
      KotoUndoContext *ctxt;

      if (manager->priv->working != NULL) {
            manager->priv->working->count++;

            return manager->priv->working;
      }

      ctxt = context_new (name);
      ctxt->count++;
      manager->priv->working = ctxt;

      return ctxt;
}

KotoUndoContext *
koto_undo_manager_context_begin_formatted (KotoUndoManager *manager,
                                 const char *format, ...)
{
      KotoUndoContext *ctxt;
      va_list args;
      char *name;
      
      va_start (args, format);
      
      name = g_strdup_vprintf (format, args);
      
      ctxt = koto_undo_manager_context_begin (manager, name);
      g_free (name);

      va_end (args);

      return ctxt;
}

void
koto_undo_manager_context_end (KotoUndoManager *manager,
                         KotoUndoContext *ctxt)
{
      /* TODO: handle start then end without any adds correctly */
      KotoUndoManagerPrivate *priv;

      priv = manager->priv;

      ctxt->count--;

      if (ctxt->count > 0) {
            return;
      }

      if (priv->contexts == NULL) {
            priv->contexts = g_list_append (NULL, ctxt);
            priv->undo = priv->contexts;
      } else {
            GList *pruned = priv->redo;
            GList *n;

            if (pruned != NULL) {
                  GList *p;

                  /* Disconnect pruned */
                  if (priv->redo && priv->redo->prev) {
                        priv->redo->prev->next = NULL;
                  }

                  /* If redo->prev == NULL then we've pruned everything */
                  if (priv->redo && priv->redo->prev == NULL) {
                        priv->contexts = NULL;
                  }

                  priv->redo = NULL;
                  pruned->prev = NULL;

                  for (p = pruned; p; p = p->next) {
                        KotoUndoContext *c = p->data;

                        context_free (c);
                  }
                  g_list_free (pruned);
            }

            /* Append our new context and move the position pointer on */
            n = g_list_append (priv->undo, ctxt);
            if (priv->undo == NULL) {
                  priv->undo = n;
            } else {
                  priv->undo = priv->undo->next;
            }
            /* If priv->contexts is NULL, then we pruned everything and need
               to start a new list */
            if (priv->contexts == NULL) {
                  priv->contexts = priv->undo;
            }
            priv->redo = NULL;
      }

      priv->working = NULL;

      g_signal_emit (manager, signals[CHANGED], 0);
}

void
koto_undo_manager_context_cancel (KotoUndoManager *manager,
                            KotoUndoContext *ctxt)
{
      g_return_if_fail (manager->priv->working == ctxt);

      manager->priv->working = NULL;
      context_free (ctxt);
}

void
koto_undo_manager_undo (KotoUndoManager *manager)
{
      
      if (manager->priv->undo) {
            context_undo ((KotoUndoContext *) manager->priv->undo->data);
            manager->priv->redo = manager->priv->undo;
            manager->priv->undo = manager->priv->undo->prev;

            g_signal_emit (manager, signals[CHANGED], 0);
      }
}

void
koto_undo_manager_redo (KotoUndoManager *manager)
{
      if (manager->priv->redo) {
            context_redo ((KotoUndoContext *) manager->priv->redo->data);
            manager->priv->undo = manager->priv->redo;
            manager->priv->redo = manager->priv->redo->next;

            g_signal_emit (manager, signals[CHANGED], 0);
      }
}

GList *
koto_undo_manager_get_history (KotoUndoManager *manager)
{
      GList *history = NULL;
      GList *p;
      KotoUndoHistory *hist;

      hist = g_new (KotoUndoHistory, 1);
      hist->name = g_strdup (_("Original Sample"));
      hist->current = FALSE;
      hist->ctxt = NULL;

      history = g_list_prepend (history, hist);

      for (p = manager->priv->contexts; p; p = p->next) {
            KotoUndoContext *ctxt = p->data;

            hist = g_new (KotoUndoHistory, 1);
            hist->name = g_strdup (ctxt->name);
            hist->ctxt = ctxt;

            history = g_list_append (history, hist);
            if (p == manager->priv->undo) {
                  hist->current = TRUE;
            } else {
                  hist->current = FALSE;
            }
      }

      return history;
}

void
koto_undo_context_add (KotoUndoContext *ctxt,
                   KotoUndoable *undoable)
{
      ctxt->undoables = g_list_prepend (ctxt->undoables, undoable);
}

Generated by  Doxygen 1.6.0   Back to index