]> Pileus Git - ~andy/gtk/blob - gtk/gtkpathbar.c
Scroll to make the current folder visible. (#152921)
[~andy/gtk] / gtk / gtkpathbar.c
1 /* gtkpathbar.c
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 "gtkalias.h"
23 #include "gtkpathbar.h"
24 #include "gtktogglebutton.h"
25 #include "gtkalignment.h"
26 #include "gtkarrow.h"
27 #include "gtkimage.h"
28 #include "gtkintl.h"
29 #include "gtkicontheme.h"
30 #include "gtkiconfactory.h"
31 #include "gtklabel.h"
32 #include "gtkhbox.h"
33 #include "gtkmain.h"
34 #include "gtkmarshalers.h"
35
36 enum {
37   PATH_CLICKED,
38   LAST_SIGNAL
39 };
40
41 typedef enum {
42   NORMAL_BUTTON,
43   ROOT_BUTTON,
44   HOME_BUTTON,
45   DESKTOP_BUTTON
46 } ButtonType;
47
48 #define BUTTON_DATA(x) ((ButtonData *)(x))
49
50 #define SCROLL_TIMEOUT           150
51 #define INITIAL_SCROLL_TIMEOUT   300
52
53 static guint path_bar_signals [LAST_SIGNAL] = { 0 };
54
55 /* Icon size for if we can't get it from the theme */
56 #define FALLBACK_ICON_SIZE 20
57
58 typedef struct _ButtonData ButtonData;
59
60 struct _ButtonData
61 {
62   GtkWidget *button;
63   ButtonType type;
64   char *dir_name;
65   GtkFilePath *path;
66   GtkWidget *image;
67   GtkWidget *label;
68   gint ignore_changes : 1;
69   gint file_is_hidden : 1;
70 };
71
72 G_DEFINE_TYPE (GtkPathBar,
73                gtk_path_bar,
74                GTK_TYPE_CONTAINER);
75
76 static void gtk_path_bar_finalize                 (GObject          *object);
77 static void gtk_path_bar_dispose                  (GObject          *object);
78 static void gtk_path_bar_size_request             (GtkWidget        *widget,
79                                                    GtkRequisition   *requisition);
80 static void gtk_path_bar_size_allocate            (GtkWidget        *widget,
81                                                    GtkAllocation    *allocation);
82 static void gtk_path_bar_add                      (GtkContainer     *container,
83                                                    GtkWidget        *widget);
84 static void gtk_path_bar_remove                   (GtkContainer     *container,
85                                                    GtkWidget        *widget);
86 static void gtk_path_bar_forall                   (GtkContainer     *container,
87                                                    gboolean          include_internals,
88                                                    GtkCallback       callback,
89                                                    gpointer          callback_data);
90 static void gtk_path_bar_scroll_up                (GtkWidget        *button,
91                                                    GtkPathBar       *path_bar);
92 static void gtk_path_bar_scroll_down              (GtkWidget        *button,
93                                                    GtkPathBar       *path_bar);
94 static gboolean gtk_path_bar_slider_button_press  (GtkWidget        *widget,
95                                                    GdkEventButton   *event,
96                                                    GtkPathBar       *path_bar);
97 static gboolean gtk_path_bar_slider_button_release(GtkWidget        *widget,
98                                                    GdkEventButton   *event,
99                                                    GtkPathBar       *path_bar);
100 static void gtk_path_bar_grab_notify              (GtkWidget        *widget,
101                                                    gboolean          was_grabbed);
102 static void gtk_path_bar_state_changed            (GtkWidget        *widget,
103                                                    GtkStateType      previous_state);
104 static void gtk_path_bar_style_set                (GtkWidget        *widget,
105                                                    GtkStyle         *previous_style);
106 static void gtk_path_bar_screen_changed           (GtkWidget        *widget,
107                                                    GdkScreen        *previous_screen);
108 static void gtk_path_bar_check_icon_theme         (GtkPathBar       *path_bar);
109 static void gtk_path_bar_update_button_appearance (GtkPathBar       *path_bar,
110                                                    ButtonData       *button_data,
111                                                    gboolean          current_dir);
112
113 static GtkWidget *
114 get_slider_button (GtkPathBar  *path_bar,
115                    GtkArrowType arrow_type)
116 {
117   GtkWidget *button;
118
119   gtk_widget_push_composite_child ();
120
121   button = gtk_button_new ();
122   gtk_container_add (GTK_CONTAINER (button), gtk_arrow_new (arrow_type, GTK_SHADOW_OUT));
123   gtk_container_add (GTK_CONTAINER (path_bar), button);
124   gtk_widget_show_all (button);
125
126   gtk_widget_pop_composite_child ();
127
128   return button;
129 }
130
131 static void
132 gtk_path_bar_init (GtkPathBar *path_bar)
133 {
134   GTK_WIDGET_SET_FLAGS (path_bar, GTK_NO_WINDOW);
135   gtk_widget_set_redraw_on_allocate (GTK_WIDGET (path_bar), FALSE);
136
137   path_bar->spacing = 3;
138   path_bar->up_slider_button = get_slider_button (path_bar, GTK_ARROW_LEFT);
139   path_bar->down_slider_button = get_slider_button (path_bar, GTK_ARROW_RIGHT);
140   path_bar->icon_size = FALLBACK_ICON_SIZE;
141   
142   g_signal_connect (path_bar->up_slider_button, "clicked", G_CALLBACK (gtk_path_bar_scroll_up), path_bar);
143   g_signal_connect (path_bar->down_slider_button, "clicked", G_CALLBACK (gtk_path_bar_scroll_down), path_bar);
144
145   g_signal_connect (path_bar->up_slider_button, "button_press_event", G_CALLBACK (gtk_path_bar_slider_button_press), path_bar);
146   g_signal_connect (path_bar->up_slider_button, "button_release_event", G_CALLBACK (gtk_path_bar_slider_button_release), path_bar);
147   g_signal_connect (path_bar->down_slider_button, "button_press_event", G_CALLBACK (gtk_path_bar_slider_button_press), path_bar);
148   g_signal_connect (path_bar->down_slider_button, "button_release_event", G_CALLBACK (gtk_path_bar_slider_button_release), path_bar);
149 }
150
151 static void
152 gtk_path_bar_class_init (GtkPathBarClass *path_bar_class)
153 {
154   GObjectClass *gobject_class;
155   GtkObjectClass *object_class;
156   GtkWidgetClass *widget_class;
157   GtkContainerClass *container_class;
158
159   gobject_class = (GObjectClass *) path_bar_class;
160   object_class = (GtkObjectClass *) path_bar_class;
161   widget_class = (GtkWidgetClass *) path_bar_class;
162   container_class = (GtkContainerClass *) path_bar_class;
163
164   gobject_class->finalize = gtk_path_bar_finalize;
165   gobject_class->dispose = gtk_path_bar_dispose;
166
167   widget_class->size_request = gtk_path_bar_size_request;
168   widget_class->size_allocate = gtk_path_bar_size_allocate;
169   widget_class->style_set = gtk_path_bar_style_set;
170   widget_class->screen_changed = gtk_path_bar_screen_changed;
171   widget_class->grab_notify = gtk_path_bar_grab_notify;
172   widget_class->state_changed = gtk_path_bar_state_changed;
173
174   container_class->add = gtk_path_bar_add;
175   container_class->forall = gtk_path_bar_forall;
176   container_class->remove = gtk_path_bar_remove;
177   /* FIXME: */
178   /*  container_class->child_type = gtk_path_bar_child_type;*/
179
180   path_bar_signals [PATH_CLICKED] =
181     g_signal_new ("path-clicked",
182                   G_OBJECT_CLASS_TYPE (object_class),
183                   G_SIGNAL_RUN_FIRST,
184                   G_STRUCT_OFFSET (GtkPathBarClass, path_clicked),
185                   NULL, NULL,
186                   _gtk_marshal_VOID__POINTER_BOOLEAN,
187                   G_TYPE_NONE, 2,
188                   G_TYPE_POINTER,
189                   G_TYPE_BOOLEAN);
190 }
191
192
193 static void
194 gtk_path_bar_finalize (GObject *object)
195 {
196   GtkPathBar *path_bar;
197
198   path_bar = GTK_PATH_BAR (object);
199   g_list_free (path_bar->button_list);
200   if (path_bar->root_path)
201     gtk_file_path_free (path_bar->root_path);
202   if (path_bar->home_path)
203     gtk_file_path_free (path_bar->home_path);
204   if (path_bar->desktop_path)
205     gtk_file_path_free (path_bar->desktop_path);
206
207   if (path_bar->root_icon)
208     g_object_unref (path_bar->root_icon);
209   if (path_bar->home_icon)
210     g_object_unref (path_bar->home_icon);
211   if (path_bar->desktop_icon)
212     g_object_unref (path_bar->desktop_icon);
213
214   if (path_bar->file_system)
215     g_object_unref (path_bar->file_system);
216
217   G_OBJECT_CLASS (gtk_path_bar_parent_class)->finalize (object);
218 }
219
220 /* Removes the settings signal handler.  It's safe to call multiple times */
221 static void
222 remove_settings_signal (GtkPathBar *path_bar,
223                         GdkScreen  *screen)
224 {
225   if (path_bar->settings_signal_id)
226     {
227       GtkSettings *settings;
228
229       settings = gtk_settings_get_for_screen (screen);
230       g_signal_handler_disconnect (settings,
231                                    path_bar->settings_signal_id);
232       path_bar->settings_signal_id = 0;
233     }
234 }
235
236 static void
237 gtk_path_bar_dispose (GObject *object)
238 {
239   remove_settings_signal (GTK_PATH_BAR (object),
240                           gtk_widget_get_screen (GTK_WIDGET (object)));
241
242   G_OBJECT_CLASS (gtk_path_bar_parent_class)->dispose (object);
243 }
244
245 /* Size requisition:
246  * 
247  * Ideally, our size is determined by another widget, and we are just filling
248  * available space.
249  */
250 static void
251 gtk_path_bar_size_request (GtkWidget      *widget,
252                            GtkRequisition *requisition)
253 {
254   ButtonData *button_data;
255   GtkPathBar *path_bar;
256   GtkRequisition child_requisition;
257   GList *list;
258
259   path_bar = GTK_PATH_BAR (widget);
260
261   requisition->width = 0;
262   requisition->height = 0;
263
264   for (list = path_bar->button_list; list; list = list->next)
265     {
266       button_data = BUTTON_DATA (list->data);
267       gtk_widget_size_request (button_data->button, &child_requisition);
268       requisition->width = MAX (child_requisition.width, requisition->width);
269       requisition->height = MAX (child_requisition.height, requisition->height);
270     }
271
272   /* Add space for slider, if we have more than one path */
273   /* Theoretically, the slider could be bigger than the other button.  But we're
274    * not going to worry about that now.
275    */
276   path_bar->slider_width = MIN(requisition->height * 2 / 3 + 5, requisition->height);
277   if (path_bar->button_list && path_bar->button_list->next != NULL)
278     requisition->width += (path_bar->spacing + path_bar->slider_width) * 2;
279
280   gtk_widget_size_request (path_bar->up_slider_button, &child_requisition);
281   gtk_widget_size_request (path_bar->down_slider_button, &child_requisition);
282
283   requisition->width += GTK_CONTAINER (widget)->border_width * 2;
284   requisition->height += GTK_CONTAINER (widget)->border_width * 2;
285
286   widget->requisition = *requisition;
287 }
288
289 static void
290 gtk_path_bar_update_slider_buttons (GtkPathBar *path_bar)
291 {
292   if (path_bar->button_list)
293     {
294       GtkWidget *button;
295
296       button = BUTTON_DATA (path_bar->button_list->data)->button;
297       if (gtk_widget_get_child_visible (button))
298         gtk_widget_set_sensitive (path_bar->down_slider_button, FALSE);
299       else
300         gtk_widget_set_sensitive (path_bar->down_slider_button, TRUE);
301
302       button = BUTTON_DATA (g_list_last (path_bar->button_list)->data)->button;
303       if (gtk_widget_get_child_visible (button))
304         gtk_widget_set_sensitive (path_bar->up_slider_button, FALSE);
305       else
306         gtk_widget_set_sensitive (path_bar->up_slider_button, TRUE);
307     }
308 }
309
310 /* This is a tad complicated
311  */
312 static void
313 gtk_path_bar_size_allocate (GtkWidget     *widget,
314                             GtkAllocation *allocation)
315 {
316   GtkWidget *child;
317   GtkPathBar *path_bar = GTK_PATH_BAR (widget);
318   GtkTextDirection direction;
319   GtkAllocation child_allocation;
320   GList *list, *first_button;
321   gint width;
322   gint allocation_width;
323   gint border_width;
324   gboolean need_sliders = FALSE;
325   gint up_slider_offset = 0;
326   gint down_slider_offset = 0;
327
328   widget->allocation = *allocation;
329
330   /* No path is set; we don't have to allocate anything. */
331   if (path_bar->button_list == NULL)
332     return;
333
334   direction = gtk_widget_get_direction (widget);
335   border_width = (gint) GTK_CONTAINER (path_bar)->border_width;
336   allocation_width = allocation->width - 2 * border_width;
337
338   /* First, we check to see if we need the scrollbars. */
339   width = BUTTON_DATA (path_bar->button_list->data)->button->requisition.width;
340   for (list = path_bar->button_list->next; list; list = list->next)
341     {
342       child = BUTTON_DATA (list->data)->button;
343
344       width += child->requisition.width + path_bar->spacing;
345     }
346
347   if (width <= allocation_width)
348     {
349       first_button = g_list_last (path_bar->button_list);
350     }
351   else
352     {
353       gboolean reached_end = FALSE;
354       gint slider_space = 2 * (path_bar->spacing + path_bar->slider_width);
355
356       if (path_bar->first_scrolled_button)
357         first_button = path_bar->first_scrolled_button;
358       else
359         first_button = path_bar->button_list;
360       need_sliders = TRUE;
361       
362       /* To see how much space we have, and how many buttons we can display.
363        * We start at the first button, count forward until hit the new
364        * button, then count backwards.
365        */
366       /* Count down the path chain towards the end. */
367       width = BUTTON_DATA (first_button->data)->button->requisition.width;
368       list = first_button->prev;
369       while (list && !reached_end)
370         {
371           child = BUTTON_DATA (list->data)->button;
372
373           if (width + child->requisition.width +
374               path_bar->spacing + slider_space > allocation_width)
375             reached_end = TRUE;
376           else
377             width += child->requisition.width + path_bar->spacing;
378
379           list = list->prev;
380         }
381
382       /* Finally, we walk up, seeing how many of the previous buttons we can
383        * add */
384       while (first_button->next && ! reached_end)
385         {
386           child = BUTTON_DATA (first_button->next->data)->button;
387
388           if (width + child->requisition.width + path_bar->spacing + slider_space > allocation_width)
389             {
390               reached_end = TRUE;
391             }
392           else
393             {
394               width += child->requisition.width + path_bar->spacing;
395               first_button = first_button->next;
396             }
397         }
398     }
399
400   /* Now, we allocate space to the buttons */
401   child_allocation.y = allocation->y + border_width;
402   child_allocation.height = MAX (1, (gint) allocation->height - border_width * 2);
403
404   if (direction == GTK_TEXT_DIR_RTL)
405     {
406       child_allocation.x = allocation->x + allocation->width - border_width;
407       if (need_sliders)
408         {
409           child_allocation.x -= (path_bar->spacing + path_bar->slider_width);
410           up_slider_offset = allocation->width - border_width - path_bar->slider_width;
411         }
412     }
413   else
414     {
415       child_allocation.x = allocation->x + border_width;
416       if (need_sliders)
417         {
418           up_slider_offset = border_width;
419           child_allocation.x += (path_bar->spacing + path_bar->slider_width);
420         }
421     }
422
423   for (list = first_button; list; list = list->prev)
424     {
425       child = BUTTON_DATA (list->data)->button;
426
427       child_allocation.width = child->requisition.width;
428       if (direction == GTK_TEXT_DIR_RTL)
429         child_allocation.x -= child_allocation.width;
430
431       /* Check to see if we've don't have any more space to allocate buttons */
432       if (need_sliders && direction == GTK_TEXT_DIR_RTL)
433         {
434           if (child_allocation.x - path_bar->spacing - path_bar->slider_width < widget->allocation.x + border_width)
435             break;
436         }
437       else if (need_sliders && direction == GTK_TEXT_DIR_LTR)
438         {
439           if (child_allocation.x + child_allocation.width + path_bar->spacing + path_bar->slider_width >
440               widget->allocation.x + border_width + allocation_width)
441             break;
442         }
443
444       gtk_widget_set_child_visible (BUTTON_DATA (list->data)->button, TRUE);
445       gtk_widget_size_allocate (child, &child_allocation);
446
447       if (direction == GTK_TEXT_DIR_RTL)
448         {
449           child_allocation.x -= path_bar->spacing;
450           down_slider_offset = child_allocation.x - widget->allocation.x - path_bar->slider_width;
451           down_slider_offset = border_width;
452         }
453       else
454         {
455           down_slider_offset = child_allocation.x - widget->allocation.x;
456           down_slider_offset = allocation->width - border_width - path_bar->slider_width;
457           child_allocation.x += child_allocation.width + path_bar->spacing;
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_style_set (GtkWidget *widget,
496                         GtkStyle  *previous_style)
497 {
498   if (GTK_WIDGET_CLASS (gtk_path_bar_parent_class)->style_set)
499     GTK_WIDGET_CLASS (gtk_path_bar_parent_class)->style_set (widget, previous_style);
500
501   gtk_path_bar_check_icon_theme (GTK_PATH_BAR (widget));
502 }
503
504 static void
505 gtk_path_bar_screen_changed (GtkWidget *widget,
506                              GdkScreen *previous_screen)
507 {
508   if (GTK_WIDGET_CLASS (gtk_path_bar_parent_class)->screen_changed)
509     GTK_WIDGET_CLASS (gtk_path_bar_parent_class)->screen_changed (widget, previous_screen);
510
511   /* We might nave a new settings, so we remove the old one */
512   if (previous_screen)
513     remove_settings_signal (GTK_PATH_BAR (widget), previous_screen);
514
515   gtk_path_bar_check_icon_theme (GTK_PATH_BAR (widget));
516 }
517
518 static void
519 gtk_path_bar_add (GtkContainer *container,
520                   GtkWidget    *widget)
521 {
522   gtk_widget_set_parent (widget, GTK_WIDGET (container));
523 }
524
525 static void
526 gtk_path_bar_remove_1 (GtkContainer *container,
527                        GtkWidget    *widget)
528 {
529   gboolean was_visible = GTK_WIDGET_VISIBLE (widget);
530   gtk_widget_unparent (widget);
531   if (was_visible)
532     gtk_widget_queue_resize (GTK_WIDGET (container));
533 }
534
535 static void
536 gtk_path_bar_remove (GtkContainer *container,
537                      GtkWidget    *widget)
538 {
539   GtkPathBar *path_bar;
540   GList *children;
541
542   path_bar = GTK_PATH_BAR (container);
543
544   if (widget == path_bar->up_slider_button)
545     {
546       gtk_path_bar_remove_1 (container, widget);
547       path_bar->up_slider_button = NULL;
548       return;
549     }
550
551   if (widget == path_bar->down_slider_button)
552     {
553       gtk_path_bar_remove_1 (container, widget);
554       path_bar->down_slider_button = NULL;
555       return;
556     }
557
558   children = path_bar->button_list;
559   while (children)
560     {
561       if (widget == BUTTON_DATA (children->data)->button)
562         {
563           gtk_path_bar_remove_1 (container, widget);
564           path_bar->button_list = g_list_remove_link (path_bar->button_list, children);
565           g_list_free (children);
566           return;
567         }
568       
569       children = children->next;
570     }
571 }
572
573 static void
574 gtk_path_bar_forall (GtkContainer *container,
575                      gboolean      include_internals,
576                      GtkCallback   callback,
577                      gpointer      callback_data)
578 {
579   GtkPathBar *path_bar;
580   GList *children;
581
582   g_return_if_fail (callback != NULL);
583   path_bar = GTK_PATH_BAR (container);
584
585   children = path_bar->button_list;
586   while (children)
587     {
588       GtkWidget *child;
589       child = BUTTON_DATA (children->data)->button;
590       children = children->next;
591
592       (* callback) (child, callback_data);
593     }
594
595   if (path_bar->up_slider_button)
596     (* callback) (path_bar->up_slider_button, callback_data);
597
598   if (path_bar->down_slider_button)
599     (* callback) (path_bar->down_slider_button, callback_data);
600 }
601
602 static void
603 gtk_path_bar_scroll_down (GtkWidget *button, GtkPathBar *path_bar)
604 {
605   GList *list;
606   GList *down_button = NULL;
607   GList *up_button = NULL;
608   gint space_available;
609   gint space_needed;
610   gint border_width;
611   GtkTextDirection direction;
612
613   if (path_bar->ignore_click)
614     {
615       path_bar->ignore_click = FALSE;
616       return;   
617     }
618
619   gtk_widget_queue_resize (GTK_WIDGET (path_bar));
620
621   border_width = GTK_CONTAINER (path_bar)->border_width;
622   direction = gtk_widget_get_direction (GTK_WIDGET (path_bar));
623   
624   /* We find the button at the 'down' end that we have to make
625    * visible */
626   for (list = path_bar->button_list; list; list = list->next)
627     {
628       if (list->next && gtk_widget_get_child_visible (BUTTON_DATA (list->next->data)->button))
629         {
630           down_button = list;
631           break;
632         }
633     }
634   
635   /* Find the last visible button on the 'up' end
636    */
637   for (list = g_list_last (path_bar->button_list); list; list = list->prev)
638     {
639       if (gtk_widget_get_child_visible (BUTTON_DATA (list->data)->button))
640         {
641           up_button = list;
642           break;
643         }
644     }
645
646   space_needed = BUTTON_DATA (down_button->data)->button->allocation.width + path_bar->spacing;
647   if (direction == GTK_TEXT_DIR_RTL)
648     space_available = path_bar->down_slider_button->allocation.x - GTK_WIDGET (path_bar)->allocation.x;
649   else
650     space_available = (GTK_WIDGET (path_bar)->allocation.x + GTK_WIDGET (path_bar)->allocation.width - border_width) -
651       (path_bar->down_slider_button->allocation.x + path_bar->down_slider_button->allocation.width);
652
653   /* We have space_available extra space that's not being used.  We
654    * need space_needed space to make the button fit.  So we walk down
655    * from the end, removing buttons until we get all the space we
656    * need. */
657   while (space_available < space_needed)
658     {
659       space_available += BUTTON_DATA (up_button->data)->button->allocation.width + path_bar->spacing;
660       up_button = up_button->prev;
661       path_bar->first_scrolled_button = up_button;
662     }
663 }
664
665 static void
666 gtk_path_bar_scroll_up (GtkWidget *button, GtkPathBar *path_bar)
667 {
668   GList *list;
669
670   if (path_bar->ignore_click)
671     {
672       path_bar->ignore_click = FALSE;
673       return;   
674     }
675
676   gtk_widget_queue_resize (GTK_WIDGET (path_bar));
677
678   for (list = g_list_last (path_bar->button_list); list; list = list->prev)
679     {
680       if (list->prev && gtk_widget_get_child_visible (BUTTON_DATA (list->prev->data)->button))
681         {
682           path_bar->first_scrolled_button = list;
683           return;
684         }
685     }
686 }
687
688 static gboolean
689 gtk_path_bar_scroll_timeout (GtkPathBar *path_bar)
690 {
691   gboolean retval = FALSE;
692
693   GDK_THREADS_ENTER ();
694
695   if (path_bar->timer)
696     {
697       if (GTK_WIDGET_HAS_FOCUS (path_bar->up_slider_button))
698         gtk_path_bar_scroll_up (path_bar->up_slider_button, path_bar);
699       else if (GTK_WIDGET_HAS_FOCUS (path_bar->down_slider_button))
700         gtk_path_bar_scroll_down (path_bar->down_slider_button, path_bar);
701
702       if (path_bar->need_timer) 
703         {
704           path_bar->need_timer = FALSE;
705
706           path_bar->timer = g_timeout_add (SCROLL_TIMEOUT,
707                                            (GSourceFunc)gtk_path_bar_scroll_timeout,
708                                            path_bar);
709           
710         }
711       else
712         retval = TRUE;
713       
714     }
715
716   GDK_THREADS_LEAVE ();
717
718   return retval;
719 }
720
721 static void 
722 gtk_path_bar_stop_scrolling (GtkPathBar *path_bar)
723 {
724   if (path_bar->timer)
725     {
726       g_source_remove (path_bar->timer);
727       path_bar->timer = 0;
728       path_bar->need_timer = FALSE;
729     }
730 }
731
732 static gboolean
733 gtk_path_bar_slider_button_press (GtkWidget      *widget, 
734                                   GdkEventButton *event,
735                                   GtkPathBar     *path_bar)
736 {
737   if (!GTK_WIDGET_HAS_FOCUS (widget))
738     gtk_widget_grab_focus (widget);
739
740   if (event->type != GDK_BUTTON_PRESS || event->button != 1)
741     return FALSE;
742
743   path_bar->ignore_click = FALSE;
744
745   if (widget == path_bar->up_slider_button)
746     gtk_path_bar_scroll_up (path_bar->up_slider_button, path_bar);
747   else if (widget == path_bar->down_slider_button)
748      gtk_path_bar_scroll_down (path_bar->down_slider_button, path_bar);
749
750   if (!path_bar->timer)
751     {
752       path_bar->need_timer = TRUE;
753       path_bar->timer = g_timeout_add (INITIAL_SCROLL_TIMEOUT,
754                                        (GSourceFunc)gtk_path_bar_scroll_timeout,
755                                        path_bar);
756     }
757
758   return FALSE;
759 }
760
761 static gboolean
762 gtk_path_bar_slider_button_release (GtkWidget      *widget, 
763                                     GdkEventButton *event,
764                                     GtkPathBar     *path_bar)
765 {
766   if (event->type != GDK_BUTTON_RELEASE)
767     return FALSE;
768
769   path_bar->ignore_click = TRUE;
770   gtk_path_bar_stop_scrolling (path_bar);
771
772   return FALSE;
773 }
774
775 static void
776 gtk_path_bar_grab_notify (GtkWidget *widget,
777                           gboolean   was_grabbed)
778 {
779   if (!was_grabbed)
780     gtk_path_bar_stop_scrolling (GTK_PATH_BAR (widget));
781 }
782
783 static void
784 gtk_path_bar_state_changed (GtkWidget    *widget,
785                             GtkStateType  previous_state)
786 {
787   if (!GTK_WIDGET_IS_SENSITIVE (widget)) 
788     gtk_path_bar_stop_scrolling (GTK_PATH_BAR (widget));
789 }
790
791
792 /* Changes the icons wherever it is needed */
793 static void
794 reload_icons (GtkPathBar *path_bar)
795 {
796   GList *list;
797
798   if (path_bar->root_icon)
799     {
800       g_object_unref (path_bar->root_icon);
801       path_bar->root_icon = NULL;
802     }
803   if (path_bar->home_icon)
804     {
805       g_object_unref (path_bar->home_icon);
806       path_bar->home_icon = NULL;
807     }
808   if (path_bar->desktop_icon)
809     {
810       g_object_unref (path_bar->desktop_icon);
811       path_bar->desktop_icon = NULL;
812     }
813
814   for (list = path_bar->button_list; list; list = list->next)
815     {
816       ButtonData *button_data;
817       gboolean current_dir;
818
819       button_data = BUTTON_DATA (list->data);
820       if (button_data->type != NORMAL_BUTTON)
821         {
822           current_dir = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button_data->button));
823           gtk_path_bar_update_button_appearance (path_bar, button_data, current_dir);
824         }
825     }
826   
827 }
828
829 static void
830 change_icon_theme (GtkPathBar *path_bar)
831 {
832   GtkSettings *settings;
833   gint width, height;
834
835   settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (path_bar)));
836
837   if (gtk_icon_size_lookup_for_settings (settings, GTK_ICON_SIZE_BUTTON, &width, &height))
838     path_bar->icon_size = MAX (width, height);
839   else
840     path_bar->icon_size = FALLBACK_ICON_SIZE;
841
842   reload_icons (path_bar);
843 }
844 /* Callback used when a GtkSettings value changes */
845 static void
846 settings_notify_cb (GObject    *object,
847                     GParamSpec *pspec,
848                     GtkPathBar *path_bar)
849 {
850   const char *name;
851
852   name = g_param_spec_get_name (pspec);
853
854   if (! strcmp (name, "gtk-icon-theme-name") ||
855       ! strcmp (name, "gtk-icon-sizes"))
856     change_icon_theme (path_bar);
857 }
858
859 static void
860 gtk_path_bar_check_icon_theme (GtkPathBar *path_bar)
861 {
862   GtkSettings *settings;
863
864   if (path_bar->settings_signal_id)
865     return;
866
867   settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (path_bar)));
868   path_bar->settings_signal_id = g_signal_connect (settings, "notify", G_CALLBACK (settings_notify_cb), path_bar);
869
870   change_icon_theme (path_bar);
871 }
872
873 /* Public functions and their helpers */
874 static void
875 gtk_path_bar_clear_buttons (GtkPathBar *path_bar)
876 {
877   while (path_bar->button_list != NULL)
878     {
879       gtk_container_remove (GTK_CONTAINER (path_bar), BUTTON_DATA (path_bar->button_list->data)->button);
880     }
881   path_bar->first_scrolled_button = NULL;
882 }
883
884 static void
885 button_clicked_cb (GtkWidget *button,
886                    gpointer   data)
887 {
888   ButtonData *button_data;
889   GtkPathBar *path_bar;
890   GList *button_list;
891   gboolean child_is_hidden;
892
893   button_data = BUTTON_DATA (data);
894   if (button_data->ignore_changes)
895     return;
896
897   path_bar = GTK_PATH_BAR (button->parent);
898
899   button_list = g_list_find (path_bar->button_list, button_data);
900   g_assert (button_list != NULL);
901
902   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
903
904   if (button_list->prev)
905     {
906       ButtonData *child_data;
907
908       child_data = BUTTON_DATA (button_list->prev->data);
909       child_is_hidden = child_data->file_is_hidden;
910     }
911   else
912     child_is_hidden = FALSE;
913
914   g_signal_emit (path_bar, path_bar_signals [PATH_CLICKED], 0, button_data->path, child_is_hidden);
915 }
916
917 static GdkPixbuf *
918 get_button_image (GtkPathBar *path_bar,
919                   ButtonType  button_type)
920 {
921   GtkFileSystemVolume *volume;
922
923   switch (button_type)
924     {
925     case ROOT_BUTTON:
926
927       if (path_bar->root_icon != NULL)
928         return path_bar->root_icon;
929       
930       volume = gtk_file_system_get_volume_for_path (path_bar->file_system, path_bar->root_path);
931       if (volume == NULL)
932         return NULL;
933
934       path_bar->root_icon = gtk_file_system_volume_render_icon (path_bar->file_system,
935                                                                 volume,
936                                                                 GTK_WIDGET (path_bar),
937                                                                 path_bar->icon_size,
938                                                                 NULL);
939       gtk_file_system_volume_free (path_bar->file_system, volume);
940
941       return path_bar->root_icon;
942     case HOME_BUTTON:
943       if (path_bar->home_icon != NULL)
944         return path_bar->home_icon;
945
946       path_bar->home_icon = gtk_file_system_render_icon (path_bar->file_system,
947                                                          path_bar->home_path,
948                                                          GTK_WIDGET (path_bar),
949                                                          path_bar->icon_size,
950                                                          NULL);
951       return path_bar->home_icon;
952     case DESKTOP_BUTTON:
953       if (path_bar->desktop_icon != NULL)
954         return path_bar->desktop_icon;
955
956       path_bar->desktop_icon = gtk_file_system_render_icon (path_bar->file_system,
957                                                             path_bar->desktop_path,
958                                                             GTK_WIDGET (path_bar),
959                                                             path_bar->icon_size,
960                                                             NULL);
961       return path_bar->desktop_icon;
962     default:
963       return NULL;
964     }
965   
966   return NULL;
967 }
968
969 static void
970 button_data_free (ButtonData *button_data)
971 {
972   gtk_file_path_free (button_data->path);
973   g_free (button_data->dir_name);
974   g_free (button_data);
975 }
976
977 static const char *
978 get_dir_name (ButtonData *button_data)
979 {
980   if (button_data->type == HOME_BUTTON)
981     return _("Home");
982   else if (button_data->type == DESKTOP_BUTTON)
983     return _("Desktop");
984   else
985     return button_data->dir_name;
986
987 }
988
989 /* We always want to request the same size for the label, whether
990  * or not the contents are bold
991  */
992 static void
993 label_size_request_cb (GtkWidget      *widget,
994                        GtkRequisition *requisition,
995                        ButtonData     *button_data)
996 {
997   const gchar *dir_name = get_dir_name (button_data);
998   PangoLayout *layout = gtk_widget_create_pango_layout (button_data->label, dir_name);
999   gint bold_width, bold_height;
1000   gchar *markup;
1001
1002   pango_layout_get_pixel_size (layout, &requisition->width, &requisition->height);
1003   
1004   markup = g_markup_printf_escaped ("<b>%s</b>", dir_name);
1005   pango_layout_set_markup (layout, markup, -1);
1006   g_free (markup);
1007
1008   pango_layout_get_pixel_size (layout, &bold_width, &bold_height);
1009   requisition->width = MAX (requisition->width, bold_width);
1010   requisition->height = MAX (requisition->height, bold_height);
1011   
1012   g_object_unref (layout);
1013 }
1014
1015 static void
1016 gtk_path_bar_update_button_appearance (GtkPathBar *path_bar,
1017                                        ButtonData *button_data,
1018                                        gboolean    current_dir)
1019 {
1020   const gchar *dir_name = get_dir_name (button_data);
1021
1022   if (button_data->label != NULL)
1023     {
1024       if (current_dir)
1025         {
1026           char *markup;
1027
1028           markup = g_markup_printf_escaped ("<b>%s</b>", dir_name);
1029           gtk_label_set_markup (GTK_LABEL (button_data->label), markup);
1030           g_free (markup);
1031         }
1032       else
1033         {
1034           gtk_label_set_text (GTK_LABEL (button_data->label), dir_name);
1035         }
1036     }
1037
1038   if (button_data->image != NULL)
1039     {
1040       GdkPixbuf *pixbuf;
1041       pixbuf = get_button_image (path_bar, button_data->type);
1042       gtk_image_set_from_pixbuf (GTK_IMAGE (button_data->image), pixbuf);
1043     }
1044
1045   if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button_data->button)) != current_dir)
1046     {
1047       button_data->ignore_changes = TRUE;
1048       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button_data->button), current_dir);
1049       button_data->ignore_changes = FALSE;
1050     }
1051 }
1052
1053 static ButtonType
1054 find_button_type (GtkPathBar  *path_bar,
1055                   GtkFilePath *path)
1056 {
1057   if (path_bar->root_path != NULL &&
1058       ! gtk_file_path_compare (path, path_bar->root_path))
1059     return ROOT_BUTTON;
1060   if (path_bar->home_path != NULL &&
1061       ! gtk_file_path_compare (path, path_bar->home_path))
1062     return HOME_BUTTON;
1063   if (path_bar->desktop_path != NULL &&
1064       ! gtk_file_path_compare (path, path_bar->desktop_path))
1065     return DESKTOP_BUTTON;
1066
1067  return NORMAL_BUTTON;
1068 }
1069
1070 static ButtonData *
1071 make_directory_button (GtkPathBar  *path_bar,
1072                        const char  *dir_name,
1073                        GtkFilePath *path,
1074                        gboolean     current_dir,
1075                        gboolean     file_is_hidden)
1076 {
1077   GtkWidget *child = NULL;
1078   GtkWidget *label_alignment = NULL;
1079   ButtonData *button_data;
1080
1081   file_is_hidden = !! file_is_hidden;
1082   /* Is it a special button? */
1083   button_data = g_new0 (ButtonData, 1);
1084
1085   button_data->type = find_button_type (path_bar, path);
1086   button_data->button = gtk_toggle_button_new ();
1087
1088   switch (button_data->type)
1089     {
1090     case ROOT_BUTTON:
1091       button_data->image = gtk_image_new ();
1092       child = button_data->image;
1093       button_data->label = NULL;
1094       break;
1095     case HOME_BUTTON:
1096     case DESKTOP_BUTTON:
1097       button_data->image = gtk_image_new ();
1098       button_data->label = gtk_label_new (NULL);
1099       label_alignment = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
1100       gtk_container_add (GTK_CONTAINER (label_alignment), button_data->label);
1101       child = gtk_hbox_new (FALSE, 2);
1102       gtk_box_pack_start (GTK_BOX (child), button_data->image, FALSE, FALSE, 0);
1103       gtk_box_pack_start (GTK_BOX (child), label_alignment, FALSE, FALSE, 0);
1104       break;
1105     case NORMAL_BUTTON:
1106     default:
1107       button_data->label = gtk_label_new (NULL);
1108       label_alignment = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
1109       gtk_container_add (GTK_CONTAINER (label_alignment), button_data->label);
1110       child = label_alignment;
1111       button_data->image = NULL;
1112     }
1113
1114   /* label_alignment is created because we can't override size-request
1115    * on label itself and still have the contents of the label centered
1116    * properly in the label's requisition
1117    */
1118   if (label_alignment)
1119     g_signal_connect (label_alignment, "size-request",
1120                       G_CALLBACK (label_size_request_cb), button_data);
1121
1122   button_data->dir_name = g_strdup (dir_name);
1123   button_data->path = gtk_file_path_new_dup (gtk_file_path_get_string (path));
1124   button_data->file_is_hidden = file_is_hidden;
1125                           
1126   gtk_container_add (GTK_CONTAINER (button_data->button), child);
1127   gtk_widget_show_all (button_data->button);
1128
1129   gtk_path_bar_update_button_appearance (path_bar, button_data, current_dir);
1130
1131   g_signal_connect (button_data->button, "clicked",
1132                     G_CALLBACK (button_clicked_cb),
1133                     button_data);
1134   g_object_weak_ref (G_OBJECT (button_data->button),
1135                      (GWeakNotify) button_data_free, button_data);
1136
1137   return button_data;
1138 }
1139
1140 static gboolean
1141 gtk_path_bar_check_parent_path (GtkPathBar         *path_bar,
1142                                 const GtkFilePath  *file_path,
1143                                 GtkFileSystem      *file_system)
1144 {
1145   GList *list;
1146   GList *current_path = NULL;
1147
1148   for (list = path_bar->button_list; list; list = list->next)
1149     {
1150       ButtonData *button_data;
1151
1152       button_data = list->data;
1153       if (! gtk_file_path_compare (file_path, button_data->path))
1154         {
1155           current_path = list;
1156           break;
1157         }
1158     }
1159
1160   if (current_path)
1161     {
1162       for (list = path_bar->button_list; list; list = list->next)
1163         {
1164           gtk_path_bar_update_button_appearance (path_bar,
1165                                                  BUTTON_DATA (list->data),
1166                                                  (list == current_path) ? TRUE : FALSE);
1167         }
1168
1169       if (!gtk_widget_get_child_visible (BUTTON_DATA (current_path->data)->button))
1170         {
1171           path_bar->first_scrolled_button = current_path;
1172           gtk_widget_queue_resize (GTK_WIDGET (path_bar));
1173         }
1174
1175       return TRUE;
1176     }
1177   return FALSE;
1178 }
1179
1180 gboolean
1181 _gtk_path_bar_set_path (GtkPathBar         *path_bar,
1182                         const GtkFilePath  *file_path,
1183                         GError            **error)
1184 {
1185   GtkFilePath *path;
1186   gboolean first_directory = TRUE;
1187   gboolean result;
1188   GList *new_buttons = NULL;
1189
1190   g_return_val_if_fail (GTK_IS_PATH_BAR (path_bar), FALSE);
1191   g_return_val_if_fail (file_path != NULL, FALSE);
1192
1193   result = TRUE;
1194
1195   /* Check whether the new path is already present in the pathbar as buttons.
1196    * This could be a parent directory or a previous selected subdirectory.
1197    */
1198   if (gtk_path_bar_check_parent_path (path_bar, file_path, path_bar->file_system))
1199     return TRUE;
1200
1201   path = gtk_file_path_copy (file_path);
1202
1203   gtk_widget_push_composite_child ();
1204
1205   while (path != NULL)
1206     {
1207       GtkFilePath *parent_path = NULL;
1208       ButtonData *button_data;
1209       const gchar *display_name;
1210       gboolean is_hidden;
1211       GtkFileFolder *file_folder;
1212       GtkFileInfo *file_info;
1213       gboolean valid;
1214
1215       valid = gtk_file_system_get_parent (path_bar->file_system,
1216                                           path,
1217                                           &parent_path,
1218                                           error);
1219       if (!valid)
1220         {
1221           result = FALSE;
1222           gtk_file_path_free (path);
1223           break;
1224         }
1225
1226       file_folder = gtk_file_system_get_folder (path_bar->file_system,
1227                                                 parent_path ? parent_path : path,
1228                                                 GTK_FILE_INFO_DISPLAY_NAME | GTK_FILE_INFO_IS_HIDDEN,
1229                                                 NULL);
1230       if (!file_folder)
1231         {
1232           result = FALSE;
1233           gtk_file_path_free (parent_path);
1234           gtk_file_path_free (path);
1235           break;
1236         }
1237
1238       file_info = gtk_file_folder_get_info (file_folder, parent_path ? path : NULL, error);
1239       g_object_unref (file_folder);
1240
1241       if (!file_info)
1242         {
1243           result = FALSE;
1244           gtk_file_path_free (parent_path);
1245           gtk_file_path_free (path);
1246           break;
1247         }
1248
1249       display_name = gtk_file_info_get_display_name (file_info);
1250       is_hidden = gtk_file_info_get_is_hidden (file_info);
1251
1252       button_data = make_directory_button (path_bar, display_name, path, first_directory, is_hidden);
1253       gtk_file_info_free (file_info);
1254       gtk_file_path_free (path);
1255
1256       g_object_ref (button_data->button);
1257       gtk_object_sink (GTK_OBJECT (button_data->button));
1258
1259       new_buttons = g_list_prepend (new_buttons, button_data);
1260
1261       if (button_data->type != NORMAL_BUTTON)
1262         {
1263           gtk_file_path_free (parent_path);
1264           break;
1265         }
1266
1267       path = parent_path;
1268       first_directory = FALSE;
1269     }
1270
1271   if (result)
1272     {
1273       GList *l;
1274
1275       gtk_path_bar_clear_buttons (path_bar);
1276       path_bar->button_list = g_list_reverse (new_buttons);
1277
1278       for (l = path_bar->button_list; l; l = l->next)
1279         {
1280           GtkWidget *button = BUTTON_DATA (l->data)->button;
1281           gtk_container_add (GTK_CONTAINER (path_bar), button);
1282         }
1283     }
1284   else
1285     {
1286       GList *l;
1287
1288       for (l = new_buttons; l; l = l->next)
1289         {
1290           ButtonData *button_data;
1291
1292           button_data = BUTTON_DATA (l->data);
1293
1294           gtk_widget_unref (button_data->button);
1295           button_data_free (button_data);
1296         }
1297
1298       g_list_free (new_buttons);
1299     }
1300
1301   gtk_widget_pop_composite_child ();
1302
1303   return result;
1304 }
1305
1306
1307 /* FIXME: This should be a construct-only property */
1308 void
1309 _gtk_path_bar_set_file_system (GtkPathBar    *path_bar,
1310                                GtkFileSystem *file_system)
1311 {
1312   const char *home;
1313   char *desktop;
1314
1315   g_return_if_fail (GTK_IS_PATH_BAR (path_bar));
1316
1317   g_assert (path_bar->file_system == NULL);
1318
1319   path_bar->file_system = g_object_ref (file_system);
1320
1321   home = g_get_home_dir ();
1322   if (home != NULL)
1323     {
1324       path_bar->home_path = gtk_file_system_filename_to_path (path_bar->file_system, home);
1325       /* FIXME: Need file system backend specific way of getting the
1326        * Desktop path.
1327        */
1328       desktop = g_build_filename (home, "Desktop", NULL);
1329       path_bar->desktop_path = gtk_file_system_filename_to_path (path_bar->file_system, desktop);
1330       g_free (desktop);
1331     }
1332   else
1333     {
1334       path_bar->home_path = NULL;
1335       path_bar->desktop_path = NULL;
1336     }
1337   path_bar->root_path = gtk_file_system_filename_to_path (path_bar->file_system, "/");
1338 }
1339
1340 /**
1341  * _gtk_path_bar_up:
1342  * @path_bar: a #GtkPathBar
1343  * 
1344  * If the selected button in the pathbar is not the furthest button "up" (in the
1345  * root direction), act as if the user clicked on the next button up.
1346  **/
1347 void
1348 _gtk_path_bar_up (GtkPathBar *path_bar)
1349 {
1350   GList *l;
1351
1352   for (l = path_bar->button_list; l; l = l->next)
1353     {
1354       GtkWidget *button = BUTTON_DATA (l->data)->button;
1355       if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
1356         {
1357           if (l->next)
1358             {
1359               GtkWidget *next_button = BUTTON_DATA (l->next->data)->button;
1360               button_clicked_cb (next_button, l->next->data);
1361             }
1362           break;
1363         }
1364     }
1365 }
1366
1367 /**
1368  * _gtk_path_bar_down:
1369  * @path_bar: a #GtkPathBar
1370  * 
1371  * If the selected button in the pathbar is not the furthest button "down" (in the
1372  * leaf direction), act as if the user clicked on the next button down.
1373  **/
1374 void
1375 _gtk_path_bar_down (GtkPathBar *path_bar)
1376 {
1377   GList *l;
1378
1379   for (l = path_bar->button_list; l; l = l->next)
1380     {
1381       GtkWidget *button = BUTTON_DATA (l->data)->button;
1382       if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
1383         {
1384           if (l->prev)
1385             {
1386               GtkWidget *prev_button = BUTTON_DATA (l->prev->data)->button;
1387               button_clicked_cb (prev_button, l->prev->data);
1388             }
1389           break;
1390         }
1391     }
1392 }