]> Pileus Git - ~andy/gtk/blob - gtk/gtkpathbar.c
Change the Down button to be end-justified, so that clicking on it is a
[~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   gint ignore_changes : 1;
66   gint file_is_hidden : 1;
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           down_slider_offset = border_width;
432         }
433       else
434         {
435           down_slider_offset = child_allocation.x - widget->allocation.x;
436           down_slider_offset = allocation->width - border_width - path_bar->slider_width;
437           child_allocation.x += child_allocation.width + path_bar->spacing;
438         }
439     }
440   /* Now we go hide all the widgets that don't fit */
441   while (list)
442     {
443       gtk_widget_set_child_visible (BUTTON_DATA (list->data)->button, FALSE);
444       list = list->prev;
445     }
446   for (list = first_button->next; list; list = list->next)
447     {
448       gtk_widget_set_child_visible (BUTTON_DATA (list->data)->button, FALSE);
449     }
450
451   if (need_sliders)
452     {
453       child_allocation.width = path_bar->slider_width;
454       
455       child_allocation.x = up_slider_offset + allocation->x;
456       gtk_widget_size_allocate (path_bar->up_slider_button, &child_allocation);
457
458       child_allocation.x = down_slider_offset + allocation->x;
459       gtk_widget_size_allocate (path_bar->down_slider_button, &child_allocation);
460
461       gtk_widget_set_child_visible (path_bar->up_slider_button, TRUE);
462       gtk_widget_set_child_visible (path_bar->down_slider_button, TRUE);
463       gtk_widget_show_all (path_bar->up_slider_button);
464       gtk_widget_show_all (path_bar->down_slider_button);
465       gtk_path_bar_update_slider_buttons (path_bar);
466     }
467   else
468     {
469       gtk_widget_set_child_visible (path_bar->up_slider_button, FALSE);
470       gtk_widget_set_child_visible (path_bar->down_slider_button, FALSE);
471     }
472 }
473
474 static void
475 gtk_path_bar_style_set (GtkWidget *widget,
476                         GtkStyle  *previous_style)
477 {
478   if (GTK_WIDGET_CLASS (gtk_path_bar_parent_class)->style_set)
479     GTK_WIDGET_CLASS (gtk_path_bar_parent_class)->style_set (widget, previous_style);
480
481   gtk_path_bar_check_icon_theme (GTK_PATH_BAR (widget));
482 }
483
484 static void
485 gtk_path_bar_screen_changed (GtkWidget *widget,
486                              GdkScreen *previous_screen)
487 {
488   if (GTK_WIDGET_CLASS (gtk_path_bar_parent_class)->screen_changed)
489     GTK_WIDGET_CLASS (gtk_path_bar_parent_class)->screen_changed (widget, previous_screen);
490
491   /* We might nave a new settings, so we remove the old one */
492   if (previous_screen)
493     remove_settings_signal (GTK_PATH_BAR (widget), previous_screen);
494
495   gtk_path_bar_check_icon_theme (GTK_PATH_BAR (widget));
496 }
497
498 static void
499 gtk_path_bar_add (GtkContainer *container,
500                   GtkWidget    *widget)
501 {
502   gtk_widget_set_parent (widget, GTK_WIDGET (container));
503 }
504
505 static void
506 gtk_path_bar_remove_1 (GtkContainer *container,
507                        GtkWidget    *widget)
508 {
509   gboolean was_visible = GTK_WIDGET_VISIBLE (widget);
510   gtk_widget_unparent (widget);
511   if (was_visible)
512     gtk_widget_queue_resize (GTK_WIDGET (container));
513 }
514
515 static void
516 gtk_path_bar_remove (GtkContainer *container,
517                      GtkWidget    *widget)
518 {
519   GtkPathBar *path_bar;
520   GList *children;
521
522   path_bar = GTK_PATH_BAR (container);
523
524   if (widget == path_bar->up_slider_button)
525     {
526       gtk_path_bar_remove_1 (container, widget);
527       path_bar->up_slider_button = NULL;
528       return;
529     }
530
531   if (widget == path_bar->down_slider_button)
532     {
533       gtk_path_bar_remove_1 (container, widget);
534       path_bar->down_slider_button = NULL;
535       return;
536     }
537
538   children = path_bar->button_list;
539   while (children)
540     {
541       if (widget == BUTTON_DATA (children->data)->button)
542         {
543           gtk_path_bar_remove_1 (container, widget);
544           path_bar->button_list = g_list_remove_link (path_bar->button_list, children);
545           g_list_free (children);
546           return;
547         }
548       
549       children = children->next;
550     }
551 }
552
553 static void
554 gtk_path_bar_forall (GtkContainer *container,
555                      gboolean      include_internals,
556                      GtkCallback   callback,
557                      gpointer      callback_data)
558 {
559   GtkPathBar *path_bar;
560   GList *children;
561
562   g_return_if_fail (callback != NULL);
563   path_bar = GTK_PATH_BAR (container);
564
565   children = path_bar->button_list;
566   while (children)
567     {
568       GtkWidget *child;
569       child = BUTTON_DATA (children->data)->button;
570       children = children->next;
571
572       (* callback) (child, callback_data);
573     }
574
575   if (path_bar->up_slider_button)
576     (* callback) (path_bar->up_slider_button, callback_data);
577
578   if (path_bar->down_slider_button)
579     (* callback) (path_bar->down_slider_button, callback_data);
580 }
581
582 static void
583 gtk_path_bar_scroll_down (GtkWidget *button, GtkPathBar *path_bar)
584 {
585   GList *list;
586   GList *down_button = NULL;
587   GList *up_button = NULL;
588   gint space_available;
589   gint space_needed;
590   gint border_width;
591   GtkTextDirection direction;
592   
593   gtk_widget_queue_resize (GTK_WIDGET (path_bar));
594
595   border_width = GTK_CONTAINER (path_bar)->border_width;
596   direction = gtk_widget_get_direction (GTK_WIDGET (path_bar));
597   
598   /* We find the button at the 'down' end that we have to make
599    * visible */
600   for (list = path_bar->button_list; list; list = list->next)
601     {
602       if (list->next && gtk_widget_get_child_visible (BUTTON_DATA (list->next->data)->button))
603         {
604           down_button = list;
605           break;
606         }
607     }
608   
609   /* Find the last visible button on the 'up' end
610    */
611   for (list = g_list_last (path_bar->button_list); list; list = list->prev)
612     {
613       if (gtk_widget_get_child_visible (BUTTON_DATA (list->data)->button))
614         {
615           up_button = list;
616           break;
617         }
618     }
619
620   space_needed = BUTTON_DATA (down_button->data)->button->allocation.width + path_bar->spacing;
621   if (direction == GTK_TEXT_DIR_RTL)
622     space_available = path_bar->down_slider_button->allocation.x - GTK_WIDGET (path_bar)->allocation.x;
623   else
624     space_available = (GTK_WIDGET (path_bar)->allocation.x + GTK_WIDGET (path_bar)->allocation.width - border_width) -
625       (path_bar->down_slider_button->allocation.x + path_bar->down_slider_button->allocation.width);
626
627   /* We have space_available extra space that's not being used.  We
628    * need space_needed space to make the button fit.  So we walk down
629    * from the end, removing buttons until we get all the space we
630    * need. */
631   while (space_available < space_needed)
632     {
633       space_available += BUTTON_DATA (up_button->data)->button->allocation.width + path_bar->spacing;
634       up_button = up_button->prev;
635       path_bar->first_scrolled_button = up_button;
636     }
637 }
638
639 static void
640 gtk_path_bar_scroll_up (GtkWidget *button, GtkPathBar *path_bar)
641 {
642   GList *list;
643
644   gtk_widget_queue_resize (GTK_WIDGET (path_bar));
645
646   for (list = g_list_last (path_bar->button_list); list; list = list->prev)
647     {
648       if (list->prev && gtk_widget_get_child_visible (BUTTON_DATA (list->prev->data)->button))
649         {
650           path_bar->first_scrolled_button = list;
651           return;
652         }
653     }
654 }
655
656 /* Changes the icons wherever it is needed */
657 static void
658 reload_icons (GtkPathBar *path_bar)
659 {
660   GList *list;
661
662   if (path_bar->root_icon)
663     {
664       g_object_unref (path_bar->root_icon);
665       path_bar->root_icon = NULL;
666     }
667   if (path_bar->home_icon)
668     {
669       g_object_unref (path_bar->home_icon);
670       path_bar->home_icon = NULL;
671     }
672   if (path_bar->desktop_icon)
673     {
674       g_object_unref (path_bar->desktop_icon);
675       path_bar->desktop_icon = NULL;
676     }
677
678   for (list = path_bar->button_list; list; list = list->next)
679     {
680       ButtonData *button_data;
681       gboolean current_dir;
682
683       button_data = BUTTON_DATA (list->data);
684       if (button_data->type != NORMAL_BUTTON)
685         {
686           current_dir = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button_data->button));
687           gtk_path_bar_update_button_appearance (path_bar, button_data, current_dir);
688         }
689     }
690   
691 }
692
693 static void
694 change_icon_theme (GtkPathBar *path_bar)
695 {
696   GtkSettings *settings;
697   gint width, height;
698
699   settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (path_bar)));
700
701   if (gtk_icon_size_lookup_for_settings (settings, GTK_ICON_SIZE_BUTTON, &width, &height))
702     path_bar->icon_size = MAX (width, height);
703   else
704     path_bar->icon_size = FALLBACK_ICON_SIZE;
705
706   reload_icons (path_bar);
707 }
708 /* Callback used when a GtkSettings value changes */
709 static void
710 settings_notify_cb (GObject    *object,
711                     GParamSpec *pspec,
712                     GtkPathBar *path_bar)
713 {
714   const char *name;
715
716   name = g_param_spec_get_name (pspec);
717
718   if (! strcmp (name, "gtk-icon-theme-name") ||
719       ! strcmp (name, "gtk-icon-sizes"))
720     change_icon_theme (path_bar);
721 }
722
723 static void
724 gtk_path_bar_check_icon_theme (GtkPathBar *path_bar)
725 {
726   GtkSettings *settings;
727
728   if (path_bar->settings_signal_id)
729     return;
730
731   settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (path_bar)));
732   path_bar->settings_signal_id = g_signal_connect (settings, "notify", G_CALLBACK (settings_notify_cb), path_bar);
733
734   change_icon_theme (path_bar);
735 }
736
737 /* Public functions and their helpers */
738 static void
739 gtk_path_bar_clear_buttons (GtkPathBar *path_bar)
740 {
741   while (path_bar->button_list != NULL)
742     {
743       gtk_container_remove (GTK_CONTAINER (path_bar), BUTTON_DATA (path_bar->button_list->data)->button);
744     }
745   path_bar->first_scrolled_button = NULL;
746 }
747
748 static void
749 button_clicked_cb (GtkWidget *button,
750                    gpointer   data)
751 {
752   ButtonData *button_data;
753   GtkPathBar *path_bar;
754   GList *button_list;
755   gboolean child_is_hidden;
756
757   button_data = BUTTON_DATA (data);
758   if (button_data->ignore_changes)
759     return;
760
761   path_bar = GTK_PATH_BAR (button->parent);
762
763   button_list = g_list_find (path_bar->button_list, button_data);
764   g_assert (button_list != NULL);
765
766   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
767
768   if (button_list->prev)
769     {
770       ButtonData *child_data;
771
772       child_data = BUTTON_DATA (button_list->prev->data);
773       child_is_hidden = child_data->file_is_hidden;
774     }
775   else
776     child_is_hidden = FALSE;
777
778   g_signal_emit (path_bar, path_bar_signals [PATH_CLICKED], 0, button_data->path, child_is_hidden);
779 }
780
781 static GdkPixbuf *
782 get_button_image (GtkPathBar *path_bar,
783                   ButtonType  button_type)
784 {
785   GtkFileSystemVolume *volume;
786
787   switch (button_type)
788     {
789     case ROOT_BUTTON:
790
791       if (path_bar->root_icon != NULL)
792         return path_bar->root_icon;
793       
794       volume = gtk_file_system_get_volume_for_path (path_bar->file_system, path_bar->root_path);
795       if (volume == NULL)
796         return NULL;
797
798       path_bar->root_icon = gtk_file_system_volume_render_icon (path_bar->file_system,
799                                                                 volume,
800                                                                 GTK_WIDGET (path_bar),
801                                                                 path_bar->icon_size,
802                                                                 NULL);
803       gtk_file_system_volume_free (path_bar->file_system, volume);
804
805       return path_bar->root_icon;
806     case HOME_BUTTON:
807       if (path_bar->home_icon != NULL)
808         return path_bar->home_icon;
809
810       path_bar->home_icon = gtk_file_system_render_icon (path_bar->file_system,
811                                                          path_bar->home_path,
812                                                          GTK_WIDGET (path_bar),
813                                                          path_bar->icon_size,
814                                                          NULL);
815       return path_bar->home_icon;
816     case DESKTOP_BUTTON:
817       if (path_bar->desktop_icon != NULL)
818         return path_bar->desktop_icon;
819
820       path_bar->desktop_icon = gtk_file_system_render_icon (path_bar->file_system,
821                                                             path_bar->desktop_path,
822                                                             GTK_WIDGET (path_bar),
823                                                             path_bar->icon_size,
824                                                             NULL);
825       return path_bar->desktop_icon;
826     default:
827       return NULL;
828     }
829   
830   return NULL;
831 }
832
833 static void
834 button_data_free (ButtonData *button_data)
835 {
836   gtk_file_path_free (button_data->path);
837   g_free (button_data->dir_name);
838   g_free (button_data);
839 }
840
841 static const char *
842 get_dir_name (ButtonData *button_data)
843 {
844   if (button_data->type == HOME_BUTTON)
845     return _("Home");
846   else if (button_data->type == DESKTOP_BUTTON)
847     return _("Desktop");
848   else
849     return button_data->dir_name;
850
851 }
852
853 /* We always want to request the same size for the label, whether
854  * or not the contents are bold
855  */
856 static void
857 label_size_request_cb (GtkWidget      *widget,
858                        GtkRequisition *requisition,
859                        ButtonData     *button_data)
860 {
861   const gchar *dir_name = get_dir_name (button_data);
862   PangoLayout *layout = gtk_widget_create_pango_layout (button_data->label, dir_name);
863   gint bold_width, bold_height;
864   gchar *markup;
865
866   pango_layout_get_pixel_size (layout, &requisition->width, &requisition->height);
867   
868   markup = g_markup_printf_escaped ("<b>%s</b>", dir_name);
869   pango_layout_set_markup (layout, markup, -1);
870   g_free (markup);
871
872   pango_layout_get_pixel_size (layout, &bold_width, &bold_height);
873   requisition->width = MAX (requisition->width, bold_width);
874   requisition->height = MAX (requisition->height, bold_height);
875   
876   g_object_unref (layout);
877 }
878
879 static void
880 gtk_path_bar_update_button_appearance (GtkPathBar *path_bar,
881                                        ButtonData *button_data,
882                                        gboolean    current_dir)
883 {
884   const gchar *dir_name = get_dir_name (button_data);
885
886   if (button_data->label != NULL)
887     {
888       if (current_dir)
889         {
890           char *markup;
891
892           markup = g_markup_printf_escaped ("<b>%s</b>", dir_name);
893           gtk_label_set_markup (GTK_LABEL (button_data->label), markup);
894           g_free (markup);
895         }
896       else
897         {
898           gtk_label_set_text (GTK_LABEL (button_data->label), dir_name);
899         }
900     }
901
902   if (button_data->image != NULL)
903     {
904       GdkPixbuf *pixbuf;
905       pixbuf = get_button_image (path_bar, button_data->type);
906       gtk_image_set_from_pixbuf (GTK_IMAGE (button_data->image), pixbuf);
907     }
908
909   if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button_data->button)) != current_dir)
910     {
911       button_data->ignore_changes = TRUE;
912       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button_data->button), current_dir);
913       button_data->ignore_changes = FALSE;
914     }
915 }
916
917 static ButtonType
918 find_button_type (GtkPathBar  *path_bar,
919                   GtkFilePath *path)
920 {
921   if (path_bar->root_path != NULL &&
922       ! gtk_file_path_compare (path, path_bar->root_path))
923     return ROOT_BUTTON;
924   if (path_bar->home_path != NULL &&
925       ! gtk_file_path_compare (path, path_bar->home_path))
926     return HOME_BUTTON;
927   if (path_bar->desktop_path != NULL &&
928       ! gtk_file_path_compare (path, path_bar->desktop_path))
929     return DESKTOP_BUTTON;
930
931  return NORMAL_BUTTON;
932 }
933
934 static ButtonData *
935 make_directory_button (GtkPathBar  *path_bar,
936                        const char  *dir_name,
937                        GtkFilePath *path,
938                        gboolean     current_dir,
939                        gboolean     file_is_hidden)
940 {
941   GtkWidget *child = NULL;
942   GtkWidget *label_alignment = NULL;
943   ButtonData *button_data;
944
945   file_is_hidden = !! file_is_hidden;
946   /* Is it a special button? */
947   button_data = g_new0 (ButtonData, 1);
948
949   button_data->type = find_button_type (path_bar, path);
950   button_data->button = gtk_toggle_button_new ();
951
952   switch (button_data->type)
953     {
954     case ROOT_BUTTON:
955       button_data->image = gtk_image_new ();
956       child = button_data->image;
957       button_data->label = NULL;
958       break;
959     case HOME_BUTTON:
960     case DESKTOP_BUTTON:
961       button_data->image = gtk_image_new ();
962       button_data->label = gtk_label_new (NULL);
963       label_alignment = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
964       gtk_container_add (GTK_CONTAINER (label_alignment), button_data->label);
965       child = gtk_hbox_new (FALSE, 2);
966       gtk_box_pack_start (GTK_BOX (child), button_data->image, FALSE, FALSE, 0);
967       gtk_box_pack_start (GTK_BOX (child), label_alignment, FALSE, FALSE, 0);
968       break;
969     case NORMAL_BUTTON:
970     default:
971       button_data->label = gtk_label_new (NULL);
972       label_alignment = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
973       gtk_container_add (GTK_CONTAINER (label_alignment), button_data->label);
974       child = label_alignment;
975       button_data->image = NULL;
976     }
977
978   /* label_alignment is created because we can't override size-request
979    * on label itself and still have the contents of the label centered
980    * properly in the label's requisition
981    */
982   if (label_alignment)
983     g_signal_connect (label_alignment, "size-request",
984                       G_CALLBACK (label_size_request_cb), button_data);
985
986   button_data->dir_name = g_strdup (dir_name);
987   button_data->path = gtk_file_path_new_dup (gtk_file_path_get_string (path));
988   button_data->file_is_hidden = file_is_hidden;
989                           
990   gtk_container_add (GTK_CONTAINER (button_data->button), child);
991   gtk_widget_show_all (button_data->button);
992
993   gtk_path_bar_update_button_appearance (path_bar, button_data, current_dir);
994
995   g_signal_connect (button_data->button, "clicked",
996                     G_CALLBACK (button_clicked_cb),
997                     button_data);
998   g_object_weak_ref (G_OBJECT (button_data->button),
999                      (GWeakNotify) button_data_free, button_data);
1000
1001   return button_data;
1002 }
1003
1004 static gboolean
1005 gtk_path_bar_check_parent_path (GtkPathBar         *path_bar,
1006                                 const GtkFilePath  *file_path,
1007                                 GtkFileSystem      *file_system)
1008 {
1009   GList *list;
1010   GList *current_path = NULL;
1011
1012   for (list = path_bar->button_list; list; list = list->next)
1013     {
1014       ButtonData *button_data;
1015
1016       button_data = list->data;
1017       if (! gtk_file_path_compare (file_path, button_data->path))
1018         {
1019           current_path = list;
1020           break;
1021         }
1022     }
1023
1024   if (current_path)
1025     {
1026       for (list = path_bar->button_list; list; list = list->next)
1027         {
1028           gtk_path_bar_update_button_appearance (path_bar,
1029                                                  BUTTON_DATA (list->data),
1030                                                  (list == current_path) ? TRUE : FALSE);
1031         }
1032       return TRUE;
1033     }
1034   return FALSE;
1035 }
1036
1037 gboolean
1038 _gtk_path_bar_set_path (GtkPathBar         *path_bar,
1039                         const GtkFilePath  *file_path,
1040                         GError            **error)
1041 {
1042   GtkFilePath *path;
1043   gboolean first_directory = TRUE;
1044   gboolean result;
1045   GList *new_buttons = NULL;
1046
1047   g_return_val_if_fail (GTK_IS_PATH_BAR (path_bar), FALSE);
1048   g_return_val_if_fail (file_path != NULL, FALSE);
1049
1050   result = TRUE;
1051
1052   /* Check whether the new path is already present in the pathbar as buttons.
1053    * This could be a parent directory or a previous selected subdirectory.
1054    */
1055   if (gtk_path_bar_check_parent_path (path_bar, file_path, path_bar->file_system))
1056     return TRUE;
1057
1058   path = gtk_file_path_copy (file_path);
1059
1060   gtk_widget_push_composite_child ();
1061
1062   while (path != NULL)
1063     {
1064       GtkFilePath *parent_path = NULL;
1065       ButtonData *button_data;
1066       const gchar *display_name;
1067       gboolean is_hidden;
1068       GtkFileFolder *file_folder;
1069       GtkFileInfo *file_info;
1070       gboolean valid;
1071
1072       valid = gtk_file_system_get_parent (path_bar->file_system,
1073                                           path,
1074                                           &parent_path,
1075                                           error);
1076       if (!valid)
1077         {
1078           result = FALSE;
1079           gtk_file_path_free (path);
1080           break;
1081         }
1082
1083       file_folder = gtk_file_system_get_folder (path_bar->file_system,
1084                                                 parent_path ? parent_path : path,
1085                                                 GTK_FILE_INFO_DISPLAY_NAME | GTK_FILE_INFO_IS_HIDDEN,
1086                                                 NULL);
1087       if (!file_folder)
1088         {
1089           result = FALSE;
1090           gtk_file_path_free (parent_path);
1091           gtk_file_path_free (path);
1092           break;
1093         }
1094
1095       file_info = gtk_file_folder_get_info (file_folder, parent_path ? path : NULL, error);
1096       g_object_unref (file_folder);
1097
1098       if (!file_info)
1099         {
1100           result = FALSE;
1101           gtk_file_path_free (parent_path);
1102           gtk_file_path_free (path);
1103           break;
1104         }
1105
1106       display_name = gtk_file_info_get_display_name (file_info);
1107       is_hidden = gtk_file_info_get_is_hidden (file_info);
1108
1109       button_data = make_directory_button (path_bar, display_name, path, first_directory, is_hidden);
1110       gtk_file_info_free (file_info);
1111       gtk_file_path_free (path);
1112
1113       g_object_ref (button_data->button);
1114       gtk_object_sink (GTK_OBJECT (button_data->button));
1115
1116       new_buttons = g_list_prepend (new_buttons, button_data);
1117
1118       if (button_data->type != NORMAL_BUTTON)
1119         {
1120           gtk_file_path_free (parent_path);
1121           break;
1122         }
1123
1124       path = parent_path;
1125       first_directory = FALSE;
1126     }
1127
1128   if (result)
1129     {
1130       GList *l;
1131
1132       gtk_path_bar_clear_buttons (path_bar);
1133       path_bar->button_list = g_list_reverse (new_buttons);
1134
1135       for (l = path_bar->button_list; l; l = l->next)
1136         {
1137           GtkWidget *button = BUTTON_DATA (l->data)->button;
1138           gtk_container_add (GTK_CONTAINER (path_bar), button);
1139         }
1140     }
1141   else
1142     {
1143       GList *l;
1144
1145       for (l = new_buttons; l; l = l->next)
1146         {
1147           ButtonData *button_data;
1148
1149           button_data = BUTTON_DATA (l->data);
1150
1151           gtk_widget_unref (button_data->button);
1152           button_data_free (button_data);
1153         }
1154
1155       g_list_free (new_buttons);
1156     }
1157
1158   gtk_widget_pop_composite_child ();
1159
1160   return result;
1161 }
1162
1163
1164 /* FIXME: This should be a construct-only property */
1165 void
1166 _gtk_path_bar_set_file_system (GtkPathBar    *path_bar,
1167                                GtkFileSystem *file_system)
1168 {
1169   const char *home;
1170   char *desktop;
1171
1172   g_return_if_fail (GTK_IS_PATH_BAR (path_bar));
1173
1174   g_assert (path_bar->file_system == NULL);
1175
1176   path_bar->file_system = g_object_ref (file_system);
1177
1178   home = g_get_home_dir ();
1179   if (home != NULL)
1180     {
1181       path_bar->home_path = gtk_file_system_filename_to_path (path_bar->file_system, home);
1182       /* FIXME: Need file system backend specific way of getting the
1183        * Desktop path.
1184        */
1185       desktop = g_build_filename (home, "Desktop", NULL);
1186       path_bar->desktop_path = gtk_file_system_filename_to_path (path_bar->file_system, desktop);
1187       g_free (desktop);
1188     }
1189   else
1190     {
1191       path_bar->home_path = NULL;
1192       path_bar->desktop_path = NULL;
1193     }
1194   path_bar->root_path = gtk_file_system_filename_to_path (path_bar->file_system, "/");
1195 }
1196
1197 /**
1198  * _gtk_path_bar_up:
1199  * @path_bar: a #GtkPathBar
1200  * 
1201  * If the selected button in the pathbar is not the furthest button "up" (in the
1202  * root direction), act as if the user clicked on the next button up.
1203  **/
1204 void
1205 _gtk_path_bar_up (GtkPathBar *path_bar)
1206 {
1207   GList *l;
1208
1209   for (l = path_bar->button_list; l; l = l->next)
1210     {
1211       GtkWidget *button = BUTTON_DATA (l->data)->button;
1212       if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
1213         {
1214           if (l->next)
1215             {
1216               GtkWidget *next_button = BUTTON_DATA (l->next->data)->button;
1217               button_clicked_cb (next_button, l->next->data);
1218             }
1219           break;
1220         }
1221     }
1222 }
1223
1224 /**
1225  * _gtk_path_bar_down:
1226  * @path_bar: a #GtkPathBar
1227  * 
1228  * If the selected button in the pathbar is not the furthest button "down" (in the
1229  * leaf direction), act as if the user clicked on the next button down.
1230  **/
1231 void
1232 _gtk_path_bar_down (GtkPathBar *path_bar)
1233 {
1234   GList *l;
1235
1236   for (l = path_bar->button_list; l; l = l->next)
1237     {
1238       GtkWidget *button = BUTTON_DATA (l->data)->button;
1239       if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
1240         {
1241           if (l->prev)
1242             {
1243               GtkWidget *prev_button = BUTTON_DATA (l->prev->data)->button;
1244               button_clicked_cb (prev_button, l->prev->data);
1245             }
1246           break;
1247         }
1248     }
1249 }