]> git.0d.be Git - empathy.git/blob - src/empathy-sidebar.c
Merge branch 'debugger'
[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), index);
110   gtk_label_set_text (GTK_LABEL (sidebar->priv->label), title);
111
112   g_free (title);
113 }
114
115 void
116 empathy_sidebar_set_page (EmpathySidebar *sidebar,
117                              GtkWidget *main_widget)
118 {
119   GtkTreeIter iter;
120   gboolean valid;
121
122   valid = gtk_tree_model_get_iter_first (sidebar->priv->page_model, &iter);
123
124   while (valid)
125     {
126       GtkWidget *widget;
127
128       gtk_tree_model_get (sidebar->priv->page_model, &iter,
129           PAGE_COLUMN_MAIN_WIDGET, &widget,
130           -1);
131
132       if (widget == main_widget)
133         {
134           empathy_sidebar_select_page (sidebar, &iter);
135           valid = FALSE;
136         }
137       else
138         {
139           valid = gtk_tree_model_iter_next (sidebar->priv->page_model, &iter);
140         }
141
142       g_object_unref (widget);
143   }
144
145   g_object_notify (G_OBJECT (sidebar), "current-page");
146 }
147
148 static GtkWidget *
149 empathy_sidebar_get_current_page (EmpathySidebar *sidebar)
150 {
151   GtkNotebook *notebook = GTK_NOTEBOOK (sidebar->priv->notebook);
152
153   return gtk_notebook_get_nth_page (
154     notebook, gtk_notebook_get_current_page (notebook));
155 }
156
157 static void
158 empathy_sidebar_set_property (GObject *object,
159                                  guint prop_id,
160                                  const GValue *value,
161                                  GParamSpec *pspec)
162 {
163   EmpathySidebar *sidebar = EMPATHY_SIDEBAR (object);
164
165   switch (prop_id)
166     {
167       case PROP_CURRENT_PAGE:
168         empathy_sidebar_set_page (sidebar, g_value_get_object (value));
169         break;
170       default:
171         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
172     }
173 }
174
175 static void
176 empathy_sidebar_get_property (GObject *object,
177                                  guint prop_id,
178                                  GValue *value,
179                                  GParamSpec *pspec)
180 {
181   EmpathySidebar *sidebar = EMPATHY_SIDEBAR (object);
182
183   switch (prop_id)
184     {
185       case PROP_CURRENT_PAGE:
186         g_value_set_object (value,
187             empathy_sidebar_get_current_page (sidebar));
188         break;
189       default:
190         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
191     }
192 }
193
194 static void
195 empathy_sidebar_class_init (EmpathySidebarClass *empathy_sidebar_class)
196 {
197   GObjectClass *g_object_class;
198   GtkWidgetClass *widget_class;
199   GtkObjectClass *gtk_object_klass;
200
201   g_object_class = G_OBJECT_CLASS (empathy_sidebar_class);
202   widget_class = GTK_WIDGET_CLASS (empathy_sidebar_class);
203   gtk_object_klass = GTK_OBJECT_CLASS (empathy_sidebar_class);
204
205   g_type_class_add_private (g_object_class, sizeof (EmpathySidebarPrivate));
206
207   gtk_object_klass->destroy = empathy_sidebar_destroy;
208   g_object_class->get_property = empathy_sidebar_get_property;
209   g_object_class->set_property = empathy_sidebar_set_property;
210
211   g_object_class_install_property (g_object_class,
212       PROP_CURRENT_PAGE,
213       g_param_spec_object ("current-page",
214           "Current page",
215           "The currently visible page",
216           GTK_TYPE_WIDGET,
217           G_PARAM_READWRITE));
218
219   signals[SIGNAL_PAGE_ADDED] = g_signal_new ("page-added",
220       EMPATHY_TYPE_SIDEBAR, G_SIGNAL_RUN_FIRST,
221       G_STRUCT_OFFSET (EmpathySidebarClass, page_added),
222       NULL, NULL, g_cclosure_marshal_VOID__OBJECT,
223       G_TYPE_NONE, 1, GTK_TYPE_WIDGET);
224
225   signals[SIGNAL_PAGE_REMOVED] = g_signal_new ("page-removed",
226       EMPATHY_TYPE_SIDEBAR, G_SIGNAL_RUN_FIRST,
227       G_STRUCT_OFFSET (EmpathySidebarClass, page_removed),
228       NULL, NULL, g_cclosure_marshal_VOID__OBJECT,
229       G_TYPE_NONE, 1, GTK_TYPE_WIDGET);
230 }
231
232 static void
233 empathy_sidebar_menu_position_under (GtkMenu *menu,
234                                          gint *x,
235                                          gint *y,
236                                          gboolean *push_in,
237                                          gpointer user_data)
238 {
239   GtkWidget *widget;
240
241   g_return_if_fail (GTK_IS_BUTTON (user_data));
242   g_return_if_fail (GTK_WIDGET_NO_WINDOW (user_data));
243
244   widget = GTK_WIDGET (user_data);
245
246   gdk_window_get_origin (widget->window, x, y);
247
248   *x += widget->allocation.x;
249   *y += widget->allocation.y + widget->allocation.height;
250
251   *push_in = FALSE;
252 }
253
254 static gboolean
255 empathy_sidebar_select_button_press_cb (GtkWidget *widget,
256                                            GdkEventButton *event,
257                                            gpointer user_data)
258 {
259   EmpathySidebar *sidebar = EMPATHY_SIDEBAR (user_data);
260
261   if (event->button == 1)
262     {
263       GtkRequisition requisition;
264       gint width;
265
266       width = widget->allocation.width;
267
268       gtk_widget_set_size_request (sidebar->priv->menu, -1, -1);
269       gtk_widget_size_request (sidebar->priv->menu, &requisition);
270       gtk_widget_set_size_request (sidebar->priv->menu,
271           MAX (width, requisition.width), -1);
272
273       gtk_widget_grab_focus (widget);
274
275       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), TRUE);
276
277       gtk_menu_popup (GTK_MENU (sidebar->priv->menu),
278           NULL, NULL, empathy_sidebar_menu_position_under, widget,
279           event->button, event->time);
280
281       return TRUE;
282     }
283
284   return FALSE;
285 }
286
287 static gboolean
288 empathy_sidebar_select_button_key_press_cb (GtkWidget *widget,
289                                                GdkEventKey *event,
290                                                gpointer user_data)
291 {
292   EmpathySidebar *sidebar = EMPATHY_SIDEBAR (user_data);
293
294   if (event->keyval == GDK_space ||
295       event->keyval == GDK_KP_Space ||
296       event->keyval == GDK_Return ||
297       event->keyval == GDK_KP_Enter)
298     {
299       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), TRUE);
300
301       gtk_menu_popup (GTK_MENU (sidebar->priv->menu),
302           NULL, NULL, empathy_sidebar_menu_position_under, widget,
303           1, event->time);
304
305       return TRUE;
306     }
307
308   return FALSE;
309 }
310
311 static void
312 empathy_sidebar_close_clicked_cb (GtkWidget *widget,
313                                      gpointer user_data)
314 {
315   EmpathySidebar *sidebar = EMPATHY_SIDEBAR (user_data);
316
317   gtk_widget_hide (GTK_WIDGET (sidebar));
318 }
319
320 static void
321 empathy_sidebar_menu_deactivate_cb (GtkWidget *widget,
322                                        gpointer user_data)
323 {
324   GtkWidget *menu_button;
325
326   menu_button = GTK_WIDGET (user_data);
327
328   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (menu_button), FALSE);
329 }
330
331 static void
332 empathy_sidebar_menu_detach_cb (GtkWidget *widget,
333                                    GtkMenu *menu)
334 {
335   EmpathySidebar *sidebar = EMPATHY_SIDEBAR (widget);
336
337   sidebar->priv->menu = NULL;
338 }
339
340 static void
341 empathy_sidebar_menu_item_activate_cb (GtkWidget *widget,
342                                           gpointer user_data)
343 {
344   EmpathySidebar *sidebar = EMPATHY_SIDEBAR (user_data);
345   GtkTreeIter iter;
346   GtkWidget *menu_item, *item;
347   gboolean valid;
348
349   menu_item = gtk_menu_get_active (GTK_MENU (sidebar->priv->menu));
350   valid = gtk_tree_model_get_iter_first (sidebar->priv->page_model, &iter);
351
352   while (valid)
353     {
354       gtk_tree_model_get (sidebar->priv->page_model, &iter,
355           PAGE_COLUMN_MENU_ITEM, &item,
356           -1);
357
358       if (item == menu_item)
359         {
360           empathy_sidebar_select_page (sidebar, &iter);
361           valid = FALSE;
362         }
363       else
364         {
365           valid = gtk_tree_model_iter_next (sidebar->priv->page_model, &iter);
366         }
367
368       g_object_unref (item);
369     }
370
371   g_object_notify (G_OBJECT (sidebar), "current-page");
372 }
373
374 static void
375 empathy_sidebar_init (EmpathySidebar *sidebar)
376 {
377   GtkWidget *hbox;
378   GtkWidget *close_button;
379   GtkWidget *select_hbox;
380   GtkWidget *arrow;
381   GtkWidget *image;
382
383   sidebar->priv = EMPATHY_SIDEBAR_GET_PRIVATE (sidebar);
384
385   /* data model */
386   sidebar->priv->page_model = (GtkTreeModel *) gtk_list_store_new (
387       PAGE_COLUMN_NUM_COLS,
388       G_TYPE_STRING,
389       GTK_TYPE_WIDGET,
390       GTK_TYPE_WIDGET,
391       G_TYPE_INT);
392
393   /* top option menu */
394   hbox = gtk_hbox_new (FALSE, 0);
395   sidebar->priv->hbox = hbox;
396   gtk_box_pack_start (GTK_BOX (sidebar), hbox, FALSE, FALSE, 0);
397   gtk_widget_show (hbox);
398
399   sidebar->priv->select_button = gtk_toggle_button_new ();
400   gtk_button_set_relief (GTK_BUTTON (sidebar->priv->select_button),
401       GTK_RELIEF_NONE);
402
403   g_signal_connect (sidebar->priv->select_button, "button_press_event",
404       G_CALLBACK (empathy_sidebar_select_button_press_cb),
405       sidebar);
406
407   g_signal_connect (sidebar->priv->select_button, "key_press_event",
408       G_CALLBACK (empathy_sidebar_select_button_key_press_cb),
409       sidebar);
410
411   select_hbox = gtk_hbox_new (FALSE, 0);
412
413   sidebar->priv->label = gtk_label_new ("");
414
415   gtk_box_pack_start (GTK_BOX (select_hbox),
416       sidebar->priv->label,
417       FALSE, FALSE, 0);
418
419   gtk_widget_show (sidebar->priv->label);
420
421   arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE);
422   gtk_box_pack_end (GTK_BOX (select_hbox), arrow, FALSE, FALSE, 0);
423   gtk_widget_show (arrow);
424
425   gtk_container_add (GTK_CONTAINER (sidebar->priv->select_button), select_hbox);
426   gtk_widget_show (select_hbox);
427
428   gtk_box_pack_start (GTK_BOX (hbox), sidebar->priv->select_button, TRUE, TRUE, 0);
429   gtk_widget_show (sidebar->priv->select_button);
430
431   close_button = gtk_button_new ();
432
433   gtk_button_set_relief (GTK_BUTTON (close_button), GTK_RELIEF_NONE);
434
435   g_signal_connect (close_button, "clicked",
436       G_CALLBACK (empathy_sidebar_close_clicked_cb),
437       sidebar);
438
439   image = gtk_image_new_from_stock (GTK_STOCK_CLOSE,
440       GTK_ICON_SIZE_MENU);
441   gtk_container_add (GTK_CONTAINER (close_button), image);
442   gtk_widget_show (image);
443
444   gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);
445   gtk_widget_show (close_button);
446
447   sidebar->priv->menu = gtk_menu_new ();
448
449   g_signal_connect (sidebar->priv->menu, "deactivate",
450       G_CALLBACK (empathy_sidebar_menu_deactivate_cb),
451       sidebar->priv->select_button);
452
453   gtk_menu_attach_to_widget (GTK_MENU (sidebar->priv->menu),
454       GTK_WIDGET (sidebar),
455       empathy_sidebar_menu_detach_cb);
456
457   gtk_widget_show (sidebar->priv->menu);
458
459   sidebar->priv->notebook = gtk_notebook_new ();
460
461   gtk_notebook_set_show_border (GTK_NOTEBOOK (sidebar->priv->notebook), FALSE);
462   gtk_notebook_set_show_tabs (GTK_NOTEBOOK (sidebar->priv->notebook), FALSE);
463
464   gtk_box_pack_start (GTK_BOX (sidebar), sidebar->priv->notebook,
465       TRUE, TRUE, 0);
466
467   gtk_widget_show (sidebar->priv->notebook);
468 }
469
470 GtkWidget *
471 empathy_sidebar_new (void)
472 {
473   GtkWidget *sidebar;
474
475   sidebar = g_object_new (EMPATHY_TYPE_SIDEBAR, NULL);
476
477   return sidebar;
478 }
479
480 void
481 empathy_sidebar_add_page (EmpathySidebar *sidebar,
482                              const gchar *title,
483                              GtkWidget *main_widget)
484 {
485   GtkTreeIter iter;
486   GtkWidget *menu_item;
487   gchar *label_title;
488   gint index;
489
490   g_return_if_fail (EMPATHY_IS_SIDEBAR (sidebar));
491   g_return_if_fail (GTK_IS_WIDGET (main_widget));
492
493   index = gtk_notebook_append_page (GTK_NOTEBOOK (sidebar->priv->notebook),
494       main_widget, NULL);
495
496   menu_item = gtk_image_menu_item_new_with_label (title);
497
498   g_signal_connect (menu_item, "activate",
499       G_CALLBACK (empathy_sidebar_menu_item_activate_cb),
500       sidebar);
501
502   gtk_widget_show (menu_item);
503
504   gtk_menu_shell_append (GTK_MENU_SHELL (sidebar->priv->menu),
505       menu_item);
506
507   /* Insert and move to end */
508   gtk_list_store_insert_with_values (GTK_LIST_STORE (sidebar->priv->page_model),
509       &iter, 0,
510       PAGE_COLUMN_TITLE, title,
511       PAGE_COLUMN_MENU_ITEM, menu_item,
512       PAGE_COLUMN_MAIN_WIDGET, main_widget,
513       PAGE_COLUMN_NOTEBOOK_INDEX, index,
514       -1);
515
516   gtk_list_store_move_before (GTK_LIST_STORE(sidebar->priv->page_model),
517       &iter,
518       NULL);
519
520   /* Set the first item added as active */
521   gtk_tree_model_get_iter_first (sidebar->priv->page_model, &iter);
522   gtk_tree_model_get (sidebar->priv->page_model,
523       &iter,
524       PAGE_COLUMN_TITLE, &label_title,
525       PAGE_COLUMN_NOTEBOOK_INDEX, &index,
526       -1);
527
528   gtk_menu_set_active (GTK_MENU (sidebar->priv->menu), index);
529
530   gtk_label_set_text (GTK_LABEL (sidebar->priv->label), label_title);
531
532   gtk_notebook_set_current_page (GTK_NOTEBOOK (sidebar->priv->notebook),
533       index);
534
535   g_free (label_title);
536
537   g_signal_emit (G_OBJECT (sidebar), signals[SIGNAL_PAGE_ADDED],
538       0, main_widget);
539 }
540
541 void
542 empathy_sidebar_remove_page (EmpathySidebar *sidebar,
543                                 GtkWidget *main_widget)
544 {
545   GtkTreeIter iter;
546   GtkWidget *widget, *menu_item;
547   gboolean valid;
548   gint index;
549
550   g_return_if_fail (EMPATHY_IS_SIDEBAR (sidebar));
551   g_return_if_fail (GTK_IS_WIDGET (main_widget));
552
553   valid = gtk_tree_model_get_iter_first (sidebar->priv->page_model, &iter);
554
555   while (valid)
556     {
557       gtk_tree_model_get (sidebar->priv->page_model, &iter,
558           PAGE_COLUMN_NOTEBOOK_INDEX, &index,
559           PAGE_COLUMN_MENU_ITEM, &menu_item,
560           PAGE_COLUMN_MAIN_WIDGET, &widget,
561           -1);
562
563       if (widget == main_widget)
564           break;
565       else
566           valid = gtk_tree_model_iter_next (sidebar->priv->page_model, &iter);
567
568       g_object_unref (menu_item);
569       g_object_unref (widget);
570     }
571
572   if (valid)
573     {
574       gtk_notebook_remove_page (GTK_NOTEBOOK (sidebar->priv->notebook),
575           index);
576
577       gtk_container_remove (GTK_CONTAINER (sidebar->priv->menu), menu_item);
578
579       gtk_list_store_remove (GTK_LIST_STORE (sidebar->priv->page_model),
580           &iter);
581
582       g_signal_emit (G_OBJECT (sidebar),
583           signals[SIGNAL_PAGE_REMOVED], 0, main_widget);
584     }
585 }
586
587 gint
588 empathy_sidebar_get_n_pages (EmpathySidebar *sidebar)
589 {
590   g_return_val_if_fail (EMPATHY_IS_SIDEBAR (sidebar), TRUE);
591
592   return gtk_tree_model_iter_n_children (
593       GTK_TREE_MODEL (sidebar->priv->page_model), NULL);
594 }
595
596 gboolean
597 empathy_sidebar_is_empty (EmpathySidebar *sidebar)
598 {
599   g_return_val_if_fail (EMPATHY_IS_SIDEBAR (sidebar), TRUE);
600
601   return gtk_tree_model_iter_n_children (
602       GTK_TREE_MODEL (sidebar->priv->page_model), NULL) == 0;
603 }