Compare commits

...

17 Commits

Author SHA1 Message Date
Benjamin Otte
21e0327ed6 undo: Add a way to record multiple commands into one
This is a so far just a simple object that collects commands, merges
them and then combines them into a single GtkUndoCommandChain at the
end. It's supposed to be used with a mechanism that goes
  begin_user_action();
  /* do stuff */
  end_user_action();
The begin_user_action() would create the recorder, then all new commands
would be put into the recorder and end_user_action() would then finish
the recorder and extract one resulting command.
2015-08-24 21:23:19 +02:00
Benjamin Otte
afb661e992 undo: Change semantics of gtk_undo_command_merge()
Merges always succeed. The fallback implementation will create a (newly
added) GtkUndoCommandChain with the 2 commands to be merged.

The merge function may still return NULL. NULL should be returned if
merging commands would create a no-op.
This is implemented in the entry command undo. You can test it by
entering a character and then typing backspace.
2015-08-23 18:01:26 +02:00
Benjamin Otte
76ab9970ae undoundocommand: Never merge
Und commands are merged during construction and should never be merged
when encountered on the stack.
2015-08-23 18:00:42 +02:00
Benjamin Otte
716878cc90 undo: Turn describe() vfunc into "title" property
This way, applications can override the title to make it more useful.
2015-08-23 16:39:15 +02:00
Benjamin Otte
3b11dcf442 undocommand: Don't merge commands that are too far apart
For now, too far apart means 5 seconds. This was decided completely
arbitrarily by me to have a number to test with.
2015-08-23 16:39:15 +02:00
Benjamin Otte
fc17f25117 undocommand: Add a timestamp property 2015-08-23 16:39:15 +02:00
Benjamin Otte
729e90fbf5 undocommand: Add gtk_undo_command_should_merge() function
This splits merging into 2 steps:

1. The actual merging

2. Determining if merging should happen for the UI.

Sometimes commands should be compressed unconditionally and in that case
the 2nd step can be omitted.
2015-08-23 16:39:15 +02:00
Benjamin Otte
d39f484161 entry: Allow entry to decide to not record undo actions
This is useful when recording would be nested (should not be happening
now, but might hppen once we add more API).

And more importantly, we don't want to allow undo for password entries.
2015-08-23 16:39:15 +02:00
Benjamin Otte
ec991f5405 entry: Redo recording of undo
Add a start/end way of capturing the begin and end of an undoable
action. This is necessary because the entry code distinguishes between
changing the contents and updating the cursor position on a very high
level.
And we want to capture the cursor position update in the undo
implementation.

As a nice side effect, the 2 functions are nicely exportable (even for
public API) should we want to. And I guess we might want to for at least
entry completion and comboboxes.
2015-08-23 16:39:15 +02:00
Benjamin Otte
0ce285d7f1 entry: Don't merge undo commands with different cursors
If the user moves the cursor between undo commands, don't merge them.
This is what other implementations do, so mirror them.
2015-08-23 16:39:15 +02:00
Benjamin Otte
131583f54b undostack: Add a simple function to print the undo stack
The function is unused.
2015-08-23 16:39:15 +02:00
Benjamin Otte
8e8ee8142e entry: Rework undo
Instead of storing the actions that were performed, store the before
and after state of the entry.

This also allows for a very easy way to do merges.
2015-08-23 16:39:15 +02:00
Benjamin Otte
aa08f89bb4 undostack: Implement redo 2015-08-23 16:39:15 +02:00
Benjamin Otte
1306256628 undo: Actually implement undo 2015-08-23 16:39:14 +02:00
Benjamin Otte
19a9c6ab60 entry: Add an undo action
Bind it to Ctrl-z for undo and Ctrl-Shift for redo, mirroring what gedit
does.
2015-08-23 16:39:14 +02:00
Benjamin Otte
fd9c18f022 entry: Add undo stack support
So far, undo commands are just recorded, no support for actually undoing
them exists.
2015-08-23 16:39:14 +02:00
Benjamin Otte
cb7756c905 undo: Add skeleton of base classes 2015-08-23 16:39:14 +02:00
16 changed files with 1962 additions and 10 deletions

View File

@@ -427,6 +427,7 @@ gtk_private_h_sources = \
gtkdialogprivate.h \
gtkdndprivate.h \
gtkentryprivate.h \
gtkentryundocommandprivate.h \
gtkeventcontrollerprivate.h \
gtkfilechooserembed.h \
gtkfilechooserentry.h \
@@ -529,6 +530,11 @@ gtk_private_h_sources = \
gtktooltipprivate.h \
gtktreedatalist.h \
gtktreeprivate.h \
gtkundocommandchainprivate.h \
gtkundocommandprivate.h \
gtkundorecorderprivate.h \
gtkundostackprivate.h \
gtkundoundocommandprivate.h \
gtkwidgetprivate.h \
gtkwin32themeprivate.h \
gtkwindowprivate.h \
@@ -674,6 +680,7 @@ gtk_base_c_sources = \
gtkentry.c \
gtkentrybuffer.c \
gtkentrycompletion.c \
gtkentryundocommand.c \
gtkeventbox.c \
gtkeventcontroller.c \
gtkexpander.c \
@@ -860,6 +867,11 @@ gtk_base_c_sources = \
gtktreeview.c \
gtktreeviewcolumn.c \
gtktypebuiltins.c \
gtkundocommand.c \
gtkundocommandchain.c \
gtkundorecorder.c \
gtkundostack.c \
gtkundoundocommand.c \
gtkvolumebutton.c \
gtkviewport.c \
gtkwidget.c \

View File

