]> www.fi.muni.cz Git - evince.git/blob - cut-n-paste/toolbar-editor/egg-toolbar-editor.c
[toolbar-editor] Sync with current libegg
[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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  *  $Id$
19  */
20
21 #include "config.h"
22
23 #include "egg-toolbar-editor.h"
24 #include "egg-editable-toolbar.h"
25
26 #include <string.h>
27 #include <libxml/tree.h>
28 #include <gtk/gtk.h>
29 #include <glib/gi18n.h>
30
31 static const GtkTargetEntry dest_drag_types[] = {
32   {EGG_TOOLBAR_ITEM_TYPE, GTK_TARGET_SAME_APP, 0},
33 };
34
35 static const GtkTargetEntry source_drag_types[] = {
36   {EGG_TOOLBAR_ITEM_TYPE, GTK_TARGET_SAME_APP, 0},
37 };
38
39
40 static void egg_toolbar_editor_finalize         (GObject *object);
41 static void update_editor_sheet                 (EggToolbarEditor *editor);
42
43 enum
44 {
45   PROP_0,
46   PROP_UI_MANAGER,
47   PROP_TOOLBARS_MODEL
48 };
49
50 enum
51 {
52   SIGNAL_HANDLER_ITEM_ADDED,
53   SIGNAL_HANDLER_ITEM_REMOVED,
54   SIGNAL_HANDLER_TOOLBAR_REMOVED,
55   SIGNAL_HANDLER_LIST_SIZE  /* Array size */
56 };
57
58 #define EGG_TOOLBAR_EDITOR_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), EGG_TYPE_TOOLBAR_EDITOR, EggToolbarEditorPrivate))
59
60 struct EggToolbarEditorPrivate
61 {
62   GtkUIManager *manager;
63   EggToolbarsModel *model;
64
65   GtkWidget *table;
66   GtkWidget *scrolled_window;
67   GList     *actions_list;
68   GList     *factory_list;
69
70   /* These handlers need to be sanely disconnected when switching models */
71   gulong     sig_handlers[SIGNAL_HANDLER_LIST_SIZE];
72 };
73
74 G_DEFINE_TYPE (EggToolbarEditor, egg_toolbar_editor, GTK_TYPE_VBOX);
75
76 static gint
77 compare_items (gconstpointer a,
78                gconstpointer b)
79 {
80   const GtkWidget *item1 = a;
81   const GtkWidget *item2 = b;
82
83   char *key1 = g_object_get_data (G_OBJECT (item1),
84                                   "egg-collate-key");
85   char *key2 = g_object_get_data (G_OBJECT (item2),
86                                   "egg-collate-key");
87
88   return strcmp (key1, key2);
89 }
90
91 static GtkAction *
92 find_action (EggToolbarEditor *t,
93              const char       *name)
94 {
95   GList *l;
96   GtkAction *action = NULL;
97
98   l = gtk_ui_manager_get_action_groups (t->priv->manager);
99
100   g_return_val_if_fail (EGG_IS_TOOLBAR_EDITOR (t), NULL);
101   g_return_val_if_fail (name != NULL, NULL);
102
103   for (; l != NULL; l = l->next)
104     {
105       GtkAction *tmp;
106
107       tmp = gtk_action_group_get_action (GTK_ACTION_GROUP (l->data), name);
108       if (tmp)
109         action = tmp;
110     }
111
112   return action;
113 }
114
115 static void
116 egg_toolbar_editor_set_ui_manager (EggToolbarEditor *t,
117                                    GtkUIManager     *manager)
118 {
119   g_return_if_fail (GTK_IS_UI_MANAGER (manager));
120
121   t->priv->manager = g_object_ref (manager);
122 }
123
124 static void
125 item_added_or_removed_cb (EggToolbarsModel   *model,
126                           int                 tpos,
127                           int                 ipos,
128                           EggToolbarEditor   *editor)
129 {
130   update_editor_sheet (editor);
131 }
132
133 static void
134 toolbar_removed_cb (EggToolbarsModel   *model,
135                     int                 position,
136                     EggToolbarEditor   *editor)
137 {
138   update_editor_sheet (editor);
139 }
140
141 static void
142 egg_toolbar_editor_disconnect_model (EggToolbarEditor *t)
143 {
144   EggToolbarEditorPrivate *priv = t->priv;
145   EggToolbarsModel *model = priv->model;
146   gulong handler;
147   int i;
148
149   for (i = 0; i < SIGNAL_HANDLER_LIST_SIZE; i++)
150     {
151       handler = priv->sig_handlers[i];
152
153       if (handler != 0)
154         {
155           if (g_signal_handler_is_connected (model, handler))
156             {
157               g_signal_handler_disconnect (model, handler);
158             }
159
160           priv->sig_handlers[i] = 0;
161         }
162     }
163 }
164
165 void
166 egg_toolbar_editor_set_model (EggToolbarEditor *t,
167                               EggToolbarsModel *model)
168 {
169   EggToolbarEditorPrivate *priv;
170
171   g_return_if_fail (EGG_IS_TOOLBAR_EDITOR (t));
172   g_return_if_fail (model != NULL);
173
174   priv = t->priv;
175
176   if (priv->model)
177     {
178       if (G_UNLIKELY (priv->model == model)) return;
179
180       egg_toolbar_editor_disconnect_model (t);
181       g_object_unref (priv->model);
182     }
183
184   priv->model = g_object_ref (model);
185
186   update_editor_sheet (t);
187
188   priv->sig_handlers[SIGNAL_HANDLER_ITEM_ADDED] =
189     g_signal_connect_object (model, "item_added",
190                              G_CALLBACK (item_added_or_removed_cb), t, 0);
191   priv->sig_handlers[SIGNAL_HANDLER_ITEM_REMOVED] =
192     g_signal_connect_object (model, "item_removed",
193                              G_CALLBACK (item_added_or_removed_cb), t, 0);
194   priv->sig_handlers[SIGNAL_HANDLER_TOOLBAR_REMOVED] =
195     g_signal_connect_object (model, "toolbar_removed",
196                              G_CALLBACK (toolbar_removed_cb), t, 0);
197 }
198
199 static void
200 egg_toolbar_editor_set_property (GObject      *object,
201                                  guint         prop_id,
202                                  const 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       egg_toolbar_editor_set_ui_manager (t, g_value_get_object (value));
211       break;
212     case PROP_TOOLBARS_MODEL:
213       egg_toolbar_editor_set_model (t, g_value_get_object (value));
214       break;
215     }
216 }
217
218 static void
219 egg_toolbar_editor_get_property (GObject    *object,
220                                  guint       prop_id,
221                                  GValue     *value,
222                                  GParamSpec *pspec)
223 {
224   EggToolbarEditor *t = EGG_TOOLBAR_EDITOR (object);
225
226   switch (prop_id)
227     {
228     case PROP_UI_MANAGER:
229       g_value_set_object (value, t->priv->manager);
230       break;
231     case PROP_TOOLBARS_MODEL:
232       g_value_set_object (value, t->priv->model);
233       break;
234     }
235 }
236
237 static void
238 egg_toolbar_editor_class_init (EggToolbarEditorClass *klass)
239 {
240   GObjectClass *object_class = G_OBJECT_CLASS (klass);
241
242   object_class->finalize = egg_toolbar_editor_finalize;
243   object_class->set_property = egg_toolbar_editor_set_property;
244   object_class->get_property = egg_toolbar_editor_get_property;
245
246   g_object_class_install_property (object_class,
247                                    PROP_UI_MANAGER,
248                                    g_param_spec_object ("ui-manager",
249                                                         "UI-Manager",
250                                                         "UI Manager",
251                                                         GTK_TYPE_UI_MANAGER,
252                                                         G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB |
253                                                         G_PARAM_CONSTRUCT_ONLY));
254  g_object_class_install_property (object_class,
255                                   PROP_TOOLBARS_MODEL,
256                                   g_param_spec_object ("model",
257                                                        "Model",
258                                                        "Toolbars Model",
259                                                        EGG_TYPE_TOOLBARS_MODEL,
260                                                        G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB |
261                                                        G_PARAM_CONSTRUCT));
262
263   g_type_class_add_private (object_class, sizeof (EggToolbarEditorPrivate));
264 }
265
266 static void
267 egg_toolbar_editor_finalize (GObject *object)
268 {
269   EggToolbarEditor *editor = EGG_TOOLBAR_EDITOR (object);
270
271   if (editor->priv->manager)
272     {
273       g_object_unref (editor->priv->manager);
274     }
275
276   if (editor->priv->model)
277     {
278       egg_toolbar_editor_disconnect_model (editor);
279       g_object_unref (editor->priv->model);
280     }
281
282   g_list_free (editor->priv->actions_list);
283   g_list_free (editor->priv->factory_list);
284
285   G_OBJECT_CLASS (egg_toolbar_editor_parent_class)->finalize (object);
286 }
287
288 GtkWidget *
289 egg_toolbar_editor_new (GtkUIManager *manager,
290                         EggToolbarsModel *model)
291 {
292   return GTK_WIDGET (g_object_new (EGG_TYPE_TOOLBAR_EDITOR,
293                                    "ui-manager", manager,
294                                    "model", model,
295                                    NULL));
296 }
297
298 static void
299 drag_begin_cb (GtkWidget          *widget,
300                GdkDragContext     *context)
301 {
302   gtk_widget_hide (widget);
303 }
304
305 static void
306 drag_end_cb (GtkWidget          *widget,
307              GdkDragContext     *context)
308 {
309   gtk_widget_show (widget);
310 }
311
312 static void
313 drag_data_get_cb (GtkWidget          *widget,
314                   GdkDragContext     *context,
315                   GtkSelectionData   *selection_data,
316                   guint               info,
317                   guint32             time,
318                   EggToolbarEditor   *editor)
319 {
320   const char *target;
321
322   target = g_object_get_data (G_OBJECT (widget), "egg-item-name");
323   g_return_if_fail (target != NULL);
324
325   gtk_selection_data_set (selection_data, selection_data->target, 8,
326                           (const guchar *) target, strlen (target));
327 }
328
329 static gchar *
330 elide_underscores (const gchar *original)
331 {
332   gchar *q, *result;
333   const gchar *p;
334   gboolean last_underscore;
335
336   q = result = g_malloc (strlen (original) + 1);
337   last_underscore = FALSE;
338
339   for (p = original; *p; p++)
340     {
341       if (!last_underscore && *p == '_')
342         last_underscore = TRUE;
343       else
344         {
345           last_underscore = FALSE;
346           *q++ = *p;
347         }
348     }
349
350   *q = '\0';
351
352   return result;
353 }
354
355 static void
356 set_drag_cursor (GtkWidget *widget)
357 {
358   GdkCursor *cursor;
359   GdkScreen *screen;
360
361   screen = gtk_widget_get_screen (widget);
362
363   cursor = gdk_cursor_new_for_display (gdk_screen_get_display (screen),
364                                        GDK_HAND2);
365   gdk_window_set_cursor (widget->window, cursor);
366   gdk_cursor_unref (cursor);
367 }
368
369 static void
370 event_box_realize_cb (GtkWidget *widget, GtkImage *icon)
371 {
372   GtkImageType type;
373
374   set_drag_cursor (widget);
375
376   type = gtk_image_get_storage_type (icon);
377   if (type == GTK_IMAGE_STOCK)
378     {
379       gchar *stock_id;
380       GdkPixbuf *pixbuf;
381
382       gtk_image_get_stock (icon, &stock_id, NULL);
383       pixbuf = gtk_widget_render_icon (widget, stock_id,
384                                        GTK_ICON_SIZE_LARGE_TOOLBAR, NULL);
385       gtk_drag_source_set_icon_pixbuf (widget, pixbuf);
386       g_object_unref (pixbuf);
387     }
388   else if (type == GTK_IMAGE_ICON_NAME)
389     {
390       const gchar *icon_name;
391       GdkScreen *screen;
392       GtkIconTheme *icon_theme;
393       GtkSettings *settings;
394       gint width, height;
395       GdkPixbuf *pixbuf;
396
397       gtk_image_get_icon_name (icon, &icon_name, NULL);
398       screen = gtk_widget_get_screen (widget);
399       icon_theme = gtk_icon_theme_get_for_screen (screen);
400       settings = gtk_settings_get_for_screen (screen);
401
402       if (!gtk_icon_size_lookup_for_settings (settings,
403                                               GTK_ICON_SIZE_LARGE_TOOLBAR,
404                                               &width, &height))
405         {
406           width = height = 24;
407         }
408
409       pixbuf = gtk_icon_theme_load_icon (icon_theme, icon_name,
410                                          MIN (width, height), 0, NULL);
411       if (G_UNLIKELY (!pixbuf))
412         return;
413
414       gtk_drag_source_set_icon_pixbuf (widget, pixbuf);
415       g_object_unref (pixbuf);
416
417     }
418   else if (type == GTK_IMAGE_PIXBUF)
419     {
420       GdkPixbuf *pixbuf = gtk_image_get_pixbuf (icon);
421       gtk_drag_source_set_icon_pixbuf (widget, pixbuf);
422     }
423 }
424
425 static GtkWidget *
426 editor_create_item (EggToolbarEditor *editor,
427                     GtkImage         *icon,
428                     const char       *label_text,
429                     GdkDragAction     action)
430 {
431   GtkWidget *event_box;
432   GtkWidget *vbox;
433   GtkWidget *label;
434   gchar *label_no_mnemonic = NULL;
435
436   event_box = gtk_event_box_new ();
437   gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE);
438   gtk_widget_show (event_box);
439   gtk_drag_source_set (event_box,
440                        GDK_BUTTON1_MASK,
441                        source_drag_types, G_N_ELEMENTS (source_drag_types), action);
442   g_signal_connect (event_box, "drag_data_get",
443                     G_CALLBACK (drag_data_get_cb), editor);
444   g_signal_connect_after (event_box, "realize",
445                           G_CALLBACK (event_box_realize_cb), icon);
446
447   if (action == GDK_ACTION_MOVE)
448     {
449       g_signal_connect (event_box, "drag_begin",
450                         G_CALLBACK (drag_begin_cb), NULL);
451       g_signal_connect (event_box, "drag_end",
452                         G_CALLBACK (drag_end_cb), NULL);
453     }
454
455   vbox = gtk_vbox_new (0, FALSE);
456   gtk_widget_show (vbox);
457   gtk_container_add (GTK_CONTAINER (event_box), vbox);
458
459   gtk_widget_show (GTK_WIDGET (icon));
460   gtk_box_pack_start (GTK_BOX (vbox), GTK_WIDGET (icon), FALSE, TRUE, 0);
461   label_no_mnemonic = elide_underscores (label_text);
462   label = gtk_label_new (label_no_mnemonic);
463   g_free (label_no_mnemonic);
464   gtk_widget_show (label);
465   gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, TRUE, 0);
466
467   return event_box;
468 }
469
470 static GtkWidget *
471 editor_create_item_from_name (EggToolbarEditor *editor,
472                               const char *      name,
473                               GdkDragAction     drag_action)
474 {
475   GtkWidget *item;
476   const char *item_name;
477   char *short_label;
478   const char *collate_key;
479
480   if (strcmp (name, "_separator") == 0)
481     {
482       GtkWidget *icon;
483
484       icon = _egg_editable_toolbar_new_separator_image ();
485       short_label = _("Separator");
486       item_name = g_strdup (name);
487       collate_key = g_utf8_collate_key (short_label, -1);
488       item = editor_create_item (editor, GTK_IMAGE (icon),
489                                  short_label, drag_action);
490     }
491   else
492     {
493       GtkAction *action;
494       GtkWidget *icon;
495       char *stock_id, *icon_name = NULL;
496
497       action = find_action (editor, name);
498       g_return_val_if_fail (action != NULL, NULL);
499
500       g_object_get (action,
501                     "icon-name", &icon_name,
502                     "stock-id", &stock_id,
503                     "short-label", &short_label,
504                     NULL);
505
506       /* This is a workaround to catch named icons. */
507       if (icon_name)
508         icon = gtk_image_new_from_icon_name (icon_name,
509                                              GTK_ICON_SIZE_LARGE_TOOLBAR);
510       else
511         icon = gtk_image_new_from_stock (stock_id ? stock_id : GTK_STOCK_DND,
512                                          GTK_ICON_SIZE_LARGE_TOOLBAR);
513
514       item_name = g_strdup (name);
515       collate_key = g_utf8_collate_key (short_label, -1);
516       item = editor_create_item (editor, GTK_IMAGE (icon),
517                                  short_label, drag_action);
518
519       g_free (short_label);
520       g_free (stock_id);
521       g_free (icon_name);
522     }
523
524   g_object_set_data_full (G_OBJECT (item), "egg-collate-key",
525                           (gpointer) collate_key, g_free);
526   g_object_set_data_full (G_OBJECT (item), "egg-item-name",
527                           (gpointer) item_name, g_free);
528
529   return item;
530 }
531
532 static gint
533 append_table (GtkTable *table, GList *items, gint y, gint width)
534 {
535   if (items != NULL)
536     {
537       gint x = 0, height;
538       GtkWidget *alignment;
539       GtkWidget *item;
540
541       height = g_list_length (items) / width + 1;
542       gtk_table_resize (table, height, width);
543
544       if (y > 0)
545         {
546           item = gtk_hseparator_new ();
547           alignment = gtk_alignment_new (0.5, 0.5, 1.0, 0.0);
548           gtk_container_add (GTK_CONTAINER (alignment), item);
549           gtk_widget_show (alignment);
550           gtk_widget_show (item);
551
552           gtk_table_attach_defaults (table, alignment, 0, width, y-1, y+1);
553         }
554
555       for (; items != NULL; items = items->next)
556         {
557           item = items->data;
558           alignment = gtk_alignment_new (0.5, 0.5, 0.0, 0.0);
559           gtk_container_add (GTK_CONTAINER (alignment), item);
560           gtk_widget_show (alignment);
561           gtk_widget_show (item);
562
563           if (x >= width)
564             {
565               x = 0;
566               y++;
567             }
568           gtk_table_attach_defaults (table, alignment, x, x+1, y, y+1);
569           x++;
570         }
571
572       y++;
573     }
574   return y;
575 }
576
577 static void
578 update_editor_sheet (EggToolbarEditor *editor)
579 {
580   gint y;
581   GPtrArray *items;
582   GList *to_move = NULL, *to_copy = NULL;
583   GtkWidget *table;
584   GtkWidget *viewport;
585
586   g_return_if_fail (EGG_IS_TOOLBAR_EDITOR (editor));
587
588   /* Create new table. */
589   table = gtk_table_new (0, 0, TRUE);
590   editor->priv->table = table;
591   gtk_container_set_border_width (GTK_CONTAINER (table), 12);
592   gtk_table_set_row_spacings (GTK_TABLE (table), 24);
593   gtk_widget_show (table);
594   gtk_drag_dest_set (table, GTK_DEST_DEFAULT_ALL,
595                      dest_drag_types, G_N_ELEMENTS (dest_drag_types),
596                      GDK_ACTION_MOVE | GDK_ACTION_COPY);
597
598   /* Build two lists of items (one for copying, one for moving). */
599   items = egg_toolbars_model_get_name_avail (editor->priv->model);
600   while (items->len > 0)
601     {
602       GtkWidget *item;
603       const char *name;
604       gint flags;
605
606       name = g_ptr_array_index (items, 0);
607       g_ptr_array_remove_index_fast (items, 0);
608
609       flags = egg_toolbars_model_get_name_flags (editor->priv->model, name);
610       if ((flags & EGG_TB_MODEL_NAME_INFINITE) == 0)
611         {
612           item = editor_create_item_from_name (editor, name, GDK_ACTION_MOVE);
613           if (item != NULL)
614             to_move = g_list_insert_sorted (to_move, item, compare_items);
615         }
616       else
617         {
618           item = editor_create_item_from_name (editor, name, GDK_ACTION_COPY);
619           if (item != NULL)
620             to_copy = g_list_insert_sorted (to_copy, item, compare_items);
621         }
622     }
623
624   /* Add them to the sheet. */
625   y = 0;
626   y = append_table (GTK_TABLE (table), to_move, y, 4);
627   y = append_table (GTK_TABLE (table), to_copy, y, 4);
628
629   g_list_free (to_move);
630   g_list_free (to_copy);
631   g_ptr_array_free (items, TRUE);
632
633   /* Delete old table. */
634   viewport = GTK_BIN (editor->priv->scrolled_window)->child;
635   if (viewport)
636     {
637       gtk_container_remove (GTK_CONTAINER (viewport),
638                             GTK_BIN (viewport)->child);
639     }
640
641   /* Add table to window. */
642   gtk_scrolled_window_add_with_viewport
643     (GTK_SCROLLED_WINDOW (editor->priv->scrolled_window), table);
644
645 }
646
647 static void
648 setup_editor (EggToolbarEditor *editor)
649 {
650   GtkWidget *scrolled_window;
651
652   gtk_container_set_border_width (GTK_CONTAINER (editor), 12);
653   scrolled_window = gtk_scrolled_window_new (NULL, NULL);
654   editor->priv->scrolled_window = scrolled_window;
655   gtk_widget_show (scrolled_window);
656   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
657                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
658   gtk_box_pack_start (GTK_BOX (editor), scrolled_window, TRUE, TRUE, 0);
659 }
660
661 static void
662 egg_toolbar_editor_init (EggToolbarEditor *t)
663 {
664   t->priv = EGG_TOOLBAR_EDITOR_GET_PRIVATE (t);
665
666   t->priv->manager = NULL;
667   t->priv->actions_list = NULL;
668
669   setup_editor (t);
670 }
671