]> Pileus Git - ~andy/gtk/blob - gtk/gtkpathbar.c
Use the GtkPathBar by default now.
[~andy/gtk] / gtk / gtkpathbar.c
1 /* gtkpathbar.h
2  * Copyright (C) 2004  Red Hat, Inc.,  Jonathan Blandford <jrb@gnome.org>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library 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 GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 #include "gtkpathbar.h"
21 #include "gtktogglebutton.h"
22 #include "gtkarrow.h"
23 #include "gtklabel.h"
24 #include "gtkmain.h"
25 #include "gtkmarshalers.h"
26
27 enum {
28   PATH_CLICKED,
29   LAST_SIGNAL
30 };
31
32 static guint path_bar_signals [LAST_SIGNAL] = { 0 };
33
34
35 G_DEFINE_TYPE (GtkPathBar,
36                gtk_path_bar,
37                GTK_TYPE_CONTAINER);
38
39
40 static void gtk_path_bar_finalize (GObject *object);
41 static void gtk_path_bar_size_request  (GtkWidget      *widget,
42                                         GtkRequisition *requisition);
43 static void gtk_path_bar_size_allocate (GtkWidget      *widget,
44                                         GtkAllocation  *allocation);
45 static void gtk_path_bar_direction_changed (GtkWidget *widget,
46                                             GtkTextDirection direction);
47 static void gtk_path_bar_add (GtkContainer *container,
48                               GtkWidget    *widget);
49 static void gtk_path_bar_remove (GtkContainer *container,
50                                  GtkWidget    *widget);
51 static void gtk_path_bar_forall (GtkContainer *container,
52                                  gboolean      include_internals,
53                                  GtkCallback   callback,
54                                  gpointer      callback_data);
55 static void gtk_path_bar_scroll_up (GtkWidget *button, GtkPathBar *path_bar);
56 static void gtk_path_bar_scroll_down (GtkWidget *button, GtkPathBar *path_bar);
57
58 static GtkWidget *
59 get_slider_button (GtkPathBar *path_bar)
60 {
61   GtkWidget *button;
62   
63   button = gtk_button_new ();
64   gtk_container_add (GTK_CONTAINER (button), gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_OUT));
65   gtk_container_add (GTK_CONTAINER (path_bar), button);
66   gtk_widget_show_all (button);
67
68   return button;
69 }
70
71 static void
72 gtk_path_bar_init (GtkPathBar *path_bar)
73 {
74   GTK_WIDGET_SET_FLAGS (path_bar, GTK_NO_WINDOW);
75   gtk_widget_set_redraw_on_allocate (GTK_WIDGET (path_bar), FALSE);
76
77   path_bar->spacing = 3;
78   path_bar->up_slider_button = get_slider_button (path_bar);
79   path_bar->down_slider_button = get_slider_button (path_bar);
80
81   g_signal_connect (path_bar->up_slider_button, "clicked", G_CALLBACK (gtk_path_bar_scroll_up), path_bar);
82   g_signal_connect (path_bar->down_slider_button, "clicked", G_CALLBACK (gtk_path_bar_scroll_down), path_bar);
83 }
84
85 static void
86 gtk_path_bar_class_init (GtkPathBarClass *path_bar_class)
87 {
88   GObjectClass *gobject_class;
89   GtkObjectClass *object_class;
90   GtkWidgetClass *widget_class;
91   GtkContainerClass *container_class;
92
93   gobject_class = (GObjectClass *) path_bar_class;
94   object_class = (GtkObjectClass *) path_bar_class;
95   widget_class = (GtkWidgetClass *) path_bar_class;
96   container_class = (GtkContainerClass *) path_bar_class;
97
98   gobject_class->finalize = gtk_path_bar_finalize;
99
100   widget_class->size_request = gtk_path_bar_size_request;
101   widget_class->size_allocate = gtk_path_bar_size_allocate;
102   widget_class->direction_changed = gtk_path_bar_direction_changed;
103
104   container_class->add = gtk_path_bar_add;
105   container_class->forall = gtk_path_bar_forall;
106   container_class->remove = gtk_path_bar_remove;
107   /* FIXME: */
108   /*  container_class->child_type = gtk_path_bar_child_type;*/
109
110   path_bar_signals [PATH_CLICKED] =
111     g_signal_new ("path_clicked",
112                   G_OBJECT_CLASS_TYPE (object_class),
113                   G_SIGNAL_RUN_FIRST,
114                   G_STRUCT_OFFSET (GtkPathBarClass, path_clicked),
115                   NULL, NULL,
116                   _gtk_marshal_VOID__STRING,
117                   G_TYPE_NONE, 1,
118                   G_TYPE_STRING);
119 }
120
121
122 static void
123 gtk_path_bar_finalize (GObject *object)
124 {
125   GtkPathBar *path_bar;
126
127   path_bar = GTK_PATH_BAR (object);
128   g_list_free (path_bar->button_list);
129
130   G_OBJECT_CLASS (parent_class)->finalize (object);
131 }
132
133 /* Size requisition:
134  * 
135  * Ideally, our size is determined by another widget, and we are just filling
136  * available space.
137  */
138 static void
139 gtk_path_bar_size_request (GtkWidget      *widget,
140                            GtkRequisition *requisition)
141 {
142   GtkPathBar *path_bar;
143   GtkRequisition child_requisition;
144   GList *list;
145
146   path_bar = GTK_PATH_BAR (widget);
147
148   requisition->width = 0;
149   requisition->height = 0;
150
151   for (list = path_bar->button_list; list; list = list->next)
152     {
153       gtk_widget_size_request (GTK_WIDGET (list->data),
154                                &child_requisition);
155       requisition->width = MAX (child_requisition.width, requisition->width);
156       requisition->height = MAX (child_requisition.height, requisition->height);
157     }
158
159   /* Add space for slider, if we have more than one path */
160   /* Theoretically, the slider could be bigger than the other button.  But we're
161    * not going to worry about that now.
162    */
163   path_bar->slider_width = requisition->height / 2 + 5;
164   if (path_bar->button_list && path_bar->button_list->next != NULL)
165     requisition->width += (path_bar->spacing + path_bar->slider_width) * 2;
166
167   gtk_widget_size_request (path_bar->up_slider_button, &child_requisition);
168   gtk_widget_size_request (path_bar->down_slider_button, &child_requisition);
169
170   requisition->width += GTK_CONTAINER (widget)->border_width * 2;
171   requisition->height += GTK_CONTAINER (widget)->border_width * 2;
172
173   widget->requisition = *requisition;
174 }
175
176 static void
177 gtk_path_bar_update_slider_buttons (GtkPathBar *path_bar)
178 {
179   GtkTextDirection direction;
180
181   direction = gtk_widget_get_direction (GTK_WIDGET (path_bar));
182   if (direction == GTK_TEXT_DIR_RTL)
183     {
184       GtkWidget *arrow;
185
186       arrow = gtk_bin_get_child (GTK_BIN (path_bar->up_slider_button));
187       g_object_set (arrow, "arrow_type", GTK_ARROW_RIGHT, NULL);
188
189       arrow = gtk_bin_get_child (GTK_BIN (path_bar->down_slider_button));
190       g_object_set (arrow, "arrow_type", GTK_ARROW_LEFT, NULL);
191     }
192   else
193     {
194       GtkWidget *arrow;
195
196       arrow = gtk_bin_get_child (GTK_BIN (path_bar->up_slider_button));
197       g_object_set (arrow, "arrow_type", GTK_ARROW_LEFT, NULL);
198
199       arrow = gtk_bin_get_child (GTK_BIN (path_bar->down_slider_button));
200       g_object_set (arrow, "arrow_type", GTK_ARROW_RIGHT, NULL);
201     }
202
203   if (path_bar->button_list)
204     {
205       GtkWidget *button;
206
207       button = path_bar->button_list->data;
208       if (gtk_widget_get_child_visible (button))
209         gtk_widget_set_sensitive (path_bar->down_slider_button, FALSE);
210       else
211         gtk_widget_set_sensitive (path_bar->down_slider_button, TRUE);
212
213       button = g_list_last (path_bar->button_list)->data;
214       if (gtk_widget_get_child_visible (button))
215         gtk_widget_set_sensitive (path_bar->up_slider_button, FALSE);
216       else
217         gtk_widget_set_sensitive (path_bar->up_slider_button, TRUE);
218     }
219 }
220
221 /* This is a tad complicated
222  */
223 static void
224 gtk_path_bar_size_allocate (GtkWidget     *widget,
225                             GtkAllocation *allocation)
226 {
227   GtkWidget *child;
228   GtkPathBar *path_bar = GTK_PATH_BAR (widget);
229   GtkTextDirection direction;
230   GtkAllocation child_allocation;
231   GList *list, *first_button;
232   gint width;
233   gint allocation_width;
234   gint border_width;
235   gboolean need_sliders = FALSE;
236   gint up_slider_offset = 0;
237   gint down_slider_offset = 0;
238
239   widget->allocation = *allocation;
240
241   /* No path is set; we don't have to allocate anything. */
242   if (path_bar->button_list == NULL)
243     return;
244
245   direction = gtk_widget_get_direction (widget);
246   border_width = (gint) GTK_CONTAINER (path_bar)->border_width;
247   allocation_width = allocation->width - 2 * border_width;
248
249   /* First, we check to see if we need the scrollbars. */
250   width = GTK_WIDGET (path_bar->button_list->data)->requisition.width;
251   for (list = path_bar->button_list->next; list; list = list->next)
252     {
253       child = GTK_WIDGET (list->data);
254
255       width += child->requisition.width + path_bar->spacing;
256     }
257
258   if (width <= allocation_width)
259     {
260       first_button = g_list_last (path_bar->button_list);
261     }
262   else
263     {
264       gboolean reached_end = FALSE;
265       gint slider_space = 2 * (path_bar->spacing + path_bar->slider_width);
266
267       if (path_bar->first_scrolled_button)
268         first_button = path_bar->first_scrolled_button;
269       else
270         first_button = path_bar->button_list;
271       need_sliders = TRUE;
272       
273       /* To see how much space we have, and how many buttons we can display.
274        * We start at the first button, count forward until hit the new
275        * button, then count backwards.
276        */
277       /* Count down the path chain towards the end. */
278       width = GTK_WIDGET (first_button->data)->requisition.width;
279       list = first_button->prev;
280       while (list && !reached_end)
281         {
282           child = GTK_WIDGET (list->data);
283
284           if (width + child->requisition.width +
285               path_bar->spacing + slider_space > allocation_width)
286             reached_end = TRUE;
287           else
288             width += child->requisition.width + path_bar->spacing;
289
290           list = list->prev;
291         }
292
293       /* Finally, we walk up, seeing how many of the previous buttons we can
294        * add */
295       while (first_button->next && ! reached_end)
296         {
297           child = GTK_WIDGET (first_button->next->data);
298
299           if (width + child->requisition.width + path_bar->spacing + slider_space > allocation_width)
300             {
301               reached_end = TRUE;
302             }
303           else
304             {
305               width += child->requisition.width + path_bar->spacing;
306               first_button = first_button->next;
307             }
308         }
309     }
310
311   /* Now, we allocate space to the buttons */
312   child_allocation.y = allocation->y + border_width;
313   child_allocation.height = MAX (1, (gint) allocation->height - border_width * 2);
314
315   if (direction == GTK_TEXT_DIR_RTL)
316     {
317       child_allocation.x = allocation->x + allocation->width - border_width;
318       if (need_sliders)
319         {
320           child_allocation.x -= (path_bar->spacing + path_bar->slider_width);
321           up_slider_offset = allocation->width - border_width - path_bar->slider_width;
322         }
323     }
324   else
325     {
326       child_allocation.x = allocation->x + border_width;
327       if (need_sliders)
328         {
329           up_slider_offset = border_width;
330           child_allocation.x += (path_bar->spacing + path_bar->slider_width);
331         }
332     }
333
334   for (list = first_button; list; list = list->prev)
335     {
336       child = GTK_WIDGET (list->data);
337
338       child_allocation.width = child->requisition.width;
339       if (direction == GTK_TEXT_DIR_RTL)
340         child_allocation.x -= child_allocation.width;
341
342       /* Check to see if we've don't have any more space to allocate buttons */
343       if (need_sliders && direction == GTK_TEXT_DIR_RTL)
344         {
345           if (child_allocation.x - path_bar->spacing - path_bar->slider_width < widget->allocation.x + border_width)
346             break;
347         }
348       else if (need_sliders && direction == GTK_TEXT_DIR_LTR)
349         {
350           if (child_allocation.x + child_allocation.width + path_bar->spacing + path_bar->slider_width >
351               widget->allocation.x + border_width + allocation_width)
352             break;
353         }
354
355       gtk_widget_set_child_visible (list->data, TRUE);
356       gtk_widget_size_allocate (child, &child_allocation);
357
358       if (direction == GTK_TEXT_DIR_RTL)
359         {
360           child_allocation.x -= path_bar->spacing;
361           down_slider_offset = child_allocation.x - widget->allocation.x - path_bar->slider_width;
362         }
363       else
364         {
365           child_allocation.x += child_allocation.width + path_bar->spacing;
366           down_slider_offset = child_allocation.x - widget->allocation.x;
367         }
368     }
369   /* Now we go hide all the widgets that don't fit */
370   while (list)
371     {
372       gtk_widget_set_child_visible (list->data, FALSE);
373       list = list->prev;
374     }
375   for (list = first_button->next; list; list = list->next)
376     {
377       gtk_widget_set_child_visible (list->data, FALSE);
378     }
379
380   if (need_sliders)
381     {
382       child_allocation.width = path_bar->slider_width;
383       
384       child_allocation.x = up_slider_offset + allocation->x;
385       gtk_widget_size_allocate (path_bar->up_slider_button, &child_allocation);
386
387       child_allocation.x = down_slider_offset + allocation->x;
388       gtk_widget_size_allocate (path_bar->down_slider_button, &child_allocation);
389
390       gtk_widget_set_child_visible (path_bar->up_slider_button, TRUE);
391       gtk_widget_set_child_visible (path_bar->down_slider_button, TRUE);
392       gtk_widget_show_all (path_bar->up_slider_button);
393       gtk_widget_show_all (path_bar->down_slider_button);
394       gtk_path_bar_update_slider_buttons (path_bar);
395     }
396   else
397     {
398       gtk_widget_set_child_visible (path_bar->up_slider_button, FALSE);
399       gtk_widget_set_child_visible (path_bar->down_slider_button, FALSE);
400     }
401 }
402
403 static void
404  gtk_path_bar_direction_changed (GtkWidget *widget,
405                                  GtkTextDirection direction)
406 {
407   gtk_path_bar_update_slider_buttons (GTK_PATH_BAR (widget));
408
409   (* GTK_WIDGET_CLASS (gtk_path_bar_parent_class)->direction_changed) (widget, direction);
410 }
411
412 static void
413 gtk_path_bar_add (GtkContainer *container,
414                   GtkWidget    *widget)
415 {
416   gtk_widget_set_parent (widget, GTK_WIDGET (container));
417 }
418
419 static void
420 gtk_path_bar_remove (GtkContainer *container,
421                      GtkWidget    *widget)
422 {
423   GtkPathBar *path_bar;
424   GList *children;
425
426   path_bar = GTK_PATH_BAR (container);
427
428   children = path_bar->button_list;
429
430   while (children)
431     {
432       if (widget == children->data)
433         {
434           gboolean was_visible;
435
436           was_visible = GTK_WIDGET_VISIBLE (widget);
437           gtk_widget_unparent (widget);
438
439           path_bar->button_list = g_list_remove_link (path_bar->button_list, children);
440           g_list_free (children);
441
442           if (was_visible)
443             gtk_widget_queue_resize (GTK_WIDGET (container));
444           break;
445         }
446       
447       children = children->next;
448     }
449 }
450
451 static void
452 gtk_path_bar_forall (GtkContainer *container,
453                      gboolean      include_internals,
454                      GtkCallback   callback,
455                      gpointer      callback_data)
456 {
457   GtkPathBar *path_bar;
458   GList *children;
459
460   g_return_if_fail (callback != NULL);
461   path_bar = GTK_PATH_BAR (container);
462
463   children = path_bar->button_list;
464   while (children)
465     {
466       GtkWidget *child;
467       child = children->data;
468       children = children->next;
469
470       (* callback) (child, callback_data);
471     }
472
473   (* callback) (path_bar->up_slider_button, callback_data);
474   (* callback) (path_bar->down_slider_button, callback_data);
475 }
476
477 static void
478  gtk_path_bar_scroll_down (GtkWidget *button, GtkPathBar *path_bar)
479 {
480   GList *list;
481   GList *down_button = NULL;
482   GList *up_button = NULL;
483   gint space_available;
484   gint space_needed;
485   gint border_width;
486   GtkTextDirection direction;
487   
488   border_width = GTK_CONTAINER (path_bar)->border_width;
489   direction = gtk_widget_get_direction (GTK_WIDGET (path_bar));
490   
491   /* We find the button at the 'down' end that we have to make
492    * visible */
493   for (list = path_bar->button_list; list; list = list->next)
494     {
495       if (list->next && gtk_widget_get_child_visible (GTK_WIDGET (list->next->data)))
496         {
497           down_button = list;
498           break;
499         }
500     }
501   
502   /* Find the last visible button on the 'up' end
503    */
504   for (list = g_list_last (path_bar->button_list); list; list = list->prev)
505     {
506       if (gtk_widget_get_child_visible (GTK_WIDGET (list->data)))
507         {
508           up_button = list;
509           break;
510         }
511     }
512
513   space_needed = GTK_WIDGET (down_button->data)->allocation.width + path_bar->spacing;
514   if (direction == GTK_TEXT_DIR_RTL)
515     space_available = GTK_WIDGET (path_bar)->allocation.x + GTK_WIDGET (path_bar)->allocation.width;
516   else
517     space_available = (GTK_WIDGET (path_bar)->allocation.x + GTK_WIDGET (path_bar)->allocation.width - border_width) -
518       (path_bar->down_slider_button->allocation.x + path_bar->down_slider_button->allocation.width);
519
520   /* We have space_available extra space that's not being used.  We
521    * need space_needed space to make the button fit.  So we walk down
522    * from the end, removing buttons until we get all the space we
523    * need. */
524   while (space_available < space_needed)
525     {
526       space_available += GTK_WIDGET (up_button->data)->allocation.width + path_bar->spacing;
527       up_button = up_button->prev;
528       path_bar->first_scrolled_button = up_button;
529     }
530 }
531
532 static void
533  gtk_path_bar_scroll_up (GtkWidget *button, GtkPathBar *path_bar)
534 {
535   GList *list;
536
537   for (list = g_list_last (path_bar->button_list); list; list = list->prev)
538     {
539       if (list->prev && gtk_widget_get_child_visible (GTK_WIDGET (list->prev->data)))
540         {
541           path_bar->first_scrolled_button = list;
542           return;
543         }
544     }
545 }
546
547
548
549 /* Public functions. */
550 static void
551 gtk_path_bar_clear_buttons (GtkPathBar *path_bar)
552 {
553   while (path_bar->button_list != NULL)
554     {
555       gtk_container_remove (GTK_CONTAINER (path_bar), path_bar->button_list->data);
556     }
557   path_bar->first_scrolled_button = NULL;
558 }
559
560 static void
561 button_clicked_cb (GtkWidget *button,
562                    gpointer   data)
563 {
564   GtkWidget *path_bar;
565
566   path_bar = button->parent;
567   g_assert (path_bar);
568
569   g_signal_emit (path_bar, path_bar_signals [PATH_CLICKED], 0, (const char *) data);
570 }
571
572 static GtkWidget *
573 make_directory_button (const char  *dir_name,
574                        GtkFilePath *path,
575                        gboolean     current_dir)
576 {
577   GtkWidget *button, *label;
578       
579   button = gtk_toggle_button_new ();
580   if (current_dir)
581     {
582       /* Yay gsignal! */
583       g_signal_connect (G_OBJECT (button), "toggled",
584                         G_CALLBACK (gtk_toggle_button_set_active),
585                         GINT_TO_POINTER (TRUE));
586     }
587   else
588     {
589       gchar *str;
590
591       str = g_strdup (gtk_file_path_get_string (path));
592       g_signal_connect (button, "clicked", G_CALLBACK (button_clicked_cb), str);
593       g_object_weak_ref (G_OBJECT (button), (GWeakNotify) g_free, str);
594     }
595
596   label = gtk_label_new (NULL);
597
598   if (current_dir)
599     {
600       gchar *label_str;
601       
602       label_str = g_markup_printf_escaped ("<b>%s</b>", dir_name);
603       gtk_label_set_markup (GTK_LABEL (label), label_str);
604       g_free (label_str);
605
606       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
607     }
608   else
609     {
610       gtk_label_set_text (GTK_LABEL (label), dir_name);
611     }
612
613   gtk_container_add (GTK_CONTAINER (button), label);
614   gtk_widget_show_all (button);
615
616   return button;
617 }
618
619
620 void
621 gtk_path_bar_set_path (GtkPathBar         *path_bar,
622                        const GtkFilePath  *file_path,
623                        GtkFileSystem      *file_system,
624                        GError            **error)
625 {
626   gboolean valid = TRUE;
627   GtkFilePath *path;
628   gboolean first_directory = TRUE;
629
630   gtk_path_bar_clear_buttons (path_bar);
631   path = gtk_file_path_copy (file_path);
632
633   while (path != NULL)
634     {
635       GtkFilePath *parent_path = NULL;
636       GtkWidget *button;
637       const gchar *display_name;
638       GError *err = NULL;
639
640       valid = gtk_file_system_get_parent (file_system,
641                                           path,
642                                           &parent_path,
643                                           &err);
644       if (!valid)
645         {
646           g_propagate_error (error, err);
647           g_error_free (err);
648           gtk_file_path_free (path);
649           break;
650         }
651
652       if (parent_path)
653         {
654           GtkFileFolder *file_folder;
655           GtkFileInfo *file_info;
656
657           file_folder = gtk_file_system_get_folder (file_system, parent_path,
658                                                     GTK_FILE_INFO_DISPLAY_NAME, NULL);
659           file_info = gtk_file_folder_get_info (file_folder, path, NULL);
660           display_name = gtk_file_info_get_display_name (file_info);
661           button = make_directory_button (display_name, path, first_directory);
662           gtk_file_info_free (file_info);
663           /* FIXME: ask owen about mem management. gtk_file_folder_free (file_folder); */
664         }
665       else
666         {
667           /* We've reached the root node */
668           /* FIXME: gtk_file_system_get_root_display_name() or something */
669           button = make_directory_button (gtk_file_path_get_string (path),
670                                           path, first_directory);
671         }
672
673       gtk_container_add (GTK_CONTAINER (path_bar), button);
674       path_bar->button_list = g_list_prepend (path_bar->button_list, button);
675       gtk_file_path_free (path);
676
677       path = parent_path;
678       first_directory = FALSE;
679     }
680
681   path_bar->button_list = g_list_reverse (path_bar->button_list);
682 }