]> www.fi.muni.cz Git - evince.git/blob - cut-n-paste/toolbar-editor/egg-toolbar-editor.c
Implement epiphany like toolbar editor. Based on patch by
[evince.git] / cut-n-paste / toolbar-editor / egg-toolbar-editor.c
1 /*
2  *  Copyright (C) 2003 Marco Pesenti Gritti
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2, or (at your option)
7  *  any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17  *
18  *  $Id$
19  */
20
21 #include "egg-toolbar-editor.h"
22 #include "egg-editable-toolbar.h"
23
24 #include <string.h>
25 #include <glib/gi18n.h>
26 #include <libxml/tree.h>
27 #include <gtk/gtkimage.h>
28 #include <gtk/gtkeventbox.h>
29 #include <gtk/gtkdnd.h>
30 #include <gtk/gtkscrolledwindow.h>
31 #include <gtk/gtklabel.h>
32 #include <gtk/gtktable.h>
33 #include <gtk/gtkstock.h>
34 #include <gtk/gtkhbox.h>
35
36 static GtkTargetEntry dest_drag_types[] = {
37   {EGG_TOOLBAR_ITEM_TYPE, GTK_TARGET_SAME_APP, 0},
38 };
39 static int n_dest_drag_types = G_N_ELEMENTS (dest_drag_types);
40
41 static GtkTargetEntry source_drag_types[] = {
42   {EGG_TOOLBAR_ITEM_TYPE, GTK_TARGET_SAME_APP, 0},
43 };
44 static int n_source_drag_types = G_N_ELEMENTS (source_drag_types);
45
46 static void egg_toolbar_editor_class_init       (EggToolbarEditorClass *klass);
47 static void egg_toolbar_editor_init             (EggToolbarEditor *t);
48 static void egg_toolbar_editor_finalize         (GObject *object);
49 static void update_actions_list                 (EggToolbarEditor *editor);
50 static void update_editor_sheet                 (EggToolbarEditor *editor);
51
52 enum
53 {
54   PROP_0,
55   PROP_UI_MANAGER,
56   PROP_TOOLBARS_MODEL
57 };
58
59 static GObjectClass *parent_class = NULL;
60
61 #define EGG_TOOLBAR_EDITOR_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), EGG_TYPE_TOOLBAR_EDITOR, EggToolbarEditorPrivate))
62
63 struct EggToolbarEditorPrivate
64 {
65   GtkUIManager *manager;
66   EggToolbarsModel *model;
67
68   GtkWidget *table;
69   GtkWidget *scrolled_window;
70
71   GList *default_actions_list;
72   GList *actions_list;
73 };
74
75 GType
76 egg_toolbar_editor_get_type (void)
77 {
78   static GType egg_toolbar_editor_type = 0;
79
80   if (egg_toolbar_editor_type == 0)
81     {
82       static const GTypeInfo our_info = {
83         sizeof (EggToolbarEditorClass),
84         NULL,                   /* base_init */
85         NULL,                   /* base_finalize */
86         (GClassInitFunc) egg_toolbar_editor_class_init,
87         NULL,
88         NULL,                   /* class_data */
89         sizeof (EggToolbarEditor),
90         0,                      /* n_preallocs */
91         (GInstanceInitFunc) egg_toolbar_editor_init
92       };
93
94       egg_toolbar_editor_type = g_type_register_static (GTK_TYPE_VBOX,
95                                                         "EggToolbarEditor",
96                                                         &our_info, 0);
97     }
98
99   return egg_toolbar_editor_type;
100 }
101
102 static gint
103 compare_actions (gconstpointer a,
104                  gconstpointer b)
105 {
106   GValue value_a = { 0, }, value_b = { 0, };
107   const char *short_label_a, *short_label_b;
108   int ret;
109
110   g_value_init (&value_a, G_TYPE_STRING);
111   g_object_get_property (G_OBJECT (a), "short_label", &value_a);
112   short_label_a = g_value_get_string (&value_a);
113
114   g_value_init (&value_b, G_TYPE_STRING);
115   g_object_get_property (G_OBJECT (b), "short_label", &value_b);
116   short_label_b = g_value_get_string (&value_b);
117
118   ret = g_utf8_collate (short_label_a, short_label_b);
119
120   g_value_unset (&value_a);
121   g_value_unset (&value_b);
122
123   return ret;
124 }
125
126 static GtkAction *
127 find_action (EggToolbarEditor *t,
128              const char       *name)
129 {
130   GList *l;
131   GtkAction *action = NULL;
132
133   l = gtk_ui_manager_get_action_groups (t->priv->manager);
134
135   g_return_val_if_fail (EGG_IS_TOOLBAR_EDITOR (t), NULL);
136   g_return_val_if_fail (name != NULL, NULL);
137
138   for (; l != NULL; l = l->next)
139     {
140       GtkAction *tmp;
141
142       tmp = gtk_action_group_get_action (GTK_ACTION_GROUP (l->data), name);
143       if (tmp)
144         action = tmp;
145     }
146
147   return action;
148 }
149
150 static void
151 egg_toolbar_editor_set_ui_manager (EggToolbarEditor *t,
152                                    GtkUIManager     *manager)
153 {
154   g_return_if_fail (GTK_IS_UI_MANAGER (manager));
155
156   t->priv->manager = g_object_ref (manager);
157 }
158
159 static void
160 toolbar_removed_cb (EggToolbarsModel   *model,
161                     int                 position,
162                     EggToolbarEditor   *editor)
163 {
164   update_actions_list (editor);
165   update_editor_sheet (editor);
166 }
167
168 static void
169 egg_toolbar_editor_set_model (EggToolbarEditor *t,
170                               EggToolbarsModel *model)
171 {
172   g_return_if_fail (EGG_IS_TOOLBAR_EDITOR (t));
173
174   t->priv->model = g_object_ref (model);
175
176   g_signal_connect_object (model, "toolbar_removed",
177                            G_CALLBACK (toolbar_removed_cb), t, 0);
178 }
179
180 static void
181 egg_toolbar_editor_set_property (GObject      *object,
182                                  guint         prop_id,
183                                  const GValue *value,
184                                  GParamSpec   *pspec)
185 {
186   EggToolbarEditor *t = EGG_TOOLBAR_EDITOR (object);
187
188   switch (prop_id)
189     {
190     case PROP_UI_MANAGER:
191       egg_toolbar_editor_set_ui_manager (t, g_value_get_object (value));
192       break;
193     case PROP_TOOLBARS_MODEL:
194       egg_toolbar_editor_set_model (t, g_value_get_object (value));
195       break;
196     }
197 }
198
199 static void
200 egg_toolbar_editor_get_property (GObject    *object,
201                                  guint       prop_id,
202                                  GValue     *value,
203                                  GParamSpec *pspec)
204 {
205   EggToolbarEditor *t = EGG_TOOLBAR_EDITOR (object);
206
207   switch (prop_id)
208     {
209     case PROP_UI_MANAGER:
210       g_value_set_object (value, t->priv->manager);
211       break;
212     case PROP_TOOLBARS_MODEL:
213       g_value_set_object (value, t->priv->model);
214       break;
215     }
216 }
217
218 static void
219 egg_toolbar_editor_class_init (EggToolbarEditorClass *klass)
220 {
221   GObjectClass *object_class = G_OBJECT_CLASS (klass);
222
223   parent_class = g_type_class_peek_parent (klass);
224
225   object_class->finalize = egg_toolbar_editor_finalize;
226   object_class->set_property = egg_toolbar_editor_set_property;
227   object_class->get_property = egg_toolbar_editor_get_property;
228
229   g_object_class_install_property (object_class,
230                                    PROP_UI_MANAGER,
231                                    g_param_spec_object ("ui-manager",
232                                                         "UI-Manager",
233                                                         "UI Manager",
234                                                         GTK_TYPE_UI_MANAGER,
235                                                         G_PARAM_READWRITE |
236                                                         G_PARAM_CONSTRUCT_ONLY));
237  g_object_class_install_property (object_class,
238                                   PROP_TOOLBARS_MODEL,
239                                   g_param_spec_object ("model",
240                                                        "Model",
241                                                        "Toolbars Model",
242                                                        EGG_TYPE_TOOLBARS_MODEL,
243                                                        G_PARAM_READWRITE |
244                                                        G_PARAM_CONSTRUCT_ONLY));
245
246   g_type_class_add_private (object_class, sizeof (EggToolbarEditorPrivate));
247 }
248
249 static void
250 egg_toolbar_editor_finalize (GObject *object)
251 {
252   EggToolbarEditor *editor = EGG_TOOLBAR_EDITOR (object);
253
254   if (editor->priv->manager)
255     {
256       g_object_unref (editor->priv->manager);
257     }
258
259   if (editor->priv->model)
260     {
261       g_object_unref (editor->priv->model);
262     }
263
264   g_list_free (editor->priv->default_actions_list);
265   g_list_free (editor->priv->actions_list);
266
267   G_OBJECT_CLASS (parent_class)->finalize (object);
268 }
269
270 GtkWidget *
271 egg_toolbar_editor_new (GtkUIManager *manager,
272                         EggToolbarsModel *model)
273 {
274   return GTK_WIDGET (g_object_new (EGG_TYPE_TOOLBAR_EDITOR,
275                                    "ui-manager", manager,
276                                    "model", model,
277                                    NULL));
278 }
279
280 static void
281 drag_begin_cb (GtkWidget          *widget,
282                GdkDragContext     *context)
283 {
284         gtk_widget_hide (widget);
285 }
286
287 static void
288 drag_end_cb (GtkWidget          *widget,
289              GdkDragContext     *context)
290 {
291         gtk_widget_show (widget);
292 }
293
294 static void
295 editor_drag_data_received_cb (GtkWidget          *widget,
296                               GdkDragContext     *context,
297                               gint                x,
298                               gint                y,
299                               GtkSelectionData   *selection_data,
300                               guint               info,
301                               guint               time_,
302                               EggToolbarEditor *editor)
303 {
304   GtkAction *action;
305   const char *data;
306
307   g_return_if_fail (EGG_IS_TOOLBAR_EDITOR (editor));
308   g_return_if_fail (selection_data != NULL);
309
310   if (selection_data->length <= 0 || selection_data->data == NULL) return;
311
312   data = (const char *) selection_data->data;
313
314   if (strcmp (data, "separator") == 0) return;
315
316   action = find_action (editor, data);
317   g_return_if_fail (action != NULL);
318
319   if (g_list_find (editor->priv->default_actions_list, action))
320     {
321       editor->priv->actions_list = g_list_insert_sorted
322             (editor->priv->actions_list, action, compare_actions);
323     }
324
325   update_editor_sheet (editor);
326 }
327
328 static void
329 editor_drag_data_delete_cb (GtkWidget          *widget,
330                             GdkDragContext     *context,
331                             EggToolbarEditor *editor)
332 {
333   GtkAction *action;
334   g_return_if_fail (EGG_IS_TOOLBAR_EDITOR (editor));
335
336   action = GTK_ACTION (g_object_get_data (G_OBJECT (widget), "egg-action"));
337   if (action)
338     {
339       editor->priv->actions_list = g_list_remove
340             (editor->priv->actions_list, action);
341     }
342
343   update_editor_sheet (editor);
344 }
345
346 static void
347 drag_data_get_cb (GtkWidget          *widget,
348                   GdkDragContext     *context,
349                   GtkSelectionData   *selection_data,
350                   guint               info,
351                   guint32             time,
352                   EggToolbarEditor   *editor)
353 {
354   GtkAction *action;
355   const char *target;
356
357   action = GTK_ACTION (g_object_get_data (G_OBJECT (widget), "egg-action"));
358
359   if (action)
360     {
361       target = gtk_action_get_name (action);
362     }
363   else
364     {
365       target = "separator";
366     }
367
368   gtk_selection_data_set (selection_data,
369                           selection_data->target, 8, target, strlen (target));
370 }
371
372 static gchar *
373 elide_underscores (const gchar *original)
374 {
375   gchar *q, *result;
376   const gchar *p;
377   gboolean last_underscore;
378
379   q = result = g_malloc (strlen (original) + 1);
380   last_underscore = FALSE;
381
382   for (p = original; *p; p++)
383     {
384       if (!last_underscore && *p == '_')
385         last_underscore = TRUE;
386       else
387         {
388           last_underscore = FALSE;
389           *q++ = *p;
390         }
391     }
392
393   *q = '\0';
394
395   return result;
396 }
397
398 static void
399 set_drag_cursor (GtkWidget *widget)
400 {
401   GdkCursor *cursor;
402   GdkPixbuf *pixbuf;
403
404   pixbuf = gdk_pixbuf_new_from_file (CURSOR_DIR "/hand-open.png", NULL);
405   cursor = gdk_cursor_new_from_pixbuf (gdk_display_get_default (), pixbuf, 12, 12);
406
407   gdk_window_set_cursor (widget->window, cursor);
408   gdk_cursor_unref (cursor);
409   g_object_unref (pixbuf);
410 }
411
412 static void
413 event_box_realize_cb (GtkWidget *widget, GtkImage *icon)
414 {
415   GtkImageType type;
416
417   set_drag_cursor (widget);
418
419   type = gtk_image_get_storage_type (icon);
420   if (type == GTK_IMAGE_STOCK)
421     {
422       gchar *stock_id;
423       GdkPixbuf *pixbuf;
424       gtk_image_get_stock (icon, &stock_id, NULL);
425       pixbuf = gtk_widget_render_icon (widget, stock_id,
426                                        GTK_ICON_SIZE_LARGE_TOOLBAR, NULL);
427       gtk_drag_source_set_icon_pixbuf (widget, pixbuf);
428       g_object_unref (pixbuf);
429     }
430   else if (type == GTK_IMAGE_PIXBUF)
431     {
432       GdkPixbuf *pixbuf = gtk_image_get_pixbuf (icon);
433       gtk_drag_source_set_icon_pixbuf (widget, pixbuf);
434     }
435 }
436
437 static GtkWidget *
438 editor_create_item (EggToolbarEditor *editor,
439                     GtkImage         *icon,
440                     const char       *label_text,
441                     GdkDragAction     action)
442 {
443   GtkWidget *event_box;
444   GtkWidget *vbox;
445   GtkWidget *label;
446   gchar *label_no_mnemonic = NULL;
447
448   event_box = gtk_event_box_new ();
449   gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
450   gtk_widget_show (event_box);
451   gtk_drag_source_set (event_box,
452                        GDK_BUTTON1_MASK,
453                        source_drag_types, n_source_drag_types, action);
454   g_signal_connect (event_box, "drag_data_get",
455                     G_CALLBACK (drag_data_get_cb), editor);
456   g_signal_connect (event_box, "drag_data_delete",
457                     G_CALLBACK (editor_drag_data_delete_cb), editor);
458   g_signal_connect_after (event_box, "realize",
459                           G_CALLBACK (event_box_realize_cb), icon);
460
461   if (action == GDK_ACTION_MOVE)
462     {
463       g_signal_connect (event_box, "drag_begin",
464                         G_CALLBACK (drag_begin_cb), NULL);
465       g_signal_connect (event_box, "drag_end",
466                         G_CALLBACK (drag_end_cb), NULL);
467     }
468
469   vbox = gtk_vbox_new (0, FALSE);
470   gtk_widget_show (vbox);
471   gtk_container_add (GTK_CONTAINER (event_box), vbox);
472
473   gtk_widget_show (GTK_WIDGET (icon));
474   gtk_box_pack_start (GTK_BOX (vbox), GTK_WIDGET (icon), FALSE, TRUE, 0);
475   label_no_mnemonic = elide_underscores (label_text);
476   label = gtk_label_new (label_no_mnemonic);
477   g_free (label_no_mnemonic);
478   gtk_widget_show (label);
479   gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, TRUE, 0);
480
481   return event_box;
482 }
483
484 static void
485 update_editor_sheet (EggToolbarEditor *editor)
486 {
487   GList *l;
488   GList *to_drag;
489   int x, y, height, width;
490   GtkWidget *table;
491   GtkWidget *viewport;
492   GtkWidget *item;
493   GtkWidget *icon;
494
495   g_return_if_fail (EGG_IS_TOOLBAR_EDITOR (editor));
496
497   viewport = GTK_BIN (editor->priv->scrolled_window)->child;
498   if (viewport)
499     {
500       table = GTK_BIN (viewport)->child;
501       gtk_container_remove (GTK_CONTAINER (viewport), table);
502     }
503   table = gtk_table_new (0, 0, TRUE);
504   editor->priv->table = table;
505   gtk_container_set_border_width (GTK_CONTAINER (table), 12);
506   gtk_widget_show (table);
507   gtk_scrolled_window_add_with_viewport
508     (GTK_SCROLLED_WINDOW (editor->priv->scrolled_window), table);
509   gtk_drag_dest_set (table, GTK_DEST_DEFAULT_ALL,
510                      dest_drag_types, n_dest_drag_types, GDK_ACTION_MOVE);
511   g_signal_connect (table, "drag_data_received",
512                     G_CALLBACK (editor_drag_data_received_cb), editor);
513
514   to_drag = editor->priv->actions_list;
515
516   x = y = 0;
517   width = 4;
518   height = (g_list_length (to_drag)) / width + 1;
519   gtk_table_resize (GTK_TABLE (editor->priv->table), height, width);
520
521   for (l = to_drag; l != NULL; l = l->next)
522     {
523       GtkAction *action = (l->data);
524       const char *stock_id, *short_label;
525       GValue value = { 0, };
526
527       g_value_init (&value, G_TYPE_STRING);
528       g_object_get_property (G_OBJECT (action), "stock_id", &value);
529       stock_id = g_value_get_string (&value);
530       icon = gtk_image_new_from_stock
531                 (stock_id ? stock_id : GTK_STOCK_DND,
532                  GTK_ICON_SIZE_LARGE_TOOLBAR);
533       g_value_unset (&value);
534
535       g_value_init (&value, G_TYPE_STRING);
536       g_object_get_property (G_OBJECT (action), "short_label", &value);
537       short_label = g_value_get_string (&value);
538       item = editor_create_item (editor, GTK_IMAGE (icon),
539                                  short_label, GDK_ACTION_MOVE);
540       g_value_unset (&value);
541       g_object_set_data (G_OBJECT (item), "egg-action", action);
542       gtk_table_attach_defaults (GTK_TABLE (editor->priv->table),
543                                  item, x, x + 1, y, y + 1);
544
545       x++;
546       if (x >= width)
547         {
548           x = 0;
549           y++;
550         }
551     }
552
553   icon = _egg_editable_toolbar_new_separator_image ();
554   item = editor_create_item (editor, GTK_IMAGE (icon), _("Separator"),
555                              GDK_ACTION_COPY);
556   gtk_table_attach_defaults (GTK_TABLE (editor->priv->table),
557                              item, x, x + 1, y, y + 1);
558 }
559
560 static void
561 setup_editor (EggToolbarEditor *editor)
562 {
563   GtkWidget *scrolled_window;
564   GtkWidget *label_hbox;
565   GtkWidget *image;
566   GtkWidget *label;
567
568   g_return_if_fail (EGG_IS_TOOLBAR_EDITOR (editor));
569
570   gtk_container_set_border_width (GTK_CONTAINER (editor), 12);
571   scrolled_window = gtk_scrolled_window_new (NULL, NULL);
572   editor->priv->scrolled_window = scrolled_window;
573   gtk_widget_show (scrolled_window);
574   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
575                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
576   gtk_box_pack_start (GTK_BOX (editor), scrolled_window, TRUE, TRUE, 0);
577   label_hbox = gtk_hbox_new (FALSE, 6);
578   gtk_widget_show (label_hbox);
579   gtk_box_pack_start (GTK_BOX (editor), label_hbox, FALSE, FALSE, 0);
580   image =
581     gtk_image_new_from_stock (GTK_STOCK_DIALOG_INFO, GTK_ICON_SIZE_DIALOG);
582   gtk_widget_show (image);
583   gtk_box_pack_start (GTK_BOX (label_hbox), image, FALSE, FALSE, 0);
584   label = gtk_label_new (_("Drag an item onto the toolbars above to add it, "
585                            "from the toolbars in the items table to remove it."));
586   gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
587   gtk_widget_show (label);
588   gtk_box_pack_start (GTK_BOX (label_hbox), label, FALSE, TRUE, 0);
589 }
590
591 static void
592 egg_toolbar_editor_init (EggToolbarEditor *t)
593 {
594   t->priv = EGG_TOOLBAR_EDITOR_GET_PRIVATE (t);
595
596   t->priv->manager = NULL;
597   t->priv->default_actions_list = NULL;
598   t->priv->actions_list = NULL;
599
600   setup_editor (t);
601 }
602
603 void
604 egg_toolbar_editor_add_action (EggToolbarEditor *editor,
605                                const char       *action_name)
606 {
607         GtkAction *action;
608
609         action = find_action (editor, action_name);
610         g_return_if_fail (action != NULL);
611
612         editor->priv->default_actions_list = g_list_insert_sorted
613                 (editor->priv->default_actions_list, action, compare_actions);
614 }
615
616 static void
617 parse_item_list (EggToolbarEditor *t,
618                  xmlNodePtr        child)
619 {
620   while (child)
621     {
622       if (xmlStrEqual (child->name, "toolitem"))
623         {
624           xmlChar *name;
625
626           name = xmlGetProp (child, "name");
627           egg_toolbar_editor_add_action (t, name);
628           xmlFree (name);
629         }
630       child = child->next;
631     }
632 }
633
634 static gboolean
635 model_has_action (EggToolbarsModel *model, GtkAction *action)
636 {
637   int i, l, n_items, n_toolbars;
638
639   n_toolbars = egg_toolbars_model_n_toolbars (model);
640   for (i = 0; i < n_toolbars; i++)
641     {
642       n_items = egg_toolbars_model_n_items (model, i);
643       for (l = 0; l < n_items; l++)
644         {
645           const char *name;
646           const char *action_name;
647           gboolean sep;
648
649           egg_toolbars_model_item_nth (model, i, l, &sep, &name, NULL);
650           action_name = gtk_action_get_name (action);
651           if (!sep && strcmp (name, action_name) == 0) return TRUE;
652         }
653     }
654
655   return FALSE;
656 }
657
658 static void
659 update_actions_list (EggToolbarEditor *editor)
660 {
661   GList *l;
662
663   if (editor->priv->actions_list)
664     g_list_free (editor->priv->actions_list);
665
666   /* Remove the already used items */
667   editor->priv->actions_list = NULL;
668
669   for (l = editor->priv->default_actions_list; l != NULL; l = l->next)
670     {
671       GtkAction *action = GTK_ACTION (l->data);
672
673       if (!model_has_action (editor->priv->model, action))
674         {
675           editor->priv->actions_list = g_list_insert_sorted
676                 (editor->priv->actions_list, action, compare_actions);
677         }
678     }
679 }
680
681 void
682 egg_toolbar_editor_load_actions (EggToolbarEditor *editor,
683                                  const char       *xml_file)
684 {
685   xmlDocPtr doc;
686   xmlNodePtr root;
687   xmlNodePtr child;
688
689   doc = xmlParseFile (xml_file);
690   root = xmlDocGetRootElement (doc);
691   child = root->children;
692
693   while (child)
694     {
695       if (xmlStrEqual (child->name, "available"))
696         {
697           parse_item_list (editor, child->children);
698         }
699       child = child->next;
700     }
701
702   xmlFreeDoc (doc);
703
704   update_actions_list (editor);
705   update_editor_sheet (editor);
706 }