2 * Copyright (C) 2004 Red Hat, Inc., Jonathan Blandford <jrb@gnome.org>
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.
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.
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.
21 #include "gtkpathbar.h"
22 #include "gtktogglebutton.h"
26 #include "gtkmarshalers.h"
33 static guint path_bar_signals [LAST_SIGNAL] = { 0 };
36 G_DEFINE_TYPE (GtkPathBar,
41 static void gtk_path_bar_finalize (GObject *object);
42 static void gtk_path_bar_size_request (GtkWidget *widget,
43 GtkRequisition *requisition);
44 static void gtk_path_bar_size_allocate (GtkWidget *widget,
45 GtkAllocation *allocation);
46 static void gtk_path_bar_direction_changed (GtkWidget *widget,
47 GtkTextDirection direction);
48 static void gtk_path_bar_add (GtkContainer *container,
50 static void gtk_path_bar_remove (GtkContainer *container,
52 static void gtk_path_bar_forall (GtkContainer *container,
53 gboolean include_internals,
55 gpointer callback_data);
56 static void gtk_path_bar_scroll_up (GtkWidget *button, GtkPathBar *path_bar);
57 static void gtk_path_bar_scroll_down (GtkWidget *button, GtkPathBar *path_bar);
60 get_slider_button (GtkPathBar *path_bar)
64 gtk_widget_push_composite_child ();
66 button = gtk_button_new ();
67 gtk_container_add (GTK_CONTAINER (button), gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_OUT));
68 gtk_container_add (GTK_CONTAINER (path_bar), button);
69 gtk_widget_show_all (button);
71 gtk_widget_pop_composite_child ();
77 gtk_path_bar_init (GtkPathBar *path_bar)
79 GTK_WIDGET_SET_FLAGS (path_bar, GTK_NO_WINDOW);
80 gtk_widget_set_redraw_on_allocate (GTK_WIDGET (path_bar), FALSE);
82 path_bar->spacing = 3;
83 path_bar->up_slider_button = get_slider_button (path_bar);
84 path_bar->down_slider_button = get_slider_button (path_bar);
86 g_signal_connect (path_bar->up_slider_button, "clicked", G_CALLBACK (gtk_path_bar_scroll_up), path_bar);
87 g_signal_connect (path_bar->down_slider_button, "clicked", G_CALLBACK (gtk_path_bar_scroll_down), path_bar);
91 gtk_path_bar_class_init (GtkPathBarClass *path_bar_class)
93 GObjectClass *gobject_class;
94 GtkObjectClass *object_class;
95 GtkWidgetClass *widget_class;
96 GtkContainerClass *container_class;
98 gobject_class = (GObjectClass *) path_bar_class;
99 object_class = (GtkObjectClass *) path_bar_class;
100 widget_class = (GtkWidgetClass *) path_bar_class;
101 container_class = (GtkContainerClass *) path_bar_class;
103 gobject_class->finalize = gtk_path_bar_finalize;
105 widget_class->size_request = gtk_path_bar_size_request;
106 widget_class->size_allocate = gtk_path_bar_size_allocate;
107 widget_class->direction_changed = gtk_path_bar_direction_changed;
109 container_class->add = gtk_path_bar_add;
110 container_class->forall = gtk_path_bar_forall;
111 container_class->remove = gtk_path_bar_remove;
113 /* container_class->child_type = gtk_path_bar_child_type;*/
115 path_bar_signals [PATH_CLICKED] =
116 g_signal_new ("path_clicked",
117 G_OBJECT_CLASS_TYPE (object_class),
119 G_STRUCT_OFFSET (GtkPathBarClass, path_clicked),
121 _gtk_marshal_VOID__POINTER,
128 gtk_path_bar_finalize (GObject *object)
130 GtkPathBar *path_bar;
132 path_bar = GTK_PATH_BAR (object);
133 g_list_free (path_bar->button_list);
134 if (path_bar->home_directory)
135 gtk_file_path_free (path_bar->home_directory);
136 if (path_bar->home_icon)
137 g_object_unref (path_bar->home_icon);
138 if (path_bar->root_icon)
139 g_object_unref (path_bar->home_icon);
141 G_OBJECT_CLASS (gtk_path_bar_parent_class)->finalize (object);
146 * Ideally, our size is determined by another widget, and we are just filling
150 gtk_path_bar_size_request (GtkWidget *widget,
151 GtkRequisition *requisition)
153 GtkPathBar *path_bar;
154 GtkRequisition child_requisition;
157 path_bar = GTK_PATH_BAR (widget);
159 requisition->width = 0;
160 requisition->height = 0;
162 for (list = path_bar->button_list; list; list = list->next)
164 gtk_widget_size_request (GTK_WIDGET (list->data),
166 requisition->width = MAX (child_requisition.width, requisition->width);
167 requisition->height = MAX (child_requisition.height, requisition->height);
170 /* Add space for slider, if we have more than one path */
171 /* Theoretically, the slider could be bigger than the other button. But we're
172 * not going to worry about that now.
174 path_bar->slider_width = requisition->height / 2 + 5;
175 if (path_bar->button_list && path_bar->button_list->next != NULL)
176 requisition->width += (path_bar->spacing + path_bar->slider_width) * 2;
178 gtk_widget_size_request (path_bar->up_slider_button, &child_requisition);
179 gtk_widget_size_request (path_bar->down_slider_button, &child_requisition);
181 requisition->width += GTK_CONTAINER (widget)->border_width * 2;
182 requisition->height += GTK_CONTAINER (widget)->border_width * 2;
184 widget->requisition = *requisition;
188 gtk_path_bar_update_slider_buttons (GtkPathBar *path_bar)
190 GtkTextDirection direction;
192 direction = gtk_widget_get_direction (GTK_WIDGET (path_bar));
193 if (direction == GTK_TEXT_DIR_RTL)
197 arrow = gtk_bin_get_child (GTK_BIN (path_bar->up_slider_button));
198 g_object_set (arrow, "arrow_type", GTK_ARROW_RIGHT, NULL);
200 arrow = gtk_bin_get_child (GTK_BIN (path_bar->down_slider_button));
201 g_object_set (arrow, "arrow_type", GTK_ARROW_LEFT, NULL);
207 arrow = gtk_bin_get_child (GTK_BIN (path_bar->up_slider_button));
208 g_object_set (arrow, "arrow_type", GTK_ARROW_LEFT, NULL);
210 arrow = gtk_bin_get_child (GTK_BIN (path_bar->down_slider_button));
211 g_object_set (arrow, "arrow_type", GTK_ARROW_RIGHT, NULL);
214 if (path_bar->button_list)
218 button = path_bar->button_list->data;
219 if (gtk_widget_get_child_visible (button))
220 gtk_widget_set_sensitive (path_bar->down_slider_button, FALSE);
222 gtk_widget_set_sensitive (path_bar->down_slider_button, TRUE);
224 button = g_list_last (path_bar->button_list)->data;
225 if (gtk_widget_get_child_visible (button))
226 gtk_widget_set_sensitive (path_bar->up_slider_button, FALSE);
228 gtk_widget_set_sensitive (path_bar->up_slider_button, TRUE);
232 /* This is a tad complicated
235 gtk_path_bar_size_allocate (GtkWidget *widget,
236 GtkAllocation *allocation)
239 GtkPathBar *path_bar = GTK_PATH_BAR (widget);
240 GtkTextDirection direction;
241 GtkAllocation child_allocation;
242 GList *list, *first_button;
244 gint allocation_width;
246 gboolean need_sliders = FALSE;
247 gint up_slider_offset = 0;
248 gint down_slider_offset = 0;
250 widget->allocation = *allocation;
252 /* No path is set; we don't have to allocate anything. */
253 if (path_bar->button_list == NULL)
256 direction = gtk_widget_get_direction (widget);
257 border_width = (gint) GTK_CONTAINER (path_bar)->border_width;
258 allocation_width = allocation->width - 2 * border_width;
260 /* First, we check to see if we need the scrollbars. */
261 width = GTK_WIDGET (path_bar->button_list->data)->requisition.width;
262 for (list = path_bar->button_list->next; list; list = list->next)
264 child = GTK_WIDGET (list->data);
266 width += child->requisition.width + path_bar->spacing;
269 if (width <= allocation_width)
271 first_button = g_list_last (path_bar->button_list);
275 gboolean reached_end = FALSE;
276 gint slider_space = 2 * (path_bar->spacing + path_bar->slider_width);
278 if (path_bar->first_scrolled_button)
279 first_button = path_bar->first_scrolled_button;
281 first_button = path_bar->button_list;
284 /* To see how much space we have, and how many buttons we can display.
285 * We start at the first button, count forward until hit the new
286 * button, then count backwards.
288 /* Count down the path chain towards the end. */
289 width = GTK_WIDGET (first_button->data)->requisition.width;
290 list = first_button->prev;
291 while (list && !reached_end)
293 child = GTK_WIDGET (list->data);
295 if (width + child->requisition.width +
296 path_bar->spacing + slider_space > allocation_width)
299 width += child->requisition.width + path_bar->spacing;
304 /* Finally, we walk up, seeing how many of the previous buttons we can
306 while (first_button->next && ! reached_end)
308 child = GTK_WIDGET (first_button->next->data);
310 if (width + child->requisition.width + path_bar->spacing + slider_space > allocation_width)
316 width += child->requisition.width + path_bar->spacing;
317 first_button = first_button->next;
322 /* Now, we allocate space to the buttons */
323 child_allocation.y = allocation->y + border_width;
324 child_allocation.height = MAX (1, (gint) allocation->height - border_width * 2);
326 if (direction == GTK_TEXT_DIR_RTL)
328 child_allocation.x = allocation->x + allocation->width - border_width;
331 child_allocation.x -= (path_bar->spacing + path_bar->slider_width);
332 up_slider_offset = allocation->width - border_width - path_bar->slider_width;
337 child_allocation.x = allocation->x + border_width;
340 up_slider_offset = border_width;
341 child_allocation.x += (path_bar->spacing + path_bar->slider_width);
345 for (list = first_button; list; list = list->prev)
347 child = GTK_WIDGET (list->data);
349 child_allocation.width = child->requisition.width;
350 if (direction == GTK_TEXT_DIR_RTL)
351 child_allocation.x -= child_allocation.width;
353 /* Check to see if we've don't have any more space to allocate buttons */
354 if (need_sliders && direction == GTK_TEXT_DIR_RTL)
356 if (child_allocation.x - path_bar->spacing - path_bar->slider_width < widget->allocation.x + border_width)
359 else if (need_sliders && direction == GTK_TEXT_DIR_LTR)
361 if (child_allocation.x + child_allocation.width + path_bar->spacing + path_bar->slider_width >
362 widget->allocation.x + border_width + allocation_width)
366 gtk_widget_set_child_visible (list->data, TRUE);
367 gtk_widget_size_allocate (child, &child_allocation);
369 if (direction == GTK_TEXT_DIR_RTL)
371 child_allocation.x -= path_bar->spacing;
372 down_slider_offset = child_allocation.x - widget->allocation.x - path_bar->slider_width;
376 child_allocation.x += child_allocation.width + path_bar->spacing;
377 down_slider_offset = child_allocation.x - widget->allocation.x;
380 /* Now we go hide all the widgets that don't fit */
383 gtk_widget_set_child_visible (list->data, FALSE);
386 for (list = first_button->next; list; list = list->next)
388 gtk_widget_set_child_visible (list->data, FALSE);
393 child_allocation.width = path_bar->slider_width;
395 child_allocation.x = up_slider_offset + allocation->x;
396 gtk_widget_size_allocate (path_bar->up_slider_button, &child_allocation);
398 child_allocation.x = down_slider_offset + allocation->x;
399 gtk_widget_size_allocate (path_bar->down_slider_button, &child_allocation);
401 gtk_widget_set_child_visible (path_bar->up_slider_button, TRUE);
402 gtk_widget_set_child_visible (path_bar->down_slider_button, TRUE);
403 gtk_widget_show_all (path_bar->up_slider_button);
404 gtk_widget_show_all (path_bar->down_slider_button);
405 gtk_path_bar_update_slider_buttons (path_bar);
409 gtk_widget_set_child_visible (path_bar->up_slider_button, FALSE);
410 gtk_widget_set_child_visible (path_bar->down_slider_button, FALSE);
415 gtk_path_bar_direction_changed (GtkWidget *widget,
416 GtkTextDirection direction)
418 gtk_path_bar_update_slider_buttons (GTK_PATH_BAR (widget));
420 (* GTK_WIDGET_CLASS (gtk_path_bar_parent_class)->direction_changed) (widget, direction);
424 gtk_path_bar_add (GtkContainer *container,
427 gtk_widget_set_parent (widget, GTK_WIDGET (container));
431 gtk_path_bar_remove (GtkContainer *container,
434 GtkPathBar *path_bar;
437 path_bar = GTK_PATH_BAR (container);
439 children = path_bar->button_list;
443 if (widget == children->data)
445 gboolean was_visible;
447 was_visible = GTK_WIDGET_VISIBLE (widget);
448 gtk_widget_unparent (widget);
450 path_bar->button_list = g_list_remove_link (path_bar->button_list, children);
451 g_list_free (children);
454 gtk_widget_queue_resize (GTK_WIDGET (container));
458 children = children->next;
463 gtk_path_bar_forall (GtkContainer *container,
464 gboolean include_internals,
465 GtkCallback callback,
466 gpointer callback_data)
468 GtkPathBar *path_bar;
471 g_return_if_fail (callback != NULL);
472 path_bar = GTK_PATH_BAR (container);
474 children = path_bar->button_list;
478 child = children->data;
479 children = children->next;
481 (* callback) (child, callback_data);
484 (* callback) (path_bar->up_slider_button, callback_data);
485 (* callback) (path_bar->down_slider_button, callback_data);
489 gtk_path_bar_scroll_down (GtkWidget *button, GtkPathBar *path_bar)
492 GList *down_button = NULL;
493 GList *up_button = NULL;
494 gint space_available;
497 GtkTextDirection direction;
499 border_width = GTK_CONTAINER (path_bar)->border_width;
500 direction = gtk_widget_get_direction (GTK_WIDGET (path_bar));
502 /* We find the button at the 'down' end that we have to make
504 for (list = path_bar->button_list; list; list = list->next)
506 if (list->next && gtk_widget_get_child_visible (GTK_WIDGET (list->next->data)))
513 /* Find the last visible button on the 'up' end
515 for (list = g_list_last (path_bar->button_list); list; list = list->prev)
517 if (gtk_widget_get_child_visible (GTK_WIDGET (list->data)))
524 space_needed = GTK_WIDGET (down_button->data)->allocation.width + path_bar->spacing;
525 if (direction == GTK_TEXT_DIR_RTL)
526 space_available = GTK_WIDGET (path_bar)->allocation.x + GTK_WIDGET (path_bar)->allocation.width;
528 space_available = (GTK_WIDGET (path_bar)->allocation.x + GTK_WIDGET (path_bar)->allocation.width - border_width) -
529 (path_bar->down_slider_button->allocation.x + path_bar->down_slider_button->allocation.width);
531 /* We have space_available extra space that's not being used. We
532 * need space_needed space to make the button fit. So we walk down
533 * from the end, removing buttons until we get all the space we
535 while (space_available < space_needed)
537 space_available += GTK_WIDGET (up_button->data)->allocation.width + path_bar->spacing;
538 up_button = up_button->prev;
539 path_bar->first_scrolled_button = up_button;
544 gtk_path_bar_scroll_up (GtkWidget *button, GtkPathBar *path_bar)
548 for (list = g_list_last (path_bar->button_list); list; list = list->prev)
550 if (list->prev && gtk_widget_get_child_visible (GTK_WIDGET (list->prev->data)))
552 path_bar->first_scrolled_button = list;
560 /* Public functions. */
562 gtk_path_bar_clear_buttons (GtkPathBar *path_bar)
564 while (path_bar->button_list != NULL)
566 gtk_container_remove (GTK_CONTAINER (path_bar), path_bar->button_list->data);
568 path_bar->first_scrolled_button = NULL;
572 button_clicked_cb (GtkWidget *button,
576 GtkFilePath *file_path;
578 path_bar = button->parent;
579 g_assert (GTK_IS_PATH_BAR (path_bar));
581 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
583 file_path = g_object_get_data (G_OBJECT (button), "gtk-path-bar-button-path");
584 g_signal_emit (path_bar, path_bar_signals [PATH_CLICKED], 0, file_path);
588 update_button_appearance (GtkWidget *button,
589 gboolean current_dir)
592 const gchar *dir_name;
594 dir_name = (const gchar *) g_object_get_data (G_OBJECT (button),
595 "gtk-path-bar-button-dir-name");
596 label = gtk_bin_get_child (GTK_BIN (button));
602 markup = g_markup_printf_escaped ("<b>%s</b>", dir_name);
603 gtk_label_set_markup (GTK_LABEL (label), markup);
608 gtk_label_set_text (GTK_LABEL (label), dir_name);
611 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)) != current_dir)
613 g_signal_handlers_block_by_func (G_OBJECT (button), button_clicked_cb, NULL);
614 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), current_dir);
615 g_signal_handlers_unblock_by_func (G_OBJECT (button), button_clicked_cb, NULL);
619 /* Since gtk_file_path_free() can be a macro, we provide a real function that
620 * can be used as a callback.
623 file_path_destroy (GtkFilePath *path)
625 gtk_file_path_free (path);
629 make_directory_button (const char *dir_name,
631 gboolean current_dir)
633 GtkWidget *button, *label;
635 button = gtk_toggle_button_new ();
636 label = gtk_label_new (NULL);
638 g_signal_connect (button, "clicked",
639 G_CALLBACK (button_clicked_cb),
642 g_object_set_data_full (G_OBJECT (button), "gtk-path-bar-button-dir-name",
644 (GDestroyNotify) g_free);
645 g_object_set_data_full (G_OBJECT (button), "gtk-path-bar-button-path",
646 gtk_file_path_new_dup (gtk_file_path_get_string (path)),
647 (GDestroyNotify) file_path_destroy);
649 gtk_container_add (GTK_CONTAINER (button), label);
650 gtk_widget_show_all (button);
652 update_button_appearance (button, current_dir);
658 gtk_path_bar_check_parent_path (GtkPathBar *path_bar,
659 const GtkFilePath *file_path,
660 GtkFileSystem *file_system)
663 GList *current_path = NULL;
665 for (list = path_bar->button_list; list; list = list->next)
667 GtkFilePath *tmp_path;
669 tmp_path = (GtkFilePath *) g_object_get_data (G_OBJECT (list->data),
670 "gtk-path-bar-button-path");
671 if (! gtk_file_path_compare (file_path, tmp_path))
681 for (list = path_bar->button_list; list; list = list->next)
683 update_button_appearance (GTK_WIDGET (list->data),
684 (list == current_path) ? TRUE : FALSE);
692 _gtk_path_bar_set_path (GtkPathBar *path_bar,
693 const GtkFilePath *file_path,
694 GtkFileSystem *file_system,
698 gboolean first_directory = TRUE;
701 g_return_val_if_fail (GTK_IS_PATH_BAR (path_bar), FALSE);
702 g_return_val_if_fail (file_path != NULL, FALSE);
703 g_return_val_if_fail (file_system != NULL, FALSE);
707 if (gtk_path_bar_check_parent_path (path_bar, file_path, file_system))
710 gtk_path_bar_clear_buttons (path_bar);
711 path = gtk_file_path_copy (file_path);
713 gtk_widget_push_composite_child ();
717 GtkFilePath *parent_path = NULL;
719 const gchar *display_name;
721 GtkFileFolder *file_folder;
722 GtkFileInfo *file_info;
725 valid = gtk_file_system_get_parent (file_system,
732 g_propagate_error (error, err);
733 gtk_file_path_free (path);
738 file_folder = gtk_file_system_get_folder (file_system, parent_path,
739 GTK_FILE_INFO_DISPLAY_NAME, NULL);
741 file_folder = gtk_file_system_get_folder (file_system, path,
742 GTK_FILE_INFO_DISPLAY_NAME, NULL);
744 file_info = gtk_file_folder_get_info (file_folder, path, &err);
748 g_propagate_error (error, err);
749 g_object_unref (file_folder);
750 gtk_file_path_free (parent_path);
751 gtk_file_path_free (path);
755 display_name = gtk_file_info_get_display_name (file_info);
756 /* FIXME: Do this better */
757 if (! strcmp ("/", display_name))
758 display_name = " / ";
759 button = make_directory_button (display_name, path, first_directory);
760 gtk_file_info_free (file_info);
761 gtk_file_path_free (path);
762 g_object_unref (file_folder);
764 gtk_container_add (GTK_CONTAINER (path_bar), button);
765 path_bar->button_list = g_list_prepend (path_bar->button_list, button);
768 first_directory = FALSE;
771 gtk_widget_pop_composite_child ();
773 path_bar->button_list = g_list_reverse (path_bar->button_list);
779 gtk_path_bar_set_root_icon (GtkPathBar *path_bar,
780 GdkPixbuf *root_icon)
782 g_return_if_fail (GTK_IS_PATH_BAR (path_bar));
785 path_bar->home_icon = g_object_ref (root_icon);
787 if (path_bar->root_icon)
788 g_object_unref (root_icon);
790 path_bar->root_icon = root_icon;
794 gtk_path_bar_set_home_icon (GtkPathBar *path_bar,
795 const GtkFilePath *home_directory,
796 GdkPixbuf *home_icon)
798 g_return_if_fail (GTK_IS_PATH_BAR (path_bar));
801 g_object_ref (home_icon);
803 if (path_bar->home_directory != NULL)
804 gtk_file_path_free (path_bar->home_directory);
805 if (path_bar->home_icon)
806 g_object_unref (home_icon);
809 path_bar->home_directory = gtk_file_path_new_dup (gtk_file_path_get_string (home_directory));
811 path_bar->home_directory = NULL;
812 path_bar->home_icon = home_icon;