gtk2/gtk/gtkpathbar.c
Matthias Clasen 63bc3542ce path bar: Don't segfault if we get disposed quickly
The file chooser code now calls into the path bar in init,
starting an async operation. This unveiled that the path bar
code was not safe against being disposed early, by causing
a crahsh of the objects-finalize test.
Fix this by making the callback check for CANCELLED.
2015-01-19 11:57:10 -05:00

1974 lines
57 KiB
C

/* -*- Mode: C; c-file-style: "gnu"; tab-width: 8 -*- */
/* gtkpathbar.c
* Copyright (C) 2004 Red Hat, Inc., Jonathan Blandford <jrb@gnome.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "gtkpathbar.h"
#include <string.h>
#include "gtkbox.h"
#include "gtkdnd.h"
#include "gtkicontheme.h"
#include "gtkimage.h"
#include "gtkintl.h"
#include "gtklabel.h"
#include "gtkmain.h"
#include "gtkmarshalers.h"
#include "gtksettings.h"
#include "gtktogglebutton.h"
#include "gtkwidgetpath.h"
#include "gtkwidgetprivate.h"
struct _GtkPathBarPrivate
{
GtkFileSystem *file_system;
GFile *root_file;
GFile *home_file;
GFile *desktop_file;
GCancellable *get_info_cancellable;
cairo_surface_t *root_icon;
cairo_surface_t *home_icon;
cairo_surface_t *desktop_icon;
GdkWindow *event_window;
GList *button_list;
GList *first_scrolled_button;
GList *fake_root;
GtkWidget *up_slider_button;
GtkWidget *down_slider_button;
guint settings_signal_id;
gint icon_size;
gint16 slider_width;
gint16 button_offset;
guint timer;
guint slider_visible : 1;
guint need_timer : 1;
guint ignore_click : 1;
guint scrolling_up : 1;
guint scrolling_down : 1;
};
enum {
PATH_CLICKED,
LAST_SIGNAL
};
typedef enum {
NORMAL_BUTTON,
ROOT_BUTTON,
HOME_BUTTON,
DESKTOP_BUTTON
} ButtonType;
#define BUTTON_DATA(x) ((ButtonData *)(x))
#define SCROLL_DELAY_FACTOR 5
#define TIMEOUT_INITIAL 500
#define TIMEOUT_REPEAT 50
static guint path_bar_signals [LAST_SIGNAL] = { 0 };
/* Icon size for if we can't get it from the theme */
#define FALLBACK_ICON_SIZE 16
typedef struct _ButtonData ButtonData;
struct _ButtonData
{
GtkWidget *button;
ButtonType type;
char *dir_name;
GFile *file;
GtkWidget *image;
GtkWidget *label;
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_WITH_PRIVATE (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_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,
GtkWidget *widget);
static void gtk_path_bar_remove (GtkContainer *container,
GtkWidget *widget);
static void gtk_path_bar_forall (GtkContainer *container,
gboolean include_internals,
GtkCallback callback,
gpointer callback_data);
static GtkWidgetPath *gtk_path_bar_get_path_for_child (GtkContainer *container,
GtkWidget *child);
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 gboolean gtk_path_bar_slider_button_press (GtkWidget *widget,
GdkEventButton *event,
GtkPathBar *path_bar);
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);
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->priv->timer &&
((widget == path_bar->priv->up_slider_button && path_bar->priv->scrolling_up) ||
(widget == path_bar->priv->down_slider_button && path_bar->priv->scrolling_down)))
gtk_path_bar_stop_scrolling (path_bar);
}
static void
gtk_path_bar_init (GtkPathBar *path_bar)
{
GtkStyleContext *context;
path_bar->priv = gtk_path_bar_get_instance_private (path_bar);
gtk_widget_init_template (GTK_WIDGET (path_bar));
/* Add the children manually because GtkPathBar derives from an abstract class,
* Glade cannot edit a <template> in gtkpathbar.ui if it's only a GtkContainer.
*/
gtk_container_add (GTK_CONTAINER (path_bar), path_bar->priv->down_slider_button);
gtk_container_add (GTK_CONTAINER (path_bar), path_bar->priv->up_slider_button);
/* GtkBuilder wont let us connect 'swapped' without specifying the signal's
* user data in the .ui file
*/
g_signal_connect_swapped (path_bar->priv->up_slider_button, "clicked",
G_CALLBACK (gtk_path_bar_scroll_up), path_bar);
g_signal_connect_swapped (path_bar->priv->down_slider_button, "clicked",
G_CALLBACK (gtk_path_bar_scroll_down), path_bar);
gtk_widget_set_has_window (GTK_WIDGET (path_bar), FALSE);
gtk_widget_set_redraw_on_allocate (GTK_WIDGET (path_bar), FALSE);
context = gtk_widget_get_style_context (GTK_WIDGET (path_bar));
gtk_style_context_add_class (context, "path-bar");
gtk_style_context_add_class (context, GTK_STYLE_CLASS_LINKED);
path_bar->priv->get_info_cancellable = NULL;
path_bar->priv->icon_size = FALLBACK_ICON_SIZE;
}
static void
gtk_path_bar_class_init (GtkPathBarClass *path_bar_class)
{
GObjectClass *gobject_class;
GtkWidgetClass *widget_class;
GtkContainerClass *container_class;
gobject_class = (GObjectClass *) 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->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_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;
container_class->get_path_for_child = gtk_path_bar_get_path_for_child;
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 (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_POINTER_BOOLEAN,
G_TYPE_NONE, 3,
G_TYPE_POINTER,
G_TYPE_POINTER,
G_TYPE_BOOLEAN);
/* Bind class to template
*/
gtk_widget_class_set_template_from_resource (widget_class,
"/org/gtk/libgtk/ui/gtkpathbar.ui");
gtk_widget_class_bind_template_child_private (widget_class, GtkPathBar, up_slider_button);
gtk_widget_class_bind_template_child_private (widget_class, GtkPathBar, down_slider_button);
gtk_widget_class_bind_template_callback (widget_class, gtk_path_bar_slider_button_press);
gtk_widget_class_bind_template_callback (widget_class, gtk_path_bar_slider_button_release);
gtk_widget_class_bind_template_callback (widget_class, gtk_path_bar_slider_up_defocus);
gtk_widget_class_bind_template_callback (widget_class, gtk_path_bar_slider_down_defocus);
gtk_widget_class_bind_template_callback (widget_class, gtk_path_bar_scroll_up);
gtk_widget_class_bind_template_callback (widget_class, gtk_path_bar_scroll_down);
gtk_widget_class_bind_template_callback (widget_class, on_slider_unmap);
}
static void
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->priv->button_list);
if (path_bar->priv->root_file)
g_object_unref (path_bar->priv->root_file);
if (path_bar->priv->home_file)
g_object_unref (path_bar->priv->home_file);
if (path_bar->priv->desktop_file)
g_object_unref (path_bar->priv->desktop_file);
if (path_bar->priv->root_icon)
cairo_surface_destroy (path_bar->priv->root_icon);
if (path_bar->priv->home_icon)
cairo_surface_destroy (path_bar->priv->home_icon);
if (path_bar->priv->desktop_icon)
cairo_surface_destroy (path_bar->priv->desktop_icon);
if (path_bar->priv->file_system)
g_object_unref (path_bar->priv->file_system);
G_OBJECT_CLASS (gtk_path_bar_parent_class)->finalize (object);
}
/* Removes the settings signal handler. It's safe to call multiple times */
static void
remove_settings_signal (GtkPathBar *path_bar,
GdkScreen *screen)
{
if (path_bar->priv->settings_signal_id)
{
GtkSettings *settings;
settings = gtk_settings_get_for_screen (screen);
g_signal_handler_disconnect (settings,
path_bar->priv->settings_signal_id);
path_bar->priv->settings_signal_id = 0;
}
}
static void
gtk_path_bar_dispose (GObject *object)
{
GtkPathBar *path_bar = GTK_PATH_BAR (object);
remove_settings_signal (path_bar, gtk_widget_get_screen (GTK_WIDGET (object)));
if (path_bar->priv->get_info_cancellable)
g_cancellable_cancel (path_bar->priv->get_info_cancellable);
path_bar->priv->get_info_cancellable = NULL;
G_OBJECT_CLASS (gtk_path_bar_parent_class)->dispose (object);
}
/* Size requisition:
*
* Ideally, our size is determined by another widget, and we are just filling
* available space.
*/
static void
gtk_path_bar_get_preferred_width (GtkWidget *widget,
gint *minimum,
gint *natural)
{
ButtonData *button_data;
GtkPathBar *path_bar;
GList *list;
gint child_height;
gint height;
gint child_min, child_nat;
path_bar = GTK_PATH_BAR (widget);
*minimum = *natural = 0;
height = 0;
for (list = path_bar->priv->button_list; list; list = list->next)
{
button_data = BUTTON_DATA (list->data);
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 = *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->priv->slider_width = MIN (height * 2 / 3 + 5, height);
if (path_bar->priv->button_list && path_bar->priv->button_list->next != NULL)
{
*minimum += path_bar->priv->slider_width * 2;
*natural += path_bar->priv->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;
path_bar = GTK_PATH_BAR (widget);
*minimum = *natural = 0;
for (list = path_bar->priv->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
gtk_path_bar_update_slider_buttons (GtkPathBar *path_bar)
{
if (path_bar->priv->button_list)
{
GtkWidget *button;
button = BUTTON_DATA (path_bar->priv->button_list->data)->button;
if (gtk_widget_get_child_visible (button))
{
gtk_path_bar_stop_scrolling (path_bar);
gtk_widget_set_sensitive (path_bar->priv->down_slider_button, FALSE);
}
else
gtk_widget_set_sensitive (path_bar->priv->down_slider_button, TRUE);
button = BUTTON_DATA (g_list_last (path_bar->priv->button_list)->data)->button;
if (gtk_widget_get_child_visible (button))
{
gtk_path_bar_stop_scrolling (path_bar);
gtk_widget_set_sensitive (path_bar->priv->up_slider_button, FALSE);
}
else
gtk_widget_set_sensitive (path_bar->priv->up_slider_button, TRUE);
}
}
static void
gtk_path_bar_map (GtkWidget *widget)
{
gdk_window_show (GTK_PATH_BAR (widget)->priv->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)->priv->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->priv->event_window = gdk_window_new (gtk_widget_get_parent_window (widget),
&attributes, attributes_mask);
gtk_widget_register_window (widget, path_bar->priv->event_window);
}
static void
gtk_path_bar_unrealize (GtkWidget *widget)
{
GtkPathBar *path_bar;
path_bar = GTK_PATH_BAR (widget);
gtk_widget_unregister_window (widget, path_bar->priv->event_window);
gdk_window_destroy (path_bar->priv->event_window);
path_bar->priv->event_window = NULL;
GTK_WIDGET_CLASS (gtk_path_bar_parent_class)->unrealize (widget);
}
static void
child_ordering_changed (GtkPathBar *path_bar)
{
GList *l;
if (path_bar->priv->up_slider_button)
_gtk_widget_invalidate_style_context (path_bar->priv->up_slider_button,
GTK_CSS_CHANGE_POSITION | GTK_CSS_CHANGE_SIBLING_POSITION);
if (path_bar->priv->down_slider_button)
_gtk_widget_invalidate_style_context (path_bar->priv->down_slider_button,
GTK_CSS_CHANGE_POSITION | GTK_CSS_CHANGE_SIBLING_POSITION);
for (l = path_bar->priv->button_list; l; l = l->next)
{
ButtonData *data = l->data;
_gtk_widget_invalidate_style_context (data->button,
GTK_CSS_CHANGE_POSITION | GTK_CSS_CHANGE_SIBLING_POSITION);
}
}
/* This is a tad complicated
*/
static void
gtk_path_bar_size_allocate (GtkWidget *widget,
GtkAllocation *allocation)
{
GtkWidget *child;
GtkPathBar *path_bar = GTK_PATH_BAR (widget);
GtkTextDirection direction;
GtkAllocation child_allocation;
GList *list, *first_button;
gint width;
gint allocation_width;
gboolean need_sliders = FALSE;
gint up_slider_offset = 0;
gint down_slider_offset = 0;
GtkRequisition child_requisition;
gboolean needs_reorder = FALSE;
gtk_widget_set_allocation (widget, allocation);
if (gtk_widget_get_realized (widget))
gdk_window_move_resize (path_bar->priv->event_window,
allocation->x, allocation->y,
allocation->width, allocation->height);
/* No path is set; we don't have to allocate anything. */
if (path_bar->priv->button_list == NULL)
{
_gtk_widget_set_simple_clip (widget, NULL);
return;
}
direction = gtk_widget_get_direction (widget);
allocation_width = allocation->width;
/* First, we check to see if we need the scrollbars. */
if (path_bar->priv->fake_root)
width = path_bar->priv->slider_width;
else
width = 0;
for (list = path_bar->priv->button_list; list; list = list->next)
{
child = BUTTON_DATA (list->data)->button;
gtk_widget_get_preferred_size (child, &child_requisition, NULL);
width += child_requisition.width;
if (list == path_bar->priv->fake_root)
break;
}
if (width <= allocation_width)
{
if (path_bar->priv->fake_root)
first_button = path_bar->priv->fake_root;
else
first_button = g_list_last (path_bar->priv->button_list);
}
else
{
gboolean reached_end = FALSE;
gint slider_space = 2 * path_bar->priv->slider_width;
if (path_bar->priv->first_scrolled_button)
first_button = path_bar->priv->first_scrolled_button;
else
first_button = path_bar->priv->button_list;
need_sliders = TRUE;
/* To see how much space we have, and how many buttons we can display.
* We start at the first button, count forward until hit the new
* button, then count backwards.
*/
/* Count down the path chain towards the end. */
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;
gtk_widget_get_preferred_size (child, &child_requisition, NULL);
if (width + child_requisition.width + slider_space > allocation_width)
reached_end = TRUE;
else if (list == path_bar->priv->fake_root)
break;
else
width += child_requisition.width;
list = list->prev;
}
/* Finally, we walk up, seeing how many of the previous buttons we can
* add */
while (first_button->next && !reached_end)
{
child = BUTTON_DATA (first_button->next->data)->button;
gtk_widget_get_preferred_size (child, &child_requisition, NULL);
if (width + child_requisition.width + slider_space > allocation_width)
{
reached_end = TRUE;
}
else
{
width += child_requisition.width;
if (first_button == path_bar->priv->fake_root)
break;
first_button = first_button->next;
}
}
}
/* Now, we allocate space to the buttons */
child_allocation.y = allocation->y;
child_allocation.height = allocation->height;
if (direction == GTK_TEXT_DIR_RTL)
{
child_allocation.x = allocation->x + allocation->width;
if (need_sliders || path_bar->priv->fake_root)
{
child_allocation.x -= path_bar->priv->slider_width;
up_slider_offset = allocation->width - path_bar->priv->slider_width;
}
}
else
{
child_allocation.x = allocation->x;
if (need_sliders || path_bar->priv->fake_root)
{
up_slider_offset = 0;
child_allocation.x += path_bar->priv->slider_width;
}
}
for (list = first_button; list; list = list->prev)
{
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 - 2 * path_bar->priv->slider_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)
{
gtk_widget_get_allocation (widget, &widget_allocation);
if (child_allocation.x - path_bar->priv->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->priv->slider_width >
widget_allocation.x + allocation_width)
break;
}
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);
needs_reorder |= gtk_widget_get_child_visible (child) == FALSE;
gtk_widget_set_child_visible (child, TRUE);
gtk_widget_size_allocate (child, &child_allocation);
if (direction == GTK_TEXT_DIR_RTL)
{
down_slider_offset = child_allocation.x - allocation->x - path_bar->priv->slider_width;
}
else
{
down_slider_offset += child_allocation.width;
child_allocation.x += child_allocation.width;
}
}
/* Now we go hide all the widgets that don't fit */
while (list)
{
child = BUTTON_DATA (list->data)->button;
needs_reorder |= gtk_widget_get_child_visible (child) == TRUE;
gtk_widget_set_child_visible (child, FALSE);
list = list->prev;
}
for (list = first_button->next; list; list = list->next)
{
child = BUTTON_DATA (list->data)->button;
needs_reorder |= gtk_widget_get_child_visible (child) == TRUE;
gtk_widget_set_child_visible (child, FALSE);
}
if (need_sliders || path_bar->priv->fake_root)
{
child_allocation.width = path_bar->priv->slider_width;
child_allocation.x = up_slider_offset + allocation->x;
gtk_widget_size_allocate (path_bar->priv->up_slider_button, &child_allocation);
needs_reorder |= gtk_widget_get_child_visible (path_bar->priv->up_slider_button) == FALSE;
gtk_widget_set_child_visible (path_bar->priv->up_slider_button, TRUE);
gtk_widget_show_all (path_bar->priv->up_slider_button);
if (direction == GTK_TEXT_DIR_LTR)
down_slider_offset += path_bar->priv->slider_width;
}
else
{
needs_reorder |= gtk_widget_get_child_visible (path_bar->priv->up_slider_button) == TRUE;
gtk_widget_set_child_visible (path_bar->priv->up_slider_button, FALSE);
}
if (need_sliders)
{
child_allocation.width = path_bar->priv->slider_width;
child_allocation.x = down_slider_offset + allocation->x;
gtk_widget_size_allocate (path_bar->priv->down_slider_button, &child_allocation);
needs_reorder |= gtk_widget_get_child_visible (path_bar->priv->down_slider_button) == FALSE;
gtk_widget_set_child_visible (path_bar->priv->down_slider_button, TRUE);
gtk_widget_show_all (path_bar->priv->down_slider_button);
gtk_path_bar_update_slider_buttons (path_bar);
}
else
{
needs_reorder |= gtk_widget_get_child_visible (path_bar->priv->down_slider_button) == TRUE;
gtk_widget_set_child_visible (path_bar->priv->down_slider_button, FALSE);
}
if (needs_reorder)
child_ordering_changed (path_bar);
_gtk_widget_set_simple_clip (widget, NULL);
}
static void
gtk_path_bar_style_updated (GtkWidget *widget)
{
GTK_WIDGET_CLASS (gtk_path_bar_parent_class)->style_updated (widget);
gtk_path_bar_check_icon_theme (GTK_PATH_BAR (widget));
}
static void
gtk_path_bar_screen_changed (GtkWidget *widget,
GdkScreen *previous_screen)
{
if (GTK_WIDGET_CLASS (gtk_path_bar_parent_class)->screen_changed)
GTK_WIDGET_CLASS (gtk_path_bar_parent_class)->screen_changed (widget, previous_screen);
/* We might nave a new settings, so we remove the old one */
if (previous_screen)
remove_settings_signal (GTK_PATH_BAR (widget), previous_screen);
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;
case GDK_SCROLL_SMOOTH:
break;
}
return TRUE;
}
static void
gtk_path_bar_add (GtkContainer *container,
GtkWidget *widget)
{
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)
{
GtkPathBar *path_bar;
GList *children;
path_bar = GTK_PATH_BAR (container);
if (widget == path_bar->priv->up_slider_button)
{
gtk_path_bar_remove_1 (container, widget);
path_bar->priv->up_slider_button = NULL;
return;
}
if (widget == path_bar->priv->down_slider_button)
{
gtk_path_bar_remove_1 (container, widget);
path_bar->priv->down_slider_button = NULL;
return;
}
children = path_bar->priv->button_list;
while (children)
{
if (widget == BUTTON_DATA (children->data)->button)
{
gtk_path_bar_remove_1 (container, widget);
path_bar->priv->button_list = g_list_remove_link (path_bar->priv->button_list, children);
g_list_free (children);
return;
}
children = children->next;
}
}
static void
gtk_path_bar_forall (GtkContainer *container,
gboolean include_internals,
GtkCallback callback,
gpointer callback_data)
{
GtkPathBar *path_bar;
GList *children;
g_return_if_fail (callback != NULL);
path_bar = GTK_PATH_BAR (container);
children = path_bar->priv->button_list;
while (children)
{
GtkWidget *child;
child = BUTTON_DATA (children->data)->button;
children = children->next;
(* callback) (child, callback_data);
}
if (path_bar->priv->up_slider_button)
(* callback) (path_bar->priv->up_slider_button, callback_data);
if (path_bar->priv->down_slider_button)
(* callback) (path_bar->priv->down_slider_button, callback_data);
}
static GtkWidgetPath *
gtk_path_bar_get_path_for_child (GtkContainer *container,
GtkWidget *child)
{
GtkPathBar *path_bar = GTK_PATH_BAR (container);
GtkWidgetPath *path;
path = _gtk_widget_create_path (GTK_WIDGET (path_bar));
if (gtk_widget_get_visible (child) &&
gtk_widget_get_child_visible (child))
{
GtkWidgetPath *sibling_path;
GList *visible_children;
GList *l;
int pos;
/* 1. Build the list of visible children, in visually left-to-right order
* (i.e. independently of the widget's direction). Note that our
* button_list is stored in innermost-to-outermost path order!
*/
visible_children = NULL;
if (gtk_widget_get_visible (path_bar->priv->down_slider_button) &&
gtk_widget_get_child_visible (path_bar->priv->down_slider_button))
visible_children = g_list_prepend (visible_children, path_bar->priv->down_slider_button);
for (l = path_bar->priv->button_list; l; l = l->next)
{
ButtonData *data = l->data;
if (gtk_widget_get_visible (data->button) &&
gtk_widget_get_child_visible (data->button))
visible_children = g_list_prepend (visible_children, data->button);
}
if (gtk_widget_get_visible (path_bar->priv->up_slider_button) &&
gtk_widget_get_child_visible (path_bar->priv->up_slider_button))
visible_children = g_list_prepend (visible_children, path_bar->priv->up_slider_button);
if (gtk_widget_get_direction (GTK_WIDGET (path_bar)) == GTK_TEXT_DIR_RTL)
visible_children = g_list_reverse (visible_children);
/* 2. Find the index of the child within that list */
pos = 0;
for (l = visible_children; l; l = l->next)
{
GtkWidget *button = l->data;
if (button == child)
break;
pos++;
}
/* 3. Build the path */
sibling_path = gtk_widget_path_new ();
for (l = visible_children; l; l = l->next)
gtk_widget_path_append_for_widget (sibling_path, l->data);
gtk_widget_path_append_with_siblings (path, sibling_path, pos);
g_list_free (visible_children);
gtk_widget_path_unref (sibling_path);
}
else
gtk_widget_path_append_for_widget (path, child);
return path;
}
static void
gtk_path_bar_scroll_down (GtkPathBar *path_bar)
{
GtkAllocation allocation, button_allocation;
GList *list;
GList *down_button = NULL;
gint space_available;
if (path_bar->priv->ignore_click)
{
path_bar->priv->ignore_click = FALSE;
return;
}
if (gtk_widget_get_child_visible (BUTTON_DATA (path_bar->priv->button_list->data)->button))
{
/* Return if the last button is already visible */
return;
}
gtk_widget_queue_resize (GTK_WIDGET (path_bar));
/* We find the button at the 'down' end that we have to make
* visible */
for (list = path_bar->priv->button_list; list; list = list->next)
{
if (list->next && gtk_widget_get_child_visible (BUTTON_DATA (list->next->data)->button))
{
down_button = list;
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->priv->slider_width
- button_allocation.width);
path_bar->priv->first_scrolled_button = down_button;
/* 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.
*/
while (space_available > 0)
{
path_bar->priv->first_scrolled_button = down_button;
down_button = down_button->next;
if (!down_button)
break;
space_available -= button_allocation.width;
}
}
static void
gtk_path_bar_scroll_up (GtkPathBar *path_bar)
{
GList *list;
if (path_bar->priv->ignore_click)
{
path_bar->priv->ignore_click = FALSE;
return;
}
list = g_list_last (path_bar->priv->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; list = list->prev)
{
if (list->prev && gtk_widget_get_child_visible (BUTTON_DATA (list->prev->data)->button))
{
if (list->prev == path_bar->priv->fake_root)
path_bar->priv->fake_root = NULL;
path_bar->priv->first_scrolled_button = list;
return;
}
}
}
static gboolean
gtk_path_bar_scroll_timeout (GtkPathBar *path_bar)
{
gboolean retval = FALSE;
if (path_bar->priv->timer)
{
if (path_bar->priv->scrolling_up)
gtk_path_bar_scroll_up (path_bar);
else if (path_bar->priv->scrolling_down)
gtk_path_bar_scroll_down (path_bar);
if (path_bar->priv->need_timer)
{
path_bar->priv->need_timer = FALSE;
path_bar->priv->timer = gdk_threads_add_timeout (TIMEOUT_REPEAT * SCROLL_DELAY_FACTOR,
(GSourceFunc)gtk_path_bar_scroll_timeout,
path_bar);
g_source_set_name_by_id (path_bar->priv->timer, "[gtk+] gtk_path_bar_scroll_timeout");
}
else
retval = TRUE;
}
return retval;
}
static void
gtk_path_bar_stop_scrolling (GtkPathBar *path_bar)
{
if (path_bar->priv->timer)
{
g_source_remove (path_bar->priv->timer);
path_bar->priv->timer = 0;
path_bar->priv->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->priv->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->priv->up_slider_button)) ||
(!gtk_widget_get_child_visible (path_bar->priv->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->priv->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->priv->down_slider_button)) ||
(!gtk_widget_get_child_visible (path_bar->priv->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 != GDK_BUTTON_PRIMARY)
return FALSE;
path_bar->priv->ignore_click = FALSE;
if (widget == path_bar->priv->up_slider_button)
{
path_bar->priv->scrolling_down = FALSE;
path_bar->priv->scrolling_up = TRUE;
gtk_path_bar_scroll_up (path_bar);
}
else if (widget == path_bar->priv->down_slider_button)
{
path_bar->priv->scrolling_up = FALSE;
path_bar->priv->scrolling_down = TRUE;
gtk_path_bar_scroll_down (path_bar);
}
if (!path_bar->priv->timer)
{
path_bar->priv->need_timer = TRUE;
path_bar->priv->timer = gdk_threads_add_timeout (TIMEOUT_INITIAL,
(GSourceFunc)gtk_path_bar_scroll_timeout,
path_bar);
g_source_set_name_by_id (path_bar->priv->timer, "[gtk+] gtk_path_bar_scroll_timeout");
}
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->priv->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)
{
GList *list;
if (path_bar->priv->root_icon)
{
cairo_surface_destroy (path_bar->priv->root_icon);
path_bar->priv->root_icon = NULL;
}
if (path_bar->priv->home_icon)
{
cairo_surface_destroy (path_bar->priv->home_icon);
path_bar->priv->home_icon = NULL;
}
if (path_bar->priv->desktop_icon)
{
cairo_surface_destroy (path_bar->priv->desktop_icon);
path_bar->priv->desktop_icon = NULL;
}
for (list = path_bar->priv->button_list; list; list = list->next)
{
ButtonData *button_data;
gboolean current_dir;
button_data = BUTTON_DATA (list->data);
if (button_data->type != NORMAL_BUTTON)
{
current_dir = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button_data->button));
gtk_path_bar_update_button_appearance (path_bar, button_data, current_dir);
}
}
}
static void
change_icon_theme (GtkPathBar *path_bar)
{
gint width, height;
if (gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &width, &height))
path_bar->priv->icon_size = MAX (width, height);
else
path_bar->priv->icon_size = FALLBACK_ICON_SIZE;
reload_icons (path_bar);
}
/* Callback used when a GtkSettings value changes */
static void
settings_notify_cb (GObject *object,
GParamSpec *pspec,
GtkPathBar *path_bar)
{
const char *name;
name = g_param_spec_get_name (pspec);
if (strcmp (name, "gtk-icon-theme-name") == 0)
change_icon_theme (path_bar);
}
static void
gtk_path_bar_check_icon_theme (GtkPathBar *path_bar)
{
if (path_bar->priv->settings_signal_id == 0)
{
GtkSettings *settings;
settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (path_bar)));
path_bar->priv->settings_signal_id = g_signal_connect (settings, "notify",
G_CALLBACK (settings_notify_cb), path_bar);
}
change_icon_theme (path_bar);
}
/* Public functions and their helpers */
static void
gtk_path_bar_clear_buttons (GtkPathBar *path_bar)
{
while (path_bar->priv->button_list != NULL)
{
gtk_container_remove (GTK_CONTAINER (path_bar), BUTTON_DATA (path_bar->priv->button_list->data)->button);
}
path_bar->priv->first_scrolled_button = NULL;
path_bar->priv->fake_root = NULL;
}
static void
button_clicked_cb (GtkWidget *button,
gpointer data)
{
ButtonData *button_data;
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 = GTK_PATH_BAR (gtk_widget_get_parent (button));
button_list = g_list_find (path_bar->priv->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;
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);
}
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);
cairo_surface_t *surface;
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;
surface = _gtk_file_info_render_symbolic_icon (info,
GTK_WIDGET (data->path_bar),
data->path_bar->priv->icon_size);
gtk_image_set_from_surface (GTK_IMAGE (data->button_data->image), surface);
switch (data->button_data->type)
{
case HOME_BUTTON:
if (data->path_bar->priv->home_icon)
cairo_surface_destroy (surface);
else
data->path_bar->priv->home_icon = surface;
break;
case DESKTOP_BUTTON:
if (data->path_bar->priv->desktop_icon)
cairo_surface_destroy (surface);
else
data->path_bar->priv->desktop_icon = surface;
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_data->type)
{
case ROOT_BUTTON:
if (path_bar->priv->root_icon != NULL)
{
gtk_image_set_from_surface (GTK_IMAGE (button_data->image), path_bar->priv->root_icon);
break;
}
volume = _gtk_file_system_get_volume_for_file (path_bar->priv->file_system, path_bar->priv->root_file);
if (volume == NULL)
return;
path_bar->priv->root_icon = _gtk_file_system_volume_render_symbolic_icon (volume,
GTK_WIDGET (path_bar),
path_bar->priv->icon_size,
NULL);
_gtk_file_system_volume_unref (volume);
gtk_image_set_from_surface (GTK_IMAGE (button_data->image), path_bar->priv->root_icon);
break;
case HOME_BUTTON:
if (path_bar->priv->home_icon != NULL)
{
gtk_image_set_from_surface (GTK_IMAGE (button_data->image), path_bar->priv->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->priv->file_system,
path_bar->priv->home_file,
"standard::symbolic-icon",
set_button_image_get_info_cb,
data);
break;
case DESKTOP_BUTTON:
if (path_bar->priv->desktop_icon != NULL)
{
gtk_image_set_from_surface (GTK_IMAGE (button_data->image), path_bar->priv->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->priv->file_system,
path_bar->priv->desktop_file,
"standard::symbolic-icon",
set_button_image_get_info_cb,
data);
break;
default:
break;
}
}
static void
button_data_free (ButtonData *button_data)
{
if (button_data->file)
g_object_unref (button_data->file);
button_data->file = NULL;
g_free (button_data->dir_name);
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;
}
static void
gtk_path_bar_update_button_appearance (GtkPathBar *path_bar,
ButtonData *button_data,
gboolean current_dir)
{
const gchar *dir_name = get_dir_name (button_data);
GtkStyleContext *context;
context = gtk_widget_get_style_context (button_data->button);
gtk_style_context_remove_class (context, "text-button");
gtk_style_context_remove_class (context, "image-button");
if (button_data->label != NULL)
{
gtk_label_set_text (GTK_LABEL (button_data->label), dir_name);
if (button_data->image == NULL)
gtk_style_context_add_class (context, "text-button");
}
if (button_data->image != NULL)
{
set_button_image (path_bar, button_data);
if (button_data->label == NULL)
gtk_style_context_add_class (context, "image-button");
}
if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button_data->button)) != current_dir)
{
button_data->ignore_changes = TRUE;
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button_data->button), current_dir);
button_data->ignore_changes = FALSE;
}
}
static ButtonType
find_button_type (GtkPathBar *path_bar,
GFile *file)
{
if (path_bar->priv->root_file != NULL &&
g_file_equal (file, path_bar->priv->root_file))
return ROOT_BUTTON;
if (path_bar->priv->home_file != NULL &&
g_file_equal (file, path_bar->priv->home_file))
return HOME_BUTTON;
if (path_bar->priv->desktop_file != NULL &&
g_file_equal (file, path_bar->priv->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,
GFile *file,
gboolean current_dir,
gboolean file_is_hidden)
{
AtkObject *atk_obj;
GtkWidget *child = 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, 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);
gtk_widget_add_events (button_data->button, GDK_SCROLL_MASK);
switch (button_data->type)
{
case ROOT_BUTTON:
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_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
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);
break;
case NORMAL_BUTTON:
default:
button_data->label = gtk_label_new (NULL);
child = button_data->label;
button_data->image = NULL;
}
button_data->dir_name = g_strdup (dir_name);
button_data->file = g_object_ref (file);
button_data->file_is_hidden = file_is_hidden;
gtk_container_add (GTK_CONTAINER (button_data->button), child);
gtk_widget_show_all (button_data->button);
gtk_path_bar_update_button_appearance (path_bar, button_data, current_dir);
g_signal_connect (button_data->button, "clicked",
G_CALLBACK (button_clicked_cb),
button_data);
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,
GFile *file)
{
GList *list;
GList *current_path = NULL;
gboolean need_new_fake_root = FALSE;
for (list = path_bar->priv->button_list; list; list = list->next)
{
ButtonData *button_data;
button_data = list->data;
if (g_file_equal (file, button_data->file))
{
current_path = list;
break;
}
if (list == path_bar->priv->fake_root)
need_new_fake_root = TRUE;
}
if (current_path)
{
if (need_new_fake_root)
{
path_bar->priv->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->priv->fake_root = list;
break;
}
}
}
for (list = path_bar->priv->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->priv->first_scrolled_button = current_path;
gtk_widget_queue_resize (GTK_WIDGET (path_bar));
}
return TRUE;
}
return FALSE;
}
struct SetFileInfo
{
GFile *file;
GFile *parent_file;
GtkPathBar *path_bar;
GList *new_buttons;
GList *fake_root;
gboolean first_directory;
};
static void
gtk_path_bar_set_file_finish (struct SetFileInfo *info,
gboolean result)
{
if (result)
{
GList *l;
gtk_path_bar_clear_buttons (info->path_bar);
info->path_bar->priv->button_list = g_list_reverse (info->new_buttons);
info->path_bar->priv->fake_root = info->fake_root;
for (l = info->path_bar->priv->button_list; l; l = l->next)
{
GtkWidget *button = BUTTON_DATA (l->data)->button;
gtk_container_add (GTK_CONTAINER (info->path_bar), button);
}
child_ordering_changed (info->path_bar);
}
else
{
GList *l;
for (l = info->new_buttons; l; l = l->next)
{
ButtonData *button_data;
button_data = BUTTON_DATA (l->data);
gtk_widget_destroy (button_data->button);
}
g_list_free (info->new_buttons);
}
if (info->file)
g_object_unref (info->file);
if (info->parent_file)
g_object_unref (info->parent_file);
g_free (info);
}
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;
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return;
if (cancellable != file_info->path_bar->priv->get_info_cancellable)
{
gtk_path_bar_set_file_finish (file_info, FALSE);
g_object_unref (cancellable);
return;
}
g_object_unref (cancellable);
file_info->path_bar->priv->get_info_cancellable = NULL;
if (cancelled || !info)
{
gtk_path_bar_set_file_finish (file_info, FALSE);
return;
}
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);
button_data = make_directory_button (file_info->path_bar, display_name,
file_info->file,
file_info->first_directory, is_hidden);
g_object_unref (file_info->file);
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;
/* We have assigned the info for the innermost button, i.e. the deepest directory.
* Now, go on to fetch the info for this directory's parent.
*/
file_info->file = file_info->parent_file;
file_info->first_directory = FALSE;
if (!file_info->file)
{
/* No parent? Okay, we are done. */
gtk_path_bar_set_file_finish (file_info, TRUE);
return;
}
file_info->parent_file = g_file_get_parent (file_info->file);
/* Recurse asynchronously */
file_info->path_bar->priv->get_info_cancellable =
_gtk_file_system_get_info (file_info->path_bar->priv->file_system,
file_info->file,
"standard::display-name,standard::is-hidden,standard::is-backup",
gtk_path_bar_get_info_callback,
file_info);
}
void
_gtk_path_bar_set_file (GtkPathBar *path_bar,
GFile *file,
const gboolean keep_trail)
{
struct SetFileInfo *info;
g_return_if_fail (GTK_IS_PATH_BAR (path_bar));
g_return_if_fail (G_IS_FILE (file));
/* 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))
return;
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->priv->get_info_cancellable)
g_cancellable_cancel (path_bar->priv->get_info_cancellable);
path_bar->priv->get_info_cancellable =
_gtk_file_system_get_info (path_bar->priv->file_system,
info->file,
"standard::display-name,standard::is-hidden,standard::is-backup",
gtk_path_bar_get_info_callback,
info);
}
/* FIXME: This should be a construct-only property */
void
_gtk_path_bar_set_file_system (GtkPathBar *path_bar,
GtkFileSystem *file_system)
{
const char *home;
g_return_if_fail (GTK_IS_PATH_BAR (path_bar));
g_assert (path_bar->priv->file_system == NULL);
path_bar->priv->file_system = g_object_ref (file_system);
home = g_get_home_dir ();
if (home != NULL)
{
const gchar *desktop;
path_bar->priv->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->priv->desktop_file = g_file_new_for_path (desktop);
else
path_bar->priv->desktop_file = NULL;
}
else
{
path_bar->priv->home_file = NULL;
path_bar->priv->desktop_file = NULL;
}
path_bar->priv->root_file = g_file_new_for_path ("/");
}
/**
* _gtk_path_bar_up:
* @path_bar: a #GtkPathBar
*
* If the selected button in the pathbar is not the furthest button “up” (in the
* root direction), act as if the user clicked on the next button up.
**/
void
_gtk_path_bar_up (GtkPathBar *path_bar)
{
GList *l;
for (l = path_bar->priv->button_list; l; l = l->next)
{
GtkWidget *button = BUTTON_DATA (l->data)->button;
if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
{
if (l->next)
{
GtkWidget *next_button = BUTTON_DATA (l->next->data)->button;
button_clicked_cb (next_button, l->next->data);
}
break;
}
}
}
/**
* _gtk_path_bar_down:
* @path_bar: a #GtkPathBar
*
* If the selected button in the pathbar is not the furthest button “down” (in the
* leaf direction), act as if the user clicked on the next button down.
**/
void
_gtk_path_bar_down (GtkPathBar *path_bar)
{
GList *l;
for (l = path_bar->priv->button_list; l; l = l->next)
{
GtkWidget *button = BUTTON_DATA (l->data)->button;
if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
{
if (l->prev)
{
GtkWidget *prev_button = BUTTON_DATA (l->prev->data)->button;
button_clicked_cb (prev_button, l->prev->data);
}
break;
}
}
}