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.
20 #include "gtkpathbar.h"
21 #include "gtktogglebutton.h"
25 #include "gtkmarshalers.h"
32 static guint path_bar_signals [LAST_SIGNAL] = { 0 };
35 G_DEFINE_TYPE (GtkPathBar,
40 static void gtk_path_bar_finalize (GObject *object);
41 static void gtk_path_bar_size_request (GtkWidget *widget,
42 GtkRequisition *requisition);
43 static void gtk_path_bar_size_allocate (GtkWidget *widget,
44 GtkAllocation *allocation);
45 static void gtk_path_bar_direction_changed (GtkWidget *widget,
46 GtkTextDirection direction);
47 static void gtk_path_bar_add (GtkContainer *container,
49 static void gtk_path_bar_remove (GtkContainer *container,
51 static void gtk_path_bar_forall (GtkContainer *container,
52 gboolean include_internals,
54 gpointer callback_data);
55 static void gtk_path_bar_scroll_up (GtkWidget *button, GtkPathBar *path_bar);
56 static void gtk_path_bar_scroll_down (GtkWidget *button, GtkPathBar *path_bar);
59 get_slider_button (GtkPathBar *path_bar)
63 button = gtk_button_new ();
64 gtk_container_add (GTK_CONTAINER (button), gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_OUT));
65 gtk_container_add (GTK_CONTAINER (path_bar), button);
66 gtk_widget_show_all (button);
72 gtk_path_bar_init (GtkPathBar *path_bar)
74 GTK_WIDGET_SET_FLAGS (path_bar, GTK_NO_WINDOW);
75 gtk_widget_set_redraw_on_allocate (GTK_WIDGET (path_bar), FALSE);
77 path_bar->spacing = 3;
78 path_bar->up_slider_button = get_slider_button (path_bar);
79 path_bar->down_slider_button = get_slider_button (path_bar);
81 g_signal_connect (path_bar->up_slider_button, "clicked", G_CALLBACK (gtk_path_bar_scroll_up), path_bar);
82 g_signal_connect (path_bar->down_slider_button, "clicked", G_CALLBACK (gtk_path_bar_scroll_down), path_bar);
86 gtk_path_bar_class_init (GtkPathBarClass *path_bar_class)
88 GObjectClass *gobject_class;
89 GtkObjectClass *object_class;
90 GtkWidgetClass *widget_class;
91 GtkContainerClass *container_class;
93 gobject_class = (GObjectClass *) path_bar_class;
94 object_class = (GtkObjectClass *) path_bar_class;
95 widget_class = (GtkWidgetClass *) path_bar_class;
96 container_class = (GtkContainerClass *) path_bar_class;
98 gobject_class->finalize = gtk_path_bar_finalize;
100 widget_class->size_request = gtk_path_bar_size_request;
101 widget_class->size_allocate = gtk_path_bar_size_allocate;
102 widget_class->direction_changed = gtk_path_bar_direction_changed;
104 container_class->add = gtk_path_bar_add;
105 container_class->forall = gtk_path_bar_forall;
106 container_class->remove = gtk_path_bar_remove;
108 /* container_class->child_type = gtk_path_bar_child_type;*/
110 path_bar_signals [PATH_CLICKED] =
111 g_signal_new ("path_clicked",
112 G_OBJECT_CLASS_TYPE (object_class),
114 G_STRUCT_OFFSET (GtkPathBarClass, path_clicked),
116 _gtk_marshal_VOID__STRING,
123 gtk_path_bar_finalize (GObject *object)
125 GtkPathBar *path_bar;
127 path_bar = GTK_PATH_BAR (object);
128 g_list_free (path_bar->button_list);
130 G_OBJECT_CLASS (parent_class)->finalize (object);
135 * Ideally, our size is determined by another widget, and we are just filling
139 gtk_path_bar_size_request (GtkWidget *widget,
140 GtkRequisition *requisition)
142 GtkPathBar *path_bar;
143 GtkRequisition child_requisition;
146 path_bar = GTK_PATH_BAR (widget);
148 requisition->width = 0;
149 requisition->height = 0;
151 for (list = path_bar->button_list; list; list = list->next)
153 gtk_widget_size_request (GTK_WIDGET (list->data),
155 requisition->width = MAX (child_requisition.width, requisition->width);
156 requisition->height = MAX (child_requisition.height, requisition->height);
159 /* Add space for slider, if we have more than one path */
160 /* Theoretically, the slider could be bigger than the other button. But we're
161 * not going to worry about that now.
163 path_bar->slider_width = requisition->height / 2 + 5;
164 if (path_bar->button_list && path_bar->button_list->next != NULL)
165 requisition->width += (path_bar->spacing + path_bar->slider_width) * 2;
167 gtk_widget_size_request (path_bar->up_slider_button, &child_requisition);
168 gtk_widget_size_request (path_bar->down_slider_button, &child_requisition);
170 requisition->width += GTK_CONTAINER (widget)->border_width * 2;
171 requisition->height += GTK_CONTAINER (widget)->border_width * 2;
173 widget->requisition = *requisition;
177 gtk_path_bar_update_slider_buttons (GtkPathBar *path_bar)
179 GtkTextDirection direction;
181 direction = gtk_widget_get_direction (GTK_WIDGET (path_bar));
182 if (direction == GTK_TEXT_DIR_RTL)
186 arrow = gtk_bin_get_child (GTK_BIN (path_bar->up_slider_button));
187 g_object_set (arrow, "arrow_type", GTK_ARROW_RIGHT, NULL);
189 arrow = gtk_bin_get_child (GTK_BIN (path_bar->down_slider_button));
190 g_object_set (arrow, "arrow_type", GTK_ARROW_LEFT, NULL);
196 arrow = gtk_bin_get_child (GTK_BIN (path_bar->up_slider_button));
197 g_object_set (arrow, "arrow_type", GTK_ARROW_LEFT, NULL);
199 arrow = gtk_bin_get_child (GTK_BIN (path_bar->down_slider_button));
200 g_object_set (arrow, "arrow_type", GTK_ARROW_RIGHT, NULL);
203 if (path_bar->button_list)
207 button = path_bar->button_list->data;
208 if (gtk_widget_get_child_visible (button))
209 gtk_widget_set_sensitive (path_bar->down_slider_button, FALSE);
211 gtk_widget_set_sensitive (path_bar->down_slider_button, TRUE);
213 button = g_list_last (path_bar->button_list)->data;
214 if (gtk_widget_get_child_visible (button))
215 gtk_widget_set_sensitive (path_bar->up_slider_button, FALSE);
217 gtk_widget_set_sensitive (path_bar->up_slider_button, TRUE);
221 /* This is a tad complicated
224 gtk_path_bar_size_allocate (GtkWidget *widget,
225 GtkAllocation *allocation)
228 GtkPathBar *path_bar = GTK_PATH_BAR (widget);
229 GtkTextDirection direction;
230 GtkAllocation child_allocation;
231 GList *list, *first_button;
233 gint allocation_width;
235 gboolean need_sliders = FALSE;
236 gint up_slider_offset = 0;
237 gint down_slider_offset = 0;
239 widget->allocation = *allocation;
241 /* No path is set; we don't have to allocate anything. */
242 if (path_bar->button_list == NULL)
245 direction = gtk_widget_get_direction (widget);
246 border_width = (gint) GTK_CONTAINER (path_bar)->border_width;
247 allocation_width = allocation->width - 2 * border_width;
249 /* First, we check to see if we need the scrollbars. */
250 width = GTK_WIDGET (path_bar->button_list->data)->requisition.width;
251 for (list = path_bar->button_list->next; list; list = list->next)
253 child = GTK_WIDGET (list->data);
255 width += child->requisition.width + path_bar->spacing;
258 if (width <= allocation_width)
260 first_button = g_list_last (path_bar->button_list);
264 gboolean reached_end = FALSE;
265 gint slider_space = 2 * (path_bar->spacing + path_bar->slider_width);
267 if (path_bar->first_scrolled_button)
268 first_button = path_bar->first_scrolled_button;
270 first_button = path_bar->button_list;
273 /* To see how much space we have, and how many buttons we can display.
274 * We start at the first button, count forward until hit the new
275 * button, then count backwards.
277 /* Count down the path chain towards the end. */
278 width = GTK_WIDGET (first_button->data)->requisition.width;
279 list = first_button->prev;
280 while (list && !reached_end)
282 child = GTK_WIDGET (list->data);
284 if (width + child->requisition.width +
285 path_bar->spacing + slider_space > allocation_width)
288 width += child->requisition.width + path_bar->spacing;
293 /* Finally, we walk up, seeing how many of the previous buttons we can
295 while (first_button->next && ! reached_end)
297 child = GTK_WIDGET (first_button->next->data);
299 if (width + child->requisition.width + path_bar->spacing + slider_space > allocation_width)
305 width += child->requisition.width + path_bar->spacing;
306 first_button = first_button->next;
311 /* Now, we allocate space to the buttons */
312 child_allocation.y = allocation->y + border_width;
313 child_allocation.height = MAX (1, (gint) allocation->height - border_width * 2);
315 if (direction == GTK_TEXT_DIR_RTL)
317 child_allocation.x = allocation->x + allocation->width - border_width;
320 child_allocation.x -= (path_bar->spacing + path_bar->slider_width);
321 up_slider_offset = allocation->width - border_width - path_bar->slider_width;
326 child_allocation.x = allocation->x + border_width;
329 up_slider_offset = border_width;
330 child_allocation.x += (path_bar->spacing + path_bar->slider_width);
334 for (list = first_button; list; list = list->prev)
336 child = GTK_WIDGET (list->data);
338 child_allocation.width = child->requisition.width;
339 if (direction == GTK_TEXT_DIR_RTL)
340 child_allocation.x -= child_allocation.width;
342 /* Check to see if we've don't have any more space to allocate buttons */
343 if (need_sliders && direction == GTK_TEXT_DIR_RTL)
345 if (child_allocation.x - path_bar->spacing - path_bar->slider_width < widget->allocation.x + border_width)
348 else if (need_sliders && direction == GTK_TEXT_DIR_LTR)
350 if (child_allocation.x + child_allocation.width + path_bar->spacing + path_bar->slider_width >
351 widget->allocation.x + border_width + allocation_width)
355 gtk_widget_set_child_visible (list->data, TRUE);
356 gtk_widget_size_allocate (child, &child_allocation);
358 if (direction == GTK_TEXT_DIR_RTL)
360 child_allocation.x -= path_bar->spacing;
361 down_slider_offset = child_allocation.x - widget->allocation.x - path_bar->slider_width;
365 child_allocation.x += child_allocation.width + path_bar->spacing;
366 down_slider_offset = child_allocation.x - widget->allocation.x;
369 /* Now we go hide all the widgets that don't fit */
372 gtk_widget_set_child_visible (list->data, FALSE);
375 for (list = first_button->next; list; list = list->next)
377 gtk_widget_set_child_visible (list->data, FALSE);
382 child_allocation.width = path_bar->slider_width;
384 child_allocation.x = up_slider_offset + allocation->x;
385 gtk_widget_size_allocate (path_bar->up_slider_button, &child_allocation);
387 child_allocation.x = down_slider_offset + allocation->x;
388 gtk_widget_size_allocate (path_bar->down_slider_button, &child_allocation);
390 gtk_widget_set_child_visible (path_bar->up_slider_button, TRUE);
391 gtk_widget_set_child_visible (path_bar->down_slider_button, TRUE);
392 gtk_widget_show_all (path_bar->up_slider_button);
393 gtk_widget_show_all (path_bar->down_slider_button);
394 gtk_path_bar_update_slider_buttons (path_bar);
398 gtk_widget_set_child_visible (path_bar->up_slider_button, FALSE);
399 gtk_widget_set_child_visible (path_bar->down_slider_button, FALSE);
404 gtk_path_bar_direction_changed (GtkWidget *widget,
405 GtkTextDirection direction)
407 gtk_path_bar_update_slider_buttons (GTK_PATH_BAR (widget));
409 (* GTK_WIDGET_CLASS (gtk_path_bar_parent_class)->direction_changed) (widget, direction);
413 gtk_path_bar_add (GtkContainer *container,
416 gtk_widget_set_parent (widget, GTK_WIDGET (container));
420 gtk_path_bar_remove (GtkContainer *container,
423 GtkPathBar *path_bar;
426 path_bar = GTK_PATH_BAR (container);
428 children = path_bar->button_list;
432 if (widget == children->data)
434 gboolean was_visible;
436 was_visible = GTK_WIDGET_VISIBLE (widget);
437 gtk_widget_unparent (widget);
439 path_bar->button_list = g_list_remove_link (path_bar->button_list, children);
440 g_list_free (children);
443 gtk_widget_queue_resize (GTK_WIDGET (container));
447 children = children->next;
452 gtk_path_bar_forall (GtkContainer *container,
453 gboolean include_internals,
454 GtkCallback callback,
455 gpointer callback_data)
457 GtkPathBar *path_bar;
460 g_return_if_fail (callback != NULL);
461 path_bar = GTK_PATH_BAR (container);
463 children = path_bar->button_list;
467 child = children->data;
468 children = children->next;
470 (* callback) (child, callback_data);
473 (* callback) (path_bar->up_slider_button, callback_data);
474 (* callback) (path_bar->down_slider_button, callback_data);
478 gtk_path_bar_scroll_down (GtkWidget *button, GtkPathBar *path_bar)
481 GList *down_button = NULL;
482 GList *up_button = NULL;
483 gint space_available;
486 GtkTextDirection direction;
488 border_width = GTK_CONTAINER (path_bar)->border_width;
489 direction = gtk_widget_get_direction (GTK_WIDGET (path_bar));
491 /* We find the button at the 'down' end that we have to make
493 for (list = path_bar->button_list; list; list = list->next)
495 if (list->next && gtk_widget_get_child_visible (GTK_WIDGET (list->next->data)))
502 /* Find the last visible button on the 'up' end
504 for (list = g_list_last (path_bar->button_list); list; list = list->prev)
506 if (gtk_widget_get_child_visible (GTK_WIDGET (list->data)))
513 space_needed = GTK_WIDGET (down_button->data)->allocation.width + path_bar->spacing;
514 if (direction == GTK_TEXT_DIR_RTL)
515 space_available = GTK_WIDGET (path_bar)->allocation.x + GTK_WIDGET (path_bar)->allocation.width;
517 space_available = (GTK_WIDGET (path_bar)->allocation.x + GTK_WIDGET (path_bar)->allocation.width - border_width) -
518 (path_bar->down_slider_button->allocation.x + path_bar->down_slider_button->allocation.width);
520 /* We have space_available extra space that's not being used. We
521 * need space_needed space to make the button fit. So we walk down
522 * from the end, removing buttons until we get all the space we
524 while (space_available < space_needed)
526 space_available += GTK_WIDGET (up_button->data)->allocation.width + path_bar->spacing;
527 up_button = up_button->prev;
528 path_bar->first_scrolled_button = up_button;
533 gtk_path_bar_scroll_up (GtkWidget *button, GtkPathBar *path_bar)
537 for (list = g_list_last (path_bar->button_list); list; list = list->prev)
539 if (list->prev && gtk_widget_get_child_visible (GTK_WIDGET (list->prev->data)))
541 path_bar->first_scrolled_button = list;
549 /* Public functions. */
551 gtk_path_bar_clear_buttons (GtkPathBar *path_bar)
553 while (path_bar->button_list != NULL)
555 gtk_container_remove (GTK_CONTAINER (path_bar), path_bar->button_list->data);
557 path_bar->first_scrolled_button = NULL;
561 button_clicked_cb (GtkWidget *button,
566 path_bar = button->parent;
569 g_signal_emit (path_bar, path_bar_signals [PATH_CLICKED], 0, (const char *) data);
573 make_directory_button (const char *dir_name,
575 gboolean current_dir)
577 GtkWidget *button, *label;
579 button = gtk_toggle_button_new ();
583 g_signal_connect (G_OBJECT (button), "toggled",
584 G_CALLBACK (gtk_toggle_button_set_active),
585 GINT_TO_POINTER (TRUE));
591 str = g_strdup (gtk_file_path_get_string (path));
592 g_signal_connect (button, "clicked", G_CALLBACK (button_clicked_cb), str);
593 g_object_weak_ref (G_OBJECT (button), (GWeakNotify) g_free, str);
596 label = gtk_label_new (NULL);
602 label_str = g_markup_printf_escaped ("<b>%s</b>", dir_name);
603 gtk_label_set_markup (GTK_LABEL (label), label_str);
606 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
610 gtk_label_set_text (GTK_LABEL (label), dir_name);
613 gtk_container_add (GTK_CONTAINER (button), label);
614 gtk_widget_show_all (button);
621 gtk_path_bar_set_path (GtkPathBar *path_bar,
622 const GtkFilePath *file_path,
623 GtkFileSystem *file_system,
626 gboolean valid = TRUE;
628 gboolean first_directory = TRUE;
630 gtk_path_bar_clear_buttons (path_bar);
631 path = gtk_file_path_copy (file_path);
635 GtkFilePath *parent_path = NULL;
637 const gchar *display_name;
640 valid = gtk_file_system_get_parent (file_system,
646 g_propagate_error (error, err);
648 gtk_file_path_free (path);
654 GtkFileFolder *file_folder;
655 GtkFileInfo *file_info;
657 file_folder = gtk_file_system_get_folder (file_system, parent_path,
658 GTK_FILE_INFO_DISPLAY_NAME, NULL);
659 file_info = gtk_file_folder_get_info (file_folder, path, NULL);
660 display_name = gtk_file_info_get_display_name (file_info);
661 button = make_directory_button (display_name, path, first_directory);
662 gtk_file_info_free (file_info);
663 /* FIXME: ask owen about mem management. gtk_file_folder_free (file_folder); */
667 /* We've reached the root node */
668 /* FIXME: gtk_file_system_get_root_display_name() or something */
669 button = make_directory_button (gtk_file_path_get_string (path),
670 path, first_directory);
673 gtk_container_add (GTK_CONTAINER (path_bar), button);
674 path_bar->button_list = g_list_prepend (path_bar->button_list, button);
675 gtk_file_path_free (path);
678 first_directory = FALSE;
681 path_bar->button_list = g_list_reverse (path_bar->button_list);