]> git.0d.be Git - empathy.git/blob - src/empathy-sidebar.c
src/empathy-sidebar.c: fix shadow declarations
[empathy.git] / src / empathy-sidebar.c
1 /*
2  * Copyright (C) 2004 Red Hat, Inc.
3  * Copyright (C) 2007 The Free Software Foundation
4  * Copyright (C) 2008 Marco Barisione <marco@barisione.org>
5  *
6  * Based on evince code (shell/ev-sidebar.c) by:
7  *      - Jonathan Blandford <jrb@alum.mit.edu>
8  *
9  * Base on eog code (src/eog-sidebar.c) by:
10  *      - Lucas Rocha <lucasr@gnome.org>
11  *
12  * This program is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or
15  * (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software
24  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
25  */
26
27 #ifdef HAVE_CONFIG_H
28 #include "config.h"
29 #endif
30
31 #include <string.h>
32 #include <gtk/gtk.h>
33 #include <gdk/gdkkeysyms.h>
34
35 #include "empathy-sidebar.h"
36
37 enum
38 {
39     PROP_0,
40     PROP_CURRENT_PAGE
41 };
42
43 enum
44 {
45     PAGE_COLUMN_TITLE,
46     PAGE_COLUMN_MENU_ITEM,
47     PAGE_COLUMN_MAIN_WIDGET,
48     PAGE_COLUMN_NOTEBOOK_INDEX,
49     PAGE_COLUMN_NUM_COLS
50 };
51
52 enum
53 {
54     SIGNAL_PAGE_ADDED,
55     SIGNAL_PAGE_REMOVED,
56     SIGNAL_LAST
57 };
58
59 static gint signals[SIGNAL_LAST];
60
61 struct _EmpathySidebarPrivate
62 {
63     GtkWidget *notebook;
64     GtkWidget *select_button;
65     GtkWidget *menu;
66     GtkWidget *hbox;
67     GtkWidget *label;
68
69     GtkTreeModel *page_model;
70 };
71
72 G_DEFINE_TYPE (EmpathySidebar, empathy_sidebar, GTK_TYPE_VBOX)
73
74 #define EMPATHY_SIDEBAR_GET_PRIVATE(object) \
75     (G_TYPE_INSTANCE_GET_PRIVATE ((object), EMPATHY_TYPE_SIDEBAR, EmpathySidebarPrivate))
76
77 static void
78 empathy_sidebar_destroy (GtkObject *object)
79 {
80   EmpathySidebar *sidebar = EMPATHY_SIDEBAR (object);
81
82   if (sidebar->priv->menu)
83     {
84       gtk_menu_detach (GTK_MENU (sidebar->priv->menu));
85       sidebar->priv->menu = NULL;
86     }
87
88   if (sidebar->priv->page_model)
89     {
90       g_object_unref (sidebar->priv->page_model);
91       sidebar->priv->page_model = NULL;
92     }
93
94   (* GTK_OBJECT_CLASS (empathy_sidebar_parent_class)->destroy) (object);
95 }
96
97 static void
98 empathy_sidebar_select_page (EmpathySidebar *sidebar,
99                                 GtkTreeIter *iter)
100 {
101   gchar *title;
102   gint index_;
103
104   gtk_tree_model_get (sidebar->priv->page_model, iter,
105       PAGE_COLUMN_TITLE, &title,
106       PAGE_COLUMN_NOTEBOOK_INDEX, &index_,
107       -1);
108
109   gtk_notebook_set_current_page (GTK_NOTEBOOK (sidebar->priv->notebook),
110       index_);
111   gtk_label_set_text (GTK_LABEL (sidebar->priv->label), title);
112
113   g_free (title);
114 }
115
116 void
117 empathy_sidebar_set_page (EmpathySidebar *sidebar,
118                              GtkWidget *main_widget)
119 {
120   GtkTreeIter iter;
121   gboolean valid;
122
123   valid = gtk_tree_model_get_iter_first (sidebar->priv->page_model, &iter);
124
125   while (valid)
126     {
127       GtkWidget *widget;
128
129       gtk_tree_model_get (sidebar->priv->page_model, &iter,
130           PAGE_COLUMN_MAIN_WIDGET, &widget,
131           -1);
132
133       if (widget == main_widget)
134         {
135           empathy_sidebar_select_page (sidebar, &iter);
136           valid = FALSE;
137         }
138       else
139         {
140           valid = gtk_tree_model_iter_next (sidebar->priv->page_model, &iter);
141         }
142
143       g_object_unref (widget);
144   }
145
146   g_object_notify (G_OBJECT (sidebar), "current-page");
147 }
148
149 static GtkWidget *
150 empathy_sidebar_get_current_page (EmpathySidebar *sidebar)
151 {
152   GtkNotebook *notebook = GTK_NOTEBOOK (sidebar->priv->notebook);
153
154   return gtk_notebook_get_nth_page (
155     notebook, gtk_notebook_get_current_page (notebook));
156 }
157
158 static void
159 empathy_sidebar_set_property (GObject *object,
160                                  guint prop_id,
161                                  const GValue *value,
162                                  GParamSpec *pspec)
163 {
164   EmpathySidebar *sidebar = EMPATHY_SIDEBAR (object);
165
166   switch (prop_id)
167     {
168       case PROP_CURRENT_PAGE:
169         empathy_sidebar_set_page (sidebar, g_value_get_object (value));
170         break;
171       default:
172         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
173     }
174 }
175
176 static void
177 empathy_sidebar_get_property (GObject *object,
178                                  guint prop_id,
179                                  GValue *value,
180                                  GParamSpec *pspec)
181 {
182   EmpathySidebar *sidebar = EMPATHY_SIDEBAR (object);
183
184   switch (prop_id)
185     {
186       case PROP_CURRENT_PAGE:
187         g_value_set_object (value,
188             empathy_sidebar_get_current_page (sidebar));
189         break;
190       default:
191         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
192     }
193 }
194
195 static void
196 empathy_sidebar_class_init (EmpathySidebarClass *empathy_sidebar_class)
197 {
198   GObjectClass *g_object_class;
199   GtkWidgetClass *widget_class;
200   GtkObjectClass *gtk_object_klass;
201
202   g_object_class = G_OBJECT_CLASS (empathy_sidebar_class);
203   widget_class = GTK_WIDGET_CLASS (empathy_sidebar_class);
204   gtk_object_klass = GTK_OBJECT_CLASS (empathy_sidebar_class);
205
206   g_type_class_add_private (g_object_class, sizeof (EmpathySidebarPrivate));
207
208   gtk_object_klass->destroy = empathy_sidebar_destroy;
209   g_object_class->get_property = empathy_sidebar_get_property;
210   g_object_class->set_property = empathy_sidebar_set_property;
211
212   g_object_class_install_property (g_object_class,
213       PROP_CURRENT_PAGE,
214       g_param_spec_object ("current-page",
215           "Current page",
216           "The currently visible page",
217           GTK_TYPE_WIDGET,
218           G_PARAM_READWRITE));
219
220   signals[SIGNAL_PAGE_ADDED] = g_signal_new ("page-added",
221       EMPATHY_TYPE_SIDEBAR, G_SIGNAL_RUN_FIRST,
222       G_STRUCT_OFFSET (EmpathySidebarClass, page_added),
223       NULL, NULL, g_cclosure_marshal_VOID__OBJECT,
224       G_TYPE_NONE, 1, GTK_TYPE_WIDGET);
225
226   signals[SIGNAL_PAGE_REMOVED] = g_signal_new ("page-removed",
227       EMPATHY_TYPE_SIDEBAR, G_SIGNAL_RUN_FIRST,
228       G_STRUCT_OFFSET (EmpathySidebarClass, page_removed),
229       NULL, NULL, g_cclosure_marshal_VOID__OBJECT,
230       G_TYPE_NONE, 1, GTK_TYPE_WIDGET);
231 }
232
233 static void
234 empathy_sidebar_menu_position_under (GtkMenu *menu,
235                                          gint *x,
236                                          gint *y,
237                                          gboolean *push_in,
238                                          gpointer user_data)
239 {
240   GtkWidget *widget;
241
242   g_return_if_fail (GTK_IS_BUTTON (user_data));
243   g_return_if_fail (GTK_WIDGET_NO_WINDOW (user_data));
244
245   widget = GTK_WIDGET (user_data);
246
247   gdk_window_get_origin (gtk_widget_get_window (widget), x, y);
248
249   *x += widget->allocation.x;
250   *y += widget->allocation.y + widget->allocation.height;
251
252   *push_in = FALSE;
253 }
254
255 static gboolean
256 empathy_sidebar_select_button_press_cb (GtkWidget *widget,
257                                            GdkEventButton *event,
258                                            gpointer user_data)
259 {
260   EmpathySidebar *sidebar = EMPATHY_SIDEBAR (user_data);
261
262   if (event->button == 1)
263     {
264       GtkRequisition requisition;
265       gint width;
266
267       width = widget->allocation.width;
268
269       gtk_widget_set_size_request (sidebar->priv->menu, -1, -1);
270       gtk_widget_size_request (sidebar->priv->menu, &requisition);
271       gtk_widget_set_size_request (sidebar->priv->menu,
272           MAX (width, requisition.width), -1);
273
274       gtk_widget_grab_focus (widget);
275
276       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), TRUE);
277
278       gtk_menu_popup (GTK_MENU (sidebar->priv->menu),
279           NULL, NULL, empathy_sidebar_menu_position_under, widget,
280           event->button, event->time);
281
282       return TRUE;
283     }
284
285   return FALSE;
286 }
287
288 static gboolean
289 empathy_sidebar_select_button_key_press_cb (GtkWidget *widget,
290                                                GdkEventKey *event,
291                                                gpointer user_data)
292 {
293   EmpathySidebar *sidebar = EMPATHY_SIDEBAR (user_data);
294
295   if (event->keyval == GDK_space ||
296       event->keyval == GDK_KP_Space ||
297       event->keyval == GDK_Return ||
298       event->keyval == GDK_KP_Enter)
299     {
300       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), TRUE);
301
302       gtk_menu_popup (GTK_MENU (sidebar->priv->menu),
303           NULL, NULL, empathy_sidebar_menu_position_under, widget,
304           1, event->time);
305
306       return TRUE;
307     }
308
309   return FALSE;
310 }
311
312 static void
313 empathy_sidebar_close_clicked_cb (GtkWidget *widget,
314                                      gpointer user_data)
315 {
316   EmpathySidebar *sidebar = EMPATHY_SIDEBAR (user_data);
317
318   gtk_widget_hide (GTK_WIDGET (sidebar));
319 }
320
321 static void
322 empathy_sidebar_menu_deactivate_cb (GtkWidget *widget,
323                                        gpointer user_data)
324 {
325   GtkWidget *menu_button;
326
327   menu_button = GTK_WIDGET (user_data);
328
329   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (menu_button), FALSE);
330 }
331
332 static void
333 empathy_sidebar_menu_detach_cb (GtkWidget *widget,
334                                    GtkMenu *menu)
335 {
336   EmpathySidebar *sidebar = EMPATHY_SIDEBAR (widget);
337
338   sidebar->priv->menu = NULL;
339 }
340
341 static void
342 empathy_sidebar_menu_item_activate_cb (GtkWidget *widget,
343                                           gpointer user_data)
344 {
345   EmpathySidebar *sidebar = EMPATHY_SIDEBAR (user_data);
346   GtkTreeIter iter;
347   GtkWidget *menu_item, *item;
348   gboolean valid;
349
350   menu_item = gtk_menu_get_active (GTK_MENU (sidebar->priv->menu));
351   valid = gtk_tree_model_get_iter_first (sidebar->priv->page_model, &iter);
352
353   while (valid)
354     {
355       gtk_tree_model_get (sidebar->priv->page_model, &iter,
356           PAGE_COLUMN_MENU_ITEM, &item,
357           -1);
358
359       if (item == menu_item)
360         {
361           empathy_sidebar_select_page (sidebar, &iter);
362           valid = FALSE;
363         }
364       else
365         {
366           valid = gtk_tree_model_iter_next (sidebar->priv->page_model, &iter);
367         }
368
369       g_object_unref (item);
370     }
371
372   g_object_notify (G_OBJECT (sidebar), "current-page");
373 }
374
375 static void
376 empathy_sidebar_init (EmpathySidebar *sidebar)
377 {
378   GtkWidget *hbox;
379   GtkWidget *close_button;
380   GtkWidget *select_hbox;
381   GtkWidget *arrow;
382   GtkWidget *image;
383
384   sidebar->priv = EMPATHY_SIDEBAR_GET_PRIVATE (sidebar);
385
386   /* data model */
387   sidebar->priv->page_model = (GtkTreeModel *) gtk_list_store_new (
388       PAGE_COLUMN_NUM_COLS,
389       G_TYPE_STRING,
390       GTK_TYPE_WIDGET,
391       GTK_TYPE_WIDGET,
392       G_TYPE_INT);
393
394   /* top option menu */
395   hbox = gtk_hbox_new (FALSE, 0);
396   sidebar->priv->hbox = hbox;
397   gtk_box_pack_start (GTK_BOX (sidebar), hbox, FALSE, FALSE, 0);
398   gtk_widget_show (hbox);
399
400   sidebar->priv->select_button = gtk_toggle_button_new ();
401   gtk_button_set_relief (GTK_BUTTON (sidebar->priv->select_button),
402       GTK_RELIEF_NONE);
403
404   g_signal_connect (sidebar->priv->select_button, "button_press_event",
405       G_CALLBACK (empathy_sidebar_select_button_press_cb),
406       sidebar);
407
408   g_signal_connect (sidebar->priv->select_button, "key_press_event",
409       G_CALLBACK (empathy_sidebar_select_button_key_press_cb),
410       sidebar);
411
412   select_hbox = gtk_hbox_new (FALSE, 0);
413
414   sidebar->priv->label = gtk_label_new ("");
415
416   gtk_box_pack_start (GTK_BOX (select_hbox),
417       sidebar->priv->label,
418       FALSE, FALSE, 0);
419
420   gtk_widget_show (sidebar->priv->label);
421
422   arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE);
423   gtk_box_pack_end (GTK_BOX (select_hbox), arrow, FALSE, FALSE, 0);
424   gtk_widget_show (arrow);
425
426   gtk_container_add (GTK_CONTAINER (sidebar->priv->select_button), select_hbox);
427   gtk_widget_show (select_hbox);
428
429   gtk_box_pack_start (GTK_BOX (hbox), sidebar->priv->select_button, TRUE, TRUE, 0);
430   gtk_widget_show (sidebar->priv->select_button);
431
432   close_button = gtk_button_new ();
433
434   gtk_button_set_relief (GTK_BUTTON (close_button), GTK_RELIEF_NONE);
435
436   g_signal_connect (close_button, "clicked",
437       G_CALLBACK (empathy_sidebar_close_clicked_cb),
438       sidebar);
439
440   image = gtk_image_new_from_stock (GTK_STOCK_CLOSE,
441       GTK_ICON_SIZE_MENU);
442   gtk_container_add (GTK_CONTAINER (close_button), image);
443   gtk_widget_show (image);
444
445   gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
446   gtk_widget_show (close_button);
447
448   sidebar->priv->menu = gtk_menu_new ();
449
450   g_signal_connect (sidebar->priv->menu, "deactivate",
451       G_CALLBACK (empathy_sidebar_menu_deactivate_cb),
452       sidebar->priv->select_button);
453
454   gtk_menu_attach_to_widget (GTK_MENU (sidebar->priv->menu),
455       GTK_WIDGET (sidebar),
456       empathy_sidebar_menu_detach_cb);
457
458   gtk_widget_show (sidebar->priv->menu);
459
460   sidebar->priv->notebook = gtk_notebook_new ();
461
462   gtk_notebook_set_show_border (GTK_NOTEBOOK (sidebar->priv->notebook), FALSE);
463   gtk_notebook_set_show_tabs (GTK_NOTEBOOK (sidebar->priv->notebook), FALSE);
464
465   gtk_box_pack_start (GTK_BOX (sidebar), sidebar->priv->notebook,
466       TRUE, TRUE, 0);
467
468   gtk_widget_show (sidebar->priv->notebook);
469 }
470
471 GtkWidget *
472 empathy_sidebar_new (void)
473 {
474   GtkWidget *sidebar;
475
476   sidebar = g_object_new (EMPATHY_TYPE_SIDEBAR, NULL);
477
478   return sidebar;
479 }
480
481 void
482 empathy_sidebar_add_page (EmpathySidebar *sidebar,
483                              const gchar *title,
484                              GtkWidget *main_widget)
485 {
486   GtkTreeIter iter;
487   GtkWidget *menu_item;
488   gchar *label_title;
489   gint index_;
490
491   g_return_if_fail (EMPATHY_IS_SIDEBAR (sidebar));
492   g_return_if_fail (GTK_IS_WIDGET (main_widget));
493
494   index_ = gtk_notebook_append_page (GTK_NOTEBOOK (sidebar->priv->notebook),
495       main_widget, NULL);
496
497   menu_item = gtk_image_menu_item_new_with_label (title);
498
499   g_signal_connect (menu_item, "activate",
500       G_CALLBACK (empathy_sidebar_menu_item_activate_cb),
501       sidebar);
502
503   gtk_widget_show (menu_item);
504
505   gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->priv->menu),
506       menu_item);
507
508   /* Insert and move to end */
509   gtk_list_store_insert_with_values (GTK_LIST_STORE (sidebar->priv->page_model),
510       &iter, 0,
511       PAGE_COLUMN_TITLE, title,
512       PAGE_COLUMN_MENU_ITEM, menu_item,
513       PAGE_COLUMN_MAIN_WIDGET, main_widget,
514       PAGE_COLUMN_NOTEBOOK_INDEX, index_,
515       -1);
516
517   gtk_list_store_move_before (GTK_LIST_STORE(sidebar->priv->page_model),
518       &iter,
519       NULL);
520
521   /* Set the first item added as active */
522   gtk_tree_model_get_iter_first (sidebar->priv->page_model, &iter);
523   gtk_tree_model_get (sidebar->priv->page_model,
524       &iter,
525       PAGE_COLUMN_TITLE, &label_title,
526       PAGE_COLUMN_NOTEBOOK_INDEX, &index_,
527       -1);
528
529   gtk_menu_set_active (GTK_MENU (sidebar->priv->menu), index_);
530
531   gtk_label_set_text (GTK_LABEL (sidebar->priv->label), label_title);
532
533   gtk_notebook_set_current_page (GTK_NOTEBOOK (sidebar->priv->notebook),
534       index_);
535
536   g_free (label_title);
537
538   g_signal_emit (G_OBJECT (sidebar), signals[SIGNAL_PAGE_ADDED],
539       0, main_widget);
540 }
541
542 void
543 empathy_sidebar_remove_page (EmpathySidebar *sidebar,
544                                 GtkWidget *main_widget)
545 {
546   GtkTreeIter iter;
547   GtkWidget *widget, *menu_item;
548   gboolean valid;
549   gint index_;
550
551   g_return_if_fail (EMPATHY_IS_SIDEBAR (sidebar));
552   g_return_if_fail (GTK_IS_WIDGET (main_widget));
553
554   valid = gtk_tree_model_get_iter_first (sidebar->priv->page_model, &iter);
555
556   while (valid)
557     {
558       gtk_tree_model_get (sidebar->priv->page_model, &iter,
559           PAGE_COLUMN_NOTEBOOK_INDEX, &index_,
560           PAGE_COLUMN_MENU_ITEM, &menu_item,
561           PAGE_COLUMN_MAIN_WIDGET, &widget,
562           -1);
563
564       if (widget == main_widget)
565           break;
566       else
567           valid = gtk_tree_model_iter_next (sidebar->priv->page_model, &iter);
568
569       g_object_unref (menu_item);
570       g_object_unref (widget);
571     }
572
573   if (valid)
574     {
575       gtk_notebook_remove_page (GTK_NOTEBOOK (sidebar->priv->notebook),
576           index_);
577
578       gtk_container_remove (GTK_CONTAINER (sidebar->priv->menu), menu_item);
579
580       gtk_list_store_remove (GTK_LIST_STORE (sidebar->priv->page_model),
581           &iter);
582
583       g_signal_emit (G_OBJECT (sidebar),
584           signals[SIGNAL_PAGE_REMOVED], 0, main_widget);
585     }
586 }
587
588 gint
589 empathy_sidebar_get_n_pages (EmpathySidebar *sidebar)
590 {
591   g_return_val_if_fail (EMPATHY_IS_SIDEBAR (sidebar), TRUE);
592
593   return gtk_tree_model_iter_n_children (
594       GTK_TREE_MODEL (sidebar->priv->page_model), NULL);
595 }
596
597 gboolean
598 empathy_sidebar_is_empty (EmpathySidebar *sidebar)
599 {
600   g_return_val_if_fail (EMPATHY_IS_SIDEBAR (sidebar), TRUE);
601
602   return gtk_tree_model_iter_n_children (
603       GTK_TREE_MODEL (sidebar->priv->page_model), NULL) == 0;
604 }