diff options
Diffstat (limited to 'src/caja-pathbar.c')
-rw-r--r-- | src/caja-pathbar.c | 2165 |
1 files changed, 2165 insertions, 0 deletions
diff --git a/src/caja-pathbar.c b/src/caja-pathbar.c new file mode 100644 index 00000000..78c100e2 --- /dev/null +++ b/src/caja-pathbar.c @@ -0,0 +1,2165 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* caja-pathbar.c + * Copyright (C) 2004 Red Hat, Inc., Jonathan Blandford <[email protected]> + * + * 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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include <config.h> +#include <string.h> +#include <eel/eel-debug.h> +#include <eel/eel-gtk-extensions.h> +#include <eel/eel-glib-extensions.h> +#include <eel/eel-preferences.h> +#include <eel/eel-string.h> +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#include <gio/gio.h> +#include <libcaja-private/caja-file.h> +#include <libcaja-private/caja-file-utilities.h> +#include <libcaja-private/caja-global-preferences.h> +#include <libcaja-private/caja-icon-names.h> +#include <libcaja-private/caja-trash-monitor.h> +#include <libcaja-private/caja-marshal.h> +#include <libcaja-private/caja-dnd.h> +#include <libcaja-private/caja-icon-dnd.h> +#include "caja-pathbar.h" +#include "caja-window.h" +#include "caja-window-private.h" +#include "caja-window-slot.h" + +enum +{ + PATH_CLICKED, + PATH_SET, + LAST_SIGNAL +}; + +typedef enum +{ + NORMAL_BUTTON, + ROOT_BUTTON, + HOME_BUTTON, + DESKTOP_BUTTON, + MOUNT_BUTTON, + DEFAULT_LOCATION_BUTTON, +} ButtonType; + +#define BUTTON_DATA(x) ((ButtonData *)(x)) + +#define SCROLL_TIMEOUT 150 +#define INITIAL_SCROLL_TIMEOUT 300 + +static guint path_bar_signals [LAST_SIGNAL] = { 0 }; + +static gboolean desktop_is_home; + +#define CAJA_PATH_BAR_ICON_SIZE 16 + +typedef struct _ButtonData ButtonData; + +struct _ButtonData +{ + GtkWidget *button; + ButtonType type; + char *dir_name; + GFile *path; + CajaFile *file; + unsigned int file_changed_signal_id; + + /* custom icon */ + GdkPixbuf *custom_icon; + + /* flag to indicate its the base folder in the URI */ + gboolean is_base_dir; + + GtkWidget *image; + GtkWidget *label; + guint ignore_changes : 1; + guint file_is_hidden : 1; + guint fake_root : 1; + + CajaDragSlotProxyInfo drag_info; +}; + +G_DEFINE_TYPE (CajaPathBar, + caja_path_bar, + GTK_TYPE_CONTAINER); + +static void caja_path_bar_finalize (GObject *object); +static void caja_path_bar_dispose (GObject *object); +static void caja_path_bar_size_request (GtkWidget *widget, + GtkRequisition *requisition); +static void caja_path_bar_unmap (GtkWidget *widget); +static void caja_path_bar_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); +static void caja_path_bar_add (GtkContainer *container, + GtkWidget *widget); +static void caja_path_bar_remove (GtkContainer *container, + GtkWidget *widget); +static void caja_path_bar_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, + gpointer callback_data); +static void caja_path_bar_scroll_up (CajaPathBar *path_bar); +static void caja_path_bar_scroll_down (CajaPathBar *path_bar); +static gboolean caja_path_bar_scroll (GtkWidget *path_bar, + GdkEventScroll *scroll); +static void caja_path_bar_stop_scrolling (CajaPathBar *path_bar); +static gboolean caja_path_bar_slider_button_press (GtkWidget *widget, + GdkEventButton *event, + CajaPathBar *path_bar); +static gboolean caja_path_bar_slider_button_release (GtkWidget *widget, + GdkEventButton *event, + CajaPathBar *path_bar); +static void caja_path_bar_grab_notify (GtkWidget *widget, + gboolean was_grabbed); +static void caja_path_bar_state_changed (GtkWidget *widget, + GtkStateType previous_state); +static void caja_path_bar_style_set (GtkWidget *widget, + GtkStyle *previous_style); +static void caja_path_bar_screen_changed (GtkWidget *widget, + GdkScreen *previous_screen); +static void caja_path_bar_check_icon_theme (CajaPathBar *path_bar); +static void caja_path_bar_update_button_appearance (ButtonData *button_data); +static void caja_path_bar_update_button_state (ButtonData *button_data, + gboolean current_dir); +static gboolean caja_path_bar_update_path (CajaPathBar *path_bar, + GFile *file_path, + gboolean emit_signal); + +static GtkWidget * +get_slider_button (CajaPathBar *path_bar, + GtkArrowType arrow_type) +{ + GtkWidget *button; + + gtk_widget_push_composite_child (); + + button = gtk_button_new (); + 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); + + gtk_widget_pop_composite_child (); + + return button; +} + +static void +update_button_types (CajaPathBar *path_bar) +{ + GList *list; + GFile *path = NULL; + + for (list = path_bar->button_list; list; list = list->next) + { + ButtonData *button_data; + button_data = BUTTON_DATA (list->data); + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button_data->button))) + { + path = button_data->path; + break; + } + } + if (path != NULL) + { + caja_path_bar_update_path (path_bar, path, TRUE); + } +} + + +static void +desktop_location_changed_callback (gpointer user_data) +{ + CajaPathBar *path_bar; + + path_bar = CAJA_PATH_BAR (user_data); + + g_object_unref (path_bar->desktop_path); + g_object_unref (path_bar->home_path); + path_bar->desktop_path = caja_get_desktop_location (); + path_bar->home_path = g_file_new_for_path (g_get_home_dir ()); + desktop_is_home = g_file_equal (path_bar->home_path, path_bar->desktop_path); + + update_button_types (path_bar); +} + +static void +trash_state_changed_cb (CajaTrashMonitor *monitor, + gboolean state, + CajaPathBar *path_bar) +{ + GFile *file; + GList *list; + + file = g_file_new_for_uri ("trash:///"); + for (list = path_bar->button_list; list; list = list->next) + { + ButtonData *button_data; + button_data = BUTTON_DATA (list->data); + if (g_file_equal (file, button_data->path)) + { + GIcon *icon; + CajaIconInfo *icon_info; + GdkPixbuf *pixbuf; + + icon = caja_trash_monitor_get_icon (); + icon_info = caja_icon_info_lookup (icon, CAJA_PATH_BAR_ICON_SIZE); + pixbuf = caja_icon_info_get_pixbuf_at_size (icon_info, CAJA_PATH_BAR_ICON_SIZE); + gtk_image_set_from_pixbuf (GTK_IMAGE (button_data->image), pixbuf); + } + } + g_object_unref (file); +} + +static gboolean +slider_timeout (gpointer user_data) +{ + CajaPathBar *path_bar; + + path_bar = CAJA_PATH_BAR (user_data); + + path_bar->drag_slider_timeout = 0; + + if (gtk_widget_get_visible (GTK_WIDGET (path_bar))) + { + if (path_bar->drag_slider_timeout_for_up_button) + { + caja_path_bar_scroll_up (path_bar); + } + else + { + caja_path_bar_scroll_down (path_bar); + } + } + + return FALSE; +} + +static void +caja_path_bar_slider_drag_motion (GtkWidget *widget, + GdkDragContext *context, + int x, + int y, + unsigned int time, + gpointer user_data) +{ + CajaPathBar *path_bar; + GtkSettings *settings; + unsigned int timeout; + + path_bar = CAJA_PATH_BAR (user_data); + + if (path_bar->drag_slider_timeout == 0) + { + settings = gtk_widget_get_settings (widget); + + g_object_get (settings, "gtk-timeout-expand", &timeout, NULL); + path_bar->drag_slider_timeout = + g_timeout_add (timeout, + slider_timeout, + path_bar); + + path_bar->drag_slider_timeout_for_up_button = + widget == path_bar->up_slider_button; + } +} + +static void +caja_path_bar_slider_drag_leave (GtkWidget *widget, + GdkDragContext *context, + unsigned int time, + gpointer user_data) +{ + CajaPathBar *path_bar; + + path_bar = CAJA_PATH_BAR (user_data); + + if (path_bar->drag_slider_timeout != 0) + { + g_source_remove (path_bar->drag_slider_timeout); + path_bar->drag_slider_timeout = 0; + } +} + +static void +caja_path_bar_init (CajaPathBar *path_bar) +{ + char *p; + + 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->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 = CAJA_PATH_BAR_ICON_SIZE; + + p = caja_get_desktop_directory (); + path_bar->desktop_path = g_file_new_for_path (p); + g_free (p); + path_bar->home_path = g_file_new_for_path (g_get_home_dir ()); + path_bar->root_path = g_file_new_for_path ("/"); + desktop_is_home = g_file_equal (path_bar->home_path, path_bar->desktop_path); + + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_DESKTOP_IS_HOME_DIR, + desktop_location_changed_callback, + path_bar, + G_OBJECT (path_bar)); + + g_signal_connect_swapped (path_bar->up_slider_button, "clicked", G_CALLBACK (caja_path_bar_scroll_up), path_bar); + g_signal_connect_swapped (path_bar->down_slider_button, "clicked", G_CALLBACK (caja_path_bar_scroll_down), path_bar); + + g_signal_connect (path_bar->up_slider_button, "button_press_event", G_CALLBACK (caja_path_bar_slider_button_press), path_bar); + g_signal_connect (path_bar->up_slider_button, "button_release_event", G_CALLBACK (caja_path_bar_slider_button_release), path_bar); + g_signal_connect (path_bar->down_slider_button, "button_press_event", G_CALLBACK (caja_path_bar_slider_button_press), path_bar); + g_signal_connect (path_bar->down_slider_button, "button_release_event", G_CALLBACK (caja_path_bar_slider_button_release), path_bar); + + gtk_drag_dest_set (GTK_WIDGET (path_bar->up_slider_button), + 0, NULL, 0, 0); + gtk_drag_dest_set_track_motion (GTK_WIDGET (path_bar->up_slider_button), TRUE); + g_signal_connect (path_bar->up_slider_button, + "drag-motion", + G_CALLBACK (caja_path_bar_slider_drag_motion), + path_bar); + g_signal_connect (path_bar->up_slider_button, + "drag-leave", + G_CALLBACK (caja_path_bar_slider_drag_leave), + path_bar); + + gtk_drag_dest_set (GTK_WIDGET (path_bar->down_slider_button), + 0, NULL, 0, 0); + gtk_drag_dest_set_track_motion (GTK_WIDGET (path_bar->up_slider_button), TRUE); + g_signal_connect (path_bar->down_slider_button, + "drag-motion", + G_CALLBACK (caja_path_bar_slider_drag_motion), + path_bar); + g_signal_connect (path_bar->down_slider_button, + "drag-leave", + G_CALLBACK (caja_path_bar_slider_drag_leave), + path_bar); + + g_signal_connect (caja_trash_monitor_get (), + "trash_state_changed", + G_CALLBACK (trash_state_changed_cb), + path_bar); +} + +static void +caja_path_bar_class_init (CajaPathBarClass *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 = caja_path_bar_finalize; + gobject_class->dispose = caja_path_bar_dispose; + + widget_class->size_request = caja_path_bar_size_request; + widget_class->unmap = caja_path_bar_unmap; + widget_class->size_allocate = caja_path_bar_size_allocate; + widget_class->style_set = caja_path_bar_style_set; + widget_class->screen_changed = caja_path_bar_screen_changed; + widget_class->grab_notify = caja_path_bar_grab_notify; + widget_class->state_changed = caja_path_bar_state_changed; + widget_class->scroll_event = caja_path_bar_scroll; + + container_class->add = caja_path_bar_add; + container_class->forall = caja_path_bar_forall; + container_class->remove = caja_path_bar_remove; + + path_bar_signals [PATH_CLICKED] = + g_signal_new ("path-clicked", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (CajaPathBarClass, path_clicked), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + G_TYPE_FILE); + path_bar_signals [PATH_SET] = + g_signal_new ("path-set", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (CajaPathBarClass, path_set), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + G_TYPE_FILE); +} + + +static void +caja_path_bar_finalize (GObject *object) +{ + CajaPathBar *path_bar; + + path_bar = CAJA_PATH_BAR (object); + + caja_path_bar_stop_scrolling (path_bar); + + if (path_bar->drag_slider_timeout != 0) + { + g_source_remove (path_bar->drag_slider_timeout); + path_bar->drag_slider_timeout = 0; + } + + g_list_free (path_bar->button_list); + if (path_bar->root_path) + { + g_object_unref (path_bar->root_path); + path_bar->root_path = NULL; + } + if (path_bar->home_path) + { + g_object_unref (path_bar->home_path); + path_bar->home_path = NULL; + } + if (path_bar->desktop_path) + { + g_object_unref (path_bar->desktop_path); + path_bar->desktop_path = NULL; + } + + g_signal_handlers_disconnect_by_func (caja_trash_monitor_get (), + trash_state_changed_cb, path_bar); + + G_OBJECT_CLASS (caja_path_bar_parent_class)->finalize (object); +} + +/* Removes the settings signal handler. It's safe to call multiple times */ +static void +remove_settings_signal (CajaPathBar *path_bar, + GdkScreen *screen) +{ + if (path_bar->settings_signal_id) + { + GtkSettings *settings; + + settings = gtk_settings_get_for_screen (screen); + g_signal_handler_disconnect (settings, + path_bar->settings_signal_id); + path_bar->settings_signal_id = 0; + } +} + +static void +caja_path_bar_dispose (GObject *object) +{ + remove_settings_signal (CAJA_PATH_BAR (object), gtk_widget_get_screen (GTK_WIDGET (object))); + + G_OBJECT_CLASS (caja_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 +caja_path_bar_size_request (GtkWidget *widget, + GtkRequisition *requisition) +{ + ButtonData *button_data; + CajaPathBar *path_bar; + GtkRequisition child_requisition; + GList *list; + guint border_width; + + path_bar = CAJA_PATH_BAR (widget); + + requisition->width = 0; + requisition->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); + } + + /* 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 = MIN(requisition->height * 2 / 3 + 5, requisition->height); + if (path_bar->button_list && path_bar->button_list->next != NULL) + { + requisition->width += (path_bar->spacing + path_bar->slider_width) * 2; + } + + gtk_widget_size_request (path_bar->up_slider_button, &child_requisition); + gtk_widget_size_request (path_bar->down_slider_button, &child_requisition); + + border_width = gtk_container_get_border_width (GTK_CONTAINER (widget)); + requisition->width += border_width * 2; + requisition->height += border_width * 2; + + gtk_widget_set_size_request (widget, requisition->width, + requisition->height); +} + +static void +caja_path_bar_update_slider_buttons (CajaPathBar *path_bar) +{ + if (path_bar->button_list) + { + + GtkWidget *button; + + 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); + } + 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); + } + else + { + gtk_widget_set_sensitive (path_bar->up_slider_button, TRUE); + } + } +} + +static void +caja_path_bar_unmap (GtkWidget *widget) +{ + caja_path_bar_stop_scrolling (CAJA_PATH_BAR (widget)); + + GTK_WIDGET_CLASS (caja_path_bar_parent_class)->unmap (widget); +} + +/* This is a tad complicated */ +static void +caja_path_bar_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GtkWidget *child; + CajaPathBar *path_bar; + GtkTextDirection direction; + GtkAllocation child_allocation; + GList *list, *first_button; + gint width; + gint allocation_width; + gint border_width; + gboolean need_sliders; + gint up_slider_offset; + gint down_slider_offset; + GtkRequisition child_requisition; + GtkAllocation widget_allocation; + + need_sliders = FALSE; + up_slider_offset = 0; + down_slider_offset = 0; + path_bar = CAJA_PATH_BAR (widget); + + gtk_widget_set_allocation (widget, allocation); + + /* No path is set so 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_get_border_width (GTK_CONTAINER (path_bar)); + allocation_width = allocation->width - 2 * border_width; + + /* First, we check to see if we need the scrollbars. */ + if (path_bar->fake_root) + { + width = path_bar->spacing + path_bar->slider_width; + } + else + { + width = 0; + } + + gtk_widget_get_child_requisition (BUTTON_DATA (path_bar->button_list->data)->button, &child_requisition); + width += child_requisition.width; + + for (list = path_bar->button_list->next; list; list = list->next) + { + child = BUTTON_DATA (list->data)->button; + gtk_widget_get_child_requisition (child, &child_requisition); + width += child_requisition.width + path_bar->spacing; + + if (list == path_bar->fake_root) + { + break; + } + } + + if (width <= allocation_width) + { + if (path_bar->fake_root) + { + first_button = path_bar->fake_root; + } + else + { + first_button = g_list_last (path_bar->button_list); + } + } + else + { + gboolean reached_end; + gint slider_space; + reached_end = FALSE; + slider_space = 2 * (path_bar->spacing + path_bar->slider_width); + + if (path_bar->first_scrolled_button) + { + first_button = path_bar->first_scrolled_button; + } + else + { + first_button = path_bar->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_child_requisition (BUTTON_DATA (first_button->data)->button, &child_requisition); + width = child_requisition.width; + list = first_button->prev; + while (list && !reached_end) + { + child = BUTTON_DATA (list->data)->button; + gtk_widget_get_child_requisition (child, &child_requisition); + + 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; + } + } + + 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_child_requisition (child, &child_requisition); + + if (width + child_requisition.width + path_bar->spacing + slider_space > allocation_width) + { + reached_end = TRUE; + } + else + { + 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); + + if (direction == GTK_TEXT_DIR_RTL) + { + child_allocation.x = allocation->x + allocation->width - border_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; + } + } + else + { + child_allocation.x = allocation->x + border_width; + if (need_sliders || path_bar->fake_root) + { + up_slider_offset = border_width; + child_allocation.x += (path_bar->spacing + path_bar->slider_width); + } + } + + for (list = first_button; list; list = list->prev) + { + child = BUTTON_DATA (list->data)->button; + gtk_widget_get_child_requisition (child, &child_requisition); + + gtk_widget_get_allocation (widget, &widget_allocation); + + 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) + { + break; + } + } + else + { + if (need_sliders && direction == GTK_TEXT_DIR_LTR) + { + if (child_allocation.x + child_allocation.width + path_bar->spacing + path_bar->slider_width > widget_allocation.x + border_width + allocation_width) + { + break; + } + } + } + + gtk_widget_set_child_visible (BUTTON_DATA (list->data)->button, 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; + down_slider_offset = border_width; + } + else + { + down_slider_offset = child_allocation.x - widget_allocation.x; + down_slider_offset = allocation->width - border_width - path_bar->slider_width; + child_allocation.x += child_allocation.width + path_bar->spacing; + } + } + /* Now we go hide all the widgets that don't fit */ + while (list) + { + gtk_widget_set_child_visible (BUTTON_DATA (list->data)->button, FALSE); + list = list->prev; + } + for (list = first_button->next; list; list = list->next) + { + gtk_widget_set_child_visible (BUTTON_DATA (list->data)->button, FALSE); + } + + 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); + + 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; + child_allocation.x = down_slider_offset + allocation->x; + gtk_widget_size_allocate (path_bar->down_slider_button, &child_allocation); + + gtk_widget_set_child_visible (path_bar->down_slider_button, TRUE); + gtk_widget_show_all (path_bar->down_slider_button); + caja_path_bar_update_slider_buttons (path_bar); + } + else + { + gtk_widget_set_child_visible (path_bar->down_slider_button, FALSE); + } +} + +static void +caja_path_bar_style_set (GtkWidget *widget, GtkStyle *previous_style) +{ + if (GTK_WIDGET_CLASS (caja_path_bar_parent_class)->style_set) + { + GTK_WIDGET_CLASS (caja_path_bar_parent_class)->style_set (widget, previous_style); + } + + caja_path_bar_check_icon_theme (CAJA_PATH_BAR (widget)); +} + +static void +caja_path_bar_screen_changed (GtkWidget *widget, + GdkScreen *previous_screen) +{ + if (GTK_WIDGET_CLASS (caja_path_bar_parent_class)->screen_changed) + { + GTK_WIDGET_CLASS (caja_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 (CAJA_PATH_BAR (widget), previous_screen); + } + caja_path_bar_check_icon_theme (CAJA_PATH_BAR (widget)); +} + +static gboolean +caja_path_bar_scroll (GtkWidget *widget, + GdkEventScroll *event) +{ + CajaPathBar *path_bar; + + path_bar = CAJA_PATH_BAR (widget); + + switch (event->direction) + { + case GDK_SCROLL_RIGHT: + case GDK_SCROLL_DOWN: + caja_path_bar_scroll_down (path_bar); + return TRUE; + + case GDK_SCROLL_LEFT: + case GDK_SCROLL_UP: + caja_path_bar_scroll_up (path_bar); + return TRUE; + } + + return FALSE; +} + + +static void +caja_path_bar_add (GtkContainer *container, + GtkWidget *widget) +{ + gtk_widget_set_parent (widget, GTK_WIDGET (container)); +} + +static void +caja_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 +caja_path_bar_remove (GtkContainer *container, + GtkWidget *widget) +{ + CajaPathBar *path_bar; + GList *children; + + path_bar = CAJA_PATH_BAR (container); + + if (widget == path_bar->up_slider_button) + { + caja_path_bar_remove_1 (container, widget); + path_bar->up_slider_button = NULL; + return; + } + + if (widget == path_bar->down_slider_button) + { + caja_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) + { + caja_path_bar_remove_1 (container, widget); + path_bar->button_list = g_list_remove_link (path_bar->button_list, children); + g_list_free_1 (children); + return; + } + children = children->next; + } +} + +static void +caja_path_bar_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, + gpointer callback_data) +{ + CajaPathBar *path_bar; + GList *children; + + g_return_if_fail (callback != NULL); + path_bar = CAJA_PATH_BAR (container); + + children = path_bar->button_list; + while (children) + { + GtkWidget *child; + child = BUTTON_DATA (children->data)->button; + children = children->next; + (* callback) (child, 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 +caja_path_bar_scroll_down (CajaPathBar *path_bar) +{ + GList *list; + GList *down_button; + GList *up_button; + gint space_available; + gint space_needed; + gint border_width; + GtkTextDirection direction; + GtkAllocation allocation, button_allocation, slider_allocation; + + down_button = NULL; + up_button = NULL; + + if (path_bar->ignore_click) + { + path_bar->ignore_click = FALSE; + return; + } + + gtk_widget_queue_resize (GTK_WIDGET (path_bar)); + + border_width = gtk_container_get_border_width (GTK_CONTAINER (path_bar)); + 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) + { + if (list->next && gtk_widget_get_child_visible (BUTTON_DATA (list->next->data)->button)) + { + down_button = list; + break; + } + } + + if (down_button == NULL) + { + return; + } + + /* Find the last visible button on the 'up' end */ + 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; + } + } + + gtk_widget_get_allocation (BUTTON_DATA (down_button->data)->button, &button_allocation); + gtk_widget_get_allocation (GTK_WIDGET (path_bar), &allocation); + gtk_widget_get_allocation (path_bar->down_slider_button, &slider_allocation); + + space_needed = button_allocation.width + path_bar->spacing; + if (direction == GTK_TEXT_DIR_RTL) + { + space_available = slider_allocation.x - allocation.x; + } + else + { + space_available = (allocation.x + allocation.width - border_width) - + (slider_allocation.x + slider_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. */ + gtk_widget_get_allocation (BUTTON_DATA (up_button->data)->button, &button_allocation); + while (space_available < space_needed) + { + space_available += button_allocation.width + path_bar->spacing; + up_button = up_button->prev; + path_bar->first_scrolled_button = up_button; + } +} + +static void +caja_path_bar_scroll_up (CajaPathBar *path_bar) +{ + GList *list; + + if (path_bar->ignore_click) + { + path_bar->ignore_click = FALSE; + return; + } + + gtk_widget_queue_resize (GTK_WIDGET (path_bar)); + + for (list = g_list_last (path_bar->button_list); 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 +caja_path_bar_scroll_timeout (CajaPathBar *path_bar) +{ + gboolean retval = FALSE; + + GDK_THREADS_ENTER (); + + if (path_bar->timer) + { + if (gtk_widget_has_focus (path_bar->up_slider_button)) + { + caja_path_bar_scroll_up (path_bar); + } + else + { + if (gtk_widget_has_focus (path_bar->down_slider_button)) + { + caja_path_bar_scroll_down (path_bar); + } + } + if (path_bar->need_timer) + { + path_bar->need_timer = FALSE; + + path_bar->timer = g_timeout_add (SCROLL_TIMEOUT, + (GSourceFunc)caja_path_bar_scroll_timeout, + path_bar); + + } + else + { + retval = TRUE; + } + } + + + GDK_THREADS_LEAVE (); + + return retval; +} + +static void +caja_path_bar_stop_scrolling (CajaPathBar *path_bar) +{ + if (path_bar->timer) + { + g_source_remove (path_bar->timer); + path_bar->timer = 0; + path_bar->need_timer = FALSE; + } +} + +static gboolean +caja_path_bar_slider_button_press (GtkWidget *widget, + GdkEventButton *event, + CajaPathBar *path_bar) +{ + if (!gtk_widget_has_focus (widget)) + { + gtk_widget_grab_focus (widget); + } + + if (event->type != GDK_BUTTON_PRESS || event->button != 1) + { + return FALSE; + } + + path_bar->ignore_click = FALSE; + + if (widget == path_bar->up_slider_button) + { + caja_path_bar_scroll_up (path_bar); + } + else + { + if (widget == path_bar->down_slider_button) + { + caja_path_bar_scroll_down (path_bar); + } + } + + if (!path_bar->timer) + { + path_bar->need_timer = TRUE; + path_bar->timer = g_timeout_add (INITIAL_SCROLL_TIMEOUT, + (GSourceFunc)caja_path_bar_scroll_timeout, + path_bar); + } + + return FALSE; +} + +static gboolean +caja_path_bar_slider_button_release (GtkWidget *widget, + GdkEventButton *event, + CajaPathBar *path_bar) +{ + if (event->type != GDK_BUTTON_RELEASE) + { + return FALSE; + } + + path_bar->ignore_click = TRUE; + caja_path_bar_stop_scrolling (path_bar); + + return FALSE; +} + +static void +caja_path_bar_grab_notify (GtkWidget *widget, + gboolean was_grabbed) +{ + if (!was_grabbed) + { + caja_path_bar_stop_scrolling (CAJA_PATH_BAR (widget)); + } +} + +static void +caja_path_bar_state_changed (GtkWidget *widget, + GtkStateType previous_state) +{ + if (!gtk_widget_get_sensitive (widget)) + { + caja_path_bar_stop_scrolling (CAJA_PATH_BAR (widget)); + } +} + + + +/* Changes the icons wherever it is needed */ +static void +reload_icons (CajaPathBar *path_bar) +{ + GList *list; + + for (list = path_bar->button_list; list; list = list->next) + { + ButtonData *button_data; + + button_data = BUTTON_DATA (list->data); + if (button_data->type != NORMAL_BUTTON || button_data->is_base_dir) + { + caja_path_bar_update_button_appearance (button_data); + } + + } +} + +static void +change_icon_theme (CajaPathBar *path_bar) +{ + path_bar->icon_size = CAJA_PATH_BAR_ICON_SIZE; + reload_icons (path_bar); +} + +/* Callback used when a GtkSettings value changes */ +static void +settings_notify_cb (GObject *object, + GParamSpec *pspec, + CajaPathBar *path_bar) +{ + const char *name; + + name = g_param_spec_get_name (pspec); + + if (! strcmp (name, "gtk-icon-theme-name") || ! strcmp (name, "gtk-icon-sizes")) + { + change_icon_theme (path_bar); + } +} + +static void +caja_path_bar_check_icon_theme (CajaPathBar *path_bar) +{ + GtkSettings *settings; + + if (path_bar->settings_signal_id) + { + return; + } + + settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (path_bar))); + path_bar->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 */ +void +caja_path_bar_clear_buttons (CajaPathBar *path_bar) +{ + while (path_bar->button_list != NULL) + { + 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 +button_clicked_cb (GtkWidget *button, + gpointer data) +{ + ButtonData *button_data; + CajaPathBar *path_bar; + GList *button_list; + gboolean child_is_hidden; + + button_data = BUTTON_DATA (data); + if (button_data->ignore_changes) + { + return; + } + + path_bar = CAJA_PATH_BAR (gtk_widget_get_parent (button)); + + button_list = g_list_find (path_bar->button_list, button_data); + g_assert (button_list != NULL); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE); + + if (button_list->prev) + { + ButtonData *child_data; + + child_data = BUTTON_DATA (button_list->prev->data); + child_is_hidden = child_data->file_is_hidden; + } + else + { + child_is_hidden = FALSE; + } + g_signal_emit (path_bar, path_bar_signals [PATH_CLICKED], 0, button_data->path); +} + +static CajaIconInfo * +get_custom_user_icon_info (ButtonData *button_data) +{ + /* Bug 80925: With tiny display sizes we get huge memory allocations. */ +#if 0 + CajaIconInfo *icon_info; + GFile *icon_file; + GIcon *icon; + char *custom_icon_uri; + + icon = NULL; + + if (button_data->file != NULL) + { + custom_icon_uri = caja_file_get_custom_icon (button_data->file); + if (custom_icon_uri != NULL) + { + icon_file = g_file_new_for_uri (custom_icon_uri); + + if (g_file_is_native (icon_file)) + { + icon = g_file_icon_new (icon_file); + } + + g_object_unref (icon_file); + g_free (custom_icon_uri); + } + } + + if (icon != NULL) + { + icon_info = caja_icon_info_lookup (icon, CAJA_PATH_BAR_ICON_SIZE); + g_object_unref (icon); + + return icon_info; + } +#endif + + return NULL; +} + +static CajaIconInfo * +get_type_icon_info (ButtonData *button_data) +{ + switch (button_data->type) + { + case ROOT_BUTTON: + return caja_icon_info_lookup_from_name (CAJA_ICON_FILESYSTEM, + CAJA_PATH_BAR_ICON_SIZE); + + case HOME_BUTTON: + return caja_icon_info_lookup_from_name (CAJA_ICON_HOME, + CAJA_PATH_BAR_ICON_SIZE); + + case DESKTOP_BUTTON: + return caja_icon_info_lookup_from_name (CAJA_ICON_DESKTOP, + CAJA_PATH_BAR_ICON_SIZE); + + case NORMAL_BUTTON: + if (button_data->is_base_dir) + { + return caja_file_get_icon (button_data->file, + CAJA_PATH_BAR_ICON_SIZE, + CAJA_FILE_ICON_FLAGS_NONE); + } + + default: + return NULL; + } + + return NULL; +} + +static void +button_data_free (ButtonData *button_data) +{ + g_object_unref (button_data->path); + g_free (button_data->dir_name); + if (button_data->custom_icon) + { + g_object_unref (button_data->custom_icon); + } + if (button_data->file != NULL) + { + g_signal_handler_disconnect (button_data->file, + button_data->file_changed_signal_id); + caja_file_monitor_remove (button_data->file, button_data); + caja_file_unref (button_data->file); + } + + g_object_unref (button_data->drag_info.target_location); + button_data->drag_info.target_location = NULL; + + g_free (button_data); +} + +static const char * +get_dir_name (ButtonData *button_data) +{ + if (button_data->type == DESKTOP_BUTTON) + { + return _("Desktop"); + } + else + { + 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 +label_size_request_cb (GtkWidget *widget, + GtkRequisition *requisition, + ButtonData *button_data) +{ + const gchar *dir_name = get_dir_name (button_data); + PangoLayout *layout; + gint bold_width, bold_height; + gchar *markup; + + layout = gtk_widget_create_pango_layout (button_data->label, dir_name); + pango_layout_get_pixel_size (layout, &requisition->width, &requisition->height); + + markup = g_markup_printf_escaped ("<b>%s</b>", dir_name); + pango_layout_set_markup (layout, markup, -1); + g_free (markup); + + pango_layout_get_pixel_size (layout, &bold_width, &bold_height); + requisition->width = MAX (requisition->width, bold_width); + requisition->height = MAX (requisition->height, bold_height); + + g_object_unref (layout); +} + +static void +caja_path_bar_update_button_appearance (ButtonData *button_data) +{ + CajaIconInfo *icon_info; + GdkPixbuf *pixbuf; + const gchar *dir_name = get_dir_name (button_data); + + if (button_data->label != NULL) + { + if (gtk_label_get_use_markup (GTK_LABEL (button_data->label))) + { + char *markup; + + markup = g_markup_printf_escaped ("<b>%s</b>", dir_name); + gtk_label_set_markup (GTK_LABEL (button_data->label), markup); + g_free (markup); + } + else + { + gtk_label_set_text (GTK_LABEL (button_data->label), dir_name); + } + } + + if (button_data->image != NULL) + { + if (button_data->custom_icon) + { + gtk_image_set_from_pixbuf (GTK_IMAGE (button_data->image), button_data->custom_icon); + gtk_widget_show (GTK_WIDGET (button_data->image)); + } + else + { + icon_info = get_custom_user_icon_info (button_data); + if (icon_info == NULL) + { + icon_info = get_type_icon_info (button_data); + } + + pixbuf = NULL; + + if (icon_info != NULL) + { + pixbuf = caja_icon_info_get_pixbuf_at_size (icon_info, CAJA_PATH_BAR_ICON_SIZE); + g_object_unref (icon_info); + } + + if (pixbuf != NULL) + { + gtk_image_set_from_pixbuf (GTK_IMAGE (button_data->image), pixbuf); + gtk_widget_show (GTK_WIDGET (button_data->image)); + g_object_unref (pixbuf); + } + else + { + gtk_widget_hide (GTK_WIDGET (button_data->image)); + } + } + } + +} + +static void +caja_path_bar_update_button_state (ButtonData *button_data, + gboolean current_dir) +{ + if (button_data->label != NULL) + { + g_object_set (button_data->label, "use-markup", current_dir, NULL); + } + + caja_path_bar_update_button_appearance (button_data); + + 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 gboolean +setup_file_path_mounted_mount (GFile *location, ButtonData *button_data) +{ + GVolumeMonitor *volume_monitor; + GList *mounts, *l; + GMount *mount; + gboolean result; + GIcon *icon; + CajaIconInfo *info; + GFile *root, *default_location; + + result = FALSE; + volume_monitor = g_volume_monitor_get (); + mounts = g_volume_monitor_get_mounts (volume_monitor); + for (l = mounts; l != NULL; l = l->next) + { + mount = l->data; + if (g_mount_is_shadowed (mount)) + { + continue; + } + if (result) + { + continue; + } + root = g_mount_get_root (mount); + if (g_file_equal (location, root)) + { + result = TRUE; + /* set mount specific details in button_data */ + if (button_data) + { + icon = g_mount_get_icon (mount); + if (icon == NULL) + { + icon = g_themed_icon_new (CAJA_ICON_FOLDER); + } + info = caja_icon_info_lookup (icon, CAJA_PATH_BAR_ICON_SIZE); + g_object_unref (icon); + button_data->custom_icon = caja_icon_info_get_pixbuf_at_size (info, CAJA_PATH_BAR_ICON_SIZE); + g_object_unref (info); + button_data->dir_name = g_mount_get_name (mount); + button_data->type = MOUNT_BUTTON; + button_data->fake_root = TRUE; + } + g_object_unref (root); + break; + } + default_location = g_mount_get_default_location (mount); + if (!g_file_equal (default_location, root) && + g_file_equal (location, default_location)) + { + result = TRUE; + /* set mount specific details in button_data */ + if (button_data) + { + icon = g_mount_get_icon (mount); + if (icon == NULL) + { + icon = g_themed_icon_new (CAJA_ICON_FOLDER); + } + info = caja_icon_info_lookup (icon, CAJA_PATH_BAR_ICON_SIZE); + g_object_unref (icon); + button_data->custom_icon = caja_icon_info_get_pixbuf_at_size (info, CAJA_PATH_BAR_ICON_SIZE); + g_object_unref (info); + button_data->type = DEFAULT_LOCATION_BUTTON; + button_data->fake_root = TRUE; + } + g_object_unref (default_location); + g_object_unref (root); + break; + } + g_object_unref (default_location); + g_object_unref (root); + } + eel_g_object_list_free (mounts); + return result; +} + +static void +setup_button_type (ButtonData *button_data, + CajaPathBar *path_bar, + GFile *location) +{ + if (path_bar->root_path != NULL && g_file_equal (location, path_bar->root_path)) + { + button_data->type = ROOT_BUTTON; + } + else if (path_bar->home_path != NULL && g_file_equal (location, path_bar->home_path)) + { + button_data->type = HOME_BUTTON; + button_data->fake_root = TRUE; + } + else if (path_bar->desktop_path != NULL && g_file_equal (location, path_bar->desktop_path)) + { + if (!desktop_is_home) + { + button_data->type = DESKTOP_BUTTON; + } + else + { + button_data->type = NORMAL_BUTTON; + } + } + else if (setup_file_path_mounted_mount (location, button_data)) + { + /* already setup */ + } + else + { + button_data->type = NORMAL_BUTTON; + } +} + +static void +button_drag_data_get_cb (GtkWidget *widget, + GdkDragContext *context, + GtkSelectionData *selection_data, + guint info, + guint time_, + gpointer user_data) +{ + ButtonData *button_data; + char *uri_list[2]; + char *tmp; + + button_data = user_data; + + uri_list[0] = g_file_get_uri (button_data->path); + uri_list[1] = NULL; + + if (info == CAJA_ICON_DND_MATE_ICON_LIST) + { + tmp = g_strdup_printf ("%s\r\n", uri_list[0]); + gtk_selection_data_set (selection_data, gtk_selection_data_get_target (selection_data), + 8, tmp, strlen (tmp)); + g_free (tmp); + } + else if (info == CAJA_ICON_DND_URI_LIST) + { + gtk_selection_data_set_uris (selection_data, uri_list); + } + + g_free (uri_list[0]); +} + +static void +setup_button_drag_source (ButtonData *button_data) +{ + GtkTargetList *target_list; + const GtkTargetEntry targets[] = + { + { CAJA_ICON_DND_MATE_ICON_LIST_TYPE, 0, CAJA_ICON_DND_MATE_ICON_LIST } + }; + + gtk_drag_source_set (button_data->button, + GDK_BUTTON1_MASK | + GDK_BUTTON2_MASK, + NULL, 0, + GDK_ACTION_MOVE | + GDK_ACTION_COPY | + GDK_ACTION_LINK | + GDK_ACTION_ASK); + + target_list = gtk_target_list_new (targets, G_N_ELEMENTS (targets)); + gtk_target_list_add_uri_targets (target_list, CAJA_ICON_DND_URI_LIST); + gtk_drag_source_set_target_list (button_data->button, target_list); + gtk_target_list_unref (target_list); + + g_signal_connect (button_data->button, "drag-data-get", + G_CALLBACK (button_drag_data_get_cb), + button_data); +} + +static void +button_data_file_changed (CajaFile *file, + ButtonData *button_data) +{ + GFile *location, *current_location, *parent, *button_parent; + ButtonData *current_button_data; + char *display_name; + CajaPathBar *path_bar; + gboolean renamed, child; + + path_bar = (CajaPathBar *) gtk_widget_get_ancestor (button_data->button, + CAJA_TYPE_PATH_BAR); + if (path_bar == NULL) + { + return; + } + + g_assert (path_bar->current_path != NULL); + g_assert (path_bar->current_button_data != NULL); + + current_button_data = path_bar->current_button_data; + + location = caja_file_get_location (file); + if (!g_file_equal (button_data->path, location)) + { + parent = g_file_get_parent (location); + button_parent = g_file_get_parent (button_data->path); + + renamed = (parent != NULL && button_parent != NULL) && + g_file_equal (parent, button_parent); + + if (parent != NULL) + { + g_object_unref (parent); + } + if (button_parent != NULL) + { + g_object_unref (button_parent); + } + + if (renamed) + { + button_data->path = g_object_ref (location); + } + else + { + /* the file has been moved. + * If it was below the currently displayed location, remove it. + * If it was not below the currently displayed location, update the path bar + */ + child = g_file_has_prefix (button_data->path, + path_bar->current_path); + + if (child) + { + /* moved file inside current path hierarchy */ + g_object_unref (location); + location = g_file_get_parent (button_data->path); + current_location = g_object_ref (path_bar->current_path); + } + else + { + /* moved current path, or file outside current path hierarchy. + * Update path bar to new locations. + */ + current_location = caja_file_get_location (current_button_data->file); + } + + caja_path_bar_update_path (path_bar, location, FALSE); + caja_path_bar_set_path (path_bar, current_location); + g_object_unref (location); + g_object_unref (current_location); + return; + } + } + else if (caja_file_is_gone (file)) + { + gint idx, position; + + /* remove this and the following buttons */ + position = g_list_position (path_bar->button_list, + g_list_find (path_bar->button_list, button_data)); + + if (position != -1) + { + for (idx = 0; idx <= position; idx++) + { + gtk_container_remove (GTK_CONTAINER (path_bar), + BUTTON_DATA (path_bar->button_list->data)->button); + } + } + + g_object_unref (location); + return; + } + g_object_unref (location); + + /* MOUNTs use the GMount as the name, so don't update for those */ + if (button_data->type != MOUNT_BUTTON) + { + display_name = caja_file_get_display_name (file); + if (eel_strcmp (display_name, button_data->dir_name) != 0) + { + g_free (button_data->dir_name); + button_data->dir_name = g_strdup (display_name); + } + + g_free (display_name); + } + caja_path_bar_update_button_appearance (button_data); +} + +static ButtonData * +make_directory_button (CajaPathBar *path_bar, + CajaFile *file, + gboolean current_dir, + gboolean base_dir, + gboolean file_is_hidden) +{ + GFile *path; + GtkWidget *child; + GtkWidget *label_alignment; + ButtonData *button_data; + + path = caja_file_get_location (file); + + child = NULL; + label_alignment = NULL; + + file_is_hidden = !! file_is_hidden; + /* Is it a special button? */ + button_data = g_new0 (ButtonData, 1); + + setup_button_type (button_data, path_bar, path); + button_data->button = gtk_toggle_button_new (); + gtk_button_set_focus_on_click (GTK_BUTTON (button_data->button), FALSE); + /* TODO update button type when xdg directories change */ + + button_data->drag_info.target_location = g_object_ref (path); + + button_data->image = gtk_image_new (); + + switch (button_data->type) + { + case ROOT_BUTTON: + child = button_data->image; + button_data->label = NULL; + break; + case HOME_BUTTON: + case DESKTOP_BUTTON: + case MOUNT_BUTTON: + case DEFAULT_LOCATION_BUTTON: + button_data->label = gtk_label_new (NULL); + 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_hbox_new (FALSE, 2); + gtk_box_pack_start (GTK_BOX (child), button_data->image, 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); + 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_hbox_new (FALSE, 2); + gtk_box_pack_start (GTK_BOX (child), button_data->image, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (child), label_alignment, FALSE, FALSE, 0); + button_data->is_base_dir = base_dir; + } + + /* label_alignment is created because we can't override size-request + * on label itself and still have the contents of the label centered + * properly in the label's requisition + */ + + if (label_alignment) + { + g_signal_connect (label_alignment, "size-request", + G_CALLBACK (label_size_request_cb), button_data); + } + + if (button_data->path == NULL) + { + button_data->path = g_object_ref (path); + } + if (button_data->dir_name == NULL) + { + button_data->dir_name = caja_file_get_display_name (file); + } + if (button_data->file == NULL) + { + button_data->file = caja_file_ref (file); + caja_file_monitor_add (button_data->file, button_data, + CAJA_FILE_ATTRIBUTES_FOR_ICON); + button_data->file_changed_signal_id = + g_signal_connect (button_data->file, "changed", + G_CALLBACK (button_data_file_changed), + button_data); + } + + button_data->file_is_hidden = file_is_hidden; + + gtk_container_add (GTK_CONTAINER (button_data->button), child); + gtk_widget_show_all (button_data->button); + + caja_path_bar_update_button_state (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); + + setup_button_drag_source (button_data); + + caja_drag_slot_proxy_init (button_data->button, + &(button_data->drag_info)); + + g_object_unref (path); + + return button_data; +} + +static gboolean +caja_path_bar_check_parent_path (CajaPathBar *path_bar, + GFile *location, + ButtonData **current_button_data) +{ + GList *list; + GList *current_path; + gboolean need_new_fake_root; + + current_path = NULL; + need_new_fake_root = FALSE; + + if (current_button_data) + { + *current_button_data = NULL; + } + + for (list = path_bar->button_list; list; list = list->next) + { + ButtonData *button_data; + + button_data = list->data; + if (g_file_equal (location, button_data->path)) + { + current_path = list; + + if (current_button_data) + { + *current_button_data = button_data; + } + 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 (list->prev != NULL && + button_data->fake_root) + { + path_bar->fake_root = list; + break; + } + } + } + + for (list = path_bar->button_list; list; list = list->next) + { + + caja_path_bar_update_button_state (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; +} + +static gboolean +caja_path_bar_update_path (CajaPathBar *path_bar, + GFile *file_path, + gboolean emit_signal) +{ + CajaFile *file, *parent_file; + gboolean first_directory, last_directory; + gboolean result; + GList *new_buttons, *l, *fake_root; + ButtonData *button_data, *current_button_data; + + g_return_val_if_fail (CAJA_IS_PATH_BAR (path_bar), FALSE); + g_return_val_if_fail (file_path != NULL, FALSE); + + fake_root = NULL; + result = TRUE; + first_directory = TRUE; + last_directory = FALSE; + new_buttons = NULL; + current_button_data = NULL; + + file = caja_file_get (file_path); + + gtk_widget_push_composite_child (); + + while (file != NULL) + { + parent_file = caja_file_get_parent (file); + last_directory = !parent_file; + button_data = make_directory_button (path_bar, file, first_directory, last_directory, FALSE); + caja_file_unref (file); + + if (first_directory) + { + current_button_data = button_data; + } + + new_buttons = g_list_prepend (new_buttons, button_data); + + if (parent_file != NULL && + button_data->fake_root) + { + fake_root = new_buttons; + } + + file = parent_file; + first_directory = FALSE; + } + + caja_path_bar_clear_buttons (path_bar); + path_bar->button_list = g_list_reverse (new_buttons); + path_bar->fake_root = fake_root; + + for (l = path_bar->button_list; l; l = l->next) + { + GtkWidget *button; + button = BUTTON_DATA (l->data)->button; + gtk_container_add (GTK_CONTAINER (path_bar), button); + } + + gtk_widget_pop_composite_child (); + + if (path_bar->current_path != NULL) + { + g_object_unref (path_bar->current_path); + } + + path_bar->current_path = g_object_ref (file_path); + path_bar->current_button_data = current_button_data; + + g_signal_emit (path_bar, path_bar_signals [PATH_SET], 0, file_path); + + return result; +} + +gboolean +caja_path_bar_set_path (CajaPathBar *path_bar, GFile *file_path) +{ + ButtonData *button_data; + + g_return_val_if_fail (CAJA_IS_PATH_BAR (path_bar), FALSE); + g_return_val_if_fail (file_path != NULL, 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 (caja_path_bar_check_parent_path (path_bar, file_path, &button_data)) + { + if (path_bar->current_path != NULL) + { + g_object_unref (path_bar->current_path); + } + + path_bar->current_path = g_object_ref (file_path); + path_bar->current_button_data = button_data; + + return TRUE; + } + + return caja_path_bar_update_path (path_bar, file_path, TRUE); +} + +GFile * +caja_path_bar_get_path_for_button (CajaPathBar *path_bar, + GtkWidget *button) +{ + GList *list; + + g_return_val_if_fail (CAJA_IS_PATH_BAR (path_bar), NULL); + g_return_val_if_fail (GTK_IS_BUTTON (button), NULL); + + for (list = path_bar->button_list; list; list = list->next) + { + ButtonData *button_data; + button_data = BUTTON_DATA (list->data); + if (button_data->button == button) + { + return g_object_ref (button_data->path); + } + } + + return NULL; +} + +/** + * _caja_path_bar_up: + * @path_bar: a #CajaPathBar + * + * 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 +caja_path_bar_up (CajaPathBar *path_bar) +{ + GList *l; + + for (l = path_bar->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; + } + } +} + +/** + * _caja_path_bar_down: + * @path_bar: a #CajaPathBar + * + * 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 +caja_path_bar_down (CajaPathBar *path_bar) +{ + GList *l; + + for (l = path_bar->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; + } + } +} + +GtkWidget * +caja_path_bar_get_button_from_button_list_entry (gpointer entry) +{ + return BUTTON_DATA(entry)->button; +} |