]> Pileus Git - ~andy/gtk/blob - gtk/gtkpathbar.c
f6996aa9910f41b636b029d441d0b367911acc43
[~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 <config.h>
21 #include <string.h>
22 #include "gtkpathbar.h"
23 #include "gtktogglebutton.h"
24 #include "gtkarrow.h"
25 #include "gtkimage.h"
26 #include "gtkintl.h"
27 #include "gtkicontheme.h"
28 #include "gtkiconfactory.h"
29 #include "gtklabel.h"
30 #include "gtkhbox.h"
31 #include "gtkmain.h"
32 #include "gtkmarshalers.h"
33
34 enum {
35   PATH_CLICKED,
36   LAST_SIGNAL
37 };
38
39 typedef enum {
40   NORMAL_BUTTON,
41   ROOT_BUTTON,
42   HOME_BUTTON,
43   DESKTOP_BUTTON
44 } ButtonType;
45
46 #define BUTTON_DATA(x) ((ButtonData *)(x))
47
48 static guint path_bar_signals [LAST_SIGNAL] = { 0 };
49
50 /* Icon size for if we can't get it from the theme */
51 #define FALLBACK_ICON_SIZE 20
52
53 typedef struct _ButtonData ButtonData;
54
55 struct _ButtonData
56 {
57   GtkWidget *button;
58   ButtonType type;
59   char *dir_name;
60   GtkFilePath *path;
61   GtkWidget *image;
62   GtkWidget *label;
63   gboolean ignore_changes;
64 };
65
66 G_DEFINE_TYPE (GtkPathBar,
67                gtk_path_bar,
68                GTK_TYPE_CONTAINER);
69
70 static void gtk_path_bar_finalize                 (GObject          *object);
71 static void gtk_path_bar_dispose                  (GObject          *object);
72 static void gtk_path_bar_size_request             (GtkWidget        *widget,
73                                                    GtkRequisition   *requisition);
74 static void gtk_path_bar_size_allocate            (GtkWidget        *widget,
75                                                    GtkAllocation    *allocation);
76 static void gtk_path_bar_direction_changed        (GtkWidget        *widget,
77                                                    GtkTextDirection  direction);
78 static void gtk_path_bar_add                      (GtkContainer     *container,
79                                                    GtkWidget        *widget);
80 static void gtk_path_bar_remove                   (GtkContainer     *container,
81                                                    GtkWidget        *widget);
82 static void gtk_path_bar_forall                   (GtkContainer     *container,
83                                                    gboolean          include_internals,
84                                                    GtkCallback       callback,
85                                                    gpointer          callback_data);
86 static void gtk_path_bar_scroll_up                (GtkWidget        *button,
87                                                    GtkPathBar       *path_bar);
88 static void gtk_path_bar_scroll_down              (GtkWidget        *button,
89                                                    GtkPathBar       *path_bar);
90 static void gtk_path_bar_style_set                (GtkWidget        *widget,
91                                                    GtkStyle         *previous_style);
92 static void gtk_path_bar_screen_changed           (GtkWidget        *widget,
93                                                    GdkScreen        *previous_screen);
94 static void gtk_path_bar_check_icon_theme         (GtkPathBar       *path_bar);
95 static void gtk_path_bar_update_button_appearance (GtkPathBar       *path_bar,
96                                                    ButtonData       *button_data,
97                                                    gboolean          current_dir);
98
99 static GtkWidget *
100 get_slider_button (GtkPathBar *path_bar)
101 {
102   GtkWidget *button;
103
104   gtk_widget_push_composite_child ();
105
106   button = gtk_button_new ();
107   gtk_container_add (GTK_CONTAINER (button), gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_OUT));
108   gtk_container_add (GTK_CONTAINER (path_bar), button);
109   gtk_widget_show_all (button);
110
111   gtk_widget_pop_composite_child ();
112
113   return button;
114 }
115
116 static void
117 gtk_path_bar_init (GtkPathBar *path_bar)
118 {
119   GTK_WIDGET_SET_FLAGS (path_bar, GTK_NO_WINDOW);
120   gtk_widget_set_redraw_on_allocate (GTK_WIDGET (path_bar), FALSE);
121
122   path_bar->spacing = 3;
123   path_bar->up_slider_button = get_slider_button (path_bar);
124   path_bar->down_slider_button = get_slider_button (path_bar);
125   path_bar->icon_size = FALLBACK_ICON_SIZE;
126
127   g_signal_connect (path_bar->up_slider_button, "clicked", G_CALLBACK (gtk_path_bar_scroll_up), path_bar);
128   g_signal_connect (path_bar->down_slider_button, "clicked", G_CALLBACK (gtk_path_bar_scroll_down), path_bar);
129 }
130
131 static void
132 gtk_path_bar_class_init (GtkPathBarClass *path_bar_class)
133 {
134   GObjectClass *gobject_class;
135   GtkObjectClass *object_class;
136   GtkWidgetClass *widget_class;
137   GtkContainerClass *container_class;
138
139   gobject_class = (GObjectClass *) path_bar_class;
140   object_class = (GtkObjectClass *) path_bar_class;
141   widget_class = (GtkWidgetClass *) path_bar_class;
142   container_class = (GtkContainerClass *) path_bar_class;
143
144   gobject_class->finalize = gtk_path_bar_finalize;
145   gobject_class->dispose = gtk_path_bar_dispose;
146
147   widget_class->size_request = gtk_path_bar_size_request;
148   widget_class->size_allocate = gtk_path_bar_size_allocate;
149   widget_class->direction_changed = gtk_path_bar_direction_changed;
150   widget_class->style_set = gtk_path_bar_style_set;
151   widget_class->screen_changed = gtk_path_bar_screen_changed;
152
153   container_class->add = gtk_path_bar_add;
154   container_class->forall = gtk_path_bar_forall;
155   container_class->remove = gtk_path_bar_remove;
156   /* FIXME: */
157   /*  container_class->child_type = gtk_path_bar_child_type;*/
158
159   path_bar_signals [PATH_CLICKED] =
160     g_signal_new ("path_clicked",
161                   G_OBJECT_CLASS_TYPE (object_class),
162                   G_SIGNAL_RUN_FIRST,
163                   G_STRUCT_OFFSET (GtkPathBarClass, path_clicked),
164                   NULL, NULL,
165                   _gtk_marshal_VOID__POINTER,
166                   G_TYPE_NONE, 1,
167                   G_TYPE_POINTER);
168 }
169
170
171 static void
172 gtk_path_bar_finalize (GObject *object)
173 {
174   GtkPathBar *path_bar;
175
176   path_bar = GTK_PATH_BAR (object);
177   g_list_free (path_bar->button_list);
178   if (path_bar->root_path)
179     gtk_file_path_free (path_bar->root_path);
180   if (path_bar->home_path)
181     gtk_file_path_free (path_bar->home_path);
182   if (path_bar->desktop_path)
183     gtk_file_path_free (path_bar->desktop_path);
184
185   if (path_bar->root_icon)
186     g_object_unref (path_bar->root_icon);
187   if (path_bar->home_icon)
188     g_object_unref (path_bar->home_icon);
189   if (path_bar->desktop_icon)
190     g_object_unref (path_bar->desktop_icon);
191
192   if (path_bar->file_system)
193     g_object_unref (path_bar->file_system);
194
195   G_OBJECT_CLASS (gtk_path_bar_parent_class)->finalize (object);
196 }
197
198 /* Removes the settings signal handler.  It's safe to call multiple times */
199 static void
200 remove_settings_signal (GtkPathBar *path_bar,
201                         GdkScreen  *screen)
202 {
203   if (path_bar->settings_signal_id)
204     {
205       GtkSettings *settings;
206
207       settings = gtk_settings_get_for_screen (screen);
208       g_signal_handler_disconnect (settings,
209                                    path_bar->settings_signal_id);
210       path_bar->settings_signal_id = 0;
211     }
212 }
213
214 static void
215 gtk_path_bar_dispose (GObject *object)
216 {
217   remove_settings_signal (GTK_PATH_BAR (object),
218                           gtk_widget_get_screen (GTK_WIDGET (object)));
219
220   G_OBJECT_CLASS (gtk_path_bar_parent_class)->dispose (object);
221 }
222
223 /* Size requisition:
224  * 
225  * Ideally, our size is determined by another widget, and we are just filling
226  * available space.
227  */
228 static void
229 gtk_path_bar_size_request (GtkWidget      *widget,
230                            GtkRequisition *requisition)
231 {
232   ButtonData *button_data;
233   GtkPathBar *path_bar;
234   GtkRequisition child_requisition;
235   GList *list;
236
237   path_bar = GTK_PATH_BAR (widget);
238
239   requisition->width = 0;
240   requisition->height = 0;
241
242   for (list = path_bar->button_list; list; list = list->next)
243     {
244       button_data = BUTTON_DATA (list->data);
245       gtk_widget_size_request (button_data->button, &child_requisition);
246       requisition->width = MAX (child_requisition.width, requisition->width);
247       requisition->height = MAX (child_requisition.height, requisition->height);
248     }
249
250   /* Add space for slider, if we have more than one path */
251   /* Theoretically, the slider could be bigger than the other button.  But we're
252    * not going to worry about that now.
253    */
254   path_bar->slider_width = requisition->height / 2 + 5;
255   if (path_bar->button_list && path_bar->button_list->next != NULL)
256     requisition->width += (path_bar->spacing + path_bar->slider_width) * 2;
257
258   gtk_widget_size_request (path_bar->up_slider_button, &child_requisition);
259   gtk_widget_size_request (path_bar->down_slider_button, &child_requisition);
260
261   requisition->width += GTK_CONTAINER (widget)->border_width * 2;
262   requisition->height += GTK_CONTAINER (widget)->border_width * 2;
263
264   widget->requisition = *requisition;
265 }
266
267 static void
268 gtk_path_bar_update_slider_buttons (GtkPathBar *path_bar)
269 {
270   GtkTextDirection direction;
271
272   direction = gtk_widget_get_direction (GTK_WIDGET (path_bar));
273   if (direction == GTK_TEXT_DIR_RTL)
274     {
275       GtkWidget *arrow;
276
277       arrow = gtk_bin_get_child (GTK_BIN (path_bar->up_slider_button));
278       g_object_set (arrow, "arrow_type", GTK_ARROW_RIGHT, NULL);
279
280       arrow = gtk_bin_get_child (GTK_BIN (path_bar->down_slider_button));
281       g_object_set (arrow, "arrow_type", GTK_ARROW_LEFT, NULL);
282     }
283   else
284     {
285       GtkWidget *arrow;
286
287       arrow = gtk_bin_get_child (GTK_BIN (path_bar->up_slider_button));
288       g_object_set (arrow, "arrow_type", GTK_ARROW_LEFT, NULL);
289
290       arrow = gtk_bin_get_child (GTK_BIN (path_bar->down_slider_button));
291       g_object_set (arrow, "arrow_type", GTK_ARROW_RIGHT, NULL);
292     }
293
294   if (path_bar->button_list)
295     {
296       GtkWidget *button;
297
298       button = BUTTON_DATA (path_bar->button_list->data)->button;
299       if (gtk_widget_get_child_visible (button))
300         gtk_widget_set_sensitive (path_bar->down_slider_button, FALSE);
301       else
302         gtk_widget_set_sensitive (path_bar->down_slider_button, TRUE);
303
304       button = BUTTON_DATA (g_list_last (path_bar->button_list)->data)->button;
305       if (gtk_widget_get_child_visible (button))
306         gtk_widget_set_sensitive (path_bar->up_slider_button, FALSE);
307       else
308         gtk_widget_set_sensitive (path_bar->up_slider_button, TRUE);
309     }
310 }
311
312 /* This is a tad complicated
313  */
314 static void
315 gtk_path_bar_size_allocate (GtkWidget     *widget,
316                             GtkAllocation *allocation)
317 {
318   GtkWidget *child;
319   GtkPathBar *path_bar = GTK_PATH_BAR (widget);
320   GtkTextDirection direction;
321   GtkAllocation child_allocation;
322   GList *list, *first_button;
323   gint width;
324   gint allocation_width;
325   gint border_width;
326   gboolean need_sliders = FALSE;
327   gint up_slider_offset = 0;
328   gint down_slider_offset = 0;
329
330   widget->allocation = *allocation;
331
332   /* No path is set; we don't have to allocate anything. */
333   if (path_bar->button_list == NULL)
334     return;
335
336   direction = gtk_widget_get_direction (widget);
337   border_width = (gint) GTK_CONTAINER (path_bar)->border_width;
338   allocation_width = allocation->width - 2 * border_width;
339
340   /* First, we check to see if we need the scrollbars. */
341   width = BUTTON_DATA (path_bar->button_list->data)->button->requisition.width;
342   for (list = path_bar->button_list->next; list; list = list->next)
343     {
344       child = BUTTON_DATA (list->data)->button;
345
346       width += child->requisition.width + path_bar->spacing;
347     }
348
349   if (width <= allocation_width)
350     {
351       first_button = g_list_last (path_bar->button_list);
352     }
353   else
354     {
355       gboolean reached_end = FALSE;
356       gint slider_space = 2 * (path_bar->spacing + path_bar->slider_width);
357
358       if (path_bar->first_scrolled_button)
359         first_button = path_bar->first_scrolled_button;
360       else
361         first_button = path_bar->button_list;
362       need_sliders = TRUE;
363       
364       /* To see how much space we have, and how many buttons we can display.
365        * We start at the first button, count forward until hit the new
366        * button, then count backwards.
367        */
368       /* Count down the path chain towards the end. */
369       width = BUTTON_DATA (first_button->data)->button->requisition.width;
370       list = first_button->prev;
371       while (list && !reached_end)
372         {
373           child = BUTTON_DATA (list->data)->button;
374
375           if (width + child->requisition.width +
376               path_bar->spacing + slider_space > allocation_width)
377             reached_end = TRUE;
378           else
379             width += child->requisition.width + path_bar->spacing;
380
381           list = list->prev;
382         }
383
384       /* Finally, we walk up, seeing how many of the previous buttons we can
385        * add */
386       while (first_button->next && ! reached_end)
387         {
388           child = BUTTON_DATA (first_button->next->data)->button;
389
390           if (width + child->requisition.width + path_bar->spacing + slider_space > allocation_width)
391             {
392               reached_end = TRUE;
393             }
394           else
395             {
396               width += child->requisition.width + path_bar->spacing;
397               first_button = first_button->next;
398             }
399         }
400     }
401
402   /* Now, we allocate space to the buttons */
403   child_allocation.y = allocation->y + border_width;
404   child_allocation.height = MAX (1, (gint) allocation->height - border_width * 2);
405
406   if (direction == GTK_TEXT_DIR_RTL)
407     {
408       child_allocation.x = allocation->x + allocation->width - border_width;
409       if (need_sliders)
410         {
411           child_allocation.x -= (path_bar->spacing + path_bar->slider_width);
412           up_slider_offset = allocation->width - border_width - path_bar->slider_width;
413         }
414     }
415   else
416     {
417       child_allocation.x = allocation->x + border_width;
418       if (need_sliders)
419         {
420           up_slider_offset = border_width;
421           child_allocation.x += (path_bar->spacing + path_bar->slider_width);
422         }
423     }
424
425   for (list = first_button; list; list = list->prev)
426     {
427       child = BUTTON_DATA (list->data)->button;
428
429       child_allocation.width = child->requisition.width;
430       if (direction == GTK_TEXT_DIR_RTL)
431         child_allocation.x -= child_allocation.width;
432
433       /* Check to see if we've don't have any more space to allocate buttons */
434       if (need_sliders && direction == GTK_TEXT_DIR_RTL)
435         {
436           if (child_allocation.x - path_bar->spacing - path_bar->slider_width < widget->allocation.x + border_width)
437             break;
438         }
439       else if (need_sliders && direction == GTK_TEXT_DIR_LTR)
440         {
441           if (child_allocation.x + child_allocation.width + path_bar->spacing + path_bar->slider_width >
442               widget->allocation.x + border_width + allocation_width)
443             break;
444         }
445
446       gtk_widget_set_child_visible (BUTTON_DATA (list->data)->button, TRUE);
447       gtk_widget_size_allocate (child, &child_allocation);
448
449       if (direction == GTK_TEXT_DIR_RTL)
450         {
451           child_allocation.x -= path_bar->spacing;
452           down_slider_offset = child_allocation.x - widget->allocation.x - path_bar->slider_width;
453         }
454       else
455         {
456           child_allocation.x += child_allocation.width + path_bar->spacing;
457           down_slider_offset = child_allocation.x - widget->allocation.x;
458         }
459     }
460   /* Now we go hide all the widgets that don't fit */
461   while (list)
462     {
463       gtk_widget_set_child_visible (BUTTON_DATA (list->data)->button, FALSE);
464       list = list->prev;
465     }
466   for (list = first_button->next; list; list = list->next)
467     {
468       gtk_widget_set_child_visible (BUTTON_DATA (list->data)->button, FALSE);
469     }
470
471   if (need_sliders)
472     {
473       child_allocation.width = path_bar->slider_width;
474       
475       child_allocation.x = up_slider_offset + allocation->x;
476       gtk_widget_size_allocate (path_bar->up_slider_button, &child_allocation);
477
478       child_allocation.x = down_slider_offset + allocation->x;
479       gtk_widget_size_allocate (path_bar->down_slider_button, &child_allocation);
480
481       gtk_widget_set_child_visible (path_bar->up_slider_button, TRUE);
482       gtk_widget_set_child_visible (path_bar->down_slider_button, TRUE);
483       gtk_widget_show_all (path_bar->up_slider_button);
484       gtk_widget_show_all (path_bar->down_slider_button);
485       gtk_path_bar_update_slider_buttons (path_bar);
486     }
487   else
488     {
489       gtk_widget_set_child_visible (path_bar->up_slider_button, FALSE);
490       gtk_widget_set_child_visible (path_bar->down_slider_button, FALSE);
491     }
492 }
493
494 static void
495  gtk_path_bar_direction_changed (GtkWidget *widget,
496                                  GtkTextDirection direction)
497 {
498   gtk_path_bar_update_slider_buttons (GTK_PATH_BAR (widget));
499
500   (* GTK_WIDGET_CLASS (gtk_path_bar_parent_class)->direction_changed) (widget, direction);
501 }
502
503 static void
504 gtk_path_bar_style_set (GtkWidget *widget,
505                         GtkStyle  *previous_style)
506 {
507   if (GTK_WIDGET_CLASS (gtk_path_bar_parent_class)->style_set)
508     GTK_WIDGET_CLASS (gtk_path_bar_parent_class)->style_set (widget, previous_style);
509
510   gtk_path_bar_check_icon_theme (GTK_PATH_BAR (widget));
511 }
512
513 static void
514 gtk_path_bar_screen_changed (GtkWidget *widget,
515                              GdkScreen *previous_screen)
516 {
517   if (GTK_WIDGET_CLASS (gtk_path_bar_parent_class)->screen_changed)
518     GTK_WIDGET_CLASS (gtk_path_bar_parent_class)->screen_changed (widget, previous_screen);
519
520   /* We might nave a new settings, so we remove the old one */
521   if (previous_screen)
522     remove_settings_signal (GTK_PATH_BAR (widget), previous_screen);
523
524   gtk_path_bar_check_icon_theme (GTK_PATH_BAR (widget));
525 }
526
527 static void
528 gtk_path_bar_add (GtkContainer *container,
529                   GtkWidget    *widget)
530 {
531   gtk_widget_set_parent (widget, GTK_WIDGET (container));
532 }
533
534 static void
535 gtk_path_bar_remove (GtkContainer *container,
536                      GtkWidget    *widget)
537 {
538   GtkPathBar *path_bar;
539   GList *children;
540
541   path_bar = GTK_PATH_BAR (container);
542
543   children = path_bar->button_list;
544
545   while (children)
546     {
547       if (widget == BUTTON_DATA (children->data)->button)
548         {
549           gboolean was_visible;
550
551           was_visible = GTK_WIDGET_VISIBLE (widget);
552           gtk_widget_unparent (widget);
553
554           path_bar->button_list = g_list_remove_link (path_bar->button_list, children);
555           g_list_free (children);
556
557           if (was_visible)
558             gtk_widget_queue_resize (GTK_WIDGET (container));
559           break;
560         }
561       
562       children = children->next;
563     }
564 }
565
566 static void
567 gtk_path_bar_forall (GtkContainer *container,
568                      gboolean      include_internals,
569                      GtkCallback   callback,
570                      gpointer      callback_data)
571 {
572   GtkPathBar *path_bar;
573   GList *children;
574
575   g_return_if_fail (callback != NULL);
576   path_bar = GTK_PATH_BAR (container);
577
578   children = path_bar->button_list;
579   while (children)
580     {
581       GtkWidget *child;
582       child = BUTTON_DATA (children->data)->button;
583       children = children->next;
584
585       (* callback) (child, callback_data);
586     }
587
588   (* callback) (path_bar->up_slider_button, callback_data);
589   (* callback) (path_bar->down_slider_button, callback_data);
590 }
591
592 static void
593 gtk_path_bar_scroll_down (GtkWidget *button, GtkPathBar *path_bar)
594 {
595   GList *list;
596   GList *down_button = NULL;
597   GList *up_button = NULL;
598   gint space_available;
599   gint space_needed;
600   gint border_width;
601   GtkTextDirection direction;
602   
603   border_width = GTK_CONTAINER (path_bar)->border_width;
604   direction = gtk_widget_get_direction (GTK_WIDGET (path_bar));
605   
606   /* We find the button at the 'down' end that we have to make
607    * visible */
608   for (list = path_bar->button_list; list; list = list->next)
609     {
610       if (list->next && gtk_widget_get_child_visible (BUTTON_DATA (list->next->data)->button))
611         {
612           down_button = list;
613           break;
614         }
615     }
616   
617   /* Find the last visible button on the 'up' end
618    */
619   for (list = g_list_last (path_bar->button_list); list; list = list->prev)
620     {
621       if (gtk_widget_get_child_visible (BUTTON_DATA (list->data)->button))
622         {
623           up_button = list;
624           break;
625         }
626     }
627
628   space_needed = BUTTON_DATA (down_button->data)->button->allocation.width + path_bar->spacing;
629   if (direction == GTK_TEXT_DIR_RTL)
630     space_available = GTK_WIDGET (path_bar)->allocation.x + GTK_WIDGET (path_bar)->allocation.width;
631   else
632     space_available = (GTK_WIDGET (path_bar)->allocation.x + GTK_WIDGET (path_bar)->allocation.width - border_width) -
633       (path_bar->down_slider_button->allocation.x + path_bar->down_slider_button->allocation.width);
634
635   /* We have space_available extra space that's not being used.  We
636    * need space_needed space to make the button fit.  So we walk down
637    * from the end, removing buttons until we get all the space we
638    * need. */
639   while (space_available < space_needed)
640     {
641       space_available += BUTTON_DATA (up_button->data)->button->allocation.width + path_bar->spacing;
642       up_button = up_button->prev;
643       path_bar->first_scrolled_button = up_button;
644     }
645 }
646
647 static void
648 gtk_path_bar_scroll_up (GtkWidget *button, GtkPathBar *path_bar)
649 {
650   GList *list;
651
652   for (list = g_list_last (path_bar->button_list); list; list = list->prev)
653     {
654       if (list->prev && gtk_widget_get_child_visible (BUTTON_DATA (list->prev->data)->button))
655         {
656           path_bar->first_scrolled_button = list;
657           return;
658         }
659     }
660 }
661
662 /* Changes the icons wherever it is needed */
663 static void
664 reload_icons (GtkPathBar *path_bar)
665 {
666   GList *list;
667
668   if (path_bar->root_icon)
669     {
670       g_object_unref (path_bar->root_icon);
671       path_bar->root_icon = NULL;
672     }
673   if (path_bar->home_icon)
674     {
675       g_object_unref (path_bar->home_icon);
676       path_bar->home_icon = NULL;
677     }
678   if (path_bar->desktop_icon)
679     {
680       g_object_unref (path_bar->desktop_icon);
681       path_bar->desktop_icon = NULL;
682     }
683
684   for (list = path_bar->button_list; list; list = list->next)
685     {
686       ButtonData *button_data;
687       gboolean current_dir;
688
689       button_data = BUTTON_DATA (list->data);
690       if (button_data->type != NORMAL_BUTTON)
691         {
692           current_dir = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button_data->button));
693           gtk_path_bar_update_button_appearance (path_bar, button_data, current_dir);
694         }
695     }
696   
697 }
698
699 static void
700 change_icon_theme (GtkPathBar *path_bar)
701 {
702   GtkSettings *settings;
703   gint width, height;
704
705   settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (path_bar)));
706
707   if (gtk_icon_size_lookup_for_settings (settings, GTK_ICON_SIZE_BUTTON, &width, &height))
708     path_bar->icon_size = MAX (width, height);
709   else
710     path_bar->icon_size = FALLBACK_ICON_SIZE;
711
712   reload_icons (path_bar);
713 }
714 /* Callback used when a GtkSettings value changes */
715 static void
716 settings_notify_cb (GObject    *object,
717                     GParamSpec *pspec,
718                     GtkPathBar *path_bar)
719 {
720   const char *name;
721
722   name = g_param_spec_get_name (pspec);
723
724   if (! strcmp (name, "gtk-icon-theme-name") ||
725       ! strcmp (name, "gtk-icon-sizes"))
726     change_icon_theme (path_bar);
727 }
728
729 static void
730 gtk_path_bar_check_icon_theme (GtkPathBar *path_bar)
731 {
732   GtkSettings *settings;
733
734   if (path_bar->settings_signal_id)
735     return;
736
737   settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (path_bar)));
738   path_bar->settings_signal_id = g_signal_connect (settings, "notify", G_CALLBACK (settings_notify_cb), path_bar);
739
740   change_icon_theme (path_bar);
741 }
742
743 /* Public functions and their helpers */
744 static void
745 gtk_path_bar_clear_buttons (GtkPathBar *path_bar)
746 {
747   while (path_bar->button_list != NULL)
748     {
749       gtk_container_remove (GTK_CONTAINER (path_bar), BUTTON_DATA (path_bar->button_list->data)->button);
750     }
751   path_bar->first_scrolled_button = NULL;
752 }
753
754 static void
755 button_clicked_cb (GtkWidget *button,
756                    gpointer   data)
757 {
758   ButtonData *button_data;
759   GtkWidget *path_bar;
760
761   button_data = BUTTON_DATA (data);
762   if (button_data->ignore_changes)
763     return;
764
765   path_bar = button->parent;
766   g_assert (GTK_IS_PATH_BAR (path_bar));
767   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
768
769   g_signal_emit (path_bar, path_bar_signals [PATH_CLICKED], 0, button_data->path);
770 }
771
772 static GdkPixbuf *
773 get_button_image (GtkPathBar *path_bar,
774                   ButtonType  button_type)
775 {
776   GtkFileSystemVolume *volume;
777
778   switch (button_type)
779     {
780     case ROOT_BUTTON:
781
782       if (path_bar->root_icon != NULL)
783         return path_bar->root_icon;
784       
785       volume = gtk_file_system_get_volume_for_path (path_bar->file_system, path_bar->root_path);
786       if (volume == NULL)
787         return NULL;
788
789       path_bar->root_icon = gtk_file_system_volume_render_icon (path_bar->file_system,
790                                                                 volume,
791                                                                 GTK_WIDGET (path_bar),
792                                                                 path_bar->icon_size,
793                                                                 NULL);
794       gtk_file_system_volume_free (path_bar->file_system, volume);
795
796       return path_bar->root_icon;
797     case HOME_BUTTON:
798       if (path_bar->home_icon != NULL)
799         return path_bar->home_icon;
800
801       path_bar->home_icon = gtk_file_system_render_icon (path_bar->file_system,
802                                                          path_bar->home_path,
803                                                          GTK_WIDGET (path_bar),
804                                                          path_bar->icon_size,
805                                                          NULL);
806       return path_bar->home_icon;
807     case DESKTOP_BUTTON:
808       if (path_bar->desktop_icon != NULL)
809         return path_bar->desktop_icon;
810
811       path_bar->desktop_icon = gtk_file_system_render_icon (path_bar->file_system,
812                                                             path_bar->desktop_path,
813                                                             GTK_WIDGET (path_bar),
814                                                             path_bar->icon_size,
815                                                             NULL);
816       return path_bar->desktop_icon;
817     default:
818       return NULL;
819     }
820   
821   return NULL;
822 }
823
824 static void
825 button_data_free (ButtonData *button_data)
826 {
827   gtk_file_path_free (button_data->path);
828   g_free (button_data->dir_name);
829   g_free (button_data);
830 }
831
832 static void
833 gtk_path_bar_update_button_appearance (GtkPathBar *path_bar,
834                                        ButtonData *button_data,
835                                        gboolean    current_dir)
836 {
837   const gchar *dir_name;
838
839   if (button_data->type == HOME_BUTTON)
840     dir_name = _("Home");
841   else if (button_data->type == DESKTOP_BUTTON)
842     dir_name = _("Desktop");
843   else
844     dir_name = button_data->dir_name;
845
846   if (button_data->label != NULL)
847     {
848       if (current_dir)
849         {
850           char *markup;
851
852           markup = g_markup_printf_escaped ("<b>%s</b>", dir_name);
853           gtk_label_set_markup (GTK_LABEL (button_data->label), markup);
854           g_free (markup);
855         }
856       else
857         {
858           gtk_label_set_text (GTK_LABEL (button_data->label), dir_name);
859         }
860     }
861
862   if (button_data->image != NULL)
863     {
864       GdkPixbuf *pixbuf;
865       pixbuf = get_button_image (path_bar, button_data->type);
866       gtk_image_set_from_pixbuf (GTK_IMAGE (button_data->image), pixbuf);
867     }
868
869   if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button_data->button)) != current_dir)
870     {
871       button_data->ignore_changes = TRUE;
872       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button_data->button), current_dir);
873       button_data->ignore_changes = FALSE;
874     }
875 }
876
877 static ButtonType
878 find_button_type (GtkPathBar  *path_bar,
879                   GtkFilePath *path)
880 {
881   if (! gtk_file_path_compare (path, path_bar->root_path))
882     return ROOT_BUTTON;
883   if (! gtk_file_path_compare (path, path_bar->home_path))
884     return HOME_BUTTON;
885   if (! gtk_file_path_compare (path, path_bar->desktop_path))
886     return DESKTOP_BUTTON;
887
888  return NORMAL_BUTTON;
889 }
890
891 static ButtonData *
892 make_directory_button (GtkPathBar  *path_bar,
893                        const char  *dir_name,
894                        GtkFilePath *path,
895                        gboolean     current_dir)
896 {
897   GtkWidget *child = NULL;
898   ButtonData *button_data;
899
900   /* Is it a special button? */
901   button_data = g_new0 (ButtonData, 1);
902
903   button_data->type = find_button_type (path_bar, path);
904   button_data->button = gtk_toggle_button_new ();
905
906   switch (button_data->type)
907     {
908     case ROOT_BUTTON:
909       button_data->image = gtk_image_new ();
910       child = button_data->image;
911       button_data->label = NULL;
912       break;
913     case HOME_BUTTON:
914     case DESKTOP_BUTTON:
915       button_data->image = gtk_image_new ();
916       button_data->label = gtk_label_new (NULL);
917       child = gtk_hbox_new (FALSE, 2);
918       gtk_box_pack_start (GTK_BOX (child), button_data->image, FALSE, FALSE, 0);
919       gtk_box_pack_start (GTK_BOX (child), button_data->label, FALSE, FALSE, 0);
920       break;
921     case NORMAL_BUTTON:
922     default:
923       button_data->label = gtk_label_new (NULL);
924       child = button_data->label;
925       button_data->image = NULL;
926     }
927
928   button_data->dir_name = g_strdup (dir_name);
929   button_data->path = gtk_file_path_new_dup (gtk_file_path_get_string (path));
930                           
931   gtk_container_add (GTK_CONTAINER (button_data->button), child);
932   gtk_widget_show_all (button_data->button);
933
934   gtk_path_bar_update_button_appearance (path_bar, button_data, current_dir);
935
936   g_signal_connect (button_data->button, "clicked",
937                     G_CALLBACK (button_clicked_cb),
938                     button_data);
939   g_object_weak_ref (G_OBJECT (button_data->button),
940                      (GWeakNotify) button_data_free, button_data);
941
942   return button_data;
943 }
944
945 static gboolean
946 gtk_path_bar_check_parent_path (GtkPathBar         *path_bar,
947                                 const GtkFilePath  *file_path,
948                                 GtkFileSystem      *file_system)
949 {
950   GList *list;
951   GList *current_path = NULL;
952
953   for (list = path_bar->button_list; list; list = list->next)
954     {
955       ButtonData *button_data;
956
957       button_data = list->data;
958       if (! gtk_file_path_compare (file_path, button_data->path))
959         {
960           current_path = list;
961           break;
962         }
963     }
964
965   if (current_path)
966     {
967       for (list = path_bar->button_list; list; list = list->next)
968         {
969           gtk_path_bar_update_button_appearance (path_bar,
970                                                  BUTTON_DATA (list->data),
971                                                  (list == current_path) ? TRUE : FALSE);
972         }
973       return TRUE;
974     }
975   return FALSE;
976 }
977
978 gboolean
979 _gtk_path_bar_set_path (GtkPathBar         *path_bar,
980                         const GtkFilePath  *file_path,
981                         GError            **error)
982 {
983   GtkFilePath *path;
984   gboolean first_directory = TRUE;
985   gboolean result;
986   GList *new_buttons = NULL;
987
988   g_return_val_if_fail (GTK_IS_PATH_BAR (path_bar), FALSE);
989   g_return_val_if_fail (file_path != NULL, FALSE);
990
991   result = TRUE;
992
993   /* Check whether the new path is already present in the pathbar as buttons.
994    * This could be a parent directory or a previous selected subdirectory.
995    */
996   if (gtk_path_bar_check_parent_path (path_bar, file_path, path_bar->file_system))
997     return TRUE;
998
999   path = gtk_file_path_copy (file_path);
1000
1001   gtk_widget_push_composite_child ();
1002
1003   while (path != NULL)
1004     {
1005       GtkFilePath *parent_path = NULL;
1006       ButtonData *button_data;
1007       const gchar *display_name;
1008       GtkFileFolder *file_folder;
1009       GtkFileInfo *file_info;
1010       gboolean valid;
1011
1012       valid = gtk_file_system_get_parent (path_bar->file_system,
1013                                           path,
1014                                           &parent_path,
1015                                           error);
1016       if (!valid)
1017         {
1018           result = FALSE;
1019           gtk_file_path_free (path);
1020           break;
1021         }
1022
1023       file_folder = gtk_file_system_get_folder (path_bar->file_system,
1024                                                 parent_path ? parent_path : path,
1025                                                 GTK_FILE_INFO_DISPLAY_NAME,
1026                                                 NULL);
1027       file_info = gtk_file_folder_get_info (file_folder, path, error);
1028       g_object_unref (file_folder);
1029
1030       if (!file_info)
1031         {
1032           result = FALSE;
1033
1034           gtk_file_path_free (parent_path);
1035           gtk_file_path_free (path);
1036           break;
1037         }
1038
1039       display_name = gtk_file_info_get_display_name (file_info);
1040
1041       button_data = make_directory_button (path_bar, display_name, path, first_directory);
1042       gtk_file_info_free (file_info);
1043       gtk_file_path_free (path);
1044
1045       new_buttons = g_list_prepend (new_buttons, button_data);
1046
1047       if (button_data->type != NORMAL_BUTTON)
1048         {
1049           gtk_file_path_free (parent_path);
1050           break;
1051         }
1052
1053       path = parent_path;
1054       first_directory = FALSE;
1055     }
1056
1057   if (result)
1058     {
1059       GList *l;
1060
1061       gtk_path_bar_clear_buttons (path_bar);
1062       path_bar->button_list = g_list_reverse (new_buttons);
1063
1064       for (l = path_bar->button_list; l; l = l->next)
1065         {
1066           GtkWidget *button = BUTTON_DATA (l->data)->button;
1067           gtk_container_add (GTK_CONTAINER (path_bar), button);
1068         }
1069     }
1070   else
1071     {
1072       GList *l;
1073
1074       for (l = new_buttons; l; l = l->next)
1075         {
1076           GtkWidget *button = BUTTON_DATA (l->data)->button;
1077           gtk_widget_destroy (button);
1078           gtk_widget_unref (button);
1079         }
1080
1081       g_list_free (new_buttons);
1082     }
1083
1084   gtk_widget_pop_composite_child ();
1085
1086   return result;
1087 }
1088
1089
1090 /* FIXME: This should be a construct-only property */
1091 void
1092 _gtk_path_bar_set_file_system (GtkPathBar    *path_bar,
1093                                GtkFileSystem *file_system)
1094 {
1095   const char *home;
1096   char *desktop;
1097
1098   g_return_if_fail (GTK_IS_PATH_BAR (path_bar));
1099
1100   g_assert (path_bar->file_system == NULL);
1101
1102   path_bar->file_system = g_object_ref (file_system);
1103
1104   home = g_get_home_dir ();
1105   desktop = g_build_filename (home, "Desktop", NULL);
1106   path_bar->home_path = gtk_file_system_filename_to_path (path_bar->file_system, home);
1107   path_bar->desktop_path = gtk_file_system_filename_to_path (path_bar->file_system, desktop);
1108   path_bar->root_path = gtk_file_system_filename_to_path (path_bar->file_system, "/");
1109 }