/* $Id: thunar-location-buttons.c 23626 2006-11-04 00:29:23Z benny $ */ /*- * Copyright (c) 2005-2006 Benedikt Meurer * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program 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 General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA * * Based on code written by Jonathan Blandford for * Red Hat, Inc. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #define THUNAR_LOCATION_BUTTONS_SCROLL_TIMEOUT (200) /* Property identifiers */ enum { PROP_0, PROP_CURRENT_DIRECTORY, PROP_SELECTED_FILES, PROP_UI_MANAGER, }; static void thunar_location_buttons_class_init (ThunarLocationButtonsClass *klass); static void thunar_location_buttons_component_init (ThunarComponentIface *iface); static void thunar_location_buttons_navigator_init (ThunarNavigatorIface *iface); static void thunar_location_buttons_location_bar_init (ThunarLocationBarIface *iface); static void thunar_location_buttons_init (ThunarLocationButtons *buttons); static void thunar_location_buttons_finalize (GObject *object); static void thunar_location_buttons_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void thunar_location_buttons_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static GtkUIManager *thunar_location_buttons_get_ui_manager (ThunarComponent *component); static void thunar_location_buttons_set_ui_manager (ThunarComponent *component, GtkUIManager *ui_manager); static ThunarFile *thunar_location_buttons_get_current_directory (ThunarNavigator *navigator); static void thunar_location_buttons_set_current_directory (ThunarNavigator *navigator, ThunarFile *current_directory); static void thunar_location_buttons_unmap (GtkWidget *widget); static void thunar_location_buttons_size_request (GtkWidget *widget, GtkRequisition *requisition); static void thunar_location_buttons_size_allocate (GtkWidget *widget, GtkAllocation *allocation); static void thunar_location_buttons_state_changed (GtkWidget *widget, GtkStateType previous_state); static void thunar_location_buttons_grab_notify (GtkWidget *widget, gboolean was_grabbed); static void thunar_location_buttons_add (GtkContainer *container, GtkWidget *widget); static void thunar_location_buttons_remove (GtkContainer *container, GtkWidget *widget); static void thunar_location_buttons_forall (GtkContainer *container, gboolean include_internals, GtkCallback callback, gpointer callback_data); static GtkWidget *thunar_location_buttons_make_button (ThunarLocationButtons *buttons, ThunarFile *file); static void thunar_location_buttons_remove_1 (GtkContainer *container, GtkWidget *widget); static gboolean thunar_location_buttons_scroll_timeout (gpointer user_data); static void thunar_location_buttons_scroll_timeout_destroy (gpointer user_data); static void thunar_location_buttons_stop_scrolling (ThunarLocationButtons *buttons); static void thunar_location_buttons_update_sliders (ThunarLocationButtons *buttons); static gboolean thunar_location_buttons_slider_button_press (GtkWidget *button, GdkEventButton *event, ThunarLocationButtons *buttons); static gboolean thunar_location_buttons_slider_button_release (GtkWidget *button, GdkEventButton *event, ThunarLocationButtons *buttons); static void thunar_location_buttons_scroll_left (GtkWidget *button, ThunarLocationButtons *buttons); static void thunar_location_buttons_scroll_right (GtkWidget *button, ThunarLocationButtons *buttons); static void thunar_location_buttons_clicked (ThunarLocationButton *button, ThunarLocationButtons *buttons); static void thunar_location_buttons_context_menu (ThunarLocationButton *button, GdkEventButton *event, ThunarLocationButtons *buttons); static void thunar_location_buttons_gone (ThunarLocationButton *button, ThunarLocationButtons *buttons); static void thunar_location_buttons_action_create_folder (GtkAction *action, ThunarLocationButtons *buttons); static void thunar_location_buttons_action_down_folder (GtkAction *action, ThunarLocationButtons *buttons); static void thunar_location_buttons_action_empty_trash (GtkAction *action, ThunarLocationButtons *buttons); static void thunar_location_buttons_action_open (GtkAction *action, ThunarLocationButtons *buttons); static void thunar_location_buttons_action_open_in_new_window (GtkAction *action, ThunarLocationButtons *buttons); static void thunar_location_buttons_action_paste_into_folder (GtkAction *action, ThunarLocationButtons *buttons); static void thunar_location_buttons_action_properties (GtkAction *action, ThunarLocationButtons *buttons); struct _ThunarLocationButtonsClass { GtkContainerClass __parent__; }; struct _ThunarLocationButtons { GtkContainer __parent__; GtkActionGroup *action_group; GtkUIManager *ui_manager; guint ui_merge_id; GtkWidget *left_slider; GtkWidget *right_slider; ThunarFile *current_directory; gint slider_width; gboolean ignore_click : 1; GList *list; GList *fake_root_button; GList *first_scrolled_button; guint scroll_timeout_id; }; static const GtkActionEntry action_entries[] = { { "location-buttons-down-folder", NULL, "Down Folder", "Down", NULL, G_CALLBACK (thunar_location_buttons_action_down_folder), }, { "location-buttons-context-menu", NULL, "Context Menu", NULL, NULL, NULL, }, { "location-buttons-open", GTK_STOCK_OPEN, N_("_Open"), NULL, NULL, G_CALLBACK (thunar_location_buttons_action_open), }, { "location-buttons-open-in-new-window", NULL, N_("Open in New Window"), NULL, NULL, G_CALLBACK (thunar_location_buttons_action_open_in_new_window), }, { "location-buttons-create-folder", NULL, N_("Create _Folder..."), NULL, NULL, G_CALLBACK (thunar_location_buttons_action_create_folder), }, { "location-buttons-empty-trash", NULL, N_("_Empty Trash"), NULL, N_("Delete all files and folders in the Trash"), G_CALLBACK (thunar_location_buttons_action_empty_trash), }, { "location-buttons-paste-into-folder", GTK_STOCK_PASTE, N_("Paste Into Folder"), NULL, NULL, G_CALLBACK (thunar_location_buttons_action_paste_into_folder), }, { "location-buttons-properties", GTK_STOCK_PROPERTIES, N_("_Properties..."), NULL, NULL, G_CALLBACK (thunar_location_buttons_action_properties), }, }; static GObjectClass *thunar_location_buttons_parent_class; GType thunar_location_buttons_get_type (void) { static GType type = G_TYPE_INVALID; if (G_UNLIKELY (type == G_TYPE_INVALID)) { static const GTypeInfo info = { sizeof (ThunarLocationButtonsClass), NULL, NULL, (GClassInitFunc) thunar_location_buttons_class_init, NULL, NULL, sizeof (ThunarLocationButtons), 0, (GInstanceInitFunc) thunar_location_buttons_init, NULL, }; static const GInterfaceInfo component_info = { (GInterfaceInitFunc) thunar_location_buttons_component_init, NULL, NULL, }; static const GInterfaceInfo navigator_info = { (GInterfaceInitFunc) thunar_location_buttons_navigator_init, NULL, NULL, }; static const GInterfaceInfo location_bar_info = { (GInterfaceInitFunc) thunar_location_buttons_location_bar_init, NULL, NULL, }; type = g_type_register_static (GTK_TYPE_CONTAINER, I_("ThunarLocationButtons"), &info, 0); g_type_add_interface_static (type, THUNAR_TYPE_NAVIGATOR, &navigator_info); g_type_add_interface_static (type, THUNAR_TYPE_COMPONENT, &component_info); g_type_add_interface_static (type, THUNAR_TYPE_LOCATION_BAR, &location_bar_info); } return type; } static void thunar_location_buttons_class_init (ThunarLocationButtonsClass *klass) { GtkContainerClass *gtkcontainer_class; GtkWidgetClass *gtkwidget_class; GObjectClass *gobject_class; /* determine the parent type class */ thunar_location_buttons_parent_class = g_type_class_peek_parent (klass); gobject_class = G_OBJECT_CLASS (klass); gobject_class->finalize = thunar_location_buttons_finalize; gobject_class->get_property = thunar_location_buttons_get_property; gobject_class->set_property = thunar_location_buttons_set_property; gtkwidget_class = GTK_WIDGET_CLASS (klass); gtkwidget_class->unmap = thunar_location_buttons_unmap; gtkwidget_class->size_request = thunar_location_buttons_size_request; gtkwidget_class->size_allocate = thunar_location_buttons_size_allocate; gtkwidget_class->state_changed = thunar_location_buttons_state_changed; gtkwidget_class->grab_notify = thunar_location_buttons_grab_notify; gtkcontainer_class = GTK_CONTAINER_CLASS (klass); gtkcontainer_class->add = thunar_location_buttons_add; gtkcontainer_class->remove = thunar_location_buttons_remove; gtkcontainer_class->forall = thunar_location_buttons_forall; /* Override ThunarNavigator's properties */ g_object_class_override_property (gobject_class, PROP_CURRENT_DIRECTORY, "current-directory"); /* Override ThunarComponent's properties */ g_object_class_override_property (gobject_class, PROP_SELECTED_FILES, "selected-files"); g_object_class_override_property (gobject_class, PROP_UI_MANAGER, "ui-manager"); /** * ThunarLocationButtons:spacing: * * The amount of space between the path buttons. **/ gtk_widget_class_install_style_property (gtkwidget_class, g_param_spec_int ("spacing", _("Spacing"), _("The amount of space between the path buttons"), 0, G_MAXINT, 3, G_PARAM_READABLE)); } static void thunar_location_buttons_component_init (ThunarComponentIface *iface) { iface->get_selected_files = (gpointer) exo_noop_null; iface->set_selected_files = (gpointer) exo_noop; iface->get_ui_manager = thunar_location_buttons_get_ui_manager; iface->set_ui_manager = thunar_location_buttons_set_ui_manager; } static void thunar_location_buttons_navigator_init (ThunarNavigatorIface *iface) { iface->get_current_directory = thunar_location_buttons_get_current_directory; iface->set_current_directory = thunar_location_buttons_set_current_directory; } static void thunar_location_buttons_location_bar_init (ThunarLocationBarIface *iface) { iface->accept_focus = (gpointer) exo_noop_false; iface->is_standalone = (gpointer) exo_noop_true; } static void thunar_location_buttons_init (ThunarLocationButtons *buttons) { GtkWidget *arrow; /* setup the action group for the location buttons */ buttons->action_group = gtk_action_group_new ("ThunarLocationButtons"); gtk_action_group_set_translation_domain (buttons->action_group, GETTEXT_PACKAGE); gtk_action_group_add_actions (buttons->action_group, action_entries, G_N_ELEMENTS (action_entries), buttons); GTK_WIDGET_SET_FLAGS (buttons, GTK_NO_WINDOW); gtk_widget_set_redraw_on_allocate (GTK_WIDGET (buttons), FALSE); buttons->left_slider = gtk_button_new (); g_signal_connect (G_OBJECT (buttons->left_slider), "button-press-event", G_CALLBACK (thunar_location_buttons_slider_button_press), buttons); g_signal_connect (G_OBJECT (buttons->left_slider), "button-release-event", G_CALLBACK (thunar_location_buttons_slider_button_release), buttons); g_signal_connect (G_OBJECT (buttons->left_slider), "clicked", G_CALLBACK (thunar_location_buttons_scroll_left), buttons); gtk_container_add (GTK_CONTAINER (buttons), buttons->left_slider); gtk_widget_show (buttons->left_slider); arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_OUT); gtk_container_add (GTK_CONTAINER (buttons->left_slider), arrow); gtk_widget_show (arrow); buttons->right_slider = gtk_button_new (); g_signal_connect (G_OBJECT (buttons->right_slider), "button-press-event", G_CALLBACK (thunar_location_buttons_slider_button_press), buttons); g_signal_connect (G_OBJECT (buttons->right_slider), "button-release-event", G_CALLBACK (thunar_location_buttons_slider_button_release), buttons); g_signal_connect (G_OBJECT (buttons->right_slider), "clicked", G_CALLBACK (thunar_location_buttons_scroll_right), buttons); gtk_container_add (GTK_CONTAINER (buttons), buttons->right_slider); gtk_widget_show (buttons->right_slider); arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_OUT); gtk_container_add (GTK_CONTAINER (buttons->right_slider), arrow); gtk_widget_show (arrow); } static void thunar_location_buttons_finalize (GObject *object) { ThunarLocationButtons *buttons = THUNAR_LOCATION_BUTTONS (object); _thunar_return_if_fail (THUNAR_IS_LOCATION_BUTTONS (buttons)); /* disconnect from the selected files and the UI manager */ thunar_component_set_selected_files (THUNAR_COMPONENT (buttons), NULL); thunar_component_set_ui_manager (THUNAR_COMPONENT (buttons), NULL); /* be sure to cancel the scrolling */ thunar_location_buttons_stop_scrolling (buttons); /* release from the current_directory */ thunar_navigator_set_current_directory (THUNAR_NAVIGATOR (buttons), NULL); /* release our action group */ g_object_unref (G_OBJECT (buttons->action_group)); (*G_OBJECT_CLASS (thunar_location_buttons_parent_class)->finalize) (object); } static void thunar_location_buttons_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { switch (prop_id) { case PROP_CURRENT_DIRECTORY: g_value_set_object (value, thunar_navigator_get_current_directory (THUNAR_NAVIGATOR (object))); break; case PROP_SELECTED_FILES: g_value_set_boxed (value, thunar_component_get_selected_files (THUNAR_COMPONENT (object))); break; case PROP_UI_MANAGER: g_value_set_object (value, thunar_component_get_ui_manager (THUNAR_COMPONENT (object))); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void thunar_location_buttons_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { switch (prop_id) { case PROP_CURRENT_DIRECTORY: thunar_navigator_set_current_directory (THUNAR_NAVIGATOR (object), g_value_get_object (value)); break; case PROP_SELECTED_FILES: thunar_component_set_selected_files (THUNAR_COMPONENT (object), g_value_get_boxed (value)); break; case PROP_UI_MANAGER: thunar_component_set_ui_manager (THUNAR_COMPONENT (object), g_value_get_object (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static GtkUIManager* thunar_location_buttons_get_ui_manager (ThunarComponent *component) { return THUNAR_LOCATION_BUTTONS (component)->ui_manager; } static void thunar_location_buttons_set_ui_manager (ThunarComponent *component, GtkUIManager *ui_manager) { ThunarLocationButtons *buttons = THUNAR_LOCATION_BUTTONS (component); GError *error = NULL; /* disconnect from the previous ui manager */ if (G_UNLIKELY (buttons->ui_manager != NULL)) { /* drop our action group from the previous UI manager */ gtk_ui_manager_remove_action_group (buttons->ui_manager, buttons->action_group); /* unmerge our ui controls from the previous UI manager */ gtk_ui_manager_remove_ui (buttons->ui_manager, buttons->ui_merge_id); /* drop our reference on the previous UI manager */ g_object_unref (G_OBJECT (buttons->ui_manager)); } /* activate the new UI manager */ buttons->ui_manager = ui_manager; /* connect to the new UI manager */ if (G_LIKELY (ui_manager != NULL)) { /* we keep a reference on the new manager */ g_object_ref (G_OBJECT (ui_manager)); /* add our action group to the new manager */ gtk_ui_manager_insert_action_group (ui_manager, buttons->action_group, -1); /* merge our UI control items with the new manager */ buttons->ui_merge_id = gtk_ui_manager_add_ui_from_string (ui_manager, thunar_location_buttons_ui, thunar_location_buttons_ui_length, &error); if (G_UNLIKELY (buttons->ui_merge_id == 0)) { g_error ("Failed to merge ThunarLocationButtons menus: %s", error->message); g_error_free (error); } } /* notify listeners */ g_object_notify (G_OBJECT (buttons), "ui-manager"); } static ThunarFile* thunar_location_buttons_get_current_directory (ThunarNavigator *navigator) { return THUNAR_LOCATION_BUTTONS (navigator)->current_directory; } static void thunar_location_buttons_set_current_directory (ThunarNavigator *navigator, ThunarFile *current_directory) { ThunarLocationButtons *buttons = THUNAR_LOCATION_BUTTONS (navigator); ThunarFile *file_parent; ThunarFile *file; GtkWidget *button; GList *lp; _thunar_return_if_fail (current_directory == NULL || THUNAR_IS_FILE (current_directory)); /* verify that we're not already using the new directory */ if (G_UNLIKELY (buttons->current_directory == current_directory)) return; /* check if we already have a button for that directory */ for (lp = buttons->list; lp != NULL; lp = lp->next) if (thunar_location_button_get_file (lp->data) == current_directory) break; /* if we already have a button for that directory, just activate it */ if (G_UNLIKELY (lp != NULL)) { /* fake a "clicked" event for that button */ thunar_location_button_clicked (THUNAR_LOCATION_BUTTON (lp->data)); } else { /* regenerate the button list */ if (G_LIKELY (buttons->current_directory != NULL)) { g_object_unref (G_OBJECT (buttons->current_directory)); /* remove all buttons */ while (buttons->list != NULL) gtk_container_remove (GTK_CONTAINER (buttons), buttons->list->data); /* clear the first scrolled and fake root buttons */ buttons->first_scrolled_button = NULL; buttons->fake_root_button = NULL; } buttons->current_directory = current_directory; if (G_LIKELY (current_directory != NULL)) { g_object_ref (G_OBJECT (current_directory)); gtk_widget_push_composite_child (); /* add the new buttons */ for (file = current_directory; file != NULL; file = file_parent) { button = thunar_location_buttons_make_button (buttons, file); buttons->list = g_list_append (buttons->list, button); gtk_container_add (GTK_CONTAINER (buttons), button); gtk_widget_show (button); /* use 'Home' as fake root button */ if (thunar_file_is_home (file)) buttons->fake_root_button = g_list_last (buttons->list); /* continue with the parent (if any) */ file_parent = thunar_file_get_parent (file, NULL); if (G_LIKELY (file != current_directory)) g_object_unref (G_OBJECT (file)); } gtk_widget_pop_composite_child (); } } g_object_notify (G_OBJECT (buttons), "current-directory"); } static void thunar_location_buttons_unmap (GtkWidget *widget) { /* be sure to stop the scroll timer before the buttons are unmapped */ thunar_location_buttons_stop_scrolling (THUNAR_LOCATION_BUTTONS (widget)); /* do the real unmap */ (*GTK_WIDGET_CLASS (thunar_location_buttons_parent_class)->unmap) (widget); } static void thunar_location_buttons_size_request (GtkWidget *widget, GtkRequisition *requisition) { ThunarLocationButtons *buttons = THUNAR_LOCATION_BUTTONS (widget); GtkRequisition child_requisition; GList *lp; gint spacing; gtk_widget_style_get (GTK_WIDGET (buttons), "spacing", &spacing, NULL); requisition->width = 0; requisition->height = 0; /* calculate the size of the biggest button */ for (lp = buttons->list; lp != NULL; lp = lp->next) { gtk_widget_size_request (GTK_WIDGET (lp->data), &child_requisition); requisition->width = MAX (child_requisition.width, requisition->width); requisition->height = MAX (child_requisition.height, requisition->height); } /* add space for the sliders if we have more than one path */ buttons->slider_width = MIN (requisition->height * 2 / 3 + 5, requisition->height); if (buttons->list != NULL && buttons->list->next != NULL) requisition->width += (spacing + buttons->slider_width) * 2; gtk_widget_size_request (buttons->left_slider, &child_requisition); gtk_widget_size_request (buttons->right_slider, &child_requisition); requisition->width += GTK_CONTAINER (widget)->border_width * 2; requisition->height += GTK_CONTAINER (widget)->border_width * 2; widget->requisition = *requisition; } static void thunar_location_buttons_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { ThunarLocationButtons *buttons = THUNAR_LOCATION_BUTTONS (widget); GtkTextDirection direction; GtkAllocation child_allocation; GtkWidget *child; gboolean need_sliders = FALSE; gboolean reached_end; GList *first_button; GList *lp; gint left_slider_offset = 0; gint right_slider_offset = 0; gint allocation_width; gint border_width; gint slider_space; gint spacing; gint width; widget->allocation = *allocation; /* if no path is set, we don't have to allocate anything */ if (G_UNLIKELY (buttons->list == NULL)) return; gtk_widget_style_get (GTK_WIDGET (buttons), "spacing", &spacing, NULL); direction = gtk_widget_get_direction (widget); border_width = GTK_CONTAINER (buttons)->border_width; allocation_width = allocation->width - 2 * border_width; /* check if we need to display the sliders */ if (G_LIKELY (buttons->fake_root_button != NULL)) width = buttons->slider_width + spacing; else width = 0; for (lp = buttons->list; lp != NULL; lp = lp->next) { width += GTK_WIDGET (lp->data)->requisition.width + spacing; if (lp == buttons->fake_root_button) break; } if (width <= allocation_width) { if (G_LIKELY (buttons->fake_root_button != NULL)) first_button = buttons->fake_root_button; else first_button = g_list_last (buttons->list); } else { slider_space = 2 * (spacing + buttons->slider_width); if (buttons->first_scrolled_button != NULL) first_button = buttons->first_scrolled_button; else first_button = buttons->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. */ width = GTK_WIDGET (first_button->data)->requisition.width; for (lp = first_button->prev, reached_end = FALSE; lp != NULL && !reached_end; lp = lp->prev) { child = lp->data; if (width + child->requisition.width + spacing + slider_space > allocation_width) reached_end = TRUE; else if (lp == buttons->fake_root_button) break; else width += child->requisition.width + spacing; } while (first_button->next != NULL && !reached_end) { child = first_button->data; if (width + child->requisition.width + spacing + slider_space > allocation_width) { reached_end = TRUE; } else { width += child->requisition.width + spacing; if (first_button == buttons->fake_root_button) 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, allocation->height - border_width * 2); if (G_UNLIKELY (direction == GTK_TEXT_DIR_RTL)) { child_allocation.x = allocation->x + allocation->width - border_width; if (need_sliders || buttons->fake_root_button != NULL) { child_allocation.x -= spacing + buttons->slider_width; left_slider_offset = allocation->width - border_width - buttons->slider_width; } } else { child_allocation.x = allocation->x + border_width; if (need_sliders || buttons->fake_root_button != NULL) { left_slider_offset = border_width; child_allocation.x += spacing + buttons->slider_width; } } for (lp = first_button; lp != NULL; lp = lp->prev) { child = lp->data; child_allocation.width = child->requisition.width; if (G_UNLIKELY (direction == GTK_TEXT_DIR_RTL)) child_allocation.x -= child_allocation.width; /* check to see if we don't have any more space to allocate buttons */ if (need_sliders && direction == GTK_TEXT_DIR_RTL) { if (child_allocation.x - spacing - buttons->slider_width < widget->allocation.x + border_width) break; } else if (need_sliders && direction == GTK_TEXT_DIR_LTR) { if (child_allocation.x + child_allocation.width + spacing + buttons->slider_width > widget->allocation.x + border_width + allocation_width) break; } gtk_widget_set_child_visible (GTK_WIDGET (lp->data), TRUE); gtk_widget_size_allocate (child, &child_allocation); if (direction == GTK_TEXT_DIR_RTL) { child_allocation.x -= spacing; right_slider_offset = border_width; } else { child_allocation.x += child_allocation.width + spacing; right_slider_offset = allocation->width - border_width - buttons->slider_width; } } /* now we go hide all the buttons that don't fit */ for (; lp != NULL; lp = lp->prev) gtk_widget_set_child_visible (GTK_WIDGET (lp->data), FALSE); for (lp = first_button->next; lp != NULL; lp = lp->next) gtk_widget_set_child_visible (GTK_WIDGET (lp->data), FALSE); if (need_sliders || buttons->fake_root_button != NULL) { child_allocation.width = buttons->slider_width; child_allocation.x = left_slider_offset + allocation->x; gtk_widget_size_allocate (buttons->left_slider, &child_allocation); gtk_widget_set_child_visible (buttons->left_slider, TRUE); gtk_widget_show_all (buttons->left_slider); thunar_location_buttons_update_sliders (buttons); } else { gtk_widget_set_child_visible (buttons->left_slider, FALSE); } if (need_sliders) { child_allocation.width = buttons->slider_width; child_allocation.x = right_slider_offset + allocation->x; gtk_widget_size_allocate (buttons->right_slider, &child_allocation); gtk_widget_set_child_visible (buttons->right_slider, TRUE); gtk_widget_show_all (buttons->right_slider); thunar_location_buttons_update_sliders (buttons); } else { gtk_widget_set_child_visible (buttons->right_slider, FALSE); } } static void thunar_location_buttons_state_changed (GtkWidget *widget, GtkStateType previous_state) { if (!GTK_WIDGET_IS_SENSITIVE (widget)) thunar_location_buttons_stop_scrolling (THUNAR_LOCATION_BUTTONS (widget)); } static void thunar_location_buttons_grab_notify (GtkWidget *widget, gboolean was_grabbed) { ThunarLocationButtons *buttons = THUNAR_LOCATION_BUTTONS (widget); if (!was_grabbed) thunar_location_buttons_stop_scrolling (buttons); } static void thunar_location_buttons_add (GtkContainer *container, GtkWidget *widget) { gtk_widget_set_parent (widget, GTK_WIDGET (container)); } static void thunar_location_buttons_remove (GtkContainer *container, GtkWidget *widget) { ThunarLocationButtons *buttons = THUNAR_LOCATION_BUTTONS (container); GList *lp; if (widget == buttons->left_slider) { thunar_location_buttons_remove_1 (container, widget); buttons->left_slider = NULL; return; } else if (widget == buttons->right_slider) { thunar_location_buttons_remove_1 (container, widget); buttons->right_slider = NULL; return; } for (lp = buttons->list; lp != NULL; lp = lp->next) if (widget == GTK_WIDGET (lp->data)) { thunar_location_buttons_remove_1 (container, widget); buttons->list = g_list_remove_link (buttons->list, lp); g_list_free (lp); return; } } static void thunar_location_buttons_forall (GtkContainer *container, gboolean include_internals, GtkCallback callback, gpointer callback_data) { ThunarLocationButtons *buttons = THUNAR_LOCATION_BUTTONS (container); GtkWidget *child; GList *lp; _thunar_return_if_fail (THUNAR_IS_LOCATION_BUTTONS (buttons)); _thunar_return_if_fail (callback != NULL); for (lp = buttons->list; lp != NULL; ) { child = GTK_WIDGET (lp->data); lp = lp->next; (*callback) (child, callback_data); } if (buttons->left_slider != NULL && include_internals) (*callback) (buttons->left_slider, callback_data); if (buttons->right_slider != NULL && include_internals) (*callback) (buttons->right_slider, callback_data); } static GtkWidget* thunar_location_buttons_make_button (ThunarLocationButtons *buttons, ThunarFile *file) { GtkWidget *button; /* allocate the button */ button = thunar_location_button_new (); /* connect to the file */ thunar_location_button_set_file (THUNAR_LOCATION_BUTTON (button), file); /* the current directory is active */ thunar_location_button_set_active (THUNAR_LOCATION_BUTTON (button), (file == buttons->current_directory)); /* connect signal handlers */ g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (thunar_location_buttons_clicked), buttons); g_signal_connect (G_OBJECT (button), "gone", G_CALLBACK (thunar_location_buttons_gone), buttons); g_signal_connect (G_OBJECT (button), "context-menu", G_CALLBACK (thunar_location_buttons_context_menu), buttons); return button; } static void thunar_location_buttons_remove_1 (GtkContainer *container, GtkWidget *widget) { gboolean need_resize = GTK_WIDGET_VISIBLE (widget); gtk_widget_unparent (widget); if (G_LIKELY (need_resize)) gtk_widget_queue_resize (GTK_WIDGET (container)); } static gboolean thunar_location_buttons_scroll_timeout (gpointer user_data) { ThunarLocationButtons *buttons = THUNAR_LOCATION_BUTTONS (user_data); GDK_THREADS_ENTER (); if (GTK_WIDGET_HAS_FOCUS (buttons->left_slider)) thunar_location_buttons_scroll_left (buttons->left_slider, buttons); else if (GTK_WIDGET_HAS_FOCUS (buttons->right_slider)) thunar_location_buttons_scroll_right (buttons->right_slider, buttons); GDK_THREADS_LEAVE (); return TRUE; } static void thunar_location_buttons_scroll_timeout_destroy (gpointer user_data) { THUNAR_LOCATION_BUTTONS (user_data)->scroll_timeout_id = 0; } static void thunar_location_buttons_stop_scrolling (ThunarLocationButtons *buttons) { if (buttons->scroll_timeout_id != 0) g_source_remove (buttons->scroll_timeout_id); } static void thunar_location_buttons_update_sliders (ThunarLocationButtons *buttons) { GtkWidget *button; if (G_LIKELY (buttons->list != NULL)) { button = GTK_WIDGET (buttons->list->data); if (gtk_widget_get_child_visible (button)) { gtk_widget_set_sensitive (buttons->right_slider, FALSE); thunar_location_buttons_stop_scrolling (buttons); } else { gtk_widget_set_sensitive (buttons->right_slider, TRUE); } button = GTK_WIDGET (g_list_last (buttons->list)->data); if (gtk_widget_get_child_visible (button) && buttons->fake_root_button == NULL) { gtk_widget_set_sensitive (buttons->left_slider, FALSE); thunar_location_buttons_stop_scrolling (buttons); } else { gtk_widget_set_sensitive (buttons->left_slider, TRUE); } } } static gboolean thunar_location_buttons_slider_button_press (GtkWidget *button, GdkEventButton *event, ThunarLocationButtons *buttons) { if (!GTK_WIDGET_HAS_FOCUS (button)) gtk_widget_grab_focus (button); if (event->type != GDK_BUTTON_PRESS || event->button != 1) return FALSE; buttons->ignore_click = FALSE; if (button == buttons->left_slider) thunar_location_buttons_scroll_left (button, buttons); else if (button == buttons->right_slider) thunar_location_buttons_scroll_right (button, buttons); if (G_LIKELY (buttons->scroll_timeout_id == 0)) { buttons->scroll_timeout_id = g_timeout_add_full (G_PRIORITY_LOW, THUNAR_LOCATION_BUTTONS_SCROLL_TIMEOUT, thunar_location_buttons_scroll_timeout, buttons, thunar_location_buttons_scroll_timeout_destroy); } return FALSE; } static gboolean thunar_location_buttons_slider_button_release (GtkWidget *button, GdkEventButton *event, ThunarLocationButtons *buttons) { if (event->type == GDK_BUTTON_RELEASE) { thunar_location_buttons_stop_scrolling (buttons); buttons->ignore_click = TRUE; } return FALSE; } static void thunar_location_buttons_scroll_left (GtkWidget *button, ThunarLocationButtons *buttons) { GList *lp; if (G_UNLIKELY (buttons->ignore_click)) { buttons->ignore_click = FALSE; return; } gtk_widget_queue_resize (GTK_WIDGET (buttons)); for (lp = g_list_last (buttons->list); lp != NULL; lp = lp->prev) if (lp->prev != NULL && gtk_widget_get_child_visible (GTK_WIDGET (lp->prev->data))) { if (lp->prev == buttons->fake_root_button) buttons->fake_root_button = NULL; buttons->first_scrolled_button = lp; break; } } static void thunar_location_buttons_scroll_right (GtkWidget *button, ThunarLocationButtons *buttons) { GtkTextDirection direction; GList *right_button = NULL; GList *left_button = NULL; GList *lp; gint space_available; gint space_needed; gint border_width; gint spacing; if (G_UNLIKELY (buttons->ignore_click)) { buttons->ignore_click = FALSE; return; } gtk_widget_style_get (GTK_WIDGET (buttons), "spacing", &spacing, NULL); gtk_widget_queue_resize (GTK_WIDGET (buttons)); border_width = GTK_CONTAINER (buttons)->border_width; direction = gtk_widget_get_direction (GTK_WIDGET (buttons)); /* find the button at the 'right' end that we have to make visible */ for (lp = buttons->list; lp != NULL; lp = lp->next) if (lp->next != NULL && gtk_widget_get_child_visible (GTK_WIDGET (lp->next->data))) { right_button = lp; break; } /* find the last visible button on the 'left' end */ for (lp = g_list_last (buttons->list); lp != NULL; lp = lp->prev) if (gtk_widget_get_child_visible (GTK_WIDGET (lp->data))) { left_button = lp; break; } space_needed = GTK_WIDGET (right_button->data)->allocation.width + spacing; if (direction == GTK_TEXT_DIR_RTL) { space_available = buttons->right_slider->allocation.x - GTK_WIDGET (buttons)->allocation.x; } else { space_available = (GTK_WIDGET (buttons)->allocation.x + GTK_WIDGET (buttons)->allocation.width - border_width) - (buttons->right_slider->allocation.x + buttons->right_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. */ while (space_available < space_needed) { space_available += GTK_WIDGET (left_button->data)->allocation.width + spacing; left_button = left_button->prev; buttons->first_scrolled_button = left_button; } } static void thunar_location_buttons_clicked (ThunarLocationButton *button, ThunarLocationButtons *buttons) { ThunarFile *directory; GList *lp; _thunar_return_if_fail (THUNAR_IS_LOCATION_BUTTON (button)); _thunar_return_if_fail (THUNAR_IS_LOCATION_BUTTONS (buttons)); /* determine the directory associated with the clicked button */ directory = thunar_location_button_get_file (button); /* disconnect from previous current directory (if any) */ if (G_LIKELY (buttons->current_directory != NULL)) g_object_unref (G_OBJECT (buttons->current_directory)); /* setup the new current directory */ buttons->current_directory = directory; /* take a reference on the new directory (if any) */ if (G_LIKELY (directory != NULL)) g_object_ref (G_OBJECT (directory)); /* check if the button is visible on the button bar */ if (!gtk_widget_get_child_visible (GTK_WIDGET (button))) { /* scroll to the button */ buttons->first_scrolled_button = g_list_find (buttons->list, button); gtk_widget_queue_resize (GTK_WIDGET (buttons)); /* we may need to reset the fake_root_button */ if (G_LIKELY (buttons->fake_root_button != NULL)) { /* check if the fake_root_button is before the first_scrolled_button (from right to left) */ for (lp = buttons->list; lp != NULL && lp != buttons->first_scrolled_button; lp = lp->next) if (lp == buttons->fake_root_button) { /* reset the fake_root_button */ buttons->fake_root_button = NULL; break; } } } /* update all buttons */ for (lp = buttons->list; lp != NULL; lp = lp->next) { /* determine the directory for this button */ button = THUNAR_LOCATION_BUTTON (lp->data); directory = thunar_location_button_get_file (button); /* update the location button state (making sure to not recurse with the "clicked" handler) */ g_signal_handlers_block_by_func (G_OBJECT (button), thunar_location_buttons_clicked, buttons); thunar_location_button_set_active (button, directory == buttons->current_directory); g_signal_handlers_unblock_by_func (G_OBJECT (button), thunar_location_buttons_clicked, buttons); } /* notify the surrounding module that we want to change to a different directory. */ thunar_navigator_change_directory (THUNAR_NAVIGATOR (buttons), buttons->current_directory); } static void thunar_location_buttons_gone (ThunarLocationButton *button, ThunarLocationButtons *buttons) { _thunar_return_if_fail (THUNAR_IS_LOCATION_BUTTON (button)); _thunar_return_if_fail (THUNAR_IS_LOCATION_BUTTONS (buttons)); _thunar_return_if_fail (g_list_find (buttons->list, button) != NULL); /* drop all buttons up to the button that emitted the "gone" signal */ while (buttons->list->data != button) gtk_widget_destroy (buttons->list->data); /* drop the button itself */ gtk_widget_destroy (GTK_WIDGET (button)); } static void thunar_location_buttons_context_menu (ThunarLocationButton *button, GdkEventButton *event, ThunarLocationButtons *buttons) { ThunarClipboardManager *clipboard; const gchar *display_name; ThunarFile *file; GtkAction *action; GtkWidget *menu; _thunar_return_if_fail (THUNAR_IS_LOCATION_BUTTONS (buttons)); _thunar_return_if_fail (THUNAR_IS_LOCATION_BUTTON (button)); /* determine the file for the button */ file = thunar_location_button_get_file (button); if (G_LIKELY (file != NULL)) { /* we cannot popup the context menu without an ui manager */ if (G_LIKELY (buttons->ui_manager != NULL)) { /* determine the display name of the file */ display_name = thunar_file_get_display_name (file); /* be sure to keep a reference on the navigation bar */ g_object_ref (G_OBJECT (buttons)); /* grab a reference on the clipboard manager for this display */ clipboard = thunar_clipboard_manager_get_for_display (gtk_widget_get_display (GTK_WIDGET (buttons))); /* setup the "Open" action */ action = gtk_action_group_get_action (buttons->action_group, "location-buttons-open"); thunar_gtk_action_set_tooltip (action, _("Open \"%s\" in this window"), display_name); g_object_set_data_full (G_OBJECT (action), I_("thunar-file"), g_object_ref (G_OBJECT (file)), (GDestroyNotify) g_object_unref); gtk_action_set_sensitive (action, (file != buttons->current_directory)); /* setup the "Open in New Window" action */ action = gtk_action_group_get_action (buttons->action_group, "location-buttons-open-in-new-window"); thunar_gtk_action_set_tooltip (action, _("Open \"%s\" in a new window"), display_name); g_object_set_data_full (G_OBJECT (action), I_("thunar-file"), g_object_ref (G_OBJECT (file)), (GDestroyNotify) g_object_unref); /* setup the "Create Folder..." action */ action = gtk_action_group_get_action (buttons->action_group, "location-buttons-create-folder"); thunar_gtk_action_set_tooltip (action, _("Create a new folder in \"%s\""), display_name); g_object_set_data_full (G_OBJECT (action), I_("thunar-file"), g_object_ref (G_OBJECT (file)), (GDestroyNotify) g_object_unref); gtk_action_set_sensitive (action, thunar_file_is_writable (file)); gtk_action_set_visible (action, !thunar_file_is_trashed (file)); /* setup the "Empty Trash" action */ action = gtk_action_group_get_action (buttons->action_group, "location-buttons-empty-trash"); gtk_action_set_visible (action, (thunar_file_is_root (file) && thunar_file_is_trashed (file))); gtk_action_set_sensitive (action, (thunar_file_get_size (file) > 0)); /* setup the "Paste Into Folder" action */ action = gtk_action_group_get_action (buttons->action_group, "location-buttons-paste-into-folder"); thunar_gtk_action_set_tooltip (action, _("Move or copy files previously selected by a Cut or Copy command into \"%s\""), display_name); g_object_set_data_full (G_OBJECT (action), I_("thunar-file"), g_object_ref (G_OBJECT (file)), (GDestroyNotify) g_object_unref); gtk_action_set_sensitive (action, thunar_clipboard_manager_get_can_paste (clipboard)); /* setup the "Properties..." action */ action = gtk_action_group_get_action (buttons->action_group, "location-buttons-properties"); thunar_gtk_action_set_tooltip (action, _("View the properties of the folder \"%s\""), display_name); g_object_set_data_full (G_OBJECT (action), I_("thunar-file"), g_object_ref (G_OBJECT (file)), (GDestroyNotify) g_object_unref); /* run the menu on the screen on the buttons' screen */ menu = gtk_ui_manager_get_widget (buttons->ui_manager, "/location-buttons-context-menu"); thunar_gtk_menu_run (GTK_MENU (menu), GTK_WIDGET (buttons), NULL, NULL, event->button, event->time); /* cleanup */ g_object_unref (G_OBJECT (buttons)); g_object_unref (G_OBJECT (clipboard)); } } } static void thunar_location_buttons_action_create_folder (GtkAction *action, ThunarLocationButtons *buttons) { ThunarVfsMimeDatabase *mime_database; ThunarVfsMimeInfo *mime_info; ThunarApplication *application; ThunarFile *directory; GList path_list; gchar *name; _thunar_return_if_fail (THUNAR_IS_LOCATION_BUTTONS (buttons)); _thunar_return_if_fail (GTK_IS_ACTION (action)); /* determine the directory for the action */ directory = g_object_get_data (G_OBJECT (action), "thunar-file"); if (G_UNLIKELY (directory == NULL)) return; /* lookup "inode/directory" mime info */ mime_database = thunar_vfs_mime_database_get_default (); mime_info = thunar_vfs_mime_database_get_info (mime_database, "inode/directory"); /* ask the user to enter a name for the new folder */ name = thunar_show_create_dialog (GTK_WIDGET (buttons), mime_info, _("New Folder"), _("Create New Folder")); if (G_LIKELY (name != NULL)) { /* fake the path list */ path_list.data = thunar_vfs_path_relative (thunar_file_get_path (directory), name); path_list.next = path_list.prev = NULL; /* launch the operation */ application = thunar_application_get (); thunar_application_mkdir (application, GTK_WIDGET (buttons), &path_list, NULL); g_object_unref (G_OBJECT (application)); /* release the path */ thunar_vfs_path_unref (path_list.data); /* release the file name */ g_free (name); } /* cleanup */ g_object_unref (G_OBJECT (mime_database)); thunar_vfs_mime_info_unref (mime_info); } static void thunar_location_buttons_action_down_folder (GtkAction *action, ThunarLocationButtons *buttons) { GList *lp; _thunar_return_if_fail (THUNAR_IS_LOCATION_BUTTONS (buttons)); _thunar_return_if_fail (GTK_IS_ACTION (action)); /* lookup the active button */ for (lp = buttons->list; lp != NULL; lp = lp->next) if (thunar_location_button_get_active (lp->data)) { /* check if we have a folder below that one */ if (G_LIKELY (lp->prev != NULL)) { /* enter that folder */ thunar_location_button_clicked (lp->prev->data); } break; } } static void thunar_location_buttons_action_empty_trash (GtkAction *action, ThunarLocationButtons *buttons) { ThunarApplication *application; _thunar_return_if_fail (THUNAR_IS_LOCATION_BUTTONS (buttons)); _thunar_return_if_fail (GTK_IS_ACTION (action)); /* launch the operation */ application = thunar_application_get (); thunar_application_empty_trash (application, GTK_WIDGET (buttons)); g_object_unref (G_OBJECT (application)); } static void thunar_location_buttons_action_open (GtkAction *action, ThunarLocationButtons *buttons) { ThunarFile *directory; _thunar_return_if_fail (THUNAR_IS_LOCATION_BUTTONS (buttons)); _thunar_return_if_fail (GTK_IS_ACTION (action)); /* determine the directory for the action */ directory = g_object_get_data (G_OBJECT (action), "thunar-file"); if (G_LIKELY (directory != NULL && thunar_file_is_directory (directory))) { /* open the folder in this window */ thunar_navigator_change_directory (THUNAR_NAVIGATOR (buttons), directory); } } static void thunar_location_buttons_action_open_in_new_window (GtkAction *action, ThunarLocationButtons *buttons) { ThunarApplication *application; ThunarFile *directory; _thunar_return_if_fail (THUNAR_IS_LOCATION_BUTTONS (buttons)); _thunar_return_if_fail (GTK_IS_ACTION (action)); /* determine the directory for the action */ directory = g_object_get_data (G_OBJECT (action), "thunar-file"); if (G_LIKELY (directory != NULL)) { /* open a new window for the directory */ application = thunar_application_get (); thunar_application_open_window (application, directory, gtk_widget_get_screen (GTK_WIDGET (buttons))); g_object_unref (G_OBJECT (application)); } } static void thunar_location_buttons_action_paste_into_folder (GtkAction *action, ThunarLocationButtons *buttons) { ThunarClipboardManager *clipboard; ThunarFile *directory; _thunar_return_if_fail (THUNAR_IS_LOCATION_BUTTONS (buttons)); _thunar_return_if_fail (GTK_IS_ACTION (action)); /* determine the directory for the action */ directory = g_object_get_data (G_OBJECT (action), "thunar-file"); if (G_LIKELY (directory != NULL)) { /* paste files from the clipboard to the folder represented by this button */ clipboard = thunar_clipboard_manager_get_for_display (gtk_widget_get_display (GTK_WIDGET (buttons))); thunar_clipboard_manager_paste_files (clipboard, thunar_file_get_path (directory), GTK_WIDGET (buttons), NULL); g_object_unref (G_OBJECT (clipboard)); } } static void thunar_location_buttons_action_properties (GtkAction *action, ThunarLocationButtons *buttons) { ThunarFile *directory; GtkWidget *toplevel; GtkWidget *dialog; _thunar_return_if_fail (THUNAR_IS_LOCATION_BUTTONS (buttons)); _thunar_return_if_fail (GTK_IS_ACTION (action)); /* determine the directory for the action */ directory = g_object_get_data (G_OBJECT (action), "thunar-file"); if (G_LIKELY (directory != NULL)) { /* determine the toplevel window */ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (buttons)); if (G_LIKELY (toplevel != NULL && GTK_WIDGET_TOPLEVEL (toplevel))) { /* popup the properties dialog */ dialog = thunar_properties_dialog_new (); gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE); thunar_properties_dialog_set_file (THUNAR_PROPERTIES_DIALOG (dialog), directory); gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (toplevel)); gtk_widget_show (dialog); } } } /** * thunar_location_buttons_new: * * Creates a new #ThunarLocationButtons object, that does * not display any path by default. * * Return value: the newly allocated #ThunarLocationButtons object. **/ GtkWidget* thunar_location_buttons_new (void) { return g_object_new (THUNAR_TYPE_LOCATION_BUTTONS, NULL); }