X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtkpathbar.c;h=24c038dd7b9b8f8e4e506ee59409713153e9f0cc;hb=eb76287995fecbb4bef991d29066549570a48164;hp=0f1aab9eac1c117087d4470f3fd698a39872ec4c;hpb=77c70924894bea1b3cc27884050fa4fbb9b483bf;p=~andy%2Fgtk diff --git a/gtk/gtkpathbar.c b/gtk/gtkpathbar.c index 0f1aab9ea..24c038dd7 100644 --- a/gtk/gtkpathbar.c +++ b/gtk/gtkpathbar.c @@ -1,4 +1,5 @@ -/* gtkpathbar.h +/* -*- Mode: C; c-file-style: "gnu"; tab-width: 8 -*- */ +/* gtkpathbar.c * Copyright (C) 2004 Red Hat, Inc., Jonathan Blandford * * This library is free software; you can redistribute it and/or @@ -17,11 +18,16 @@ * Boston, MA 02111-1307, USA. */ -#include -#include +#include "config.h" + #include "gtkpathbar.h" + +#include + #include "gtktogglebutton.h" +#include "gtkalignment.h" #include "gtkarrow.h" +#include "gtkdnd.h" #include "gtkimage.h" #include "gtkintl.h" #include "gtkicontheme.h" @@ -31,6 +37,7 @@ #include "gtkmain.h" #include "gtkmarshalers.h" + enum { PATH_CLICKED, LAST_SIGNAL @@ -45,10 +52,12 @@ typedef enum { #define BUTTON_DATA(x) ((ButtonData *)(x)) +#define SCROLL_DELAY_FACTOR 5 + static guint path_bar_signals [LAST_SIGNAL] = { 0 }; /* Icon size for if we can't get it from the theme */ -#define FALLBACK_ICON_SIZE 20 +#define FALLBACK_ICON_SIZE 16 typedef struct _ButtonData ButtonData; @@ -57,20 +66,33 @@ struct _ButtonData GtkWidget *button; ButtonType type; char *dir_name; - GtkFilePath *path; + GFile *file; GtkWidget *image; GtkWidget *label; - gboolean ignore_changes; + GCancellable *cancellable; + guint ignore_changes : 1; + guint file_is_hidden : 1; }; +/* This macro is used to check if a button can be used as a fake root. + * All buttons in front of a fake root are automatically hidden when in a + * directory below a fake root and replaced with the "<" arrow button. + */ +#define BUTTON_IS_FAKE_ROOT(button) ((button)->type == HOME_BUTTON) -G_DEFINE_TYPE (GtkPathBar, - gtk_path_bar, - GTK_TYPE_CONTAINER); +G_DEFINE_TYPE (GtkPathBar, gtk_path_bar, GTK_TYPE_CONTAINER) static void gtk_path_bar_finalize (GObject *object); static void gtk_path_bar_dispose (GObject *object); -static void gtk_path_bar_size_request (GtkWidget *widget, - GtkRequisition *requisition); +static void gtk_path_bar_realize (GtkWidget *widget); +static void gtk_path_bar_unrealize (GtkWidget *widget); +static void gtk_path_bar_get_preferred_width (GtkWidget *widget, + gint *minimum, + gint *natural); +static void gtk_path_bar_get_preferred_height (GtkWidget *widget, + gint *minimum, + gint *natural); +static void gtk_path_bar_map (GtkWidget *widget); +static void gtk_path_bar_unmap (GtkWidget *widget); static void gtk_path_bar_size_allocate (GtkWidget *widget, GtkAllocation *allocation); static void gtk_path_bar_add (GtkContainer *container, @@ -81,12 +103,28 @@ static void gtk_path_bar_forall (GtkContainer *container, gboolean include_internals, GtkCallback callback, gpointer callback_data); -static void gtk_path_bar_scroll_up (GtkWidget *button, +static gboolean gtk_path_bar_scroll (GtkWidget *widget, + GdkEventScroll *event); +static void gtk_path_bar_scroll_up (GtkPathBar *path_bar); +static void gtk_path_bar_scroll_down (GtkPathBar *path_bar); +static void gtk_path_bar_stop_scrolling (GtkPathBar *path_bar); +static gboolean gtk_path_bar_slider_up_defocus (GtkWidget *widget, + GdkEventButton *event, + GtkPathBar *path_bar); +static gboolean gtk_path_bar_slider_down_defocus (GtkWidget *widget, + GdkEventButton *event, GtkPathBar *path_bar); -static void gtk_path_bar_scroll_down (GtkWidget *button, +static gboolean gtk_path_bar_slider_button_press (GtkWidget *widget, + GdkEventButton *event, GtkPathBar *path_bar); -static void gtk_path_bar_style_set (GtkWidget *widget, - GtkStyle *previous_style); +static gboolean gtk_path_bar_slider_button_release(GtkWidget *widget, + GdkEventButton *event, + GtkPathBar *path_bar); +static void gtk_path_bar_grab_notify (GtkWidget *widget, + gboolean was_grabbed); +static void gtk_path_bar_state_changed (GtkWidget *widget, + GtkStateType previous_state); +static void gtk_path_bar_style_updated (GtkWidget *widget); static void gtk_path_bar_screen_changed (GtkWidget *widget, GdkScreen *previous_screen); static void gtk_path_bar_check_icon_theme (GtkPathBar *path_bar); @@ -94,19 +132,41 @@ static void gtk_path_bar_update_button_appearance (GtkPathBar *path_bar, ButtonData *button_data, gboolean current_dir); +static void +on_slider_unmap (GtkWidget *widget, + GtkPathBar *path_bar) +{ + if (path_bar->timer && + ((widget == path_bar->up_slider_button && path_bar->scrolling_up) || + (widget == path_bar->down_slider_button && path_bar->scrolling_down))) + gtk_path_bar_stop_scrolling (path_bar); +} + static GtkWidget * get_slider_button (GtkPathBar *path_bar, GtkArrowType arrow_type) { GtkWidget *button; + AtkObject *atk_obj; gtk_widget_push_composite_child (); button = gtk_button_new (); - gtk_container_add (GTK_CONTAINER (button), gtk_arrow_new (arrow_type, GTK_SHADOW_OUT)); + atk_obj = gtk_widget_get_accessible (button); + if (arrow_type == GTK_ARROW_LEFT) + atk_object_set_name (atk_obj, _("Up Path")); + else + atk_object_set_name (atk_obj, _("Down Path")); + + gtk_button_set_focus_on_click (GTK_BUTTON (button), FALSE); + gtk_container_add (GTK_CONTAINER (button), + gtk_arrow_new (arrow_type, GTK_SHADOW_OUT)); gtk_container_add (GTK_CONTAINER (path_bar), button); gtk_widget_show_all (button); + g_signal_connect (G_OBJECT (button), "unmap", + G_CALLBACK (on_slider_unmap), path_bar); + gtk_widget_pop_composite_child (); return button; @@ -115,54 +175,81 @@ get_slider_button (GtkPathBar *path_bar, static void gtk_path_bar_init (GtkPathBar *path_bar) { - GTK_WIDGET_SET_FLAGS (path_bar, GTK_NO_WINDOW); + gtk_widget_set_has_window (GTK_WIDGET (path_bar), FALSE); gtk_widget_set_redraw_on_allocate (GTK_WIDGET (path_bar), FALSE); - path_bar->spacing = 3; + path_bar->get_info_cancellable = NULL; + + path_bar->spacing = 0; path_bar->up_slider_button = get_slider_button (path_bar, GTK_ARROW_LEFT); path_bar->down_slider_button = get_slider_button (path_bar, GTK_ARROW_RIGHT); path_bar->icon_size = FALLBACK_ICON_SIZE; - - g_signal_connect (path_bar->up_slider_button, "clicked", G_CALLBACK (gtk_path_bar_scroll_up), path_bar); - g_signal_connect (path_bar->down_slider_button, "clicked", G_CALLBACK (gtk_path_bar_scroll_down), path_bar); + + g_signal_connect_swapped (path_bar->up_slider_button, "clicked", + G_CALLBACK (gtk_path_bar_scroll_up), path_bar); + g_signal_connect_swapped (path_bar->down_slider_button, "clicked", + G_CALLBACK (gtk_path_bar_scroll_down), path_bar); + + g_signal_connect (path_bar->up_slider_button, "focus-out-event", + G_CALLBACK (gtk_path_bar_slider_up_defocus), path_bar); + g_signal_connect (path_bar->down_slider_button, "focus-out-event", + G_CALLBACK (gtk_path_bar_slider_down_defocus), path_bar); + + g_signal_connect (path_bar->up_slider_button, "button-press-event", + G_CALLBACK (gtk_path_bar_slider_button_press), path_bar); + g_signal_connect (path_bar->up_slider_button, "button-release-event", + G_CALLBACK (gtk_path_bar_slider_button_release), path_bar); + g_signal_connect (path_bar->down_slider_button, "button-press-event", + G_CALLBACK (gtk_path_bar_slider_button_press), path_bar); + g_signal_connect (path_bar->down_slider_button, "button-release-event", + G_CALLBACK (gtk_path_bar_slider_button_release), path_bar); } static void gtk_path_bar_class_init (GtkPathBarClass *path_bar_class) { GObjectClass *gobject_class; - GtkObjectClass *object_class; GtkWidgetClass *widget_class; GtkContainerClass *container_class; gobject_class = (GObjectClass *) path_bar_class; - object_class = (GtkObjectClass *) path_bar_class; widget_class = (GtkWidgetClass *) path_bar_class; container_class = (GtkContainerClass *) path_bar_class; gobject_class->finalize = gtk_path_bar_finalize; gobject_class->dispose = gtk_path_bar_dispose; - widget_class->size_request = gtk_path_bar_size_request; + widget_class->get_preferred_width = gtk_path_bar_get_preferred_width; + widget_class->get_preferred_height = gtk_path_bar_get_preferred_height; + widget_class->realize = gtk_path_bar_realize; + widget_class->unrealize = gtk_path_bar_unrealize; + widget_class->map = gtk_path_bar_map; + widget_class->unmap = gtk_path_bar_unmap; widget_class->size_allocate = gtk_path_bar_size_allocate; - widget_class->style_set = gtk_path_bar_style_set; + widget_class->style_updated = gtk_path_bar_style_updated; widget_class->screen_changed = gtk_path_bar_screen_changed; + widget_class->grab_notify = gtk_path_bar_grab_notify; + widget_class->state_changed = gtk_path_bar_state_changed; + widget_class->scroll_event = gtk_path_bar_scroll; container_class->add = gtk_path_bar_add; container_class->forall = gtk_path_bar_forall; container_class->remove = gtk_path_bar_remove; + gtk_container_class_handle_border_width (container_class); /* FIXME: */ /* container_class->child_type = gtk_path_bar_child_type;*/ path_bar_signals [PATH_CLICKED] = - g_signal_new ("path_clicked", - G_OBJECT_CLASS_TYPE (object_class), + g_signal_new (I_("path-clicked"), + G_OBJECT_CLASS_TYPE (gobject_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GtkPathBarClass, path_clicked), NULL, NULL, - _gtk_marshal_VOID__POINTER, - G_TYPE_NONE, 1, - G_TYPE_POINTER); + _gtk_marshal_VOID__POINTER_POINTER_BOOLEAN, + G_TYPE_NONE, 3, + G_TYPE_POINTER, + G_TYPE_POINTER, + G_TYPE_BOOLEAN); } @@ -172,13 +259,16 @@ gtk_path_bar_finalize (GObject *object) GtkPathBar *path_bar; path_bar = GTK_PATH_BAR (object); + + gtk_path_bar_stop_scrolling (path_bar); + g_list_free (path_bar->button_list); - if (path_bar->root_path) - gtk_file_path_free (path_bar->root_path); - if (path_bar->home_path) - gtk_file_path_free (path_bar->home_path); - if (path_bar->desktop_path) - gtk_file_path_free (path_bar->desktop_path); + if (path_bar->root_file) + g_object_unref (path_bar->root_file); + if (path_bar->home_file) + g_object_unref (path_bar->home_file); + if (path_bar->desktop_file) + g_object_unref (path_bar->desktop_file); if (path_bar->root_icon) g_object_unref (path_bar->root_icon); @@ -212,8 +302,13 @@ remove_settings_signal (GtkPathBar *path_bar, static void gtk_path_bar_dispose (GObject *object) { - remove_settings_signal (GTK_PATH_BAR (object), - gtk_widget_get_screen (GTK_WIDGET (object))); + GtkPathBar *path_bar = GTK_PATH_BAR (object); + + remove_settings_signal (path_bar, gtk_widget_get_screen (GTK_WIDGET (object))); + + if (path_bar->get_info_cancellable) + g_cancellable_cancel (path_bar->get_info_cancellable); + path_bar->get_info_cancellable = NULL; G_OBJECT_CLASS (gtk_path_bar_parent_class)->dispose (object); } @@ -224,42 +319,74 @@ gtk_path_bar_dispose (GObject *object) * available space. */ static void -gtk_path_bar_size_request (GtkWidget *widget, - GtkRequisition *requisition) +gtk_path_bar_get_preferred_width (GtkWidget *widget, + gint *minimum, + gint *natural) { ButtonData *button_data; GtkPathBar *path_bar; - GtkRequisition child_requisition; GList *list; + gint child_height; + gint height; + gint child_min, child_nat; path_bar = GTK_PATH_BAR (widget); - requisition->width = 0; - requisition->height = 0; + *minimum = *natural = 0; + height = 0; for (list = path_bar->button_list; list; list = list->next) { button_data = BUTTON_DATA (list->data); - gtk_widget_size_request (button_data->button, &child_requisition); - requisition->width = MAX (child_requisition.width, requisition->width); - requisition->height = MAX (child_requisition.height, requisition->height); + gtk_widget_get_preferred_width (button_data->button, &child_min, &child_nat); + gtk_widget_get_preferred_height (button_data->button, &child_height, NULL); + height = MAX (height, child_height); + + if (button_data->type == NORMAL_BUTTON) + { + /* Use 2*Height as button width because of ellipsized label. */ + child_min = MAX (child_min, child_height * 2); + child_nat = MAX (child_min, child_height * 2); + } + + *minimum = MAX (*minimum, child_min); + *natural = MAX (*natural, child_nat); } /* Add space for slider, if we have more than one path */ /* Theoretically, the slider could be bigger than the other button. But we're * not going to worry about that now. */ - path_bar->slider_width = requisition->height / 2 + 5; + path_bar->slider_width = MIN (height * 2 / 3 + 5, height); if (path_bar->button_list && path_bar->button_list->next != NULL) - requisition->width += (path_bar->spacing + path_bar->slider_width) * 2; + { + *minimum += (path_bar->spacing + path_bar->slider_width) * 2; + *natural += (path_bar->spacing + path_bar->slider_width) * 2; + } +} + +static void +gtk_path_bar_get_preferred_height (GtkWidget *widget, + gint *minimum, + gint *natural) +{ + ButtonData *button_data; + GtkPathBar *path_bar; + GList *list; + gint child_min, child_nat; - gtk_widget_size_request (path_bar->up_slider_button, &child_requisition); - gtk_widget_size_request (path_bar->down_slider_button, &child_requisition); + path_bar = GTK_PATH_BAR (widget); - requisition->width += GTK_CONTAINER (widget)->border_width * 2; - requisition->height += GTK_CONTAINER (widget)->border_width * 2; + *minimum = *natural = 0; - widget->requisition = *requisition; + for (list = path_bar->button_list; list; list = list->next) + { + button_data = BUTTON_DATA (list->data); + gtk_widget_get_preferred_height (button_data->button, &child_min, &child_nat); + + *minimum = MAX (*minimum, child_min); + *natural = MAX (*natural, child_nat); + } } static void @@ -271,18 +398,88 @@ gtk_path_bar_update_slider_buttons (GtkPathBar *path_bar) button = BUTTON_DATA (path_bar->button_list->data)->button; if (gtk_widget_get_child_visible (button)) - gtk_widget_set_sensitive (path_bar->down_slider_button, FALSE); + { + gtk_path_bar_stop_scrolling (path_bar); + gtk_widget_set_sensitive (path_bar->down_slider_button, FALSE); + } else gtk_widget_set_sensitive (path_bar->down_slider_button, TRUE); button = BUTTON_DATA (g_list_last (path_bar->button_list)->data)->button; if (gtk_widget_get_child_visible (button)) - gtk_widget_set_sensitive (path_bar->up_slider_button, FALSE); + { + gtk_path_bar_stop_scrolling (path_bar); + gtk_widget_set_sensitive (path_bar->up_slider_button, FALSE); + } else gtk_widget_set_sensitive (path_bar->up_slider_button, TRUE); } } +static void +gtk_path_bar_map (GtkWidget *widget) +{ + gdk_window_show (GTK_PATH_BAR (widget)->event_window); + + GTK_WIDGET_CLASS (gtk_path_bar_parent_class)->map (widget); +} + +static void +gtk_path_bar_unmap (GtkWidget *widget) +{ + gtk_path_bar_stop_scrolling (GTK_PATH_BAR (widget)); + gdk_window_hide (GTK_PATH_BAR (widget)->event_window); + + GTK_WIDGET_CLASS (gtk_path_bar_parent_class)->unmap (widget); +} + +static void +gtk_path_bar_realize (GtkWidget *widget) +{ + GtkPathBar *path_bar; + GtkAllocation allocation; + GdkWindow *window; + GdkWindowAttr attributes; + gint attributes_mask; + + gtk_widget_set_realized (widget, TRUE); + + path_bar = GTK_PATH_BAR (widget); + window = gtk_widget_get_parent_window (widget); + gtk_widget_set_window (widget, window); + g_object_ref (window); + + gtk_widget_get_allocation (widget, &allocation); + + attributes.window_type = GDK_WINDOW_CHILD; + attributes.x = allocation.x; + attributes.y = allocation.y; + attributes.width = allocation.width; + attributes.height = allocation.height; + attributes.wclass = GDK_INPUT_ONLY; + attributes.event_mask = gtk_widget_get_events (widget); + attributes.event_mask |= GDK_SCROLL_MASK; + attributes_mask = GDK_WA_X | GDK_WA_Y; + + path_bar->event_window = gdk_window_new (gtk_widget_get_parent_window (widget), + &attributes, attributes_mask); + gdk_window_set_user_data (path_bar->event_window, widget); +} + +static void +gtk_path_bar_unrealize (GtkWidget *widget) +{ + GtkPathBar *path_bar; + + path_bar = GTK_PATH_BAR (widget); + + gdk_window_set_user_data (path_bar->event_window, NULL); + gdk_window_destroy (path_bar->event_window); + path_bar->event_window = NULL; + + GTK_WIDGET_CLASS (gtk_path_bar_parent_class)->unrealize (widget); +} + /* This is a tad complicated */ static void @@ -296,33 +493,47 @@ gtk_path_bar_size_allocate (GtkWidget *widget, GList *list, *first_button; gint width; gint allocation_width; - gint border_width; gboolean need_sliders = FALSE; gint up_slider_offset = 0; - gint down_slider_offset = 0; + GtkRequisition child_requisition; - widget->allocation = *allocation; + gtk_widget_set_allocation (widget, allocation); + + if (gtk_widget_get_realized (widget)) + gdk_window_move_resize (path_bar->event_window, + allocation->x, allocation->y, + allocation->width, allocation->height); /* No path is set; we don't have to allocate anything. */ if (path_bar->button_list == NULL) return; direction = gtk_widget_get_direction (widget); - border_width = (gint) GTK_CONTAINER (path_bar)->border_width; - allocation_width = allocation->width - 2 * border_width; + allocation_width = allocation->width; /* First, we check to see if we need the scrollbars. */ - width = BUTTON_DATA (path_bar->button_list->data)->button->requisition.width; - for (list = path_bar->button_list->next; list; list = list->next) + if (path_bar->fake_root) + width = path_bar->spacing + path_bar->slider_width; + else + width = 0; + + for (list = path_bar->button_list; list; list = list->next) { child = BUTTON_DATA (list->data)->button; - width += child->requisition.width + path_bar->spacing; + gtk_widget_get_preferred_size (child, &child_requisition, NULL); + + width += child_requisition.width + path_bar->spacing; + if (list == path_bar->fake_root) + break; } if (width <= allocation_width) { - first_button = g_list_last (path_bar->button_list); + if (path_bar->fake_root) + first_button = path_bar->fake_root; + else + first_button = g_list_last (path_bar->button_list); } else { @@ -340,96 +551,119 @@ gtk_path_bar_size_allocate (GtkWidget *widget, * button, then count backwards. */ /* Count down the path chain towards the end. */ - width = BUTTON_DATA (first_button->data)->button->requisition.width; + gtk_widget_get_preferred_size (BUTTON_DATA (first_button->data)->button, + &child_requisition, NULL); + + width = child_requisition.width; list = first_button->prev; while (list && !reached_end) { child = BUTTON_DATA (list->data)->button; - if (width + child->requisition.width + + gtk_widget_get_preferred_size (child, &child_requisition, NULL); + + if (width + child_requisition.width + path_bar->spacing + slider_space > allocation_width) reached_end = TRUE; + else if (list == path_bar->fake_root) + break; else - width += child->requisition.width + path_bar->spacing; + width += child_requisition.width + path_bar->spacing; list = list->prev; } /* Finally, we walk up, seeing how many of the previous buttons we can * add */ - while (first_button->next && ! reached_end) + while (first_button->next && !reached_end) { child = BUTTON_DATA (first_button->next->data)->button; - if (width + child->requisition.width + path_bar->spacing + slider_space > allocation_width) + gtk_widget_get_preferred_size (child, &child_requisition, NULL); + + if (width + child_requisition.width + path_bar->spacing + slider_space > allocation_width) { reached_end = TRUE; } else { - width += child->requisition.width + path_bar->spacing; + width += child_requisition.width + path_bar->spacing; + if (first_button == path_bar->fake_root) + break; first_button = first_button->next; } } } /* Now, we allocate space to the buttons */ - child_allocation.y = allocation->y + border_width; - child_allocation.height = MAX (1, (gint) allocation->height - border_width * 2); + child_allocation.y = allocation->y; + child_allocation.height = allocation->height; if (direction == GTK_TEXT_DIR_RTL) { - child_allocation.x = allocation->x + allocation->width - border_width; - if (need_sliders) + child_allocation.x = allocation->x + allocation->width; + if (need_sliders || path_bar->fake_root) { child_allocation.x -= (path_bar->spacing + path_bar->slider_width); - up_slider_offset = allocation->width - border_width - path_bar->slider_width; + up_slider_offset = allocation->width - path_bar->slider_width; } } else { - child_allocation.x = allocation->x + border_width; - if (need_sliders) + child_allocation.x = allocation->x; + if (need_sliders || path_bar->fake_root) { - up_slider_offset = border_width; + up_slider_offset = 0; child_allocation.x += (path_bar->spacing + path_bar->slider_width); } } for (list = first_button; list; list = list->prev) { - child = BUTTON_DATA (list->data)->button; + GtkAllocation widget_allocation; + ButtonData *button_data; + + button_data = BUTTON_DATA (list->data); + child = button_data->button; + + gtk_widget_get_preferred_size (child, &child_requisition, NULL); + + child_allocation.width = MIN (child_requisition.width, + allocation_width - (path_bar->spacing + path_bar->slider_width) * 2); - child_allocation.width = child->requisition.width; if (direction == GTK_TEXT_DIR_RTL) child_allocation.x -= child_allocation.width; /* Check to see if we've don't have any more space to allocate buttons */ if (need_sliders && direction == GTK_TEXT_DIR_RTL) { - if (child_allocation.x - path_bar->spacing - path_bar->slider_width < widget->allocation.x + border_width) + gtk_widget_get_allocation (widget, &widget_allocation); + if (child_allocation.x - path_bar->spacing - path_bar->slider_width < widget_allocation.x) break; } else if (need_sliders && direction == GTK_TEXT_DIR_LTR) { + gtk_widget_get_allocation (widget, &widget_allocation); if (child_allocation.x + child_allocation.width + path_bar->spacing + path_bar->slider_width > - widget->allocation.x + border_width + allocation_width) + widget_allocation.x + allocation_width) break; } - gtk_widget_set_child_visible (BUTTON_DATA (list->data)->button, TRUE); + if (child_allocation.width < child_requisition.width) + { + if (!gtk_widget_get_has_tooltip (child)) + gtk_widget_set_tooltip_text (child, button_data->dir_name); + } + else if (gtk_widget_get_has_tooltip (child)) + gtk_widget_set_tooltip_text (child, NULL); + + gtk_widget_set_child_visible (child, TRUE); gtk_widget_size_allocate (child, &child_allocation); if (direction == GTK_TEXT_DIR_RTL) - { - child_allocation.x -= path_bar->spacing; - down_slider_offset = child_allocation.x - widget->allocation.x - path_bar->slider_width; - } + child_allocation.x -= path_bar->spacing; else - { - child_allocation.x += child_allocation.width + path_bar->spacing; - down_slider_offset = child_allocation.x - widget->allocation.x; - } + child_allocation.x += child_allocation.width + path_bar->spacing; } /* Now we go hide all the widgets that don't fit */ while (list) @@ -442,35 +676,43 @@ gtk_path_bar_size_allocate (GtkWidget *widget, gtk_widget_set_child_visible (BUTTON_DATA (list->data)->button, FALSE); } - if (need_sliders) + if (need_sliders || path_bar->fake_root) { child_allocation.width = path_bar->slider_width; - child_allocation.x = up_slider_offset + allocation->x; gtk_widget_size_allocate (path_bar->up_slider_button, &child_allocation); - child_allocation.x = down_slider_offset + allocation->x; + gtk_widget_set_child_visible (path_bar->up_slider_button, TRUE); + gtk_widget_show_all (path_bar->up_slider_button); + } + else + gtk_widget_set_child_visible (path_bar->up_slider_button, FALSE); + + if (need_sliders) + { + child_allocation.width = path_bar->slider_width; + + if (direction == GTK_TEXT_DIR_RTL) + child_allocation.x = 0; + else + child_allocation.x = allocation->width - path_bar->slider_width; + + child_allocation.x += allocation->x; + gtk_widget_size_allocate (path_bar->down_slider_button, &child_allocation); - gtk_widget_set_child_visible (path_bar->up_slider_button, TRUE); gtk_widget_set_child_visible (path_bar->down_slider_button, TRUE); - gtk_widget_show_all (path_bar->up_slider_button); gtk_widget_show_all (path_bar->down_slider_button); gtk_path_bar_update_slider_buttons (path_bar); } else - { - gtk_widget_set_child_visible (path_bar->up_slider_button, FALSE); - gtk_widget_set_child_visible (path_bar->down_slider_button, FALSE); - } + gtk_widget_set_child_visible (path_bar->down_slider_button, FALSE); } static void -gtk_path_bar_style_set (GtkWidget *widget, - GtkStyle *previous_style) +gtk_path_bar_style_updated (GtkWidget *widget) { - if (GTK_WIDGET_CLASS (gtk_path_bar_parent_class)->style_set) - GTK_WIDGET_CLASS (gtk_path_bar_parent_class)->style_set (widget, previous_style); + GTK_WIDGET_CLASS (gtk_path_bar_parent_class)->style_updated (widget); gtk_path_bar_check_icon_theme (GTK_PATH_BAR (widget)); } @@ -489,6 +731,25 @@ gtk_path_bar_screen_changed (GtkWidget *widget, gtk_path_bar_check_icon_theme (GTK_PATH_BAR (widget)); } +static gboolean +gtk_path_bar_scroll (GtkWidget *widget, + GdkEventScroll *event) +{ + switch (event->direction) + { + case GDK_SCROLL_RIGHT: + case GDK_SCROLL_DOWN: + gtk_path_bar_scroll_down (GTK_PATH_BAR (widget)); + break; + case GDK_SCROLL_LEFT: + case GDK_SCROLL_UP: + gtk_path_bar_scroll_up (GTK_PATH_BAR (widget)); + break; + } + + return TRUE; +} + static void gtk_path_bar_add (GtkContainer *container, GtkWidget *widget) @@ -496,6 +757,16 @@ gtk_path_bar_add (GtkContainer *container, gtk_widget_set_parent (widget, GTK_WIDGET (container)); } +static void +gtk_path_bar_remove_1 (GtkContainer *container, + GtkWidget *widget) +{ + gboolean was_visible = gtk_widget_get_visible (widget); + gtk_widget_unparent (widget); + if (was_visible) + gtk_widget_queue_resize (GTK_WIDGET (container)); +} + static void gtk_path_bar_remove (GtkContainer *container, GtkWidget *widget) @@ -505,23 +776,29 @@ gtk_path_bar_remove (GtkContainer *container, path_bar = GTK_PATH_BAR (container); - children = path_bar->button_list; + if (widget == path_bar->up_slider_button) + { + gtk_path_bar_remove_1 (container, widget); + path_bar->up_slider_button = NULL; + return; + } + + if (widget == path_bar->down_slider_button) + { + gtk_path_bar_remove_1 (container, widget); + path_bar->down_slider_button = NULL; + return; + } + children = path_bar->button_list; while (children) { if (widget == BUTTON_DATA (children->data)->button) { - gboolean was_visible; - - was_visible = GTK_WIDGET_VISIBLE (widget); - gtk_widget_unparent (widget); - + gtk_path_bar_remove_1 (container, widget); path_bar->button_list = g_list_remove_link (path_bar->button_list, children); g_list_free (children); - - if (was_visible) - gtk_widget_queue_resize (GTK_WIDGET (container)); - break; + return; } children = children->next; @@ -550,26 +827,35 @@ gtk_path_bar_forall (GtkContainer *container, (* callback) (child, callback_data); } - (* callback) (path_bar->up_slider_button, callback_data); - (* callback) (path_bar->down_slider_button, callback_data); + if (path_bar->up_slider_button) + (* callback) (path_bar->up_slider_button, callback_data); + + if (path_bar->down_slider_button) + (* callback) (path_bar->down_slider_button, callback_data); } static void -gtk_path_bar_scroll_down (GtkWidget *button, GtkPathBar *path_bar) +gtk_path_bar_scroll_down (GtkPathBar *path_bar) { + GtkAllocation allocation, button_allocation; GList *list; GList *down_button = NULL; - GList *up_button = NULL; gint space_available; - gint space_needed; - gint border_width; - GtkTextDirection direction; - + + if (path_bar->ignore_click) + { + path_bar->ignore_click = FALSE; + return; + } + + if (gtk_widget_get_child_visible (BUTTON_DATA (path_bar->button_list->data)->button)) + { + /* Return if the last button is already visible */ + return; + } + gtk_widget_queue_resize (GTK_WIDGET (path_bar)); - border_width = GTK_CONTAINER (path_bar)->border_width; - direction = gtk_widget_get_direction (GTK_WIDGET (path_bar)); - /* We find the button at the 'down' end that we have to make * visible */ for (list = path_bar->button_list; list; list = list->next) @@ -580,54 +866,231 @@ gtk_path_bar_scroll_down (GtkWidget *button, GtkPathBar *path_bar) break; } } + + gtk_widget_get_allocation (GTK_WIDGET (path_bar), &allocation); + gtk_widget_get_allocation (BUTTON_DATA (down_button->data)->button, &button_allocation); + + space_available = (allocation.width + - 2 * path_bar->spacing - 2 * path_bar->slider_width + - button_allocation.width); + path_bar->first_scrolled_button = down_button; - /* Find the last visible button on the 'up' end + /* We have space_available free space that's not being used. + * So we walk down from the end, adding buttons until we use all free space. */ - for (list = g_list_last (path_bar->button_list); list; list = list->prev) + while (space_available > 0) { - if (gtk_widget_get_child_visible (BUTTON_DATA (list->data)->button)) - { - up_button = list; - break; - } - } - - space_needed = BUTTON_DATA (down_button->data)->button->allocation.width + path_bar->spacing; - if (direction == GTK_TEXT_DIR_RTL) - space_available = path_bar->down_slider_button->allocation.x - GTK_WIDGET (path_bar)->allocation.x; - else - space_available = (GTK_WIDGET (path_bar)->allocation.x + GTK_WIDGET (path_bar)->allocation.width - border_width) - - (path_bar->down_slider_button->allocation.x + path_bar->down_slider_button->allocation.width); - - /* We have space_available extra space that's not being used. We - * need space_needed space to make the button fit. So we walk down - * from the end, removing buttons until we get all the space we - * need. */ - while (space_available < space_needed) - { - space_available += BUTTON_DATA (up_button->data)->button->allocation.width + path_bar->spacing; - up_button = up_button->prev; - path_bar->first_scrolled_button = up_button; + path_bar->first_scrolled_button = down_button; + down_button = down_button->next; + if (!down_button) + break; + space_available -= (button_allocation.width + + path_bar->spacing); } } static void -gtk_path_bar_scroll_up (GtkWidget *button, GtkPathBar *path_bar) +gtk_path_bar_scroll_up (GtkPathBar *path_bar) { GList *list; + if (path_bar->ignore_click) + { + path_bar->ignore_click = FALSE; + return; + } + + list = g_list_last (path_bar->button_list); + + if (gtk_widget_get_child_visible (BUTTON_DATA (list->data)->button)) + { + /* Return if the first button is already visible */ + return; + } + gtk_widget_queue_resize (GTK_WIDGET (path_bar)); - for (list = g_list_last (path_bar->button_list); list; list = list->prev) + for ( ; list; list = list->prev) { if (list->prev && gtk_widget_get_child_visible (BUTTON_DATA (list->prev->data)->button)) { + if (list->prev == path_bar->fake_root) + path_bar->fake_root = NULL; path_bar->first_scrolled_button = list; return; } } } +static gboolean +gtk_path_bar_scroll_timeout (GtkPathBar *path_bar) +{ + gboolean retval = FALSE; + + if (path_bar->timer) + { + if (path_bar->scrolling_up) + gtk_path_bar_scroll_up (path_bar); + else if (path_bar->scrolling_down) + gtk_path_bar_scroll_down (path_bar); + + if (path_bar->need_timer) + { + GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (path_bar)); + guint timeout; + + g_object_get (settings, "gtk-timeout-repeat", &timeout, NULL); + + path_bar->need_timer = FALSE; + + path_bar->timer = gdk_threads_add_timeout (timeout * SCROLL_DELAY_FACTOR, + (GSourceFunc)gtk_path_bar_scroll_timeout, + path_bar); + } + else + retval = TRUE; + } + + return retval; +} + +static void +gtk_path_bar_stop_scrolling (GtkPathBar *path_bar) +{ + if (path_bar->timer) + { + g_source_remove (path_bar->timer); + path_bar->timer = 0; + path_bar->need_timer = FALSE; + } +} + +static gboolean +gtk_path_bar_slider_up_defocus (GtkWidget *widget, + GdkEventButton *event, + GtkPathBar *path_bar) +{ + GList *list; + GList *up_button = NULL; + + if (event->type != GDK_FOCUS_CHANGE) + return FALSE; + + for (list = g_list_last (path_bar->button_list); list; list = list->prev) + { + if (gtk_widget_get_child_visible (BUTTON_DATA (list->data)->button)) + { + up_button = list; + break; + } + } + + /* don't let the focus vanish */ + if ((!gtk_widget_is_sensitive (path_bar->up_slider_button)) || + (!gtk_widget_get_child_visible (path_bar->up_slider_button))) + gtk_widget_grab_focus (BUTTON_DATA (up_button->data)->button); + + return FALSE; +} + +static gboolean +gtk_path_bar_slider_down_defocus (GtkWidget *widget, + GdkEventButton *event, + GtkPathBar *path_bar) +{ + GList *list; + GList *down_button = NULL; + + if (event->type != GDK_FOCUS_CHANGE) + return FALSE; + + for (list = path_bar->button_list; list; list = list->next) + { + if (gtk_widget_get_child_visible (BUTTON_DATA (list->data)->button)) + { + down_button = list; + break; + } + } + + /* don't let the focus vanish */ + if ((!gtk_widget_is_sensitive (path_bar->down_slider_button)) || + (!gtk_widget_get_child_visible (path_bar->down_slider_button))) + gtk_widget_grab_focus (BUTTON_DATA (down_button->data)->button); + + return FALSE; +} + +static gboolean +gtk_path_bar_slider_button_press (GtkWidget *widget, + GdkEventButton *event, + GtkPathBar *path_bar) +{ + if (event->type != GDK_BUTTON_PRESS || event->button != 1) + return FALSE; + + path_bar->ignore_click = FALSE; + + if (widget == path_bar->up_slider_button) + { + path_bar->scrolling_down = FALSE; + path_bar->scrolling_up = TRUE; + gtk_path_bar_scroll_up (path_bar); + } + else if (widget == path_bar->down_slider_button) + { + path_bar->scrolling_up = FALSE; + path_bar->scrolling_down = TRUE; + gtk_path_bar_scroll_down (path_bar); + } + + if (!path_bar->timer) + { + GtkSettings *settings = gtk_widget_get_settings (widget); + guint timeout; + + g_object_get (settings, "gtk-timeout-initial", &timeout, NULL); + + path_bar->need_timer = TRUE; + path_bar->timer = gdk_threads_add_timeout (timeout, + (GSourceFunc)gtk_path_bar_scroll_timeout, + path_bar); + } + + return FALSE; +} + +static gboolean +gtk_path_bar_slider_button_release (GtkWidget *widget, + GdkEventButton *event, + GtkPathBar *path_bar) +{ + if (event->type != GDK_BUTTON_RELEASE) + return FALSE; + + path_bar->ignore_click = TRUE; + gtk_path_bar_stop_scrolling (path_bar); + + return FALSE; +} + +static void +gtk_path_bar_grab_notify (GtkWidget *widget, + gboolean was_grabbed) +{ + if (!was_grabbed) + gtk_path_bar_stop_scrolling (GTK_PATH_BAR (widget)); +} + +static void +gtk_path_bar_state_changed (GtkWidget *widget, + GtkStateType previous_state) +{ + if (!gtk_widget_is_sensitive (widget)) + gtk_path_bar_stop_scrolling (GTK_PATH_BAR (widget)); +} + + /* Changes the icons wherever it is needed */ static void reload_icons (GtkPathBar *path_bar) @@ -673,7 +1136,7 @@ change_icon_theme (GtkPathBar *path_bar) settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (path_bar))); - if (gtk_icon_size_lookup_for_settings (settings, GTK_ICON_SIZE_BUTTON, &width, &height)) + if (gtk_icon_size_lookup_for_settings (settings, GTK_ICON_SIZE_MENU, &width, &height)) path_bar->icon_size = MAX (width, height); else path_bar->icon_size = FALLBACK_ICON_SIZE; @@ -718,6 +1181,7 @@ gtk_path_bar_clear_buttons (GtkPathBar *path_bar) gtk_container_remove (GTK_CONTAINER (path_bar), BUTTON_DATA (path_bar->button_list->data)->button); } path_bar->first_scrolled_button = NULL; + path_bar->fake_root = NULL; } static void @@ -725,77 +1189,229 @@ button_clicked_cb (GtkWidget *button, gpointer data) { ButtonData *button_data; - GtkWidget *path_bar; + GtkPathBar *path_bar; + GList *button_list; + gboolean child_is_hidden; + GFile *child_file; button_data = BUTTON_DATA (data); if (button_data->ignore_changes) return; - path_bar = button->parent; - g_assert (GTK_IS_PATH_BAR (path_bar)); + path_bar = GTK_PATH_BAR (gtk_widget_get_parent (button)); + + button_list = g_list_find (path_bar->button_list, button_data); + g_assert (button_list != NULL); + + g_signal_handlers_block_by_func (button, + G_CALLBACK (button_clicked_cb), data); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE); + g_signal_handlers_unblock_by_func (button, + G_CALLBACK (button_clicked_cb), data); + + if (button_list->prev) + { + ButtonData *child_data; - g_signal_emit (path_bar, path_bar_signals [PATH_CLICKED], 0, button_data->path); + child_data = BUTTON_DATA (button_list->prev->data); + child_file = child_data->file; + child_is_hidden = child_data->file_is_hidden; + } + else + { + child_file = NULL; + child_is_hidden = FALSE; + } + + g_signal_emit (path_bar, path_bar_signals [PATH_CLICKED], 0, + button_data->file, child_file, child_is_hidden); } -static GdkPixbuf * -get_button_image (GtkPathBar *path_bar, - ButtonType button_type) +struct SetButtonImageData +{ + GtkPathBar *path_bar; + ButtonData *button_data; +}; + +static void +set_button_image_get_info_cb (GCancellable *cancellable, + GFileInfo *info, + const GError *error, + gpointer user_data) +{ + gboolean cancelled = g_cancellable_is_cancelled (cancellable); + GdkPixbuf *pixbuf; + struct SetButtonImageData *data = user_data; + + if (cancellable != data->button_data->cancellable) + goto out; + + data->button_data->cancellable = NULL; + + if (!data->button_data->button) + { + g_free (data->button_data); + goto out; + } + + if (cancelled || error) + goto out; + + pixbuf = _gtk_file_info_render_icon (info, GTK_WIDGET (data->path_bar), + data->path_bar->icon_size); + gtk_image_set_from_pixbuf (GTK_IMAGE (data->button_data->image), pixbuf); + + switch (data->button_data->type) + { + case HOME_BUTTON: + if (data->path_bar->home_icon) + g_object_unref (pixbuf); + else + data->path_bar->home_icon = pixbuf; + break; + + case DESKTOP_BUTTON: + if (data->path_bar->desktop_icon) + g_object_unref (pixbuf); + else + data->path_bar->desktop_icon = pixbuf; + break; + + default: + break; + }; + +out: + g_free (data); + g_object_unref (cancellable); +} + +static void +set_button_image (GtkPathBar *path_bar, + ButtonData *button_data) { GtkFileSystemVolume *volume; + struct SetButtonImageData *data; - switch (button_type) + switch (button_data->type) { case ROOT_BUTTON: if (path_bar->root_icon != NULL) - return path_bar->root_icon; - - volume = gtk_file_system_get_volume_for_path (path_bar->file_system, path_bar->root_path); + { + gtk_image_set_from_pixbuf (GTK_IMAGE (button_data->image), path_bar->root_icon); + break; + } + + volume = _gtk_file_system_get_volume_for_file (path_bar->file_system, path_bar->root_file); if (volume == NULL) - return NULL; + return; - path_bar->root_icon = gtk_file_system_volume_render_icon (path_bar->file_system, - volume, - GTK_WIDGET (path_bar), - path_bar->icon_size, - NULL); - gtk_file_system_volume_free (path_bar->file_system, volume); + path_bar->root_icon = _gtk_file_system_volume_render_icon (volume, + GTK_WIDGET (path_bar), + path_bar->icon_size, + NULL); + _gtk_file_system_volume_unref (volume); + + gtk_image_set_from_pixbuf (GTK_IMAGE (button_data->image), path_bar->root_icon); + break; - return path_bar->root_icon; case HOME_BUTTON: if (path_bar->home_icon != NULL) - return path_bar->home_icon; - - path_bar->home_icon = gtk_file_system_render_icon (path_bar->file_system, - path_bar->home_path, - GTK_WIDGET (path_bar), - path_bar->icon_size, - NULL); - return path_bar->home_icon; + { + gtk_image_set_from_pixbuf (GTK_IMAGE (button_data->image), path_bar->home_icon); + break; + } + + data = g_new0 (struct SetButtonImageData, 1); + data->path_bar = path_bar; + data->button_data = button_data; + + if (button_data->cancellable) + g_cancellable_cancel (button_data->cancellable); + + button_data->cancellable = + _gtk_file_system_get_info (path_bar->file_system, + path_bar->home_file, + "standard::icon", + set_button_image_get_info_cb, + data); + break; + case DESKTOP_BUTTON: if (path_bar->desktop_icon != NULL) - return path_bar->desktop_icon; - - path_bar->desktop_icon = gtk_file_system_render_icon (path_bar->file_system, - path_bar->desktop_path, - GTK_WIDGET (path_bar), - path_bar->icon_size, - NULL); - return path_bar->desktop_icon; + { + gtk_image_set_from_pixbuf (GTK_IMAGE (button_data->image), path_bar->desktop_icon); + break; + } + + data = g_new0 (struct SetButtonImageData, 1); + data->path_bar = path_bar; + data->button_data = button_data; + + if (button_data->cancellable) + g_cancellable_cancel (button_data->cancellable); + + button_data->cancellable = + _gtk_file_system_get_info (path_bar->file_system, + path_bar->desktop_file, + "standard::icon", + set_button_image_get_info_cb, + data); + break; default: - return NULL; + break; } - - return NULL; } static void button_data_free (ButtonData *button_data) { - gtk_file_path_free (button_data->path); + if (button_data->file) + g_object_unref (button_data->file); + button_data->file = NULL; + g_free (button_data->dir_name); - g_free (button_data); + button_data->dir_name = NULL; + + button_data->button = NULL; + + if (button_data->cancellable) + g_cancellable_cancel (button_data->cancellable); + else + g_free (button_data); +} + +static const char * +get_dir_name (ButtonData *button_data) +{ + return button_data->dir_name; +} + +/* We always want to request the same size for the label, whether + * or not the contents are bold + */ +static void +set_label_size_request (GtkWidget *alignment, + ButtonData *button_data) +{ + const gchar *dir_name = get_dir_name (button_data); + PangoLayout *layout = gtk_widget_create_pango_layout (button_data->label, dir_name); + gint width, height, bold_width, bold_height; + gchar *markup; + + pango_layout_get_pixel_size (layout, &width, &height); + + markup = g_markup_printf_escaped ("%s", dir_name); + pango_layout_set_markup (layout, markup, -1); + g_free (markup); + + pango_layout_get_pixel_size (layout, &bold_width, &bold_height); + + gtk_widget_set_size_request (alignment, + MAX (width, bold_width), + MAX (height, bold_height)); + g_object_unref (layout); } static void @@ -803,14 +1419,7 @@ gtk_path_bar_update_button_appearance (GtkPathBar *path_bar, ButtonData *button_data, gboolean current_dir) { - const gchar *dir_name; - - if (button_data->type == HOME_BUTTON) - dir_name = _("Home"); - else if (button_data->type == DESKTOP_BUTTON) - dir_name = _("Desktop"); - else - dir_name = button_data->dir_name; + const gchar *dir_name = get_dir_name (button_data); if (button_data->label != NULL) { @@ -830,9 +1439,7 @@ gtk_path_bar_update_button_appearance (GtkPathBar *path_bar, if (button_data->image != NULL) { - GdkPixbuf *pixbuf; - pixbuf = get_button_image (path_bar, button_data->type); - gtk_image_set_from_pixbuf (GTK_IMAGE (button_data->image), pixbuf); + set_button_image (path_bar, button_data); } if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button_data->button)) != current_dir) @@ -845,32 +1452,62 @@ gtk_path_bar_update_button_appearance (GtkPathBar *path_bar, static ButtonType find_button_type (GtkPathBar *path_bar, - GtkFilePath *path) + GFile *file) { - if (! gtk_file_path_compare (path, path_bar->root_path)) + if (path_bar->root_file != NULL && + g_file_equal (file, path_bar->root_file)) return ROOT_BUTTON; - if (! gtk_file_path_compare (path, path_bar->home_path)) + if (path_bar->home_file != NULL && + g_file_equal (file, path_bar->home_file)) return HOME_BUTTON; - if (! gtk_file_path_compare (path, path_bar->desktop_path)) + if (path_bar->desktop_file != NULL && + g_file_equal (file, path_bar->desktop_file)) return DESKTOP_BUTTON; return NORMAL_BUTTON; } +static void +button_drag_data_get_cb (GtkWidget *widget, + GdkDragContext *context, + GtkSelectionData *selection_data, + guint info, + guint time_, + gpointer data) +{ + ButtonData *button_data; + char *uris[2]; + + button_data = data; + + uris[0] = g_file_get_uri (button_data->file); + uris[1] = NULL; + + gtk_selection_data_set_uris (selection_data, uris); + + g_free (uris[0]); +} + static ButtonData * make_directory_button (GtkPathBar *path_bar, const char *dir_name, - GtkFilePath *path, - gboolean current_dir) + GFile *file, + gboolean current_dir, + gboolean file_is_hidden) { + AtkObject *atk_obj; GtkWidget *child = NULL; + GtkWidget *label_alignment = NULL; ButtonData *button_data; + file_is_hidden = !! file_is_hidden; /* Is it a special button? */ button_data = g_new0 (ButtonData, 1); - button_data->type = find_button_type (path_bar, path); + button_data->type = find_button_type (path_bar, file); button_data->button = gtk_toggle_button_new (); + atk_obj = gtk_widget_get_accessible (button_data->button); + gtk_button_set_focus_on_click (GTK_BUTTON (button_data->button), FALSE); switch (button_data->type) { @@ -878,25 +1515,41 @@ make_directory_button (GtkPathBar *path_bar, button_data->image = gtk_image_new (); child = button_data->image; button_data->label = NULL; + atk_object_set_name (atk_obj, _("File System Root")); break; case HOME_BUTTON: case DESKTOP_BUTTON: button_data->image = gtk_image_new (); button_data->label = gtk_label_new (NULL); - child = gtk_hbox_new (FALSE, 2); + label_alignment = gtk_alignment_new (0.5, 0.5, 1.0, 1.0); + gtk_container_add (GTK_CONTAINER (label_alignment), button_data->label); + child = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2); gtk_box_pack_start (GTK_BOX (child), button_data->image, FALSE, FALSE, 0); - gtk_box_pack_start (GTK_BOX (child), button_data->label, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (child), label_alignment, FALSE, FALSE, 0); break; case NORMAL_BUTTON: default: button_data->label = gtk_label_new (NULL); - child = button_data->label; + gtk_label_set_ellipsize (GTK_LABEL (button_data->label), PANGO_ELLIPSIZE_END); + label_alignment = gtk_alignment_new (0.5, 0.5, 1.0, 1.0); + gtk_container_add (GTK_CONTAINER (label_alignment), button_data->label); + child = label_alignment; button_data->image = NULL; } button_data->dir_name = g_strdup (dir_name); - button_data->path = gtk_file_path_new_dup (gtk_file_path_get_string (path)); - + button_data->file = g_object_ref (file); + button_data->file_is_hidden = file_is_hidden; + + /* FIXME: Maybe we dont need this alignment at all and we can + * use GtkMisc aligments or even GtkWidget:halign/valign center. + * + * The following function ensures that the alignment will always + * request the same size whether the button's text is bold or not. + */ + if (label_alignment) + set_label_size_request (label_alignment, button_data); + gtk_container_add (GTK_CONTAINER (button_data->button), child); gtk_widget_show_all (button_data->button); @@ -908,160 +1561,225 @@ make_directory_button (GtkPathBar *path_bar, g_object_weak_ref (G_OBJECT (button_data->button), (GWeakNotify) button_data_free, button_data); + gtk_drag_source_set (button_data->button, + GDK_BUTTON1_MASK, + NULL, 0, + GDK_ACTION_COPY); + gtk_drag_source_add_uri_targets (button_data->button); + g_signal_connect (button_data->button, "drag-data-get", + G_CALLBACK (button_drag_data_get_cb), button_data); + return button_data; } static gboolean gtk_path_bar_check_parent_path (GtkPathBar *path_bar, - const GtkFilePath *file_path, + GFile *file, GtkFileSystem *file_system) { GList *list; GList *current_path = NULL; + gboolean need_new_fake_root = FALSE; for (list = path_bar->button_list; list; list = list->next) { ButtonData *button_data; button_data = list->data; - if (! gtk_file_path_compare (file_path, button_data->path)) + if (g_file_equal (file, button_data->file)) { current_path = list; break; } + if (list == path_bar->fake_root) + need_new_fake_root = TRUE; } if (current_path) { + if (need_new_fake_root) + { + path_bar->fake_root = NULL; + for (list = current_path; list; list = list->next) + { + ButtonData *button_data; + + button_data = list->data; + if (BUTTON_IS_FAKE_ROOT (button_data)) + { + path_bar->fake_root = list; + break; + } + } + } + for (list = path_bar->button_list; list; list = list->next) { gtk_path_bar_update_button_appearance (path_bar, BUTTON_DATA (list->data), (list == current_path) ? TRUE : FALSE); } + + if (!gtk_widget_get_child_visible (BUTTON_DATA (current_path->data)->button)) + { + path_bar->first_scrolled_button = current_path; + gtk_widget_queue_resize (GTK_WIDGET (path_bar)); + } + return TRUE; } return FALSE; } -gboolean -_gtk_path_bar_set_path (GtkPathBar *path_bar, - const GtkFilePath *file_path, - GError **error) -{ - GtkFilePath *path; - gboolean first_directory = TRUE; - gboolean result; - GList *new_buttons = NULL; - - g_return_val_if_fail (GTK_IS_PATH_BAR (path_bar), FALSE); - g_return_val_if_fail (file_path != NULL, FALSE); - result = TRUE; - - /* Check whether the new path is already present in the pathbar as buttons. - * This could be a parent directory or a previous selected subdirectory. - */ - if (gtk_path_bar_check_parent_path (path_bar, file_path, path_bar->file_system)) - return TRUE; +struct SetFileInfo +{ + GFile *file; + GFile *parent_file; + GtkPathBar *path_bar; + GList *new_buttons; + GList *fake_root; + gboolean first_directory; +}; - path = gtk_file_path_copy (file_path); +static void +gtk_path_bar_set_file_finish (struct SetFileInfo *info, + gboolean result) +{ + if (result) + { + GList *l; - gtk_widget_push_composite_child (); + gtk_path_bar_clear_buttons (info->path_bar); + info->path_bar->button_list = g_list_reverse (info->new_buttons); + info->path_bar->fake_root = info->fake_root; - while (path != NULL) - { - GtkFilePath *parent_path = NULL; - ButtonData *button_data; - const gchar *display_name; - GtkFileFolder *file_folder; - GtkFileInfo *file_info; - gboolean valid; - - valid = gtk_file_system_get_parent (path_bar->file_system, - path, - &parent_path, - error); - if (!valid) + for (l = info->path_bar->button_list; l; l = l->next) { - result = FALSE; - gtk_file_path_free (path); - break; + GtkWidget *button = BUTTON_DATA (l->data)->button; + gtk_container_add (GTK_CONTAINER (info->path_bar), button); } + } + else + { + GList *l; - file_folder = gtk_file_system_get_folder (path_bar->file_system, - parent_path ? parent_path : path, - GTK_FILE_INFO_DISPLAY_NAME, - NULL); - if (!file_folder) + for (l = info->new_buttons; l; l = l->next) { - result = FALSE; - gtk_file_path_free (parent_path); - gtk_file_path_free (path); - break; - } - - file_info = gtk_file_folder_get_info (file_folder, parent_path ? path : NULL, error); - g_object_unref (file_folder); + ButtonData *button_data; - if (!file_info) - { - result = FALSE; - gtk_file_path_free (parent_path); - gtk_file_path_free (path); - break; + button_data = BUTTON_DATA (l->data); + gtk_widget_destroy (button_data->button); } - display_name = gtk_file_info_get_display_name (file_info); - - button_data = make_directory_button (path_bar, display_name, path, first_directory); - gtk_file_info_free (file_info); - gtk_file_path_free (path); + g_list_free (info->new_buttons); + } - new_buttons = g_list_prepend (new_buttons, button_data); + if (info->file) + g_object_unref (info->file); + if (info->parent_file) + g_object_unref (info->parent_file); + g_free (info); +} - if (button_data->type != NORMAL_BUTTON) - { - gtk_file_path_free (parent_path); - break; - } +static void +gtk_path_bar_get_info_callback (GCancellable *cancellable, + GFileInfo *info, + const GError *error, + gpointer data) +{ + gboolean cancelled = g_cancellable_is_cancelled (cancellable); + struct SetFileInfo *file_info = data; + ButtonData *button_data; + const gchar *display_name; + gboolean is_hidden; - path = parent_path; - first_directory = FALSE; + if (cancellable != file_info->path_bar->get_info_cancellable) + { + gtk_path_bar_set_file_finish (file_info, FALSE); + g_object_unref (cancellable); + return; } - if (result) + g_object_unref (cancellable); + file_info->path_bar->get_info_cancellable = NULL; + + if (cancelled || !info) { - GList *l; + gtk_path_bar_set_file_finish (file_info, FALSE); + return; + } - gtk_path_bar_clear_buttons (path_bar); - path_bar->button_list = g_list_reverse (new_buttons); + display_name = g_file_info_get_display_name (info); + is_hidden = g_file_info_get_is_hidden (info) || g_file_info_get_is_backup (info); - for (l = path_bar->button_list; l; l = l->next) - { - GtkWidget *button = BUTTON_DATA (l->data)->button; - gtk_container_add (GTK_CONTAINER (path_bar), button); - } - } - else - { - GList *l; + gtk_widget_push_composite_child (); + button_data = make_directory_button (file_info->path_bar, display_name, + file_info->file, + file_info->first_directory, is_hidden); + gtk_widget_pop_composite_child (); + g_object_unref (file_info->file); - for (l = new_buttons; l; l = l->next) - { - GtkWidget *button = BUTTON_DATA (l->data)->button; - gtk_widget_destroy (button); - gtk_widget_unref (button); - } + file_info->new_buttons = g_list_prepend (file_info->new_buttons, button_data); + + if (BUTTON_IS_FAKE_ROOT (button_data)) + file_info->fake_root = file_info->new_buttons; - g_list_free (new_buttons); + file_info->file = file_info->parent_file; + file_info->first_directory = FALSE; + + if (!file_info->file) + { + gtk_path_bar_set_file_finish (file_info, TRUE); + return; } - gtk_widget_pop_composite_child (); + file_info->parent_file = g_file_get_parent (file_info->file); - return result; + file_info->path_bar->get_info_cancellable = + _gtk_file_system_get_info (file_info->path_bar->file_system, + file_info->file, + "standard::display-name,standard::is-hidden,standard::is-backup", + gtk_path_bar_get_info_callback, + file_info); } +gboolean +_gtk_path_bar_set_file (GtkPathBar *path_bar, + GFile *file, + const gboolean keep_trail, + GError **error) +{ + struct SetFileInfo *info; + + g_return_val_if_fail (GTK_IS_PATH_BAR (path_bar), FALSE); + g_return_val_if_fail (G_IS_FILE (file), FALSE); + + /* Check whether the new path is already present in the pathbar as buttons. + * This could be a parent directory or a previous selected subdirectory. + */ + if (keep_trail && + gtk_path_bar_check_parent_path (path_bar, file, path_bar->file_system)) + return TRUE; + + info = g_new0 (struct SetFileInfo, 1); + info->file = g_object_ref (file); + info->path_bar = path_bar; + info->first_directory = TRUE; + info->parent_file = g_file_get_parent (info->file); + + if (path_bar->get_info_cancellable) + g_cancellable_cancel (path_bar->get_info_cancellable); + + path_bar->get_info_cancellable = + _gtk_file_system_get_info (path_bar->file_system, + info->file, + "standard::display-name,standard::is-hidden,standard::is-backup", + gtk_path_bar_get_info_callback, + info); + + return TRUE; +} /* FIXME: This should be a construct-only property */ void @@ -1069,7 +1787,6 @@ _gtk_path_bar_set_file_system (GtkPathBar *path_bar, GtkFileSystem *file_system) { const char *home; - char *desktop; g_return_if_fail (GTK_IS_PATH_BAR (path_bar)); @@ -1078,11 +1795,26 @@ _gtk_path_bar_set_file_system (GtkPathBar *path_bar, path_bar->file_system = g_object_ref (file_system); home = g_get_home_dir (); - desktop = g_build_filename (home, "Desktop", NULL); - path_bar->home_path = gtk_file_system_filename_to_path (path_bar->file_system, home); - path_bar->desktop_path = gtk_file_system_filename_to_path (path_bar->file_system, desktop); - path_bar->root_path = gtk_file_system_filename_to_path (path_bar->file_system, "/"); - g_free (desktop); + if (home != NULL) + { + const gchar *desktop; + + path_bar->home_file = g_file_new_for_path (home); + /* FIXME: Need file system backend specific way of getting the + * Desktop path. + */ + desktop = g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP); + if (desktop != NULL) + path_bar->desktop_file = g_file_new_for_path (desktop); + else + path_bar->desktop_file = NULL; + } + else + { + path_bar->home_file = NULL; + path_bar->desktop_file = NULL; + } + path_bar->root_file = g_file_new_for_path ("/"); } /**