@@ -38,6 +38,7 @@
#include "gtkdnd.h"
#include "gtkentry.h"
#include "gtkentrybuffer.h"
#include "gtkentryundocommandprivate.h"
#include "gtkiconhelperprivate.h"
#include "gtkimcontextsimple.h"
#include "gtkimmulticontext.h"
@@ -65,6 +66,7 @@
#include "gtkwidgetprivate.h"
#include "gtkstylecontextprivate.h"
#include "gtktexthandleprivate.h"
#include "gtkundostackprivate.h"
#include "gtkpopover.h"
#include "gtktoolbar.h"
#include "gtkmagnifierprivate.h"
@@ -150,7 +152,8 @@ struct _GtkEntryPrivate
PangoAttrList *attrs;
PangoTabArray *tabs;
gchar *im_module;
gchar *im_module;
GtkUndoStack *undo_stack;
gdouble progress_fraction;
gdouble progress_pulse_fraction;
@@ -210,6 +213,7 @@ struct _GtkEntryPrivate
guint overwrite_mode : 1;
guint visible : 1;
GtkEntryUndoMode undo_mode : 2;
guint activates_default : 1;
guint cache_includes_preedit : 1;
guint caps_lock_warning : 1;
@@ -275,6 +279,7 @@ enum {
COPY_CLIPBOARD,
PASTE_CLIPBOARD,
TOGGLE_OVERWRITE,
UNDO,
ICON_PRESS,
ICON_RELEASE,
PREEDIT_CHANGED,
@@ -504,6 +509,8 @@ static void gtk_entry_cut_clipboard (GtkEntry *entry);
static void gtk_entry_copy_clipboard (GtkEntry *entry);
static void gtk_entry_paste_clipboard (GtkEntry *entry);
static void gtk_entry_toggle_overwrite (GtkEntry *entry);
static void gtk_entry_undo (GtkEntry *entry,
gint count);
static void gtk_entry_select_all (GtkEntry *entry);
static void gtk_entry_real_activate (GtkEntry *entry);
static gboolean gtk_entry_popup_menu (GtkWidget *widget);
@@ -754,6 +761,7 @@ gtk_entry_class_init (GtkEntryClass *class)
class->activate = gtk_entry_real_activate;
class->get_text_area_size = gtk_entry_get_text_area_size;
class->get_frame_size = gtk_entry_get_frame_size;
class->undo = gtk_entry_undo;
quark_inner_border = g_quark_from_static_string ("gtk-entry-inner-border");
quark_password_hint = g_quark_from_static_string ("gtk-entry-password-hint");
@@ -1802,6 +1810,27 @@ gtk_entry_class_init (GtkEntryClass *class)
_gtk_marshal_VOID__VOID,
G_TYPE_NONE, 0);
/**
* GtkEntry::undo:
* @entry: the object which received the signal
* @count: the number of steps to undo (or redo if @count is negative)
*
* The ::undo signal is a [keybinding signal][GtkBindingSignal]
* which gets emitted to undo or redo user input.
*
* The default bindings for this signal are Ctrl-z for undo and
* Ctrl-Shift-z for redo.
*/
signals[UNDO] =
g_signal_new (I_("undo"),
G_OBJECT_CLASS_TYPE (gobject_class),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (GtkEntryClass, undo),
NULL, NULL,
_gtk_marshal_VOID__INT,
G_TYPE_NONE, 1,
G_TYPE_INT);
/**
* GtkEntry::icon-press:
* @entry: The entry on which the signal is emitted
@@ -2039,6 +2068,15 @@ gtk_entry_class_init (GtkEntryClass *class)
gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Insert, 0,
"toggle-overwrite", 0);
/* Undo */
gtk_binding_entry_add_signal (binding_set, GDK_KEY_z, GDK_CONTROL_MASK,
"undo", 1,
G_TYPE_INT, 1);
gtk_binding_entry_add_signal (binding_set, GDK_KEY_z, GDK_CONTROL_MASK | GDK_SHIFT_MASK,
"undo", 1,
G_TYPE_INT, -1);
/**
* GtkEntry:inner-border:
*
@@ -2737,6 +2775,8 @@ gtk_entry_init (GtkEntry *entry)
g_signal_connect (priv->im_context, "delete-surrounding",
G_CALLBACK (gtk_entry_delete_surrounding_cb), entry);
priv->undo_stack = gtk_undo_stack_new ();
context = gtk_widget_get_style_context (GTK_WIDGET (entry));
gtk_style_context_add_class (context, GTK_STYLE_CLASS_ENTRY);
@@ -3027,6 +3067,8 @@ gtk_entry_finalize (GObject *object)
g_object_unref (priv->im_context);
g_object_unref (priv->undo_stack);
if (priv->blink_timeout)
g_source_remove (priv->blink_timeout);
@@ -5337,6 +5379,7 @@ gtk_entry_real_insert_text (GtkEditable *editable,
gint new_text_length,
gint *position)
{
GtkEntry *entry = GTK_ENTRY (editable);
guint n_inserted;
gint n_chars;
@@ -5347,11 +5390,11 @@ gtk_entry_real_insert_text (GtkEditable *editable,
* following signal handlers: buffer_inserted_text(), buffer_notify_display_text(),
* buffer_notify_text(), buffer_notify_length()
*/
begin_change (GTK_ENTRY (editable));
begin_change (entry);
n_inserted = gtk_entry_buffer_insert_text (get_buffer (GTK_ENTRY (editable)), *position, new_text, n_chars);
n_inserted = gtk_entry_buffer_insert_text (get_buffer (entry), *position, new_text, n_chars);
end_change (GTK_ENTRY (editable));
end_change (entry);
if (n_inserted != n_chars)
gtk_widget_error_bell (GTK_WIDGET (editable));
@@ -5364,17 +5407,18 @@ gtk_entry_real_delete_text (GtkEditable *editable,
gint start_pos,
gint end_pos)
{
GtkEntry *entry = GTK_ENTRY (editable);
/*
* The actual deletion from the buffer. This will end up firing the
* following signal handlers: buffer_deleted_text(), buffer_notify_display_text(),
* buffer_notify_text(), buffer_notify_length()
*/
begin_change (entry);
begin_change (GTK_ENTRY (editable));
gtk_entry_buffer_delete_text (get_buffer (entry), start_pos, end_pos - start_pos);
gtk_entry_buffer_delete_text (get_buffer (GTK_ENTRY (editable)), start_pos, end_pos - start_pos);
end_change (GTK_ENTRY (editable));
end_change (entry);
}
/* GtkEntryBuffer signal handlers
@@ -5401,6 +5445,9 @@ buffer_inserted_text (GtkEntryBuffer *buffer,
gtk_entry_set_positions (entry, current_pos, selection_bound);
if (priv->undo_mode == GTK_ENTRY_UNDO_RESET)
gtk_undo_stack_clear (priv->undo_stack);
/* Calculate the password hint if it needs to be displayed. */
if (n_chars == 1 && !priv->visible)
{
@@ -5453,6 +5500,9 @@ buffer_deleted_text (GtkEntryBuffer *buffer,
/* We might have deleted the selection */
gtk_entry_update_primary_selection (entry);
if (priv->undo_mode == GTK_ENTRY_UNDO_RESET)
gtk_undo_stack_clear (priv->undo_stack);
/* Disable the password hint if one exists. */
if (!priv->visible)
{
@@ -5759,11 +5809,80 @@ gtk_entry_delete_from_cursor (GtkEntry *entry,
gtk_entry_pend_cursor_blink (entry);
}
typedef struct _GtkEntryRecording GtkEntryRecording;
struct _GtkEntryRecording {
GtkEntryUndoMode old_mode;
GtkEntrySnapshot snapshot;
};
static gboolean
gtk_entry_should_record (GtkEntry *entry)
{
GtkEntryPrivate *priv = entry->priv;
/* Password entries should not allow undo because
* the undo stack might otherwise contain sensitive
* information about passwords.
*/
if (!priv->visible)
return FALSE;
/* Somebody is already recording and recording the same
* thing twice makes no sense. */
if (priv->undo_mode == GTK_ENTRY_UNDO_RECORD)
return FALSE;
return TRUE;
}
static GtkEntryRecording *
gtk_entry_start_recording (GtkEntry *entry)
{
GtkEntryRecording *recording;
g_return_val_if_fail (GTK_IS_ENTRY (entry), NULL);
if (!gtk_entry_should_record (entry))
return NULL;
recording = g_slice_new0 (GtkEntryRecording);
recording->old_mode = gtk_entry_set_undo_mode (entry, GTK_ENTRY_UNDO_RECORD);
gtk_entry_snapshot_init_from_entry (&recording->snapshot, entry);
return recording;
}
static void
gtk_entry_end_recording (GtkEntry *entry,
GtkEntryRecording *recording,
gboolean commit)
{
g_return_if_fail (GTK_IS_ENTRY (entry));
if (recording == NULL)
return;
if (commit)
{
GtkUndoCommand *command;
command = gtk_entry_undo_command_new (entry, &recording->snapshot);
gtk_undo_stack_push (entry->priv->undo_stack, command);
g_object_unref (command);
}
gtk_entry_set_undo_mode (entry, recording->old_mode);
gtk_entry_snapshot_clear (&recording->snapshot);
g_slice_free (GtkEntryRecording, recording);
}
static void
gtk_entry_backspace (GtkEntry *entry)
{
GtkEntryPrivate *priv = entry->priv;
GtkEditable *editable = GTK_EDITABLE (entry);
GtkEntryRecording *recording;
gint prev_pos;
gtk_entry_reset_im_context (entry);
@@ -5776,7 +5895,9 @@ gtk_entry_backspace (GtkEntry *entry)
if (priv->selection_bound != priv->current_pos)
{
recording = gtk_entry_start_recording (entry);
gtk_editable_delete_selection (editable);
gtk_entry_end_recording (entry, recording, TRUE);
return;
}
@@ -5788,6 +5909,8 @@ gtk_entry_backspace (GtkEntry *entry)
PangoLogAttr *log_attrs;
gint n_attrs;
recording = gtk_entry_start_recording (entry);
pango_layout_get_log_attrs (layout, &log_attrs, &n_attrs);
/* Deleting parts of characters */
@@ -5822,6 +5945,8 @@ gtk_entry_backspace (GtkEntry *entry)
{
gtk_editable_delete_text (editable, prev_pos, priv->current_pos);
}
gtk_entry_end_recording (entry, recording, TRUE);
g_free (log_attrs);
}
@@ -5862,6 +5987,7 @@ gtk_entry_cut_clipboard (GtkEntry *entry)
{
GtkEntryPrivate *priv = entry->priv;
GtkEditable *editable = GTK_EDITABLE (entry);
GtkEntryRecording *recording;
gint start, end;
if (!priv->visible)
@@ -5875,7 +6001,11 @@ gtk_entry_cut_clipboard (GtkEntry *entry)
if (priv->editable)
{
if (gtk_editable_get_selection_bounds (editable, &start, &end))
gtk_editable_delete_text (editable, start, end);
{
recording = gtk_entry_start_recording (entry);
gtk_editable_delete_text (editable, start, end);
gtk_entry_end_recording (entry, recording, TRUE);
}
}
else
{
@@ -5940,6 +6070,28 @@ gtk_entry_toggle_overwrite (GtkEntry *entry)
gtk_widget_queue_draw (GTK_WIDGET (entry));
}
static void
gtk_entry_undo (GtkEntry *entry,
gint count)
{
GtkEntryPrivate *priv = entry->priv;
int i;
if (priv->editable)
{
for (i = 0; i < count; i++)
{
if (!gtk_undo_stack_undo (priv->undo_stack))
break;
}
for (i = 0; i > count; i--)
{
if (!gtk_undo_stack_redo (priv->undo_stack))
break;
}
}
}
static void
gtk_entry_select_all (GtkEntry *entry)
{
@@ -6044,12 +6196,17 @@ gtk_entry_delete_surrounding_cb (GtkIMContext *slave,
GtkEntry *entry)
{
GtkEntryPrivate *priv = entry->priv;
GtkEntryRecording *recording;
recording = gtk_entry_start_recording (entry);
if (priv->editable)
gtk_editable_delete_text (GTK_EDITABLE (entry),
priv->current_pos + offset,
priv->current_pos + offset + n_chars);
gtk_entry_end_recording (entry, recording, TRUE);
return TRUE;
}
@@ -6063,12 +6220,15 @@ gtk_entry_enter_text (GtkEntry *entry,
{
GtkEntryPrivate *priv = entry->priv;
GtkEditable *editable = GTK_EDITABLE (entry);
GtkEntryRecording *recording;
gint tmp_pos;
gboolean old_need_im_reset;
old_need_im_reset = priv->need_im_reset;
priv->need_im_reset = FALSE;
recording = gtk_entry_start_recording (entry);
if (gtk_editable_get_selection_bounds (editable, NULL, NULL))
gtk_editable_delete_selection (editable);
else
@@ -6081,6 +6241,8 @@ gtk_entry_enter_text (GtkEntry *entry,
gtk_editable_insert_text (editable, str, strlen (str), &tmp_pos);
gtk_editable_set_position (editable, tmp_pos);
gtk_entry_end_recording (entry, recording, TRUE);
priv->need_im_reset = old_need_im_reset;
}
@@ -7329,6 +7491,7 @@ paste_received (GtkClipboard *clipboard,
GtkEntry *entry = GTK_ENTRY (data);
GtkEditable *editable = GTK_EDITABLE (entry);
GtkEntryPrivate *priv = entry->priv;
GtkEntryRecording *recording;
guint button;
button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (priv->multipress_gesture));
@@ -7368,12 +7531,14 @@ paste_received (GtkClipboard *clipboard,
}
begin_change (entry);
recording = gtk_entry_start_recording (entry);
if (gtk_editable_get_selection_bounds (editable, &start, &end))
gtk_editable_delete_text (editable, start, end);
pos = priv->current_pos;
gtk_editable_insert_text (editable, text, length, &pos);
gtk_editable_set_position (editable, pos);
gtk_entry_end_recording (entry, recording, TRUE);
end_change (entry);
if (completion &&
@@ -10956,6 +11121,17 @@ keymap_state_changed (GdkKeymap *keymap,
remove_capslock_feedback (entry);
}
GtkEntryUndoMode
gtk_entry_set_undo_mode (GtkEntry *entry,
GtkEntryUndoMode mode)
{
GtkEntryUndoMode result = entry->priv->undo_mode;
entry->priv->undo_mode = mode;
return result;
}
/*
* _gtk_entry_set_is_cell_renderer:
* @entry: a #GtkEntry

View File

@@ -159,10 +159,11 @@ struct _GtkEntryClass
gint *width,
gint *height);
void (* undo) (GtkEntry *entry,
gint count);
/*< private >*/
/* Padding for future expansion */
void (*_gtk_reserved1) (void);
void (*_gtk_reserved2) (void);
void (*_gtk_reserved3) (void);
void (*_gtk_reserved4) (void);

View File

@@ -26,6 +26,12 @@
G_BEGIN_DECLS
typedef enum {
GTK_ENTRY_UNDO_RESET,
GTK_ENTRY_UNDO_RECORD,
GTK_ENTRY_UNDO_REPLAY
} GtkEntryUndoMode;
struct _GtkEntryCompletionPrivate
{
GtkWidget *entry;
@@ -79,6 +85,9 @@ void _gtk_entry_completion_popdown (GtkEntryCompletion *completion);
void _gtk_entry_completion_connect (GtkEntryCompletion *completion,
GtkEntry *entry);
void _gtk_entry_completion_disconnect (GtkEntryCompletion *completion);
GtkEntryUndoMode
gtk_entry_set_undo_mode (GtkEntry *entry,
GtkEntryUndoMode mode);
gchar* _gtk_entry_get_display_text (GtkEntry *entry,
gint start_pos,

284
gtk/gtkentryundocommand.c Normal file
View File

@@ -0,0 +1,284 @@
/*
* Copyright © 2015 Red Hat Inc.
*
* 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 2.1 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 library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtkentryundocommandprivate.h"
#include <glib/gi18n-lib.h>
#include "gtkentry.h"
#include "gtkentryprivate.h"
typedef struct _GtkEntryUndoCommandPrivate GtkEntryUndoCommandPrivate;
struct _GtkEntryUndoCommandPrivate {
GtkEntry *entry; /* entry we're operating on or NULL if entry was deleted */
GtkEntrySnapshot before; /* what we undo to */
GtkEntrySnapshot after; /* what we redo to */
};
G_DEFINE_TYPE_WITH_CODE (GtkEntryUndoCommand, gtk_entry_undo_command, GTK_TYPE_UNDO_COMMAND,
G_ADD_PRIVATE (GtkEntryUndoCommand))
void
gtk_entry_snapshot_init_from_entry (GtkEntrySnapshot *snapshot,
GtkEntry *entry)
{
gint start, end;
snapshot->text = g_strdup (gtk_entry_get_text (entry));
gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), &start, &end);
snapshot->cursor = gtk_editable_get_position (GTK_EDITABLE (entry));
if (start == snapshot->cursor)
snapshot->selection_start = end;
else
snapshot->selection_start = start;
}
void
gtk_entry_snapshot_clear (GtkEntrySnapshot *snapshot)
{
g_free (snapshot->text);
snapshot->text = NULL;
}
static void
gtk_entry_snapshot_copy (GtkEntrySnapshot *target,
const GtkEntrySnapshot *source)
{
target->text = g_strdup (source->text);
target->cursor = source->cursor;
target->selection_start = source->selection_start;
}
static gboolean
gtk_entry_undo_command_run (GtkEntry *entry,
const GtkEntrySnapshot *snapshot)
{
GtkEntryUndoMode old_mode;
if (entry == NULL)
return FALSE;
old_mode = gtk_entry_set_undo_mode (entry, GTK_ENTRY_UNDO_REPLAY);
gtk_entry_set_text (entry, snapshot->text);
gtk_editable_select_region (GTK_EDITABLE (entry), snapshot->cursor, snapshot->selection_start);
gtk_entry_set_undo_mode (entry, old_mode);
return TRUE;
}
static gboolean
gtk_entry_undo_command_undo (GtkUndoCommand *command)
{
GtkEntryUndoCommandPrivate *priv = gtk_entry_undo_command_get_instance_private (GTK_ENTRY_UNDO_COMMAND (command));
return gtk_entry_undo_command_run (priv->entry, &priv->before);
}
gboolean
gtk_entry_undo_command_redo (GtkUndoCommand *command)
{
GtkEntryUndoCommandPrivate *priv = gtk_entry_undo_command_get_instance_private (GTK_ENTRY_UNDO_COMMAND (command));
return gtk_entry_undo_command_run (priv->entry, &priv->after);
}
static guint
get_prefix_len (const char *str1,
const char *str2)
{
guint i;
for (i = 0; str1[i] == str2[i] && str1[i] != 0; i++)
{
/* nothing to do here */
}
return i;
}
static guint
get_suffix_len (const char *str1,
guint len1,
const char *str2,
guint len2)
{
const char *cur1, *cur2;
guint i, max_len;
cur1 = str1 + len1 - 1;
cur2 = str2 + len2 - 1;
max_len = MIN (len1, len2);
for (i = 0; *cur1 == *cur2 && i < max_len; i++)
{
cur1--;
cur2--;
}
return i;
}
char *
generate_title (const GtkEntrySnapshot *before,
const GtkEntrySnapshot *after)
{
guint before_len, after_len, prefix_len, suffix_len;
before_len = strlen (before->text);
after_len = strlen (after->text);
prefix_len = get_prefix_len (before->text, after->text);
suffix_len = get_suffix_len (before->text, before_len,
after->text, after_len);
if (before_len == after_len && before_len == prefix_len)
return g_strdup (_("No changes")); /* huh? */
else if (prefix_len + suffix_len == before_len)
return g_strdup_printf (_("Entered `%.*s'"), after_len - prefix_len - suffix_len, after->text + prefix_len);
else if (prefix_len + suffix_len == after_len)
return g_strdup_printf (_("Deleted `%.*s'"), before_len - prefix_len - suffix_len, before->text + prefix_len);
else
return g_strdup (_("Text changed"));
}
static GtkUndoCommand *
gtk_entry_undo_command_new_from_snapshots (GtkEntry *entry,
gint64 timestamp,
const GtkEntrySnapshot *before,
const GtkEntrySnapshot *after)
{
GtkEntryUndoCommand *command;
GtkEntryUndoCommandPrivate *priv;
char *title;
title = generate_title (before, after);
command = g_object_new (GTK_TYPE_ENTRY_UNDO_COMMAND,
"timestamp", timestamp,
"title", title,
NULL);
priv = gtk_entry_undo_command_get_instance_private (command);
g_free (title);
priv->entry = entry;
gtk_entry_snapshot_copy (&priv->before, before);
gtk_entry_snapshot_copy (&priv->after, after);
return GTK_UNDO_COMMAND (command);
}
GtkUndoCommand *
gtk_entry_undo_command_merge (GtkUndoCommand *command,
GtkUndoCommand *followup)
{
GtkEntryUndoCommandPrivate *command_priv = gtk_entry_undo_command_get_instance_private (GTK_ENTRY_UNDO_COMMAND (command));
GtkEntryUndoCommandPrivate *followup_priv = gtk_entry_undo_command_get_instance_private (GTK_ENTRY_UNDO_COMMAND (followup));
if (!GTK_IS_ENTRY_UNDO_COMMAND (followup))
return GTK_UNDO_COMMAND_CLASS (gtk_entry_undo_command_parent_class)->merge (command, followup);
if (command_priv->entry != followup_priv->entry)
return GTK_UNDO_COMMAND_CLASS (gtk_entry_undo_command_parent_class)->merge (command, followup);
if (g_str_equal (command_priv->before.text, followup_priv->after.text))
return NULL;
return gtk_entry_undo_command_new_from_snapshots (command_priv->entry,
gtk_undo_command_get_timestamp (followup),
&command_priv->before,
&followup_priv->after);
}
gboolean
gtk_entry_undo_command_should_merge (GtkUndoCommand *command,
GtkUndoCommand *followup)
{
GtkEntryUndoCommandPrivate *command_priv = gtk_entry_undo_command_get_instance_private (GTK_ENTRY_UNDO_COMMAND (command));
GtkEntryUndoCommandPrivate *followup_priv = gtk_entry_undo_command_get_instance_private (GTK_ENTRY_UNDO_COMMAND (followup));
if (!GTK_UNDO_COMMAND_CLASS (gtk_entry_undo_command_parent_class)->should_merge (command, followup))
return FALSE;
if (!GTK_IS_ENTRY_UNDO_COMMAND (followup))
return FALSE;
if (command_priv->entry != followup_priv->entry)
return FALSE;
if (!g_str_equal (command_priv->after.text, followup_priv->before.text) ||
command_priv->after.cursor != followup_priv->before.cursor ||
command_priv->after.selection_start != followup_priv->before.selection_start)
return FALSE;
return TRUE;
}
static void
gtk_entry_undo_command_finalize (GObject *object)
{
GtkEntryUndoCommandPrivate *priv = gtk_entry_undo_command_get_instance_private (GTK_ENTRY_UNDO_COMMAND (object));
gtk_entry_snapshot_clear (&priv->before);
gtk_entry_snapshot_clear (&priv->after);
G_OBJECT_CLASS (gtk_entry_undo_command_parent_class)->finalize (object);
}
static void
gtk_entry_undo_command_class_init (GtkEntryUndoCommandClass *klass)
{
GtkUndoCommandClass *undo_class = GTK_UNDO_COMMAND_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = gtk_entry_undo_command_finalize;
undo_class->undo = gtk_entry_undo_command_undo;
undo_class->redo = gtk_entry_undo_command_redo;
undo_class->merge = gtk_entry_undo_command_merge;
undo_class->should_merge = gtk_entry_undo_command_should_merge;
}
static void
gtk_entry_undo_command_init (GtkEntryUndoCommand *command)
{
}
GtkUndoCommand *
gtk_entry_undo_command_new (GtkEntry *entry,
const GtkEntrySnapshot *before)
{
GtkEntrySnapshot after;
GtkUndoCommand *result;
g_return_val_if_fail (GTK_IS_ENTRY (entry), NULL);
g_return_val_if_fail (before != NULL, NULL);
gtk_entry_snapshot_init_from_entry (&after, entry);
result = gtk_entry_undo_command_new_from_snapshots (entry, 0, before, &after);
gtk_entry_snapshot_clear (&after);
return result;
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright © 2015 Red Hat Inc.
*
* 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 2.1 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 library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_ENTRY_UNDO_COMMAND_PRIVATE_H__
#define __GTK_ENTRY_UNDO_COMMAND_PRIVATE_H__
#include <gtk/gtkentry.h>
#include <gtk/gtkundocommandprivate.h>
G_BEGIN_DECLS
#define GTK_TYPE_ENTRY_UNDO_COMMAND (gtk_entry_undo_command_get_type ())
#define GTK_ENTRY_UNDO_COMMAND(obj) (G_TYPE_CHECK_INSTANCE_CAST (obj, GTK_TYPE_ENTRY_UNDO_COMMAND, GtkEntryUndoCommand))
#define GTK_ENTRY_UNDO_COMMAND_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST (cls, GTK_TYPE_ENTRY_UNDO_COMMAND, GtkEntryUndoCommandClass))
#define GTK_IS_ENTRY_UNDO_COMMAND(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, GTK_TYPE_ENTRY_UNDO_COMMAND))
#define GTK_IS_ENTRY_UNDO_COMMAND_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE (obj, GTK_TYPE_ENTRY_UNDO_COMMAND))
#define GTK_ENTRY_UNDO_COMMAND_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_ENTRY_UNDO_COMMAND, GtkEntryUndoCommandClass))
typedef struct _GtkEntryUndoCommand GtkEntryUndoCommand;
typedef struct _GtkEntryUndoCommandClass GtkEntryUndoCommandClass;
typedef struct _GtkEntrySnapshot GtkEntrySnapshot;
struct _GtkEntrySnapshot {
char *text; /* text of the whole entry */
guint cursor; /* cursor position */
guint selection_start; /* selection start. Equal to cursor if no selection */
};
struct _GtkEntryUndoCommand
{
GtkUndoCommand parent;
};
struct _GtkEntryUndoCommandClass
{
GtkUndoCommandClass parent_class;
};
GType gtk_entry_undo_command_get_type (void) G_GNUC_CONST;
GtkUndoCommand * gtk_entry_undo_command_new (GtkEntry *entry,
const GtkEntrySnapshot *before);
void gtk_entry_snapshot_init_from_entry (GtkEntrySnapshot *snapshot,
GtkEntry *entry);
void gtk_entry_snapshot_clear (GtkEntrySnapshot *snapshot);
G_END_DECLS
#endif /* __GTK_ENTRY_UNDO_COMMAND_PRIVATE_H__ */

307
gtk/gtkundocommand.c Normal file
View File

@@ -0,0 +1,307 @@
/*
* Copyright © 2015 Red Hat Inc.
*
* 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 2.1 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 library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtkundocommandprivate.h"
#include <glib/gi18n-lib.h>
#include "gtkundocommandchainprivate.h"
typedef struct _GtkUndoCommandPrivate GtkUndoCommandPrivate;
struct _GtkUndoCommandPrivate {
gint64 timestamp;
char *title;
};
enum {
PROP_0,
PROP_TIMESTAMP,
PROP_TITLE,
/* add more */
NUM_PROPERTIES
};
static GParamSpec *properties[NUM_PROPERTIES];
G_DEFINE_TYPE_WITH_CODE (GtkUndoCommand, gtk_undo_command, G_TYPE_OBJECT,
G_ADD_PRIVATE (GtkUndoCommand))
static void
gtk_undo_command_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
GtkUndoCommandPrivate *priv = gtk_undo_command_get_instance_private (GTK_UNDO_COMMAND (object));
switch (property_id)
{
case PROP_TIMESTAMP:
g_value_set_int64 (value, priv->timestamp);
break;
case PROP_TITLE:
g_value_set_string (value, priv->title);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
}
static void
gtk_undo_command_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GtkUndoCommand *command = GTK_UNDO_COMMAND (object);
GtkUndoCommandPrivate *priv = gtk_undo_command_get_instance_private (command);
switch (property_id)
{
case PROP_TIMESTAMP:
priv->timestamp = g_value_get_int64 (value);
if (priv->timestamp == 0)
priv->timestamp = g_get_real_time ();
break;
case PROP_TITLE:
gtk_undo_command_set_title (command, g_value_get_string (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
}
static void
gtk_undo_command_finalize (GObject *object)
{
GtkUndoCommandPrivate *priv = gtk_undo_command_get_instance_private (GTK_UNDO_COMMAND (object));
g_free (priv->title);
G_OBJECT_CLASS (gtk_undo_command_parent_class)->finalize (object);
}
static gboolean
gtk_undo_command_real_undo (GtkUndoCommand *command)
{
g_warning ("%s class failed to implement undo", G_OBJECT_TYPE_NAME (command));
return FALSE;
}
gboolean
gtk_undo_command_real_redo (GtkUndoCommand *command)
{
g_warning ("%s class failed to implement redo", G_OBJECT_TYPE_NAME (command));
return FALSE;
}
GtkUndoCommand *
gtk_undo_command_real_merge (GtkUndoCommand *command,
GtkUndoCommand *followup)
{
return gtk_undo_command_chain_new_merge (command, followup);
}
gboolean
gtk_undo_command_real_should_merge (GtkUndoCommand *command,
GtkUndoCommand *followup)
{
GtkUndoCommandPrivate *command_priv = gtk_undo_command_get_instance_private (command);
GtkUndoCommandPrivate *followup_priv = gtk_undo_command_get_instance_private (followup);
if (followup_priv->timestamp - command_priv->timestamp > 5 * G_USEC_PER_SEC)
return FALSE;
return TRUE;
}
static void
gtk_undo_command_class_init (GtkUndoCommandClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->set_property = gtk_undo_command_set_property;
object_class->get_property = gtk_undo_command_get_property;
object_class->finalize = gtk_undo_command_finalize;
klass->undo = gtk_undo_command_real_undo;
klass->redo = gtk_undo_command_real_redo;
klass->merge = gtk_undo_command_real_merge;
klass->should_merge = gtk_undo_command_real_should_merge;
/*
* GtkUndoCommand:timestamp:
*
* Timestamp this command was recorded at. See
* gtk_undo_command_get_timestamp() for details.
*/
properties[PROP_TIMESTAMP] = g_param_spec_int64 ("timestamp", "Timestamp",
"Time at which this command was recorded",
0, G_MAXINT64, 0,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/*
* GtkUndoCommand:title:
*
* Title of this command. See
* gtk_undo_command_get_title() for details.
*/
properties[PROP_TITLE] = g_param_spec_string ("title", "Title",
"Title of this command",
NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT |
G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (object_class, NUM_PROPERTIES, properties);
}
static void
gtk_undo_command_init (GtkUndoCommand *command)
{
}
gboolean
gtk_undo_command_undo (GtkUndoCommand *command)
{
g_return_val_if_fail (GTK_IS_UNDO_COMMAND (command), FALSE);
return GTK_UNDO_COMMAND_GET_CLASS (command)->undo (command);
}
gboolean
gtk_undo_command_redo (GtkUndoCommand *command)
{
g_return_val_if_fail (GTK_IS_UNDO_COMMAND (command), FALSE);
return GTK_UNDO_COMMAND_GET_CLASS (command)->redo (command);
}
GtkUndoCommand *
gtk_undo_command_merge (GtkUndoCommand *command,
GtkUndoCommand *followup_command)
{
g_return_val_if_fail (GTK_IS_UNDO_COMMAND (command), NULL);
g_return_val_if_fail (GTK_IS_UNDO_COMMAND (followup_command), NULL);
return GTK_UNDO_COMMAND_GET_CLASS (command)->merge (command, followup_command);
}
/*
* gtk_undo_command_should_merge:
* @command: The command that would be undone second
* @followup_command: The command that would be undone first
*
* Determines if @command and @followup_comand should be merged for
* undo. That is, it determines if when the user triggers an undo
* (for example by pressing Ctrl-z), if both commands should be
* undone at once or if they should require two seperate undos.
*
* Returns: %TRUE if the commands should be merged for UI purposes.
*/
gboolean
gtk_undo_command_should_merge (GtkUndoCommand *command,
GtkUndoCommand *followup_command)
{
g_return_val_if_fail (GTK_IS_UNDO_COMMAND (command), FALSE);
g_return_val_if_fail (GTK_IS_UNDO_COMMAND (followup_command), FALSE);
return GTK_UNDO_COMMAND_GET_CLASS (command)->should_merge (command, followup_command);
}
/*
* gtk_undo_command_get_title:
* @command: The command
*
* Gets the title of the @command. The title is a translated string
* for presentation in a user interface, for example a list of actions
* to undo.
*
* Undo commands always have a title set, but that title is often generic.
* Applications may want to update titles via gtk_undo_command_set_title()
*
* Returns: The title for the command
*/
const char *
gtk_undo_command_get_title (GtkUndoCommand *command)
{
GtkUndoCommandPrivate *priv = gtk_undo_command_get_instance_private (command);
g_return_val_if_fail (GTK_IS_UNDO_COMMAND (command), NULL);
return priv->title;
}
/*
* gtk_undo_command_set_title:
* @command: The command
* @title: The new title
*
* Updates the title for the given @command. This function should be called
* when applications can provide a better title for an action than the generic
* title provided upon creation of commands.
*/
void
gtk_undo_command_set_title (GtkUndoCommand *command,
const char *title)
{
GtkUndoCommandPrivate *priv = gtk_undo_command_get_instance_private (command);
g_return_if_fail (GTK_IS_UNDO_COMMAND (command));
if (title == NULL)
/* translators: This is the (hopefully never used) fallback string for undo commands without a name */
title = _("Unknown command");
if (g_strcmp0 (priv->title, title) == 0)
return;
g_free (priv->title);
priv->title = g_strdup (title);
g_object_notify_by_pspec (G_OBJECT (command), properties[PROP_TITLE]);
}
/*
* gtk_undo_command_get_timestamp:
* @command: The command
*
* Returns the timestamp when this command was recorded. If multiple
* commands get combined into one, the timestamp will be the timestamp
* of the newest command.
*
* Returns: Timestamp as returned by g_get_real_time()
**/
gint64
gtk_undo_command_get_timestamp (GtkUndoCommand *command)
{
GtkUndoCommandPrivate *priv = gtk_undo_command_get_instance_private (command);
g_return_val_if_fail (GTK_IS_UNDO_COMMAND (command), 0);
return priv->timestamp;
}

209
gtk/gtkundocommandchain.c Normal file
View File

@@ -0,0 +1,209 @@
/*
* Copyright © 2015 Red Hat Inc.
*
* 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 2.1 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 library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtkundocommandchainprivate.h"
#include <glib/gi18n-lib.h>
#include <string.h>
typedef struct _GtkUndoCommandChainPrivate GtkUndoCommandChainPrivate;
struct _GtkUndoCommandChainPrivate {
GtkUndoCommand **commands;
gsize n_commands;
};
G_DEFINE_TYPE_WITH_CODE (GtkUndoCommandChain, gtk_undo_command_chain, GTK_TYPE_UNDO_COMMAND,
G_ADD_PRIVATE (GtkUndoCommandChain))
static gboolean
gtk_undo_command_chain_undo (GtkUndoCommand *command)
{
GtkUndoCommandChainPrivate *priv = gtk_undo_command_chain_get_instance_private (GTK_UNDO_COMMAND_CHAIN (command));
gboolean result;
gsize i;
result = FALSE;
for (i = priv->n_commands; i --> 0; )
{
result |= gtk_undo_command_undo (priv->commands[i]);
}
return result;
}
gboolean
gtk_undo_command_chain_redo (GtkUndoCommand *command)
{
GtkUndoCommandChainPrivate *priv = gtk_undo_command_chain_get_instance_private (GTK_UNDO_COMMAND_CHAIN (command));
gboolean result;
gsize i;
result = FALSE;
for (i = 0; i < priv->n_commands; i++)
{
result |= gtk_undo_command_redo (priv->commands[i]);
}
return result;
}
GtkUndoCommand *
gtk_undo_command_chain_merge (GtkUndoCommand *command,
GtkUndoCommand *followup)
{
return gtk_undo_command_chain_new_merge (command, followup);
}
static void
gtk_undo_command_chain_dispose (GObject *object)
{
GtkUndoCommandChainPrivate *priv = gtk_undo_command_chain_get_instance_private (GTK_UNDO_COMMAND_CHAIN (object));
gsize i;
for (i = 0; i < priv->n_commands; i++)
{
g_object_unref (priv->commands[i]);
}
g_free (priv->commands);
priv->n_commands = 0;
G_OBJECT_CLASS (gtk_undo_command_chain_parent_class)->dispose (object);
}
static void
gtk_undo_command_chain_class_init (GtkUndoCommandChainClass *klass)
{
GtkUndoCommandClass *undo_class = GTK_UNDO_COMMAND_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = gtk_undo_command_chain_dispose;
undo_class->undo = gtk_undo_command_chain_undo;
undo_class->redo = gtk_undo_command_chain_redo;
undo_class->merge = gtk_undo_command_chain_merge;
}
static void
gtk_undo_command_chain_init (GtkUndoCommandChain *command)
{
}
/**
* gtk_undo_command_chain_new:
* @commands: (array length=n_commands): commands to chain
* @n_commands: number of commands
*
* Creates a new command that undoes or redoes all given @commands
* in order. The first command in the @commands array is the oldest
* command. So it is the first command to be executed during redo
* and the last command executed during undo.
*
* Returns: a new #GtkUndoCommand
*/
GtkUndoCommand *
gtk_undo_command_chain_new (GtkUndoCommand **commands,
gsize n_commands)
{
GtkUndoCommandChainPrivate *priv;
GtkUndoCommand *result;
char *title;
gsize i;
g_return_val_if_fail (commands != NULL, NULL);
g_return_val_if_fail (n_commands > 0, NULL);
title = g_strdup_printf (_("Execute %zu commands"), n_commands);
result = g_object_new (GTK_TYPE_UNDO_COMMAND_CHAIN,
"timestamp", gtk_undo_command_get_timestamp (commands[n_commands - 1]),
"title", title,
NULL);
g_free (title);
priv = gtk_undo_command_chain_get_instance_private (GTK_UNDO_COMMAND_CHAIN (result));
priv->commands = g_new (GtkUndoCommand *, n_commands);
priv->n_commands = n_commands;
for (i = 0; i < n_commands; i++)
{
priv->commands[i] = g_object_ref (commands[i]);
}
return result;
}
/**
* gtk_undo_command_chain_new_merge:
* @command: first command to merge
* @followup: followup command to merge
*
* Merges the two given command. This is a convenience function for use
* in GtkUndCommandClass::merge implementations as a fallback.
*
* Returns: A new command
*/
GtkUndoCommand *
gtk_undo_command_chain_new_merge (GtkUndoCommand *command,
GtkUndoCommand *followup)
{
GtkUndoCommand **commands, **first;
GtkUndoCommand *result;
gsize n_commands, n_first;
g_return_val_if_fail (GTK_IS_UNDO_COMMAND (command), NULL);
g_return_val_if_fail (GTK_IS_UNDO_COMMAND (followup), NULL);
if (GTK_IS_UNDO_COMMAND_CHAIN (command))
{
GtkUndoCommandChainPrivate *priv = gtk_undo_command_chain_get_instance_private (GTK_UNDO_COMMAND_CHAIN (command));
n_commands = n_first = priv->n_commands;
first = priv->commands;
}
else
{
n_commands = n_first = 1;
first = &command;
}
if (GTK_IS_UNDO_COMMAND_CHAIN (followup))
{
GtkUndoCommandChainPrivate *priv = gtk_undo_command_chain_get_instance_private (GTK_UNDO_COMMAND_CHAIN (followup));
n_commands += priv->n_commands;
commands = g_new (GtkUndoCommand *, n_commands);
memcpy (commands + n_first, priv->commands, sizeof (GtkUndoCommand *) * priv->n_commands);
}
else
{
n_commands++;
commands = g_new (GtkUndoCommand *, n_commands);
commands[n_first] = followup;
}
memcpy (commands, first, sizeof (GtkUndoCommand *) * n_first);
result = gtk_undo_command_chain_new (commands, n_commands);
g_free (commands);
return result;
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright © 2015 Red Hat Inc.
*
* 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 2.1 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 library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_UNDO_COMMAND_CHAIN_PRIVATE_H__
#define __GTK_UNDO_COMMAND_CHAIN_PRIVATE_H__
#include <gtk/gtkentry.h>
#include <gtk/gtkundocommandprivate.h>
G_BEGIN_DECLS
#define GTK_TYPE_UNDO_COMMAND_CHAIN (gtk_undo_command_chain_get_type ())
#define GTK_UNDO_COMMAND_CHAIN(obj) (G_TYPE_CHECK_INSTANCE_CAST (obj, GTK_TYPE_UNDO_COMMAND_CHAIN, GtkUndoCommandChain))
#define GTK_UNDO_COMMAND_CHAIN_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST (cls, GTK_TYPE_UNDO_COMMAND_CHAIN, GtkUndoCommandChainClass))
#define GTK_IS_UNDO_COMMAND_CHAIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, GTK_TYPE_UNDO_COMMAND_CHAIN))
#define GTK_IS_UNDO_COMMAND_CHAIN_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE (obj, GTK_TYPE_UNDO_COMMAND_CHAIN))
#define GTK_UNDO_COMMAND_CHAIN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_UNDO_COMMAND_CHAIN, GtkUndoCommandChainClass))
typedef struct _GtkUndoCommandChain GtkUndoCommandChain;
typedef struct _GtkUndoCommandChainClass GtkUndoCommandChainClass;
struct _GtkUndoCommandChain
{
GtkUndoCommand parent;
};
struct _GtkUndoCommandChainClass
{
GtkUndoCommandClass parent_class;
};
GType gtk_undo_command_chain_get_type (void) G_GNUC_CONST;
GtkUndoCommand * gtk_undo_command_chain_new (GtkUndoCommand **commnds,
gsize n_commands);
GtkUndoCommand * gtk_undo_command_chain_new_merge (GtkUndoCommand *command,
GtkUndoCommand *followup);
G_END_DECLS
#endif /* __GTK_UNDO_COMMAND_CHAIN_PRIVATE_H__ */

View File

@@ -0,0 +1,75 @@
/*
* Copyright © 2015 Red Hat Inc.
*
* 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 2.1 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 library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_UNDO_COMMAND_PRIVATE_H__
#define __GTK_UNDO_COMMAND_PRIVATE_H__
#include <glib-object.h>
G_BEGIN_DECLS
#define GTK_TYPE_UNDO_COMMAND (gtk_undo_command_get_type ())
#define GTK_UNDO_COMMAND(obj) (G_TYPE_CHECK_INSTANCE_CAST (obj, GTK_TYPE_UNDO_COMMAND, GtkUndoCommand))
#define GTK_UNDO_COMMAND_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST (cls, GTK_TYPE_UNDO_COMMAND, GtkUndoCommandClass))
#define GTK_IS_UNDO_COMMAND(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, GTK_TYPE_UNDO_COMMAND))
#define GTK_IS_UNDO_COMMAND_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE (obj, GTK_TYPE_UNDO_COMMAND))
#define GTK_UNDO_COMMAND_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_UNDO_COMMAND, GtkUndoCommandClass))
typedef struct _GtkUndoCommand GtkUndoCommand;
typedef struct _GtkUndoCommandClass GtkUndoCommandClass;
struct _GtkUndoCommand
{
GObject parent;
};
struct _GtkUndoCommandClass
{
GObjectClass parent_class;
/* undo the command, return FALSE if undo failed */
gboolean (* undo) (GtkUndoCommand *command);
/* run the command again, return FALSE if redo failed */
gboolean (* redo) (GtkUndoCommand *command);
/* merge this command with a followup command, return NULL if merge not possible */
GtkUndoCommand * (* merge) (GtkUndoCommand *command,
GtkUndoCommand *followup_command);
/* should the two commands be merged for user undo purposes? */
gboolean (* should_merge) (GtkUndoCommand *command,
GtkUndoCommand *followup_command);
};
GType gtk_undo_command_get_type (void) G_GNUC_CONST;
gint64 gtk_undo_command_get_timestamp (GtkUndoCommand *command);
const char * gtk_undo_command_get_title (GtkUndoCommand *command);
void gtk_undo_command_set_title (GtkUndoCommand *command,
const char *title);
gboolean gtk_undo_command_undo (GtkUndoCommand *command);
gboolean gtk_undo_command_redo (GtkUndoCommand *command);
GtkUndoCommand * gtk_undo_command_merge (GtkUndoCommand *command,
GtkUndoCommand *followup_command);
gboolean gtk_undo_command_should_merge (GtkUndoCommand *command,
GtkUndoCommand *followup_command);
G_END_DECLS
#endif /* __GTK_UNDO_COMMAND_PRIVATE_H__ */

154
gtk/gtkundorecorder.c Normal file
View File

@@ -0,0 +1,154 @@
/*
* Copyright © 2015 Red Hat Inc.
*
* 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 2.1 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 library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtkundorecorderprivate.h"
#include <glib/gi18n-lib.h>
#include "gtkundocommandchainprivate.h"
typedef struct _GtkUndoRecorderPrivate GtkUndoRecorderPrivate;
struct _GtkUndoRecorderPrivate {
GSList *commands;
};
G_DEFINE_TYPE_WITH_CODE (GtkUndoRecorder, gtk_undo_recorder, G_TYPE_OBJECT,
G_ADD_PRIVATE (GtkUndoRecorder))
static void
gtk_undo_recorder_dispose (GObject *object)
{
gtk_undo_recorder_clear (GTK_UNDO_RECORDER (object));
G_OBJECT_CLASS (gtk_undo_recorder_parent_class)->dispose (object);
}
static void
gtk_undo_recorder_class_init (GtkUndoRecorderClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = gtk_undo_recorder_dispose;
}
static void
gtk_undo_recorder_init (GtkUndoRecorder *recorder)
{
}
GtkUndoRecorder *
gtk_undo_recorder_new (void)
{
return g_object_new (GTK_TYPE_UNDO_RECORDER, NULL);
}
void
gtk_undo_recorder_push (GtkUndoRecorder *recorder,
GtkUndoCommand *command)
{
GtkUndoRecorderPrivate *priv = gtk_undo_recorder_get_instance_private (recorder);
g_return_if_fail (GTK_IS_UNDO_RECORDER (recorder));
g_return_if_fail (GTK_IS_UNDO_COMMAND (command));
if (priv->commands)
{
GtkUndoCommand *merge;
merge = gtk_undo_command_merge (priv->commands->data, command);
if (merge == NULL)
{
/* undo commands cancel out */
g_object_unref (priv->commands->data);
priv->commands = g_slist_remove (priv->commands, priv->commands->data);
return;
}
else if (GTK_IS_UNDO_COMMAND_CHAIN (merge))
{
}
}
else
{
g_object_ref (command);
}
priv->commands = g_slist_prepend (priv->commands, command);
}
GtkUndoCommand *
gtk_undo_recorder_create_command_chain (const GSList *list)
{
GtkUndoCommand **commands;
GtkUndoCommand *result;
gsize i, n_commands;
n_commands = g_slist_length ((GSList *) list);
commands = g_newa (GtkUndoCommand *, n_commands);
for (i = 0; i < n_commands; i++)
{
commands[i] = list->data;
list = list->next;
}
result = gtk_undo_command_chain_new (commands, n_commands);
return result;
}
GtkUndoCommand *
gtk_undo_recorder_finish (GtkUndoRecorder *recorder)
{
GtkUndoRecorderPrivate *priv = gtk_undo_recorder_get_instance_private (recorder);
GtkUndoCommand *result;
g_return_val_if_fail (GTK_IS_UNDO_RECORDER (recorder), NULL);
if (priv->commands == NULL)
{
result = NULL;
}
else if (priv->commands->next == NULL)
{
result = g_object_ref (priv->commands->data);
}
else
{
result = gtk_undo_recorder_create_command_chain (priv->commands);
}
gtk_undo_recorder_clear (recorder);
return result;
}
void
gtk_undo_recorder_clear (GtkUndoRecorder *recorder)
{
GtkUndoRecorderPrivate *priv = gtk_undo_recorder_get_instance_private (recorder);
g_return_if_fail (GTK_IS_UNDO_RECORDER (recorder));
g_slist_free_full (priv->commands, g_object_unref);
priv->commands = NULL;
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright © 2015 Red Hat Inc.
*
* 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 2.1 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 library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_UNDO_RECORDER_PRIVATE_H__
#define __GTK_UNDO_RECORDER_PRIVATE_H__
#include <gtk/gtkundocommandprivate.h>
G_BEGIN_DECLS
#define GTK_TYPE_UNDO_RECORDER (gtk_undo_recorder_get_type ())
#define GTK_UNDO_RECORDER(obj) (G_TYPE_CHECK_INSTANCE_CAST (obj, GTK_TYPE_UNDO_RECORDER, GtkUndoRecorder))
#define GTK_UNDO_RECORDER_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST (cls, GTK_TYPE_UNDO_RECORDER, GtkUndoRecorderClass))
#define GTK_IS_UNDO_RECORDER(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, GTK_TYPE_UNDO_RECORDER))
#define GTK_IS_UNDO_RECORDER_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE (obj, GTK_TYPE_UNDO_RECORDER))
#define GTK_UNDO_RECORDER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_UNDO_RECORDER, GtkUndoRecorderClass))
typedef struct _GtkUndoRecorder GtkUndoRecorder;
typedef struct _GtkUndoRecorderClass GtkUndoRecorderClass;
struct _GtkUndoRecorder
{
GObject parent;
};
struct _GtkUndoRecorderClass
{
GObjectClass parent_class;
};
GType gtk_undo_recorder_get_type (void) G_GNUC_CONST;
GtkUndoRecorder * gtk_undo_recorder_new (void);
void gtk_undo_recorder_push (GtkUndoRecorder *recorder,
GtkUndoCommand *command);
GtkUndoCommand * gtk_undo_recorder_finish (GtkUndoRecorder *recorder);
void gtk_undo_recorder_clear (GtkUndoRecorder *recorder);
G_END_DECLS
#endif /* __GTK_UNDO_RECORDER_PRIVATE_H__ */

278
gtk/gtkundostack.c Normal file
View File

@@ -0,0 +1,278 @@
/*
* Copyright © 2015 Red Hat Inc.
*
* 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 2.1 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 library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtkundostackprivate.h"
#include <glib/gi18n-lib.h>
#include <gio/gio.h>
#include "gtkundoundocommandprivate.h"
typedef struct _GtkUndoStackPrivate GtkUndoStackPrivate;
struct _GtkUndoStackPrivate {
GSequence *commands; /* latest added command is at front of queue */
};
static void gtk_undo_stack_list_model_iface_init (GListModelInterface *iface);
G_DEFINE_TYPE_WITH_CODE (GtkUndoStack, gtk_undo_stack, G_TYPE_OBJECT,
G_ADD_PRIVATE (GtkUndoStack);
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_undo_stack_list_model_iface_init));
static GType
gtk_undo_stack_list_model_get_item_type (GListModel *list)
{
return GTK_TYPE_UNDO_COMMAND;
}
static guint
gtk_undo_stack_list_model_get_n_items (GListModel *list)
{
GtkUndoStackPrivate *priv = gtk_undo_stack_get_instance_private (GTK_UNDO_STACK (list));
return g_sequence_get_length (priv->commands);
}
static gpointer
gtk_undo_stack_list_model_get_item (GListModel *list,
guint position)
{
GtkUndoStackPrivate *priv = gtk_undo_stack_get_instance_private (GTK_UNDO_STACK (list));
guint len;
/* XXX: This is sloooow. */
len = g_sequence_get_length (priv->commands);
if (position >= len)
return NULL;
return g_object_ref (g_sequence_get (g_sequence_get_iter_at_pos (priv->commands, len - position - 1)));
}
static void
gtk_undo_stack_list_model_iface_init (GListModelInterface *iface)
{
iface->get_item_type = gtk_undo_stack_list_model_get_item_type;
iface->get_n_items = gtk_undo_stack_list_model_get_n_items;
iface->get_item = gtk_undo_stack_list_model_get_item;
}
static void
gtk_undo_stack_dispose (GObject *object)
{
GtkUndoStack *stack = GTK_UNDO_STACK (object);
gtk_undo_stack_clear (stack);
G_OBJECT_CLASS (gtk_undo_stack_parent_class)->dispose (object);
}
static void
gtk_undo_stack_finalize (GObject *object)
{
GtkUndoStack *stack = GTK_UNDO_STACK (object);
GtkUndoStackPrivate *priv = gtk_undo_stack_get_instance_private (stack);
g_sequence_free (priv->commands);
G_OBJECT_CLASS (gtk_undo_stack_parent_class)->finalize (object);
}
static void
gtk_undo_stack_class_init (GtkUndoStackClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = gtk_undo_stack_dispose;
object_class->finalize = gtk_undo_stack_finalize;
}
static void
gtk_undo_stack_init (GtkUndoStack *stack)
{
GtkUndoStackPrivate *priv = gtk_undo_stack_get_instance_private (stack);
priv->commands = g_sequence_new (g_object_unref);
}
GtkUndoStack *
gtk_undo_stack_new (void)
{
return g_object_new (GTK_TYPE_UNDO_STACK, NULL);
}
void
gtk_undo_stack_clear (GtkUndoStack *stack)
{
GtkUndoStackPrivate *priv = gtk_undo_stack_get_instance_private (stack);
guint len;
g_return_if_fail (GTK_IS_UNDO_STACK (stack));
len = g_sequence_get_length (priv->commands);
g_sequence_remove_range (g_sequence_get_begin_iter (priv->commands),
g_sequence_get_end_iter (priv->commands));
g_list_model_items_changed (G_LIST_MODEL (stack), 0, len, 0);
}
static void G_GNUC_UNUSED
gtk_undo_stack_dump (GtkUndoStack *stack)
{
GtkUndoStackPrivate *priv = gtk_undo_stack_get_instance_private (stack);
GSequenceIter *iter;
g_print ("Undo Stack @ %p (%u items)\n", stack, g_sequence_get_length (priv->commands));
for (iter = g_sequence_get_begin_iter (priv->commands);
!g_sequence_iter_is_end (iter);
iter = g_sequence_iter_next (iter))
{
GtkUndoCommand *command = g_sequence_get (iter);
g_print (" %s\n", gtk_undo_command_get_title (command));
}
}
static void
gtk_undo_stack_push_internal (GtkUndoStack *stack,
GtkUndoCommand *command,
gboolean replace)
{
GtkUndoStackPrivate *priv = gtk_undo_stack_get_instance_private (stack);
if (replace)
g_sequence_remove (g_sequence_get_begin_iter (priv->commands));
if (command)
{
g_object_ref (command);
g_sequence_prepend (priv->commands, command);
}
g_list_model_items_changed (G_LIST_MODEL (stack), 0,
replace ? 1 : 0,
command ? 1 : 0);
gtk_undo_stack_dump (stack);
}
void
gtk_undo_stack_push (GtkUndoStack *stack,
GtkUndoCommand *command)
{
GtkUndoStackPrivate *priv = gtk_undo_stack_get_instance_private (stack);
GSequenceIter *begin_iter;
GtkUndoCommand *previous, *merge;
g_return_if_fail (GTK_IS_UNDO_STACK (stack));
g_return_if_fail (GTK_IS_UNDO_COMMAND (command));
begin_iter = g_sequence_get_begin_iter (priv->commands);
if (!g_sequence_iter_is_end (begin_iter))
{
previous = g_sequence_get (begin_iter);
if (gtk_undo_command_should_merge (previous, command))
{
merge = gtk_undo_command_merge (previous, command);
gtk_undo_stack_push_internal (stack, merge, TRUE);
if (merge)
g_object_unref (merge);
return;
}
}
gtk_undo_stack_push_internal (stack, command, FALSE);
}
/* n_commands negative means we redo */
static gboolean
gtk_undo_stack_run_undo (GtkUndoStack *stack,
int n_commands)
{
GtkUndoStackPrivate *priv = gtk_undo_stack_get_instance_private (stack);
GSequenceIter *begin_iter, *end_iter;
GtkUndoCommand *command;
int prev_commands, total_commands;
gboolean replace = FALSE;
begin_iter = g_sequence_get_begin_iter (priv->commands);
if (g_sequence_iter_is_end (begin_iter))
return FALSE;
command = g_sequence_get (begin_iter);
if (GTK_IS_UNDO_UNDO_COMMAND (command))
{
begin_iter = g_sequence_iter_next (begin_iter);
prev_commands = gtk_undo_undo_command_get_n_items (GTK_UNDO_UNDO_COMMAND (command));
total_commands = g_sequence_get_length (priv->commands) - 1;
replace = TRUE;
}
else
{
prev_commands = 0;
total_commands = g_sequence_get_length (priv->commands);
replace = FALSE;
}
n_commands = CLAMP (prev_commands + n_commands, 0, total_commands);
if (n_commands == prev_commands)
return FALSE;
end_iter = g_sequence_iter_move (begin_iter, prev_commands);
while (prev_commands < n_commands)
{
GtkUndoCommand *undo = g_sequence_get (end_iter);
gtk_undo_command_undo (undo);
end_iter = g_sequence_iter_next (end_iter);
prev_commands++;
}
while (prev_commands > n_commands)
{
GtkUndoCommand *redo;
end_iter = g_sequence_iter_prev (end_iter);
prev_commands--;
redo = g_sequence_get (end_iter);
gtk_undo_command_redo (redo);
}
command = gtk_undo_undo_command_new (begin_iter, end_iter);
gtk_undo_stack_push_internal (stack, command, replace);
g_object_unref (command);
return TRUE;
}
gboolean
gtk_undo_stack_undo (GtkUndoStack *stack)
{
g_return_val_if_fail (GTK_IS_UNDO_STACK (stack), FALSE);
return gtk_undo_stack_run_undo (stack, 1);
}
gboolean
gtk_undo_stack_redo (GtkUndoStack *stack)
{
g_return_val_if_fail (GTK_IS_UNDO_STACK (stack), FALSE);
return gtk_undo_stack_run_undo (stack, -1);
}

61
gtk/gtkundostackprivate.h Normal file
View File

@@ -0,0 +1,61 @@
/*
* Copyright © 2015 Red Hat Inc.
*
* 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 2.1 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 library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_UNDO_STACK_PRIVATE_H__
#define __GTK_UNDO_STACK_PRIVATE_H__
#include <gtk/gtkundocommandprivate.h>
G_BEGIN_DECLS
#define GTK_TYPE_UNDO_STACK (gtk_undo_stack_get_type ())
#define GTK_UNDO_STACK(obj) (G_TYPE_CHECK_INSTANCE_CAST (obj, GTK_TYPE_UNDO_STACK, GtkUndoStack))
#define GTK_UNDO_STACK_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST (cls, GTK_TYPE_UNDO_STACK, GtkUndoStackClass))
#define GTK_IS_UNDO_STACK(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, GTK_TYPE_UNDO_STACK))
#define GTK_IS_UNDO_STACK_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE (obj, GTK_TYPE_UNDO_STACK))
#define GTK_UNDO_STACK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_UNDO_STACK, GtkUndoStackClass))
typedef struct _GtkUndoStack GtkUndoStack;
typedef struct _GtkUndoStackClass GtkUndoStackClass;
struct _GtkUndoStack
{
GObject parent;
};
struct _GtkUndoStackClass
{
GObjectClass parent_class;
};
GType gtk_undo_stack_get_type (void) G_GNUC_CONST;
GtkUndoStack * gtk_undo_stack_new (void);
void gtk_undo_stack_clear (GtkUndoStack *stack);
void gtk_undo_stack_push (GtkUndoStack *stack,
GtkUndoCommand *command);
gboolean gtk_undo_stack_undo (GtkUndoStack *stack);
gboolean gtk_undo_stack_redo (GtkUndoStack *stack);
G_END_DECLS
#endif /* __GTK_UNDO_STACK_PRIVATE_H__ */

147
gtk/gtkundoundocommand.c Normal file
View File

@@ -0,0 +1,147 @@
/*
* Copyright © 2015 Red Hat Inc.
*
* 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 2.1 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 library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gtkundoundocommandprivate.h"
#include <glib/gi18n-lib.h>
typedef struct _GtkUndoUndoCommandPrivate GtkUndoUndoCommandPrivate;
struct _GtkUndoUndoCommandPrivate {
GSequence *commands; /* sequence of GtkUndoCommand, first to undo command first */
};
G_DEFINE_TYPE_WITH_CODE (GtkUndoUndoCommand, gtk_undo_undo_command, GTK_TYPE_UNDO_COMMAND,
G_ADD_PRIVATE (GtkUndoUndoCommand))
static gboolean
gtk_undo_undo_command_undo (GtkUndoCommand *command)
{
GtkUndoUndoCommandPrivate *priv = gtk_undo_undo_command_get_instance_private (GTK_UNDO_UNDO_COMMAND (command));
GSequenceIter *iter;
gboolean result;
result = FALSE;
for (iter = g_sequence_get_begin_iter (priv->commands);
!g_sequence_iter_is_end (iter);
iter = g_sequence_iter_next (iter))
{
result |= gtk_undo_command_redo (g_sequence_get (iter));
}
return result;
}
gboolean
gtk_undo_undo_command_redo (GtkUndoCommand *command)
{
GtkUndoUndoCommandPrivate *priv = gtk_undo_undo_command_get_instance_private (GTK_UNDO_UNDO_COMMAND (command));
GSequenceIter *iter;
gboolean result;
result = FALSE;
iter = g_sequence_get_end_iter (priv->commands);
while (!g_sequence_iter_is_begin (iter))
{
iter = g_sequence_iter_prev (iter);
result |= gtk_undo_command_undo (g_sequence_get (iter));
}
return result;
}
gboolean
gtk_undo_undo_command_should_merge (GtkUndoCommand *command,
GtkUndoCommand *followup)
{
return FALSE;
}
static void
gtk_undo_undo_command_finalize (GObject *object)
{
GtkUndoUndoCommandPrivate *priv = gtk_undo_undo_command_get_instance_private (GTK_UNDO_UNDO_COMMAND (object));
g_sequence_free (priv->commands);
G_OBJECT_CLASS (gtk_undo_undo_command_parent_class)->finalize (object);
}
static void
gtk_undo_undo_command_class_init (GtkUndoUndoCommandClass *klass)
{
GtkUndoCommandClass *undo_class = GTK_UNDO_COMMAND_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = gtk_undo_undo_command_finalize;
undo_class->undo = gtk_undo_undo_command_undo;
undo_class->redo = gtk_undo_undo_command_redo;
undo_class->should_merge = gtk_undo_undo_command_should_merge;
}
static void
gtk_undo_undo_command_init (GtkUndoUndoCommand *command)
{
GtkUndoUndoCommandPrivate *priv = gtk_undo_undo_command_get_instance_private (GTK_UNDO_UNDO_COMMAND (command));
priv->commands = g_sequence_new (g_object_unref);
}
GtkUndoCommand *
gtk_undo_undo_command_new (GSequenceIter *begin_iter,
GSequenceIter *end_iter)
{
GtkUndoUndoCommand *result;
GtkUndoUndoCommandPrivate *priv;
GSequenceIter *iter;
char *title;
g_return_val_if_fail (begin_iter != NULL, NULL);
g_return_val_if_fail (end_iter != NULL, NULL);
result = g_object_new (GTK_TYPE_UNDO_UNDO_COMMAND, NULL);
priv = gtk_undo_undo_command_get_instance_private (result);
for (iter = begin_iter; iter != end_iter; iter = g_sequence_iter_next (iter))
{
g_sequence_prepend (priv->commands, g_object_ref (g_sequence_get (iter)));
}
title = g_strdup_printf (_("Undo last %u commands"), g_sequence_get_length (priv->commands));
gtk_undo_command_set_title (GTK_UNDO_COMMAND (result), title);
g_free (title);
return GTK_UNDO_COMMAND (result);
}
guint
gtk_undo_undo_command_get_n_items (GtkUndoUndoCommand *command)
{
GtkUndoUndoCommandPrivate *priv = gtk_undo_undo_command_get_instance_private (GTK_UNDO_UNDO_COMMAND (command));
g_return_val_if_fail (GTK_IS_UNDO_UNDO_COMMAND (command), 0);
return g_sequence_get_length (priv->commands);
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright © 2015 Red Hat Inc.
*
* 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 2.1 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 library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#ifndef __GTK_UNDO_UNDO_COMMAND_PRIVATE_H__
#define __GTK_UNDO_UNDO_COMMAND_PRIVATE_H__
#include <gtk/gtkundocommandprivate.h>
G_BEGIN_DECLS
#define GTK_TYPE_UNDO_UNDO_COMMAND (gtk_undo_undo_command_get_type ())
#define GTK_UNDO_UNDO_COMMAND(obj) (G_TYPE_CHECK_INSTANCE_CAST (obj, GTK_TYPE_UNDO_UNDO_COMMAND, GtkUndoUndoCommand))
#define GTK_UNDO_UNDO_COMMAND_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST (cls, GTK_TYPE_UNDO_UNDO_COMMAND, GtkUndoUndoCommandClass))
#define GTK_IS_UNDO_UNDO_COMMAND(obj) (G_TYPE_CHECK_INSTANCE_TYPE (obj, GTK_TYPE_UNDO_UNDO_COMMAND))
#define GTK_IS_UNDO_UNDO_COMMAND_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE (obj, GTK_TYPE_UNDO_UNDO_COMMAND))
#define GTK_UNDO_UNDO_COMMAND_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_UNDO_UNDO_COMMAND, GtkUndoUndoCommandClass))
typedef struct _GtkUndoUndoCommand GtkUndoUndoCommand;
typedef struct _GtkUndoUndoCommandClass GtkUndoUndoCommandClass;
struct _GtkUndoUndoCommand
{
GtkUndoCommand parent;
};
struct _GtkUndoUndoCommandClass
{
GtkUndoCommandClass parent_class;
};
GType gtk_undo_undo_command_get_type (void) G_GNUC_CONST;
GtkUndoCommand * gtk_undo_undo_command_new (GSequenceIter *begin_iter,
GSequenceIter *end_iter);
guint gtk_undo_undo_command_get_n_items (GtkUndoUndoCommand *command);
G_END_DECLS
#endif /* __GTK_UNDO_UNDO_COMMAND_PRIVATE_H__ */