]> Pileus Git - ~andy/gtk/blob - gtk/gtkpathbar.c
unref the correct icon.
[~andy/gtk] / gtk / gtkpathbar.c
1 /* gtkpathbar.h
2  * Copyright (C) 2004  Red Hat, Inc.,  Jonathan Blandford <jrb@gnome.org>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 #include <config.h>
21 #include <string.h>
22 #include "gtkpathbar.h"
23 #include "gtktogglebutton.h"
24 #include "gtkarrow.h"
25 #include "gtkimage.h"
26 #include "gtkintl.h"
27 #include "gtklabel.h"
28 #include "gtkhbox.h"
29 #include "gtkmain.h"
30 #include "gtkmarshalers.h"
31
32 enum {
33   PATH_CLICKED,
34   LAST_SIGNAL
35 };
36
37 typedef enum {
38   NORMAL_BUTTON,
39   ROOT_BUTTON,
40   HOME_BUTTON,
41 } ButtonType;
42
43 static guint path_bar_signals [LAST_SIGNAL] = { 0 };
44
45 /* FIXME: this should correspond to gtk_icon_size_lookup_for_settings  */
46 #define ICON_SIZE 20
47
48
49 G_DEFINE_TYPE (GtkPathBar,
50                gtk_path_bar,
51                GTK_TYPE_CONTAINER);
52
53 static void gtk_path_bar_finalize (GObject *object);
54 static void gtk_path_bar_size_request  (GtkWidget      *widget,
55                                         GtkRequisition *requisition);
56 static void gtk_path_bar_size_allocate (GtkWidget      *widget,
57                                         GtkAllocation  *allocation);
58 static void gtk_path_bar_direction_changed (GtkWidget *widget,
59                                             GtkTextDirection direction);
60 static void gtk_path_bar_add (GtkContainer *container,
61                               GtkWidget    *widget);
62 static void gtk_path_bar_remove (GtkContainer *container,
63                                  GtkWidget    *widget);
64 static void gtk_path_bar_forall (GtkContainer *container,
65                                  gboolean      include_internals,
66                                  GtkCallback   callback,
67                                  gpointer      callback_data);
68 static void gtk_path_bar_scroll_up (GtkWidget *button, GtkPathBar *path_bar);
69 static void gtk_path_bar_scroll_down (GtkWidget *button, GtkPathBar *path_bar);
70
71 static GtkWidget *
72 get_slider_button (GtkPathBar *path_bar)
73 {
74   GtkWidget *button;
75
76   gtk_widget_push_composite_child ();
77
78   button = gtk_button_new ();
79   gtk_container_add (GTK_CONTAINER (button), gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_OUT));
80   gtk_container_add (GTK_CONTAINER (path_bar), button);
81   gtk_widget_show_all (button);
82
83   gtk_widget_pop_composite_child ();
84
85   return button;
86 }
87
88 static void
89 gtk_path_bar_init (GtkPathBar *path_bar)
90 {
91   GTK_WIDGET_SET_FLAGS (path_bar, GTK_NO_WINDOW);
92   gtk_widget_set_redraw_on_allocate (GTK_WIDGET (path_bar), FALSE);
93
94   path_bar->spacing = 3;
95   path_bar->up_slider_button = get_slider_button (path_bar);
96   path_bar->down_slider_button = get_slider_button (path_bar);
97
98   g_signal_connect (path_bar->up_slider_button, "clicked", G_CALLBACK (gtk_path_bar_scroll_up), path_bar);
99   g_signal_connect (path_bar->down_slider_button, "clicked", G_CALLBACK (gtk_path_bar_scroll_down), path_bar);
100 }
101
102 static void
103 gtk_path_bar_class_init (GtkPathBarClass *path_bar_class)
104 {
105   GObjectClass *gobject_class;
106   GtkObjectClass *object_class;
107   GtkWidgetClass *widget_class;
108   GtkContainerClass *container_class;
109
110   gobject_class = (GObjectClass *) path_bar_class;
111   object_class = (GtkObjectClass *) path_bar_class;
112   widget_class = (GtkWidgetClass *) path_bar_class;
113   container_class = (GtkContainerClass *) path_bar_class;
114
115   gobject_class->finalize = gtk_path_bar_finalize;
116
117   widget_class->size_request = gtk_path_bar_size_request;
118   widget_class->size_allocate = gtk_path_bar_size_allocate;
119   widget_class->direction_changed = gtk_path_bar_direction_changed;
120
121   container_class->add = gtk_path_bar_add;
122   container_class->forall = gtk_path_bar_forall;
123   container_class->remove = gtk_path_bar_remove;
124   /* FIXME: */
125   /*  container_class->child_type = gtk_path_bar_child_type;*/
126
127   path_bar_signals [PATH_CLICKED] =
128     g_signal_new ("path_clicked",
129                   G_OBJECT_CLASS_TYPE (object_class),
130                   G_SIGNAL_RUN_FIRST,
131                   G_STRUCT_OFFSET (GtkPathBarClass, path_clicked),
132                   NULL, NULL,
133                   _gtk_marshal_VOID__POINTER,
134                   G_TYPE_NONE, 1,
135                   G_TYPE_POINTER);
136 }
137
138
139 static void
140 gtk_path_bar_finalize (GObject *object)
141 {
142   GtkPathBar *path_bar;
143
144   path_bar = GTK_PATH_BAR (object);
145   g_list_free (path_bar->button_list);
146   if (path_bar->home_path)
147     gtk_file_path_free (path_bar->home_path);
148   if (path_bar->root_path)
149     gtk_file_path_free (path_bar->root_path);
150   if (path_bar->home_icon)
151     g_object_unref (path_bar->home_icon);
152   if (path_bar->root_icon)
153     g_object_unref (path_bar->root_icon);
154   if (path_bar->file_system)
155     g_object_unref (path_bar->file_system);
156
157   G_OBJECT_CLASS (gtk_path_bar_parent_class)->finalize (object);
158 }
159
160 /* Size requisition:
161  * 
162  * Ideally, our size is determined by another widget, and we are just filling
163  * available space.
164  */
165 static void
166 gtk_path_bar_size_request (GtkWidget      *widget,
167                            GtkRequisition *requisition)
168 {
169   GtkPathBar *path_bar;
170   GtkRequisition child_requisition;
171   GList *list;
172
173   path_bar = GTK_PATH_BAR (widget);
174
175   requisition->width = 0;
176   requisition->height = 0;
177
178   for (list = path_bar->button_list; list; list = list->next)
179     {
180       gtk_widget_size_request (GTK_WIDGET (list->data),
181                                &child_requisition);
182       requisition->width = MAX (child_requisition.width, requisition->width);
183       requisition->height = MAX (child_requisition.height, requisition->height);
184     }
185
186   /* Add space for slider, if we have more than one path */
187   /* Theoretically, the slider could be bigger than the other button.  But we're
188    * not going to worry about that now.
189    */
190   path_bar->slider_width = requisition->height / 2 + 5;
191   if (path_bar->button_list && path_bar->button_list->next != NULL)
192     requisition->width += (path_bar->spacing + path_bar->slider_width) * 2;
193
194   gtk_widget_size_request (path_bar->up_slider_button, &child_requisition);
195   gtk_widget_size_request (path_bar->down_slider_button, &child_requisition);
196
197   requisition->width += GTK_CONTAINER (widget)->border_width * 2;
198   requisition->height += GTK_CONTAINER (widget)->border_width * 2;
199
200   widget->requisition = *requisition;
201 }
202
203 static void
204 gtk_path_bar_update_slider_buttons (GtkPathBar *path_bar)
205 {
206   GtkTextDirection direction;
207
208   direction = gtk_widget_get_direction (GTK_WIDGET (path_bar));
209   if (direction == GTK_TEXT_DIR_RTL)
210     {
211       GtkWidget *arrow;
212
213       arrow = gtk_bin_get_child (GTK_BIN (path_bar->up_slider_button));
214       g_object_set (arrow, "arrow_type", GTK_ARROW_RIGHT, NULL);
215
216       arrow = gtk_bin_get_child (GTK_BIN (path_bar->down_slider_button));
217       g_object_set (arrow, "arrow_type", GTK_ARROW_LEFT, NULL);
218     }
219   else
220     {
221       GtkWidget *arrow;
222
223       arrow = gtk_bin_get_child (GTK_BIN (path_bar->up_slider_button));
224       g_object_set (arrow, "arrow_type", GTK_ARROW_LEFT, NULL);
225
226       arrow = gtk_bin_get_child (GTK_BIN (path_bar->down_slider_button));
227       g_object_set (arrow, "arrow_type", GTK_ARROW_RIGHT, NULL);
228     }
229
230   if (path_bar->button_list)
231     {
232       GtkWidget *button;
233
234       button = path_bar->button_list->data;
235       if (gtk_widget_get_child_visible (button))
236         gtk_widget_set_sensitive (path_bar->down_slider_button, FALSE);
237       else
238         gtk_widget_set_sensitive (path_bar->down_slider_button, TRUE);
239
240       button = g_list_last (path_bar->button_list)->data;
241       if (gtk_widget_get_child_visible (button))
242         gtk_widget_set_sensitive (path_bar->up_slider_button, FALSE);
243       else
244         gtk_widget_set_sensitive (path_bar->up_slider_button, TRUE);
245     }
246 }
247
248 /* This is a tad complicated
249  */
250 static void
251 gtk_path_bar_size_allocate (GtkWidget     *widget,
252                             GtkAllocation *allocation)
253 {
254   GtkWidget *child;
255   GtkPathBar *path_bar = GTK_PATH_BAR (widget);
256   GtkTextDirection direction;
257   GtkAllocation child_allocation;
258   GList *list, *first_button;
259   gint width;
260   gint allocation_width;
261   gint border_width;
262   gboolean need_sliders = FALSE;
263   gint up_slider_offset = 0;
264   gint down_slider_offset = 0;
265
266   widget->allocation = *allocation;
267
268   /* No path is set; we don't have to allocate anything. */
269   if (path_bar->button_list == NULL)
270     return;
271
272   direction = gtk_widget_get_direction (widget);
273   border_width = (gint) GTK_CONTAINER (path_bar)->border_width;
274   allocation_width = allocation->width - 2 * border_width;
275
276   /* First, we check to see if we need the scrollbars. */
277   width = GTK_WIDGET (path_bar->button_list->data)->requisition.width;
278   for (list = path_bar->button_list->next; list; list = list->next)
279     {
280       child = GTK_WIDGET (list->data);
281
282       width += child->requisition.width + path_bar->spacing;
283     }
284
285   if (width <= allocation_width)
286     {
287       first_button = g_list_last (path_bar->button_list);
288     }
289   else
290     {
291       gboolean reached_end = FALSE;
292       gint slider_space = 2 * (path_bar->spacing + path_bar->slider_width);
293
294       if (path_bar->first_scrolled_button)
295         first_button = path_bar->first_scrolled_button;
296       else
297         first_button = path_bar->button_list;
298       need_sliders = TRUE;
299       
300       /* To see how much space we have, and how many buttons we can display.
301        * We start at the first button, count forward until hit the new
302        * button, then count backwards.
303        */
304       /* Count down the path chain towards the end. */
305       width = GTK_WIDGET (first_button->data)->requisition.width;
306       list = first_button->prev;
307       while (list && !reached_end)
308         {
309           child = GTK_WIDGET (list->data);
310
311           if (width + child->requisition.width +
312               path_bar->spacing + slider_space > allocation_width)
313             reached_end = TRUE;
314           else
315             width += child->requisition.width + path_bar->spacing;
316
317           list = list->prev;
318         }
319
320       /* Finally, we walk up, seeing how many of the previous buttons we can
321        * add */
322       while (first_button->next && ! reached_end)
323         {
324           child = GTK_WIDGET (first_button->next->data);
325
326           if (width + child->requisition.width + path_bar->spacing + slider_space > allocation_width)
327             {
328               reached_end = TRUE;
329             }
330           else
331             {
332               width += child->requisition.width + path_bar->spacing;
333               first_button = first_button->next;
334             }
335         }
336     }
337
338   /* Now, we allocate space to the buttons */
339   child_allocation.y = allocation->y + border_width;
340   child_allocation.height = MAX (1, (gint) allocation->height - border_width * 2);
341
342   if (direction == GTK_TEXT_DIR_RTL)
343     {
344       child_allocation.x = allocation->x + allocation->width - border_width;
345       if (need_sliders)
346         {
347           child_allocation.x -= (path_bar->spacing + path_bar->slider_width);
348           up_slider_offset = allocation->width - border_width - path_bar->slider_width;
349         }
350     }
351   else
352     {
353       child_allocation.x = allocation->x + border_width;
354       if (need_sliders)
355         {
356           up_slider_offset = border_width;
357           child_allocation.x += (path_bar->spacing + path_bar->slider_width);
358         }
359     }
360
361   for (list = first_button; list; list = list->prev)
362     {
363       child = GTK_WIDGET (list->data);
364
365       child_allocation.width = child->requisition.width;
366       if (direction == GTK_TEXT_DIR_RTL)
367         child_allocation.x -= child_allocation.width;
368
369       /* Check to see if we've don't have any more space to allocate buttons */
370       if (need_sliders && direction == GTK_TEXT_DIR_RTL)
371         {
372           if (child_allocation.x - path_bar->spacing - path_bar->slider_width < widget->allocation.x + border_width)
373             break;
374         }
375       else if (need_sliders && direction == GTK_TEXT_DIR_LTR)
376         {
377           if (child_allocation.x + child_allocation.width + path_bar->spacing + path_bar->slider_width >
378               widget->allocation.x + border_width + allocation_width)
379             break;
380         }
381
382       gtk_widget_set_child_visible (list->data, TRUE);
383       gtk_widget_size_allocate (child, &child_allocation);
384
385       if (direction == GTK_TEXT_DIR_RTL)
386         {
387           child_allocation.x -= path_bar->spacing;
388           down_slider_offset = child_allocation.x - widget->allocation.x - path_bar->slider_width;
389         }
390       else
391         {
392           child_allocation.x += child_allocation.width + path_bar->spacing;
393           down_slider_offset = child_allocation.x - widget->allocation.x;
394         }
395     }
396   /* Now we go hide all the widgets that don't fit */
397   while (list)
398     {
399       gtk_widget_set_child_visible (list->data, FALSE);
400       list = list->prev;
401     }
402   for (list = first_button->next; list; list = list->next)
403     {
404       gtk_widget_set_child_visible (list->data, FALSE);
405     }
406
407   if (need_sliders)
408     {
409       child_allocation.width = path_bar->slider_width;
410       
411       child_allocation.x = up_slider_offset + allocation->x;
412       gtk_widget_size_allocate (path_bar->up_slider_button, &child_allocation);
413
414       child_allocation.x = down_slider_offset + allocation->x;
415       gtk_widget_size_allocate (path_bar->down_slider_button, &child_allocation);
416
417       gtk_widget_set_child_visible (path_bar->up_slider_button, TRUE);
418       gtk_widget_set_child_visible (path_bar->down_slider_button, TRUE);
419       gtk_widget_show_all (path_bar->up_slider_button);
420       gtk_widget_show_all (path_bar->down_slider_button);
421       gtk_path_bar_update_slider_buttons (path_bar);
422     }
423   else
424     {
425       gtk_widget_set_child_visible (path_bar->up_slider_button, FALSE);
426       gtk_widget_set_child_visible (path_bar->down_slider_button, FALSE);
427     }
428 }
429
430 static void
431  gtk_path_bar_direction_changed (GtkWidget *widget,
432                                  GtkTextDirection direction)
433 {
434   gtk_path_bar_update_slider_buttons (GTK_PATH_BAR (widget));
435
436   (* GTK_WIDGET_CLASS (gtk_path_bar_parent_class)->direction_changed) (widget, direction);
437 }
438
439 static void
440 gtk_path_bar_add (GtkContainer *container,
441                   GtkWidget    *widget)
442 {
443   gtk_widget_set_parent (widget, GTK_WIDGET (container));
444 }
445
446 static void
447 gtk_path_bar_remove (GtkContainer *container,
448                      GtkWidget    *widget)
449 {
450   GtkPathBar *path_bar;
451   GList *children;
452
453   path_bar = GTK_PATH_BAR (container);
454
455   children = path_bar->button_list;
456
457   while (children)
458     {
459       if (widget == children->data)
460         {
461           gboolean was_visible;
462
463           was_visible = GTK_WIDGET_VISIBLE (widget);
464           gtk_widget_unparent (widget);
465
466           path_bar->button_list = g_list_remove_link (path_bar->button_list, children);
467           g_list_free (children);
468
469           if (was_visible)
470             gtk_widget_queue_resize (GTK_WIDGET (container));
471           break;
472         }
473       
474       children = children->next;
475     }
476 }
477
478 static void
479 gtk_path_bar_forall (GtkContainer *container,
480                      gboolean      include_internals,
481                      GtkCallback   callback,
482                      gpointer      callback_data)
483 {
484   GtkPathBar *path_bar;
485   GList *children;
486
487   g_return_if_fail (callback != NULL);
488   path_bar = GTK_PATH_BAR (container);
489
490   children = path_bar->button_list;
491   while (children)
492     {
493       GtkWidget *child;
494       child = children->data;
495       children = children->next;
496
497       (* callback) (child, callback_data);
498     }
499
500   (* callback) (path_bar->up_slider_button, callback_data);
501   (* callback) (path_bar->down_slider_button, callback_data);
502 }
503
504 static void
505  gtk_path_bar_scroll_down (GtkWidget *button, GtkPathBar *path_bar)
506 {
507   GList *list;
508   GList *down_button = NULL;
509   GList *up_button = NULL;
510   gint space_available;
511   gint space_needed;
512   gint border_width;
513   GtkTextDirection direction;
514   
515   border_width = GTK_CONTAINER (path_bar)->border_width;
516   direction = gtk_widget_get_direction (GTK_WIDGET (path_bar));
517   
518   /* We find the button at the 'down' end that we have to make
519    * visible */
520   for (list = path_bar->button_list; list; list = list->next)
521     {
522       if (list->next && gtk_widget_get_child_visible (GTK_WIDGET (list->next->data)))
523         {
524           down_button = list;
525           break;
526         }
527     }
528   
529   /* Find the last visible button on the 'up' end
530    */
531   for (list = g_list_last (path_bar->button_list); list; list = list->prev)
532     {
533       if (gtk_widget_get_child_visible (GTK_WIDGET (list->data)))
534         {
535           up_button = list;
536           break;
537         }
538     }
539
540   space_needed = GTK_WIDGET (down_button->data)->allocation.width + path_bar->spacing;
541   if (direction == GTK_TEXT_DIR_RTL)
542     space_available = GTK_WIDGET (path_bar)->allocation.x + GTK_WIDGET (path_bar)->allocation.width;
543   else
544     space_available = (GTK_WIDGET (path_bar)->allocation.x + GTK_WIDGET (path_bar)->allocation.width - border_width) -
545       (path_bar->down_slider_button->allocation.x + path_bar->down_slider_button->allocation.width);
546
547   /* We have space_available extra space that's not being used.  We
548    * need space_needed space to make the button fit.  So we walk down
549    * from the end, removing buttons until we get all the space we
550    * need. */
551   while (space_available < space_needed)
552     {
553       space_available += GTK_WIDGET (up_button->data)->allocation.width + path_bar->spacing;
554       up_button = up_button->prev;
555       path_bar->first_scrolled_button = up_button;
556     }
557 }
558
559 static void
560  gtk_path_bar_scroll_up (GtkWidget *button, GtkPathBar *path_bar)
561 {
562   GList *list;
563
564   for (list = g_list_last (path_bar->button_list); list; list = list->prev)
565     {
566       if (list->prev && gtk_widget_get_child_visible (GTK_WIDGET (list->prev->data)))
567         {
568           path_bar->first_scrolled_button = list;
569           return;
570         }
571     }
572 }
573
574 /* Public functions. */
575 static void
576 gtk_path_bar_clear_buttons (GtkPathBar *path_bar)
577 {
578   while (path_bar->button_list != NULL)
579     {
580       gtk_container_remove (GTK_CONTAINER (path_bar), path_bar->button_list->data);
581     }
582   path_bar->first_scrolled_button = NULL;
583 }
584
585 static void
586 button_clicked_cb (GtkWidget *button,
587                    gpointer   data)
588 {
589   GtkWidget *path_bar;
590   GtkFilePath *file_path;
591
592   path_bar = button->parent;
593   g_assert (GTK_IS_PATH_BAR (path_bar));
594
595   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
596
597   file_path = g_object_get_data (G_OBJECT (button), "gtk-path-bar-button-path");
598   g_signal_emit (path_bar, path_bar_signals [PATH_CLICKED], 0, file_path);
599 }
600
601 static GdkPixbuf *
602 get_button_image (GtkPathBar *path_bar,
603                   ButtonType  button_type)
604 {
605   if (button_type == ROOT_BUTTON)
606     {
607       GtkFileSystemVolume *volume;
608
609       if (path_bar->root_icon != NULL)
610         return path_bar->root_icon;
611       
612       volume = gtk_file_system_get_volume_for_path (path_bar->file_system, path_bar->root_path);
613       if (volume == NULL)
614         return NULL;
615
616       path_bar->root_icon = gtk_file_system_volume_render_icon (path_bar->file_system,
617                                                                 volume,
618                                                                 GTK_WIDGET (path_bar),
619                                                                 ICON_SIZE,
620                                                                 NULL);
621       gtk_file_system_volume_free (path_bar->file_system, volume);
622
623       return path_bar->root_icon;
624     }
625   else if (button_type == HOME_BUTTON)
626     {
627       if (path_bar->home_icon != NULL)
628         return path_bar->home_icon;
629
630       path_bar->home_icon = gtk_file_system_render_icon (path_bar->file_system,
631                                                          path_bar->home_path,
632                                                          GTK_WIDGET (path_bar),
633                                                          ICON_SIZE, NULL);
634       return path_bar->home_icon;
635     }
636   
637   return NULL;
638 }
639
640 static void
641 update_button_appearance (GtkPathBar *path_bar,
642                           GtkWidget  *button,
643                           gboolean    current_dir)
644 {
645   GtkWidget *image;
646   GtkWidget *label;
647   const gchar *dir_name;
648   ButtonType button_type;
649
650   button_type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), "gtk-path-bar-button-type"));
651   if (button_type == HOME_BUTTON)
652     dir_name = _("Home");
653   else
654     dir_name = (const gchar *) g_object_get_data (G_OBJECT (button), "gtk-path-bar-button-dir-name");
655
656   label = g_object_get_data (G_OBJECT (button), "gtk-path-bar-button-label");
657   image = g_object_get_data (G_OBJECT (button), "gtk-path-bar-button-image");
658
659   if (label != NULL)
660     {
661       if (current_dir)
662         {
663           char *markup;
664
665           markup = g_markup_printf_escaped ("<b>%s</b>", dir_name);
666           gtk_label_set_markup (GTK_LABEL (label), markup);
667           g_free (markup);
668         }
669       else
670         {
671           gtk_label_set_text (GTK_LABEL (label), dir_name);
672         }
673     }
674
675   if (image != NULL)
676     {
677       GdkPixbuf *pixbuf;
678       pixbuf = get_button_image (path_bar, button_type);
679       gtk_image_set_from_pixbuf (GTK_IMAGE (image), pixbuf);
680     }
681
682   if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)) != current_dir)
683     {
684       g_signal_handlers_block_by_func (G_OBJECT (button), button_clicked_cb, NULL);
685       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), current_dir);
686       g_signal_handlers_unblock_by_func (G_OBJECT (button), button_clicked_cb, NULL);
687     }
688 }
689
690 /* Since gtk_file_path_free() can be a macro, we provide a real function that
691  * can be used as a callback.
692  */
693 static void
694 file_path_destroy (GtkFilePath *path)
695 {
696   gtk_file_path_free (path);
697 }
698
699 static GtkWidget *
700 make_directory_button (GtkPathBar  *path_bar,
701                        const char  *dir_name,
702                        GtkFilePath *path,
703                        gboolean     current_dir)
704 {
705   GtkWidget *button;
706   GtkWidget *child = NULL;
707   GtkWidget *label = NULL;
708   GtkWidget *image = NULL;
709   ButtonType button_type;
710
711   /* Is it a special button? */
712   button_type = NORMAL_BUTTON;
713   if (! gtk_file_path_compare (path, path_bar->root_path))
714     button_type = ROOT_BUTTON;
715   if (! gtk_file_path_compare (path, path_bar->home_path))
716     button_type = HOME_BUTTON;
717
718   button = gtk_toggle_button_new ();
719
720   switch (button_type)
721     {
722     case ROOT_BUTTON:
723       image = gtk_image_new ();
724       child = image;
725       label = NULL;
726       break;
727     case HOME_BUTTON:
728       image = gtk_image_new ();
729       label = gtk_label_new (NULL);
730       child = gtk_hbox_new (FALSE, 2);
731       gtk_box_pack_start (GTK_BOX (child), image, FALSE, FALSE, 0);
732       gtk_box_pack_start (GTK_BOX (child), label, FALSE, FALSE, 0);
733       break;
734     case NORMAL_BUTTON:
735       label = gtk_label_new (NULL);
736       child = label;
737       image = NULL;
738       break;
739     default:
740       g_assert_not_reached ();
741     }
742
743   g_signal_connect (button, "clicked",
744                     G_CALLBACK (button_clicked_cb),
745                     NULL);
746
747   /* FIXME: setting all this data is ugly.  I really need a ButtonInfo
748    * struct. */
749   g_object_set_data_full (G_OBJECT (button), "gtk-path-bar-button-dir-name",
750                           g_strdup (dir_name),
751                           (GDestroyNotify) g_free);
752   g_object_set_data_full (G_OBJECT (button), "gtk-path-bar-button-path",
753                           gtk_file_path_new_dup (gtk_file_path_get_string (path)),
754                           (GDestroyNotify) file_path_destroy);
755   g_object_set_data (G_OBJECT (button), "gtk-path-bar-button-type",
756                      GINT_TO_POINTER (button_type));
757   g_object_set_data (G_OBJECT (button), "gtk-path-bar-button-image", image);
758   g_object_set_data (G_OBJECT (button), "gtk-path-bar-button-label", label);
759
760   gtk_container_add (GTK_CONTAINER (button), child);
761   gtk_widget_show_all (button);
762
763   update_button_appearance (path_bar, button, current_dir);
764
765   return button;
766 }
767
768 static gboolean
769 gtk_path_bar_check_parent_path (GtkPathBar         *path_bar,
770                                 const GtkFilePath  *file_path,
771                                 GtkFileSystem      *file_system)
772 {
773   GList *list;
774   GList *current_path = NULL;
775
776   for (list = path_bar->button_list; list; list = list->next)
777     {
778       GtkFilePath *tmp_path;
779
780       tmp_path = (GtkFilePath *) g_object_get_data (G_OBJECT (list->data),
781                                                     "gtk-path-bar-button-path");
782       if (! gtk_file_path_compare (file_path, tmp_path))
783         {
784           current_path = list;
785           break;
786         }
787     }
788
789   if (current_path)
790     {
791       for (list = path_bar->button_list; list; list = list->next)
792         {
793           update_button_appearance (path_bar,
794                                     GTK_WIDGET (list->data),
795                                     (list == current_path) ? TRUE : FALSE);
796         }
797       return TRUE;
798     }
799   return FALSE;
800 }
801
802 gboolean
803 _gtk_path_bar_set_path (GtkPathBar         *path_bar,
804                         const GtkFilePath  *file_path,
805                         GError            **error)
806 {
807   GtkFilePath *path;
808   gboolean first_directory = TRUE;
809   gboolean result;
810   GList *new_buttons = NULL;
811
812   g_return_val_if_fail (GTK_IS_PATH_BAR (path_bar), FALSE);
813   g_return_val_if_fail (file_path != NULL, FALSE);
814
815   result = TRUE;
816
817   /* Check whether the new path is already present in the pathbar as buttons.
818    * This could be a parent directory or a previous selected subdirectory.
819    */
820   if (gtk_path_bar_check_parent_path (path_bar, file_path, path_bar->file_system))
821     return TRUE;
822
823   path = gtk_file_path_copy (file_path);
824
825   gtk_widget_push_composite_child ();
826
827   while (path != NULL)
828     {
829       GtkFilePath *parent_path = NULL;
830       GtkWidget *button;
831       const gchar *display_name;
832       GtkFileFolder *file_folder;
833       GtkFileInfo *file_info;
834       gboolean valid;
835       ButtonType button_type;
836
837       valid = gtk_file_system_get_parent (path_bar->file_system,
838                                           path,
839                                           &parent_path,
840                                           error);
841       if (!valid)
842         {
843           result = FALSE;
844           gtk_file_path_free (path);
845           break;
846         }
847
848       file_folder = gtk_file_system_get_folder (path_bar->file_system,
849                                                 parent_path ? parent_path : path,
850                                                 GTK_FILE_INFO_DISPLAY_NAME,
851                                                 NULL);
852       file_info = gtk_file_folder_get_info (file_folder, path, error);
853       g_object_unref (file_folder);
854
855       if (!file_info)
856         {
857           result = FALSE;
858
859           gtk_file_path_free (parent_path);
860           gtk_file_path_free (path);
861           break;
862         }
863
864       display_name = gtk_file_info_get_display_name (file_info);
865
866       button = make_directory_button (path_bar, display_name, path, first_directory);
867       gtk_file_info_free (file_info);
868       gtk_file_path_free (path);
869
870       new_buttons = g_list_prepend (new_buttons, button);
871
872       button_type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), "gtk-path-bar-button-type"));
873       if (button_type != NORMAL_BUTTON)
874         {
875           gtk_file_path_free (parent_path);
876           break;
877         }
878
879       path = parent_path;
880       first_directory = FALSE;
881     }
882
883   if (result)
884     {
885       GList *l;
886
887       gtk_path_bar_clear_buttons (path_bar);
888       path_bar->button_list = g_list_reverse (new_buttons);
889
890       for (l = path_bar->button_list; l; l = l->next)
891         {
892           GtkWidget *button = l->data;
893           gtk_container_add (GTK_CONTAINER (path_bar), button);
894         }
895     }
896   else
897     {
898       GList *l;
899
900       for (l = new_buttons; l; l = l->next)
901         {
902           GtkWidget *button = l->data;
903           gtk_widget_destroy (button);
904           gtk_widget_unref (button);
905         }
906
907       g_list_free (new_buttons);
908     }
909
910   gtk_widget_pop_composite_child ();
911
912   return result;
913 }
914
915
916 /* FIXME: This should be a construct-only property */
917 void
918 _gtk_path_bar_set_file_system (GtkPathBar    *path_bar,
919                                GtkFileSystem *file_system)
920 {
921   const char *home;
922   g_return_if_fail (GTK_IS_PATH_BAR (path_bar));
923
924   g_assert (path_bar->file_system == NULL);
925
926   path_bar->file_system = g_object_ref (file_system);
927
928   home = g_get_home_dir ();
929   path_bar->home_path = gtk_file_system_filename_to_path (path_bar->file_system, home);
930   path_bar->root_path = gtk_file_system_filename_to_path (path_bar->file_system, "/");
931
932 }