1 /*
2 * This program is free software; you can redistribute it and/or modify it
3 * under the terms of the GNU Lesser General Public License as published by
4 * the Free Software Foundation.
5 *
6 * This program is distributed in the hope that it will be useful, but
7 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
8 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
9 * for more details.
10 *
11 * You should have received a copy of the GNU Lesser General Public License
12 * along with this program; if not, see <http://www.gnu.org/licenses/>.
13 *
14 *
15 * Authors:
16 * Milan Crha <mcrha@redhat.com>
17 *
18 * Copyright (C) 2014 Red Hat, Inc. (www.redhat.com)
19 *
20 */
21
22 #include "evolution-config.h"
23
24 #include <gtk/gtk.h>
25 #include <glib/gi18n-lib.h>
26
27 #include <string.h>
28
29 #include "e-focus-tracker.h"
30 #include "e-widget-undo.h"
31
32 #define DEFAULT_MAX_UNDO_LEVEL 256
33 #define UNDO_DATA_KEY "e-undo-data-ptr"
34
35 /* calculates real index in EUndoData::undo_stack */
36 #define REAL_INDEX(x) ((data->undo_from + (x) + 2 * data->undo_len) % data->undo_len)
37
38 typedef enum {
39 E_UNDO_INSERT,
40 E_UNDO_DELETE
41 } EUndoType;
42
43 typedef enum {
44 E_UNDO_DO_UNDO,
45 E_UNDO_DO_REDO
46 } EUndoDoType;
47
48 typedef struct _EUndoInfo {
49 EUndoType type;
50 gchar *text;
51 gint position_start;
52 gint position_end; /* valid for delete type only */
53 } EUndoInfo;
54
55 typedef struct _EUndoData {
56 EUndoInfo **undo_stack; /* stack for undo, with max_undo_level elements, some are NULL */
57 gint undo_len; /* how many undo actions can be saved */
58 gint undo_from; /* where the first undo action begins */
59 gint n_undos; /* how many undo actions are saved;
60 [(undo_from + n_undos) % undo_len] is the next free undo item (or the first redo) */
61 gint n_redos; /* how many redo actions are saved */
62
63 EUndoInfo *current_info; /* the top undo action */
64
65 gulong insert_handler_id;
66 gulong delete_handler_id;
67 } EUndoData;
68
69 static void
free_undo_info(gpointer ptr)70 free_undo_info (gpointer ptr)
71 {
72 EUndoInfo *info = ptr;
73
74 if (info) {
75 g_free (info->text);
76 g_free (info);
77 }
78 }
79
80 static void
free_undo_data(gpointer ptr)81 free_undo_data (gpointer ptr)
82 {
83 EUndoData *data = ptr;
84
85 if (data) {
86 gint ii;
87
88 for (ii = 0; ii < data->undo_len; ii++) {
89 free_undo_info (data->undo_stack[ii]);
90 }
91 g_free (data->undo_stack);
92 g_free (data);
93 }
94 }
95
96 static void
reset_redos(EUndoData * data)97 reset_redos (EUndoData *data)
98 {
99 gint ii, index;
100
101 for (ii = 0; ii < data->n_redos; ii++) {
102 index = REAL_INDEX (data->n_undos + ii);
103
104 free_undo_info (data->undo_stack[index]);
105 data->undo_stack[index] = NULL;
106 }
107
108 data->n_redos = 0;
109 }
110
111 static void
push_undo(EUndoData * data,EUndoInfo * info)112 push_undo (EUndoData *data,
113 EUndoInfo *info)
114 {
115 gint index;
116
117 reset_redos (data);
118
119 if (data->n_undos == data->undo_len) {
120 data->undo_from = (data->undo_from + 1) % data->undo_len;
121 } else {
122 data->n_undos++;
123 }
124
125 index = REAL_INDEX (data->n_undos - 1);
126 free_undo_info (data->undo_stack[index]);
127 data->undo_stack[index] = info;
128 }
129
130 static gboolean
can_merge_insert_undos(EUndoInfo * current_info,const gchar * text,gint text_len,gint position)131 can_merge_insert_undos (EUndoInfo *current_info,
132 const gchar *text,
133 gint text_len,
134 gint position)
135 {
136 gint len;
137
138 /* allow only one letter merge */
139 if (!current_info || current_info->type != E_UNDO_INSERT ||
140 !text || text_len <= 0 || text_len > 1)
141 return FALSE;
142
143 if (text[0] == '\r' || text[0] == '\n')
144 return FALSE;
145
146 len = strlen (current_info->text);
147 if (position != current_info->position_start + len)
148 return FALSE;
149
150 if (g_ascii_isspace (text[0])) {
151 if (len <= 0 || !g_ascii_isspace (current_info->text[len - 1]))
152 return FALSE;
153 }
154
155 return TRUE;
156 }
157
158 static void
push_insert_undo(GObject * object,const gchar * text,gint text_len,gint position)159 push_insert_undo (GObject *object,
160 const gchar *text,
161 gint text_len,
162 gint position)
163 {
164 EUndoData *data;
165 EUndoInfo *info;
166
167 data = g_object_get_data (object, UNDO_DATA_KEY);
168 if (!data) {
169 g_warn_if_reached ();
170 return;
171 }
172
173 /* one letter long text, divide undos on spaces */
174 if (data->current_info &&
175 can_merge_insert_undos (data->current_info, text, text_len, position)) {
176 gchar *new_text;
177
178 new_text = g_strdup_printf ("%s%*s", data->current_info->text, text_len, text);
179 g_free (data->current_info->text);
180 data->current_info->text = new_text;
181
182 return;
183 }
184
185 info = g_new0 (EUndoInfo, 1);
186 info->type = E_UNDO_INSERT;
187 info->text = g_strndup (text, text_len);
188 info->position_start = position;
189
190 push_undo (data, info);
191
192 data->current_info = info;
193 }
194
195 static void
push_delete_undo(GObject * object,gchar * text,gint position_start,gint position_end)196 push_delete_undo (GObject *object,
197 gchar *text, /* takes ownership of the 'text' */
198 gint position_start,
199 gint position_end)
200 {
201 EUndoData *data;
202 EUndoInfo *info;
203
204 data = g_object_get_data (object, UNDO_DATA_KEY);
205 if (!data) {
206 g_warn_if_reached ();
207 return;
208 }
209
210 if (data->current_info && data->current_info->type == E_UNDO_DELETE &&
211 position_end - position_start == 1 && !g_ascii_isspace (*text)) {
212 info = data->current_info;
213
214 if (info->position_start == position_start) {
215 gchar *new_text;
216
217 new_text = g_strconcat (info->text, text, NULL);
218 g_free (info->text);
219 info->text = new_text;
220 g_free (text);
221
222 info->position_end++;
223
224 return;
225 } else if (data->current_info->position_start == position_end) {
226 gchar *new_text;
227
228 new_text = g_strconcat (text, info->text, NULL);
229 g_free (info->text);
230 info->text = new_text;
231 g_free (text);
232
233 info->position_start = position_start;
234
235 return;
236 }
237 }
238
239 info = g_new0 (EUndoInfo, 1);
240 info->type = E_UNDO_DELETE;
241 info->text = text;
242 info->position_start = position_start;
243 info->position_end = position_end;
244
245 push_undo (data, info);
246
247 data->current_info = info;
248 }
249
250 static void
editable_undo_insert_text_cb(GtkEditable * editable,gchar * text,gint text_length,gint * position,gpointer user_data)251 editable_undo_insert_text_cb (GtkEditable *editable,
252 gchar *text,
253 gint text_length,
254 gint *position,
255 gpointer user_data)
256 {
257 push_insert_undo (G_OBJECT (editable), text, text_length, *position);
258 }
259
260 static void
editable_undo_delete_text_cb(GtkEditable * editable,gint start_pos,gint end_pos,gpointer user_data)261 editable_undo_delete_text_cb (GtkEditable *editable,
262 gint start_pos,
263 gint end_pos,
264 gpointer user_data)
265 {
266 push_delete_undo (G_OBJECT (editable), gtk_editable_get_chars (editable, start_pos, end_pos), start_pos, end_pos);
267 }
268
269 static void
editable_undo_insert_text(GObject * object,const gchar * text,gint position)270 editable_undo_insert_text (GObject *object,
271 const gchar *text,
272 gint position)
273 {
274 g_return_if_fail (GTK_IS_EDITABLE (object));
275
276 gtk_editable_insert_text (GTK_EDITABLE (object), text, -1, &position);
277 }
278
279 static void
editable_undo_delete_text(GObject * object,gint position_start,gint position_end)280 editable_undo_delete_text (GObject *object,
281 gint position_start,
282 gint position_end)
283 {
284 g_return_if_fail (GTK_IS_EDITABLE (object));
285
286 gtk_editable_delete_text (GTK_EDITABLE (object), position_start, position_end);
287 }
288
289 static void
text_buffer_undo_insert_text_cb(GtkTextBuffer * text_buffer,GtkTextIter * location,gchar * text,gint text_length,gpointer user_data)290 text_buffer_undo_insert_text_cb (GtkTextBuffer *text_buffer,
291 GtkTextIter *location,
292 gchar *text,
293 gint text_length,
294 gpointer user_data)
295 {
296 push_insert_undo (G_OBJECT (text_buffer), text, text_length, gtk_text_iter_get_offset (location));
297 }
298
299 static void
text_buffer_undo_delete_range_cb(GtkTextBuffer * text_buffer,GtkTextIter * start,GtkTextIter * end,gpointer user_data)300 text_buffer_undo_delete_range_cb (GtkTextBuffer *text_buffer,
301 GtkTextIter *start,
302 GtkTextIter *end,
303 gpointer user_data)
304 {
305 push_delete_undo (
306 G_OBJECT (text_buffer),
307 gtk_text_iter_get_text (start, end),
308 gtk_text_iter_get_offset (start),
309 gtk_text_iter_get_offset (end));
310 }
311
312 static void
text_buffer_undo_insert_text(GObject * object,const gchar * text,gint position)313 text_buffer_undo_insert_text (GObject *object,
314 const gchar *text,
315 gint position)
316 {
317 GtkTextBuffer *text_buffer;
318 GtkTextIter iter;
319
320 g_return_if_fail (GTK_IS_TEXT_BUFFER (object));
321
322 text_buffer = GTK_TEXT_BUFFER (object);
323
324 gtk_text_buffer_get_iter_at_offset (text_buffer, &iter, position);
325 gtk_text_buffer_insert (text_buffer, &iter, text, -1);
326 }
327
328 static void
text_buffer_undo_delete_text(GObject * object,gint position_start,gint position_end)329 text_buffer_undo_delete_text (GObject *object,
330 gint position_start,
331 gint position_end)
332 {
333 GtkTextBuffer *text_buffer;
334 GtkTextIter start_iter, end_iter;
335
336 g_return_if_fail (GTK_IS_TEXT_BUFFER (object));
337
338 text_buffer = GTK_TEXT_BUFFER (object);
339
340 gtk_text_buffer_get_iter_at_offset (text_buffer, &start_iter, position_start);
341 gtk_text_buffer_get_iter_at_offset (text_buffer, &end_iter, position_end);
342 gtk_text_buffer_delete (text_buffer, &start_iter, &end_iter);
343 }
344
345 static void
widget_undo_place_cursor_at(GObject * object,gint char_pos)346 widget_undo_place_cursor_at (GObject *object,
347 gint char_pos)
348 {
349 if (GTK_IS_EDITABLE (object))
350 gtk_editable_set_position (GTK_EDITABLE (object), char_pos);
351 else if (GTK_IS_TEXT_BUFFER (object)) {
352 GtkTextBuffer *buffer;
353 GtkTextIter pos;
354
355 buffer = GTK_TEXT_BUFFER (object);
356
357 gtk_text_buffer_get_iter_at_offset (buffer, &pos, char_pos);
358 gtk_text_buffer_place_cursor (buffer, &pos);
359 }
360 }
361
362 static void
undo_do_something(GObject * object,EUndoDoType todo,void (* insert_func)(GObject * object,const gchar * text,gint position),void (* delete_func)(GObject * object,gint position_start,gint position_end))363 undo_do_something (GObject *object,
364 EUndoDoType todo,
365 void (* insert_func) (GObject *object,
366 const gchar *text,
367 gint position),
368 void (* delete_func) (GObject *object,
369 gint position_start,
370 gint position_end))
371 {
372 EUndoData *data;
373 EUndoInfo *info = NULL;
374
375 data = g_object_get_data (object, UNDO_DATA_KEY);
376 if (!data)
377 return;
378
379 if (todo == E_UNDO_DO_UNDO && data->n_undos > 0) {
380 info = data->undo_stack[REAL_INDEX (data->n_undos - 1)];
381 data->n_undos--;
382 data->n_redos++;
383 } else if (todo == E_UNDO_DO_REDO && data->n_redos > 0) {
384 info = data->undo_stack[REAL_INDEX (data->n_undos)];
385 data->n_undos++;
386 data->n_redos--;
387 }
388
389 if (!info)
390 return;
391
392 g_signal_handler_block (object, data->insert_handler_id);
393 g_signal_handler_block (object, data->delete_handler_id);
394
395 if (info->type == E_UNDO_INSERT) {
396 if (todo == E_UNDO_DO_UNDO) {
397 delete_func (object, info->position_start, info->position_start + g_utf8_strlen (info->text, -1));
398 widget_undo_place_cursor_at (object, info->position_start);
399 } else {
400 insert_func (object, info->text, info->position_start);
401 widget_undo_place_cursor_at (object, info->position_start + g_utf8_strlen (info->text, -1));
402 }
403 } else if (info->type == E_UNDO_DELETE) {
404 if (todo == E_UNDO_DO_UNDO) {
405 insert_func (object, info->text, info->position_start);
406 widget_undo_place_cursor_at (object, info->position_start + g_utf8_strlen (info->text, -1));
407 } else {
408 delete_func (object, info->position_start, info->position_end);
409 widget_undo_place_cursor_at (object, info->position_start);
410 }
411 }
412
413 data->current_info = NULL;
414
415 g_signal_handler_unblock (object, data->delete_handler_id);
416 g_signal_handler_unblock (object, data->insert_handler_id);
417 }
418
419 static gchar *
undo_describe_info(EUndoInfo * info,EUndoDoType undo_type)420 undo_describe_info (EUndoInfo *info,
421 EUndoDoType undo_type)
422 {
423 if (!info)
424 return NULL;
425
426 if (info->type == E_UNDO_INSERT) {
427 if (undo_type == E_UNDO_DO_UNDO)
428 return g_strdup (_("Undo “Insert text”"));
429 else
430 return g_strdup (_("Redo “Insert text”"));
431 /* if (strlen (info->text) > 15) {
432 if (undo_type == E_UNDO_DO_UNDO)
433 return g_strdup_printf (_("Undo “Insert “%.12s...””"), info->text);
434 else
435 return g_strdup_printf (_("Redo “Insert “%.12s...””"), info->text);
436 }
437 *
438 if (undo_type == E_UNDO_DO_UNDO)
439 return g_strdup_printf (_("Undo “Insert “%s””"), info->text);
440 else
441 return g_strdup_printf (_("Redo “Insert “%s””"), info->text); */
442 } else if (info->type == E_UNDO_DELETE) {
443 if (undo_type == E_UNDO_DO_UNDO)
444 return g_strdup (_("Undo “Delete text”"));
445 else
446 return g_strdup (_("Redo “Delete text”"));
447 /* if (strlen (info->text) > 15) {
448 if (undo_type == E_UNDO_DO_UNDO)
449 return g_strdup_printf (_("Undo “Delete “%.12s...””"), info->text);
450 else
451 return g_strdup_printf (_("Redo “Delete “%.12s...””"), info->text);
452 }
453 *
454 if (undo_type == E_UNDO_DO_UNDO)
455 return g_strdup_printf (_("Undo “Delete “%s””"), info->text);
456 else
457 return g_strdup_printf (_("Redo “Delete “%s””"), info->text); */
458 }
459
460 return NULL;
461 }
462
463 static gboolean
undo_check_undo(GObject * object,gchar ** description)464 undo_check_undo (GObject *object,
465 gchar **description)
466 {
467 EUndoData *data;
468
469 data = g_object_get_data (object, UNDO_DATA_KEY);
470 if (!data)
471 return FALSE;
472
473 if (data->n_undos <= 0)
474 return FALSE;
475
476 if (description)
477 *description = undo_describe_info (data->undo_stack[REAL_INDEX (data->n_undos - 1)], E_UNDO_DO_UNDO);
478
479 return TRUE;
480 }
481
482 static gboolean
undo_check_redo(GObject * object,gchar ** description)483 undo_check_redo (GObject *object,
484 gchar **description)
485 {
486 EUndoData *data;
487
488 data = g_object_get_data (object, UNDO_DATA_KEY);
489 if (!data)
490 return FALSE;
491
492 if (data->n_redos <= 0)
493 return FALSE;
494
495 if (description)
496 *description = undo_describe_info (data->undo_stack[REAL_INDEX (data->n_undos)], E_UNDO_DO_REDO);
497
498 return TRUE;
499 }
500
501 static void
undo_reset(GObject * object)502 undo_reset (GObject *object)
503 {
504 EUndoData *data;
505
506 data = g_object_get_data (object, UNDO_DATA_KEY);
507 if (!data)
508 return;
509
510 data->n_undos = 0;
511 data->n_redos = 0;
512 data->current_info = NULL;
513 }
514
515 static void
widget_undo_popup_activate_cb(GObject * menu_item,GtkWidget * widget)516 widget_undo_popup_activate_cb (GObject *menu_item,
517 GtkWidget *widget)
518 {
519 EUndoDoType undo_type = GPOINTER_TO_INT (g_object_get_data (menu_item, UNDO_DATA_KEY));
520
521 if (undo_type == E_UNDO_DO_UNDO)
522 e_widget_undo_do_undo (widget);
523 else
524 e_widget_undo_do_redo (widget);
525 }
526
527 static gboolean
widget_undo_prepend_popup(GtkWidget * widget,GtkMenuShell * menu,EUndoDoType undo_type,gboolean already_added)528 widget_undo_prepend_popup (GtkWidget *widget,
529 GtkMenuShell *menu,
530 EUndoDoType undo_type,
531 gboolean already_added)
532 {
533 gchar *description = NULL;
534 const gchar *icon_name = NULL;
535
536 if (undo_type == E_UNDO_DO_UNDO && e_widget_undo_has_undo (widget)) {
537 description = e_widget_undo_describe_undo (widget);
538 icon_name = "edit-undo";
539 } else if (undo_type == E_UNDO_DO_REDO && e_widget_undo_has_redo (widget)) {
540 description = e_widget_undo_describe_redo (widget);
541 icon_name = "edit-redo";
542 }
543
544 if (description) {
545 GtkWidget *item, *image;
546
547 if (!already_added) {
548 item = gtk_separator_menu_item_new ();
549 gtk_widget_show (item);
550 gtk_menu_shell_prepend (menu, item);
551
552 already_added = TRUE;
553 }
554
555 image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_MENU);
556 item = gtk_image_menu_item_new_with_label (description);
557 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
558 gtk_widget_show (item);
559
560 g_object_set_data (G_OBJECT (item), UNDO_DATA_KEY, GINT_TO_POINTER (undo_type));
561 g_signal_connect (item, "activate", G_CALLBACK (widget_undo_popup_activate_cb), widget);
562
563 gtk_menu_shell_prepend (menu, item);
564
565 g_free (description);
566 }
567
568 return already_added;
569 }
570
571 static void
widget_undo_populate_popup_cb(GtkWidget * widget,GtkWidget * popup,gpointer user_data)572 widget_undo_populate_popup_cb (GtkWidget *widget,
573 GtkWidget *popup,
574 gpointer user_data)
575 {
576 GtkMenuShell *menu;
577 gboolean added = FALSE;
578
579 if (!GTK_IS_MENU (popup))
580 return;
581
582 menu = GTK_MENU_SHELL (popup);
583
584 /* first redo, because prependend, thus undo gets before it */
585 if (e_widget_undo_has_redo (widget))
586 added = widget_undo_prepend_popup (widget, menu, E_UNDO_DO_REDO, added);
587
588 if (e_widget_undo_has_undo (widget))
589 widget_undo_prepend_popup (widget, menu, E_UNDO_DO_UNDO, added);
590 }
591
592 /**
593 * e_widget_undo_attach:
594 * @widget: a #GtkWidget, where to attach undo functionality
595 * @focus_tracker: an #EFocusTracker, can be %NULL
596 *
597 * The function does nothing, if the widget is not of a supported type
598 * for undo functionality, same as when the undo is already attached.
599 * It is ensured that the actions of the provided @focus_tracker are
600 * updated on change of the @widget.
601 *
602 * See @e_widget_undo_is_attached().
603 *
604 * Since: 3.12
605 **/
606 void
e_widget_undo_attach(GtkWidget * widget,EFocusTracker * focus_tracker)607 e_widget_undo_attach (GtkWidget *widget,
608 EFocusTracker *focus_tracker)
609 {
610 EUndoData *data;
611
612 if (e_widget_undo_is_attached (widget))
613 return;
614
615 if (GTK_IS_EDITABLE (widget)) {
616 data = g_new0 (EUndoData, 1);
617 data->undo_len = DEFAULT_MAX_UNDO_LEVEL;
618 data->undo_stack = g_new0 (EUndoInfo *, data->undo_len);
619
620 g_object_set_data_full (G_OBJECT (widget), UNDO_DATA_KEY, data, free_undo_data);
621
622 data->insert_handler_id = g_signal_connect (
623 widget, "insert-text",
624 G_CALLBACK (editable_undo_insert_text_cb), NULL);
625 data->delete_handler_id = g_signal_connect (
626 widget, "delete-text",
627 G_CALLBACK (editable_undo_delete_text_cb), NULL);
628
629 if (focus_tracker)
630 g_signal_connect_swapped (
631 widget, "changed",
632 G_CALLBACK (e_focus_tracker_update_actions), focus_tracker);
633
634 if (GTK_IS_ENTRY (widget))
635 g_signal_connect (
636 widget, "populate-popup",
637 G_CALLBACK (widget_undo_populate_popup_cb), NULL);
638 } else if (GTK_IS_TEXT_VIEW (widget)) {
639 GtkTextBuffer *text_buffer;
640
641 text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
642
643 data = g_new0 (EUndoData, 1);
644 data->undo_len = DEFAULT_MAX_UNDO_LEVEL;
645 data->undo_stack = g_new0 (EUndoInfo *, data->undo_len);
646
647 g_object_set_data_full (G_OBJECT (text_buffer), UNDO_DATA_KEY, data, free_undo_data);
648
649 data->insert_handler_id = g_signal_connect (
650 text_buffer, "insert-text",
651 G_CALLBACK (text_buffer_undo_insert_text_cb), NULL);
652 data->delete_handler_id = g_signal_connect (
653 text_buffer, "delete-range",
654 G_CALLBACK (text_buffer_undo_delete_range_cb), NULL);
655
656 if (focus_tracker)
657 g_signal_connect_swapped (
658 text_buffer, "changed",
659 G_CALLBACK (e_focus_tracker_update_actions), focus_tracker);
660
661 g_signal_connect (
662 widget, "populate-popup",
663 G_CALLBACK (widget_undo_populate_popup_cb), NULL);
664 }
665 }
666
667 /**
668 * e_widget_undo_is_attached:
669 * @widget: a #GtkWidget, where to test whether undo functionality is attached.
670 *
671 * Checks whether the given widget has already attached an undo
672 * functionality - it is done with @e_widget_undo_attach().
673 *
674 * Returns: Whether the given @widget has already attached undo functionality.
675 *
676 * Since: 3.12
677 **/
678 gboolean
e_widget_undo_is_attached(GtkWidget * widget)679 e_widget_undo_is_attached (GtkWidget *widget)
680 {
681 gboolean res = FALSE;
682
683 if (GTK_IS_EDITABLE (widget)) {
684 res = g_object_get_data (G_OBJECT (widget), UNDO_DATA_KEY) != NULL;
685 } else if (GTK_IS_TEXT_VIEW (widget)) {
686 GtkTextBuffer *text_buffer;
687
688 text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
689
690 res = g_object_get_data (G_OBJECT (text_buffer), UNDO_DATA_KEY) != NULL;
691 }
692
693 return res;
694 }
695
696 /**
697 * e_widget_undo_has_undo:
698 * @widget: a #GtkWidget
699 *
700 * Returns: Whether the given @widget has any undo available.
701 *
702 * See: @e_widget_undo_describe_undo, @e_widget_undo_do_undo
703 *
704 * Since: 3.12
705 **/
706 gboolean
e_widget_undo_has_undo(GtkWidget * widget)707 e_widget_undo_has_undo (GtkWidget *widget)
708 {
709 if (GTK_IS_EDITABLE (widget)) {
710 return undo_check_undo (G_OBJECT (widget), NULL);
711 } else if (GTK_IS_TEXT_VIEW (widget)) {
712 GtkTextBuffer *text_buffer;
713
714 text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
715
716 return undo_check_undo (G_OBJECT (text_buffer), NULL);
717 }
718
719 return FALSE;
720 }
721
722 /**
723 * e_widget_undo_has_redo:
724 * @widget: a #GtkWidget
725 *
726 * Returns: Whether the given @widget has any redo available.
727 *
728 * See: @e_widget_undo_describe_redo, @e_widget_undo_do_redo
729 *
730 * Since: 3.12
731 **/
732 gboolean
e_widget_undo_has_redo(GtkWidget * widget)733 e_widget_undo_has_redo (GtkWidget *widget)
734 {
735 if (GTK_IS_EDITABLE (widget)) {
736 return undo_check_redo (G_OBJECT (widget), NULL);
737 } else if (GTK_IS_TEXT_VIEW (widget)) {
738 GtkTextBuffer *text_buffer;
739
740 text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
741
742 return undo_check_redo (G_OBJECT (text_buffer), NULL);
743 }
744
745 return FALSE;
746 }
747
748 /**
749 * e_widget_undo_describe_undo:
750 * @widget: a #GtkWidget
751 *
752 * Returns: (transfer full): Description of a top undo action available
753 * for the @widget, %NULL when there is no undo action. Returned pointer,
754 * if not %NULL, should be freed with g_free().
755 *
756 * See: @e_widget_undo_has_undo, @e_widget_undo_do_undo
757 *
758 * Since: 3.12
759 **/
760 gchar *
e_widget_undo_describe_undo(GtkWidget * widget)761 e_widget_undo_describe_undo (GtkWidget *widget)
762 {
763 gchar *res = NULL;
764
765 if (GTK_IS_EDITABLE (widget)) {
766 if (!undo_check_undo (G_OBJECT (widget), &res)) {
767 g_warn_if_fail (res == NULL);
768 }
769 } else if (GTK_IS_TEXT_VIEW (widget)) {
770 GtkTextBuffer *text_buffer;
771
772 text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
773
774 if (!undo_check_undo (G_OBJECT (text_buffer), &res)) {
775 g_warn_if_fail (res == NULL);
776 }
777 }
778
779 return res;
780 }
781
782 /**
783 * e_widget_undo_describe_redo:
784 * @widget: a #GtkWidget
785 *
786 * Returns: (transfer full): Description of a top redo action available
787 * for the @widget, %NULL when there is no redo action. Returned pointer,
788 * if not %NULL, should be freed with g_free().
789 *
790 * See: @e_widget_undo_has_redo, @e_widget_undo_do_redo
791 *
792 * Since: 3.12
793 **/
794 gchar *
e_widget_undo_describe_redo(GtkWidget * widget)795 e_widget_undo_describe_redo (GtkWidget *widget)
796 {
797 gchar *res = NULL;
798
799 if (GTK_IS_EDITABLE (widget)) {
800 if (!undo_check_redo (G_OBJECT (widget), &res)) {
801 g_warn_if_fail (res == NULL);
802 }
803 } else if (GTK_IS_TEXT_VIEW (widget)) {
804 GtkTextBuffer *text_buffer;
805
806 text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
807
808 if (!undo_check_redo (G_OBJECT (text_buffer), &res)) {
809 g_warn_if_fail (res == NULL);
810 }
811 }
812
813 return res;
814 }
815
816 /**
817 * e_widget_undo_do_undo:
818 * @widget: a #GtkWidget
819 *
820 * Applies the top undo action on the @widget, which also remembers
821 * a redo action. It does nothing if the widget doesn't have
822 * attached undo functionality (@e_widget_undo_attach()), neither
823 * when there is no undo action available.
824 *
825 * See: @e_widget_undo_attach, @e_widget_undo_has_undo, @e_widget_undo_describe_undo
826 *
827 * Since: 3.12
828 **/
829 void
e_widget_undo_do_undo(GtkWidget * widget)830 e_widget_undo_do_undo (GtkWidget *widget)
831 {
832 if (GTK_IS_EDITABLE (widget)) {
833 undo_do_something (
834 G_OBJECT (widget),
835 E_UNDO_DO_UNDO,
836 editable_undo_insert_text,
837 editable_undo_delete_text);
838 } else if (GTK_IS_TEXT_VIEW (widget)) {
839 GtkTextBuffer *text_buffer;
840
841 text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
842
843 undo_do_something (
844 G_OBJECT (text_buffer),
845 E_UNDO_DO_UNDO,
846 text_buffer_undo_insert_text,
847 text_buffer_undo_delete_text);
848 }
849 }
850
851 /**
852 * e_widget_undo_do_redo:
853 * @widget: a #GtkWidget
854 *
855 * Applies the top redo action on the @widget, which also remembers
856 * an undo action. It does nothing if the widget doesn't have
857 * attached undo functionality (@e_widget_undo_attach()), neither
858 * when there is no redo action available.
859 *
860 * See: @e_widget_undo_attach, @e_widget_undo_has_redo, @e_widget_undo_describe_redo
861 *
862 * Since: 3.12
863 **/
864 void
e_widget_undo_do_redo(GtkWidget * widget)865 e_widget_undo_do_redo (GtkWidget *widget)
866 {
867 if (GTK_IS_EDITABLE (widget)) {
868 undo_do_something (
869 G_OBJECT (widget),
870 E_UNDO_DO_REDO,
871 editable_undo_insert_text,
872 editable_undo_delete_text);
873 } else if (GTK_IS_TEXT_VIEW (widget)) {
874 GtkTextBuffer *text_buffer;
875
876 text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
877
878 undo_do_something (
879 G_OBJECT (text_buffer),
880 E_UNDO_DO_REDO,
881 text_buffer_undo_insert_text,
882 text_buffer_undo_delete_text);
883 }
884 }
885
886 /**
887 * e_widget_undo_reset:
888 * @widget: a #GtkWidget, on which might be attached undo functionality
889 *
890 * Resets undo and redo stack to empty on a widget with attached
891 * undo functionality. It does nothing, if the widget does not have
892 * the undo functionality attached (see @e_widget_undo_attach()).
893 *
894 * Since: 3.12
895 **/
896 void
e_widget_undo_reset(GtkWidget * widget)897 e_widget_undo_reset (GtkWidget *widget)
898 {
899 if (GTK_IS_EDITABLE (widget)) {
900 undo_reset (G_OBJECT (widget));
901 } else if (GTK_IS_TEXT_VIEW (widget)) {
902 GtkTextBuffer *text_buffer;
903
904 text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
905
906 undo_reset (G_OBJECT (text_buffer));
907 }
908 }
909