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