/* vi:set et ai sw=2 sts=2 ts=2: */
/*-
 * Copyright (c) 2005-2006 Benedikt Meurer <benny@xfce.org>
 * Copyright (c) 2009 Jannis Pohlmann <jannis@xfce.org>
 *
 * 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 <jrb@gnome.org> for
 * Red Hat, Inc.
 */

#include "thunar/thunar-application.h"
#include "thunar/thunar-clipboard-manager.h"
#include "thunar/thunar-dialogs.h"
#include "thunar/thunar-gio-extensions.h"
#include "thunar/thunar-gobject-extensions.h"
#include "thunar/thunar-gtk-extensions.h"
#include "thunar/thunar-location-button.h"
#include "thunar/thunar-location-buttons.h"
#include "thunar/thunar-menu.h"
#include "thunar/thunar-preferences.h"
#include "thunar/thunar-private.h"
#include "thunar/thunar-properties-dialog.h"



#define THUNAR_LOCATION_BUTTON_MAX_WIDTH 350
#define THUNAR_LOCATION_BUTTON_FACTOR 0.9
#define THUNAR_LOCATION_BUTTONS_SCROLL_TIMEOUT 200



/* Property identifiers */
enum
{
  PROP_0,
  PROP_CURRENT_DIRECTORY,
};



static void
thunar_location_buttons_navigator_init (ThunarNavigatorIface *iface);
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 ThunarFile *
thunar_location_buttons_get_current_directory (ThunarNavigator *navigator);
static void
thunar_location_buttons_set_current_directory (ThunarNavigator *navigator,
                                               ThunarFile      *current_directory,
                                               gboolean         grab_focus);
static void
thunar_location_buttons_unmap (GtkWidget *widget);
static void
thunar_location_buttons_on_filler_clicked (ThunarLocationButtons *buttons);
static void
thunar_location_buttons_get_preferred_width (GtkWidget *widget,
                                             gint      *minimum,
                                             gint      *natural);
static void
thunar_location_buttons_get_preferred_height (GtkWidget *widget,
                                              gint      *minimum,
                                              gint      *natural);
static GtkWidgetPath *
thunar_location_buttons_get_path_for_child (GtkContainer *container,
                                            GtkWidget    *child);
static void
thunar_location_buttons_child_ordering_changed (ThunarLocationButtons *buttons);
static GList *
thunar_location_buttons_detect_first_visible_button (ThunarLocationButtons *buttons,
                                                     gint                   available_width,
                                                     gint                  *resulting_occupied_width);
static GList *
thunar_location_buttons_detect_last_visible_button (ThunarLocationButtons *buttons,
                                                    gint                   available_width,
                                                    gint                  *resulting_occupied_width);
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_draw (GtkWidget *buttons,
                              cairo_t   *cr);
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,
                                 gboolean               open_in_tab,
                                 ThunarLocationButtons *buttons);
static gboolean
thunar_location_buttons_context_menu (ThunarLocationButton  *button,
                                      ThunarLocationButtons *buttons);
static void
thunar_location_buttons_gone (ThunarLocationButton  *button,
                              ThunarLocationButtons *buttons);



struct _ThunarLocationButtonsClass
{
  GtkContainerClass __parent__;

  void (*entry_requested) (const gchar *initial_text);
};

struct _ThunarLocationButtons
{
  GtkContainer __parent__;

  ThunarActionManager *action_mgr;

  GtkWidget *left_slider;
  GtkWidget *right_slider;
  GtkWidget *filler_widget;

  ThunarFile *current_directory;

  gint  button_max_width;
  gint  slider_width;
  guint ignore_click : 1;

  GList *list;
  GList *fake_root_button;
  GList *first_visible_button;
  GList *last_visible_button;

  guint scroll_timeout_id;
};



static GQuark thunar_file_quark = 0;



G_DEFINE_TYPE_WITH_CODE (ThunarLocationButtons, thunar_location_buttons, GTK_TYPE_CONTAINER,
    G_IMPLEMENT_INTERFACE (THUNAR_TYPE_NAVIGATOR, thunar_location_buttons_navigator_init))



static void
thunar_location_buttons_class_init (ThunarLocationButtonsClass *klass)
{
  GtkContainerClass *gtkcontainer_class;
  GtkWidgetClass    *gtkwidget_class;
  GObjectClass      *gobject_class;

  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->draw = thunar_location_buttons_draw;
  gtkwidget_class->unmap = thunar_location_buttons_unmap;
  gtkwidget_class->get_preferred_width = thunar_location_buttons_get_preferred_width;
  gtkwidget_class->get_preferred_height = thunar_location_buttons_get_preferred_height;
  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;
  gtkcontainer_class->get_path_for_child = thunar_location_buttons_get_path_for_child;

  /* Override ThunarNavigator's properties */
  g_object_class_override_property (gobject_class, PROP_CURRENT_DIRECTORY, "current-directory");

  /* signals */
  g_signal_new ("entry-requested",
                G_TYPE_FROM_CLASS (klass),
                G_SIGNAL_RUN_LAST,
                G_STRUCT_OFFSET (ThunarLocationButtonsClass, entry_requested),
                NULL, NULL,
                NULL,
                G_TYPE_NONE,
                1,
                G_TYPE_STRING);


  thunar_file_quark = g_quark_from_static_string ("button-thunar-file");
}


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_on_filler_clicked (ThunarLocationButtons *buttons)
{
  g_signal_emit_by_name (buttons, "entry-requested", NULL);
}



static void
thunar_location_buttons_init (ThunarLocationButtons *buttons)
{
  ThunarPreferences *preferences;
  GtkWidget         *icon;
  gboolean           use_symbolic_icons;

  preferences = thunar_preferences_get ();
  g_object_get (G_OBJECT (preferences), "misc-symbolic-icons-in-toolbar", &use_symbolic_icons, NULL);
  g_object_unref (G_OBJECT (preferences));

  gtk_widget_set_has_window (GTK_WIDGET (buttons), FALSE);
  gtk_widget_set_redraw_on_allocate (GTK_WIDGET (buttons), FALSE);

  buttons->button_max_width = THUNAR_LOCATION_BUTTON_MAX_WIDTH;

  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);

  icon = gtk_image_new_from_icon_name (use_symbolic_icons ? "pan-start-symbolic" : "pan-start", GTK_ICON_SIZE_BUTTON);
  gtk_container_add (GTK_CONTAINER (buttons->left_slider), icon);
  gtk_widget_show (icon);

  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);

  icon = gtk_image_new_from_icon_name (use_symbolic_icons ? "pan-end-symbolic" : "pan-end", GTK_ICON_SIZE_BUTTON);
  gtk_container_add (GTK_CONTAINER (buttons->right_slider), icon);
  gtk_widget_show (icon);

  buttons->filler_widget = gtk_button_new ();
  g_signal_connect_swapped (buttons->filler_widget, "clicked", G_CALLBACK (thunar_location_buttons_on_filler_clicked), buttons);

  icon = gtk_image_new_from_icon_name (use_symbolic_icons ? "document-edit-symbolic" : "document-edit", GTK_ICON_SIZE_BUTTON);
  gtk_widget_set_halign (icon, GTK_ALIGN_END);
  gtk_container_add (GTK_CONTAINER (buttons->filler_widget), icon);
  gtk_widget_show (icon);

  gtk_container_add (GTK_CONTAINER (buttons), buttons->filler_widget);
  gtk_widget_show (buttons->filler_widget);

  g_object_set (buttons, "valign", GTK_ALIGN_CENTER, NULL);

  /* setup style classes */
  gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (buttons)),
                               GTK_STYLE_CLASS_LINKED);
  gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (buttons)),
                               "path-bar");

  /* add sub-buttons to css class which matches all buttons in the path-bar */
  /* keep as well the old name 'path-bar-button'. It might be used by themes */
  gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (buttons->left_slider)),
                               "location-button");
  gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (buttons->right_slider)),
                               "location-button");
  gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (buttons->filler_widget)),
                               "location-button");
  gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (buttons->left_slider)),
                               "path-bar-button");
  gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (buttons->right_slider)),
                               "path-bar-button");
  gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (buttons->filler_widget)),
                               "path-bar-button");

  buttons->action_mgr = g_object_new (THUNAR_TYPE_ACTION_MANAGER, "widget", GTK_WIDGET (buttons), NULL);
  g_signal_connect_swapped (G_OBJECT (buttons->action_mgr), "change-directory", G_CALLBACK (thunar_location_buttons_set_current_directory), buttons);
  g_signal_connect_swapped (G_OBJECT (buttons->action_mgr), "open-new-tab", G_CALLBACK (thunar_navigator_open_new_tab), buttons);
}



static void
thunar_location_buttons_finalize (GObject *object)
{
  ThunarLocationButtons *buttons = THUNAR_LOCATION_BUTTONS (object);

  _thunar_return_if_fail (THUNAR_IS_LOCATION_BUTTONS (buttons));

  /* 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, TRUE);

  /* release reference on the action manager */
  g_object_unref (buttons->action_mgr);

  (*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;

    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), TRUE);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}



static ThunarFile *
thunar_location_buttons_get_current_directory (ThunarNavigator *navigator)
{
  return THUNAR_LOCATION_BUTTONS (navigator)->current_directory;
}



static inline gboolean
eglible_for_fake_root (ThunarFile *file)
{
  /* use 'Home' as fake root button */
  return thunar_file_is_home (file);

  /* TODO: mounted devices */
}



static void
thunar_location_buttons_set_current_directory (ThunarNavigator *navigator,
                                               ThunarFile      *current_directory,
                                               gboolean         grab_focus)
{
  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 visible button for that directory */
  for (lp = buttons->list; lp != NULL; lp = lp->next)
    {
      if (thunar_location_button_get_file (lp->data) == current_directory && gtk_widget_get_child_visible (GTK_WIDGET (lp->data)))
        {
          /* fake a "clicked" event for that button */
          gtk_button_clicked (GTK_BUTTON (lp->data));
          return;
        }
    }

  if (G_LIKELY (buttons->current_directory != NULL))
    {
      /* remove all buttons */
      g_object_unref (G_OBJECT (buttons->current_directory));

      while (buttons->list != NULL)
        gtk_container_remove (GTK_CONTAINER (buttons), buttons->list->data);

      /* clear scroll positions and fake root button */
      buttons->first_visible_button = NULL;
      buttons->last_visible_button = NULL;
      buttons->fake_root_button = NULL;
    }

  buttons->current_directory = current_directory;

  /* regenerate the button list */
  if (G_LIKELY (current_directory != NULL))
    {
      g_object_ref (G_OBJECT (current_directory));

      /* 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 (!buttons->fake_root_button && eglible_for_fake_root (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));
        }
    }

  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_get_preferred_width (GtkWidget *widget,
                                             gint      *minimum,
                                             gint      *natural)
{
  ThunarLocationButtons *buttons = THUNAR_LOCATION_BUTTONS (widget);
  gint                   width = 0, height = 0, child_width = 0, child_height = 0, filler_width = 0;
  GList                 *lp;

  /* calculate the size of the biggest button */
  for (lp = buttons->list; lp != NULL; lp = lp->next)
    {
      gtk_widget_get_preferred_width (GTK_WIDGET (lp->data), &child_width, NULL);
      gtk_widget_get_preferred_height (GTK_WIDGET (lp->data), &child_height, NULL);
      width = MAX (width, child_width);
      height = MAX (height, child_height);
    }

  /* add space for the sliders if we have more than one path */
  buttons->slider_width = MIN (height * 2 / 3 + 5, height);
  if (buttons->list != NULL && buttons->list->next != NULL)
    width += (buttons->slider_width) * 2;

  gtk_widget_get_preferred_width (buttons->filler_widget, &filler_width, NULL);
  width += filler_width; // default is the minimum

  *minimum = *natural = width;
}



static void
thunar_location_buttons_get_preferred_height (GtkWidget *widget,
                                              gint      *minimum,
                                              gint      *natural)
{
  ThunarLocationButtons *buttons = THUNAR_LOCATION_BUTTONS (widget);
  gint                   height = 0, child_height = 0;
  GList                 *lp;

  /* calculate the size of the biggest button */
  for (lp = buttons->list; lp != NULL; lp = lp->next)
    {
      gtk_widget_get_preferred_height (GTK_WIDGET (lp->data), &child_height, NULL);
      height = MAX (height, child_height);
    }

  *minimum = *natural = height;
}



static GtkWidgetPath *
thunar_location_buttons_get_path_for_child (GtkContainer *container,
                                            GtkWidget    *child)
{
  ThunarLocationButtons *path_bar = THUNAR_LOCATION_BUTTONS (container);
  GtkWidgetPath         *path;

  path = gtk_widget_path_copy (gtk_widget_get_path (GTK_WIDGET (path_bar)));

  if (gtk_widget_get_visible (child) && gtk_widget_get_child_visible (child))
    {
      GtkWidgetPath *sibling_path;
      GList         *visible_children;
      GList         *l;
      int            pos;

      /* 1. Build the list of visible children, in visually left-to-right order
       * (i.e. independently of the widget's direction).  Note that our
       * list is stored in innermost-to-outermost path order!
       */
      visible_children = NULL;
      visible_children = g_list_prepend (visible_children, path_bar->right_slider);
      visible_children = g_list_prepend (visible_children, path_bar->filler_widget);

      for (l = path_bar->list; l; l = l->next)
        {
          if (gtk_widget_get_visible (GTK_WIDGET (l->data)) && gtk_widget_get_child_visible (GTK_WIDGET (l->data)))
            visible_children = g_list_prepend (visible_children, l->data);
        }

      visible_children = g_list_prepend (visible_children, path_bar->left_slider);

      if (gtk_widget_get_direction (GTK_WIDGET (path_bar)) == GTK_TEXT_DIR_RTL)
        {
          visible_children = g_list_reverse (visible_children);
        }

      /* 2. Find the index of the child within that list */
      pos = 0;

      for (l = visible_children; l; l = l->next)
        {
          GtkWidget *button = l->data;

          if (button == child)
            break;

          pos++;
        }

      /* 3. Build the path */
      sibling_path = gtk_widget_path_new ();

      for (l = visible_children; l; l = l->next)
        {
          gtk_widget_path_append_for_widget (sibling_path, l->data);
        }

      gtk_widget_path_append_with_siblings (path, sibling_path, pos);

      g_list_free (visible_children);
      gtk_widget_path_unref (sibling_path);
    }
  else
    {
      gtk_widget_path_append_for_widget (path, child);
    }

  return path;
}



static void
thunar_location_buttons_child_ordering_changed (ThunarLocationButtons *location_buttons)
{
  gtk_widget_reset_style (GTK_WIDGET (location_buttons));
}



static GList *
thunar_location_buttons_detect_first_visible_button (ThunarLocationButtons *buttons,
                                                     gint                   available_width,
                                                     gint                  *resulting_occupied_width)
{
  GList *lp;
  gint   button_width = 0;
  gint   resulting_width = 0;

  /* walk all buttons starting by last, until no space is left */
  for (lp = buttons->last_visible_button; lp != NULL; lp = lp->next)
    {
      gtk_widget_get_preferred_width (GTK_WIDGET (lp->data), &button_width, NULL);
      if (resulting_width + button_width > available_width)
        {
          return lp->prev;
        }
      resulting_width += button_width;
      if (resulting_occupied_width != NULL)
        *resulting_occupied_width = resulting_width;

      /* stop at the fake root button */
      if (lp == buttons->fake_root_button)
        return lp;
    }
  return g_list_last (buttons->list);
}



static GList *
thunar_location_buttons_detect_last_visible_button (ThunarLocationButtons *buttons,
                                                    gint                   available_width,
                                                    gint                  *resulting_occupied_width)
{
  GList *lp;
  gint   button_width = 0;
  gint   resulting_width = 0;

  /* walk all buttons starting by first, until no space is left */
  for (lp = buttons->first_visible_button; lp != NULL; lp = lp->prev)
    {
      gtk_widget_get_preferred_width (GTK_WIDGET (lp->data), &button_width, NULL);
      if (resulting_width + button_width > available_width)
        {
          return lp->next;
        }
      resulting_width += button_width;
      if (resulting_occupied_width != NULL)
        *resulting_occupied_width = resulting_width;
    }
  return buttons->list;
}



static void
thunar_location_buttons_size_allocate (GtkWidget     *widget,
                                       GtkAllocation *allocation)
{
  ThunarLocationButtons *buttons = THUNAR_LOCATION_BUTTONS (widget);
  GtkTextDirection       direction;
  GtkAllocation          available_space;
  GtkAllocation          folder_button_allocation;
  GtkAllocation          first_slider_allocation;  /* left slider if direction is LTR */
  GtkAllocation          second_slider_allocation; /* right slider if direction is LTR */
  GtkAllocation          filler_allocation;
  GList                 *lp;
  gint                   sliders_filler_width;
  gint                   buttons_area_width;
  gint                   buttons_total_width;
  gint                   border_width;
  gint                   temp_width;
  gboolean               need_reorder = FALSE;

  gtk_widget_set_allocation (widget, allocation);

  /* if no path is set, we don't have to allocate anything */
  if (G_UNLIKELY (buttons->list == NULL))
    return;

  direction = gtk_widget_get_direction (widget);
  border_width = gtk_container_get_border_width (GTK_CONTAINER (buttons));
  available_space.x = allocation->x + border_width;
  available_space.y = allocation->y + border_width;
  available_space.width = allocation->width - 2 * border_width;
  available_space.height = MAX (1, allocation->height - 2 * border_width);

  first_slider_allocation.width = buttons->slider_width;
  first_slider_allocation.height = available_space.height;
  first_slider_allocation.y = available_space.y;

  second_slider_allocation.width = buttons->slider_width;
  second_slider_allocation.height = available_space.height;
  second_slider_allocation.y = available_space.y;

  if (G_LIKELY (direction == GTK_TEXT_DIR_LTR))
    {
      first_slider_allocation.x = available_space.x;
      second_slider_allocation.x = available_space.x + available_space.width - buttons->slider_width;
    }
  else
    {
      first_slider_allocation.x = available_space.x + available_space.width - buttons->slider_width;
      second_slider_allocation.x = available_space.x;
    }

  gtk_widget_get_preferred_width (buttons->filler_widget, &filler_allocation.width, NULL);
  filler_allocation.height = available_space.height;
  filler_allocation.y = available_space.y;

  /* space used by sliders + filler */
  sliders_filler_width = buttons->slider_width * 2 + filler_allocation.width;

  /* space available for buttons */
  buttons_area_width = available_space.width - sliders_filler_width;

  /* dynamically calculate the maximum button width relative to the available space
   * by using a factor between 0 and 1 to allow the button to shrink in size */
  buttons->button_max_width = MIN (buttons_area_width * THUNAR_LOCATION_BUTTON_FACTOR,
                                   THUNAR_LOCATION_BUTTON_MAX_WIDTH);

  /* apply the value to all buttons */
  for (lp = buttons->list; lp != NULL; lp = lp->next)
    thunar_location_button_set_max_width (THUNAR_LOCATION_BUTTON (lp->data), buttons->button_max_width);

  /* calculate the total width of all buttons */
  buttons_total_width = 0;
  for (lp = buttons->list; lp != NULL; lp = lp->next)
    {
      gtk_widget_get_preferred_width (GTK_WIDGET (lp->data), &temp_width, NULL);
      buttons_total_width += temp_width;
      if (lp == buttons->fake_root_button)
        break;
    }

  /* determine how many buttons fit into the available area */
  if (buttons_total_width <= buttons_area_width)
    {
      /* Show full list of buttons, starting by fake root, if available */
      if (buttons->fake_root_button == NULL)
        buttons->first_visible_button = g_list_last (buttons->list);
      else
        buttons->first_visible_button = buttons->fake_root_button;
      buttons->last_visible_button = buttons->list;
    }
  else
    {
      /* reset calculation for buttons_total_width, because we need to cut off some buttons */
      buttons_total_width = 0;

      /* No scrolling defined ? Use last button as fixed start! */
      if (buttons->last_visible_button == NULL && buttons->first_visible_button == NULL)
        buttons->last_visible_button = buttons->list;

      /* if the last visible button is the active one, do not cut it off */
      if (buttons->last_visible_button != NULL
          && gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (buttons->last_visible_button->data)))
        {
          buttons->first_visible_button = thunar_location_buttons_detect_first_visible_button (buttons, buttons_area_width, NULL);
          buttons->last_visible_button = thunar_location_buttons_detect_last_visible_button (buttons, buttons_area_width, &buttons_total_width);
        }
      else if (buttons->first_visible_button == NULL) /* last button is fixed defined */
        buttons->first_visible_button = thunar_location_buttons_detect_first_visible_button (buttons, buttons_area_width, &buttons_total_width);
      else /* first button is fixed defined */
        buttons->last_visible_button = thunar_location_buttons_detect_last_visible_button (buttons, buttons_area_width, &buttons_total_width);
    }

  /* hide buttons before first_visible_button */
  if (buttons->first_visible_button != NULL)
    {
      for (lp = buttons->first_visible_button->next; lp != NULL; lp = lp->next)
        {
          need_reorder |= gtk_widget_get_child_visible (GTK_WIDGET (lp->data)) == TRUE;
          gtk_widget_set_child_visible (GTK_WIDGET (lp->data), FALSE);
        }
    }

  /* hide buttons after last visible_button */
  if (buttons->last_visible_button != NULL)
    {
      for (lp = buttons->last_visible_button->prev; lp != NULL; lp = lp->prev)
        {
          need_reorder |= gtk_widget_get_child_visible (GTK_WIDGET (lp->data)) == TRUE;
          gtk_widget_set_child_visible (GTK_WIDGET (lp->data), FALSE);
        }
    }

  /* allocate space for the buttons */
  folder_button_allocation.y = available_space.y;
  folder_button_allocation.height = available_space.height;
  if (G_LIKELY (direction == GTK_TEXT_DIR_LTR))
    folder_button_allocation.x = first_slider_allocation.x + first_slider_allocation.width;
  else
    {
      if (first_slider_allocation.width == 0)
        folder_button_allocation.x = available_space.x + available_space.width;
      else
        folder_button_allocation.x = first_slider_allocation.x;
    }

  for (lp = buttons->first_visible_button; lp != NULL; lp = lp->prev)
    {
      gtk_widget_get_preferred_width (GTK_WIDGET (lp->data), &folder_button_allocation.width, NULL);

      if (G_UNLIKELY (direction == GTK_TEXT_DIR_RTL))
        folder_button_allocation.x -= folder_button_allocation.width;

      need_reorder |= gtk_widget_get_child_visible (GTK_WIDGET (lp->data)) == FALSE;
      gtk_widget_set_child_visible (GTK_WIDGET (lp->data), TRUE);
      gtk_widget_size_allocate (lp->data, &folder_button_allocation);

      if (G_LIKELY (direction == GTK_TEXT_DIR_LTR))
        folder_button_allocation.x += folder_button_allocation.width;

      if (lp == buttons->last_visible_button)
        break;
    }

  /* allocate the filler */
  filler_allocation.width += available_space.width - sliders_filler_width - buttons_total_width;
  filler_allocation.x = folder_button_allocation.x;
  if (G_UNLIKELY (direction == GTK_TEXT_DIR_RTL))
    filler_allocation.x -= filler_allocation.width;
  gtk_widget_size_allocate (buttons->filler_widget, &filler_allocation);

  /* allocate the sliders */
  gtk_widget_get_preferred_width (GTK_WIDGET (buttons->left_slider), &temp_width, NULL);  /* to avoid warnings in gtk >= 3.20 */
  gtk_widget_get_preferred_width (GTK_WIDGET (buttons->right_slider), &temp_width, NULL); /* to avoid warnings in gtk >= 3.20 */
  gtk_widget_size_allocate (buttons->left_slider, &first_slider_allocation);
  gtk_widget_size_allocate (buttons->right_slider, &second_slider_allocation);
  thunar_location_buttons_update_sliders (buttons);

  if (need_reorder)
    thunar_location_buttons_child_ordering_changed (buttons);

  gtk_widget_queue_draw (GTK_WIDGET (buttons));
}



static gboolean
thunar_location_buttons_draw (GtkWidget *widget,
                              cairo_t   *cr)
{
  GtkStyleContext *context;
  GtkAllocation    alloc;

  context = gtk_widget_get_style_context (widget);
  gtk_widget_get_allocation (widget, &alloc);

  gtk_render_background (context, cr, 0, 0, alloc.width, alloc.height);
  gtk_render_frame (context, cr, 0, 0, alloc.width, alloc.height);

  return GTK_WIDGET_CLASS (thunar_location_buttons_parent_class)->draw (widget, cr);
}



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;
      }

  /* fallback case */
  thunar_location_buttons_remove_1 (container, widget);
}



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);

  if (buttons->filler_widget != NULL && include_internals)
    (*callback) (buttons->filler_widget, callback_data);
}



static GtkWidget *
thunar_location_buttons_make_button (ThunarLocationButtons *buttons,
                                     ThunarFile            *file)
{
  GtkWidget *button;

  /* allocate the button */
  button = g_object_new (THUNAR_TYPE_LOCATION_BUTTON, "max-width", buttons->button_max_width, NULL);

  /* connect to the file */
  thunar_location_button_set_file (THUNAR_LOCATION_BUTTON (button), file);

  /* the current directory is active */
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), (file == buttons->current_directory));

  /* connect signal handlers */
  g_signal_connect (G_OBJECT (button), "location-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), "popup-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_get_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);

  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);

  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 = gdk_threads_add_timeout_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)
{
  if (G_UNLIKELY (buttons->ignore_click))
    {
      buttons->ignore_click = FALSE;
      return;
    }

  gtk_widget_queue_resize (GTK_WIDGET (buttons));
  if (buttons->first_visible_button->next != NULL)
    {
      if (buttons->first_visible_button == buttons->fake_root_button)
        buttons->fake_root_button = NULL;
      buttons->first_visible_button = buttons->first_visible_button->next;
      buttons->last_visible_button = NULL;
    }
}



static void
thunar_location_buttons_scroll_right (GtkWidget             *button,
                                      ThunarLocationButtons *buttons)
{
  if (G_UNLIKELY (buttons->ignore_click))
    {
      buttons->ignore_click = FALSE;
      return;
    }

  gtk_widget_queue_resize (GTK_WIDGET (buttons));
  if (buttons->last_visible_button->prev != NULL)
    {
      buttons->last_visible_button = buttons->last_visible_button->prev;
      buttons->first_visible_button = NULL;
    }
}



static void
thunar_location_buttons_clicked (ThunarLocationButton  *button,
                                 gboolean               open_in_tab,
                                 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);

  /* open in tab */
  if (open_in_tab)
    {
      thunar_navigator_open_new_tab (THUNAR_NAVIGATOR (buttons), directory);
      return;
    }

  /* 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)))
    {
      for (lp = buttons->list; lp != NULL; lp = lp->next)
        {
          if (lp->data == button)
            buttons->first_visible_button = lp;
        }
      buttons->last_visible_button = NULL;
    }

  /* 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);
      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (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, TRUE);
}



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));

  /* clear both scroll positions */
  buttons->first_visible_button = NULL;
  buttons->last_visible_button = NULL;
}



static gboolean
thunar_location_buttons_context_menu (ThunarLocationButton  *button,
                                      ThunarLocationButtons *buttons)
{
  ThunarFile *file;
  GtkWidget  *window;
  GList      *files;
  ThunarMenu *context_menu;
  gboolean    is_current_directory;

  _thunar_return_val_if_fail (THUNAR_IS_LOCATION_BUTTONS (buttons), FALSE);
  _thunar_return_val_if_fail (THUNAR_IS_LOCATION_BUTTON (button), FALSE);

  /* determine the file for the button */
  file = thunar_location_button_get_file (button);
  if (G_UNLIKELY (file == NULL))
    return FALSE;

  files = g_list_append (NULL, file);
  g_object_set (G_OBJECT (buttons->action_mgr), "current-directory", file, NULL);
  g_object_set (G_OBJECT (buttons->action_mgr), "selected-files", files, NULL);
  g_list_free (files);
  is_current_directory = g_file_equal (thunar_file_get_file (file), thunar_file_get_file (buttons->current_directory));
  context_menu = g_object_new (THUNAR_TYPE_MENU, "menu-type", THUNAR_MENU_TYPE_CONTEXT_LOCATION_BUTTONS,
                               "action_mgr", buttons->action_mgr,
                               "force-section-open", TRUE,
                               "change_directory-support-disabled", is_current_directory, NULL);
  if (is_current_directory)
    {
      /* context menu on current directory */
      thunar_menu_add_sections (context_menu, THUNAR_MENU_SECTION_OPEN
                                              | THUNAR_MENU_SECTION_SENDTO);
      thunar_menu_add_sections (context_menu, THUNAR_MENU_SECTION_CREATE_NEW_FILES
                                              | THUNAR_MENU_SECTION_COPY_PASTE
                                              | THUNAR_MENU_SECTION_EMPTY_TRASH
                                              | THUNAR_MENU_SECTION_CUSTOM_ACTIONS
                                              | THUNAR_MENU_SECTION_PROPERTIES);
    }
  else
    {
      /* context menu on some other location button */
      thunar_menu_add_sections (context_menu, THUNAR_MENU_SECTION_OPEN
                                              | THUNAR_MENU_SECTION_SENDTO
                                              | THUNAR_MENU_SECTION_COPY_PASTE
                                              | THUNAR_MENU_SECTION_CUSTOM_ACTIONS
                                              | THUNAR_MENU_SECTION_PROPERTIES);
    }
  thunar_gtk_menu_hide_accel_labels (GTK_MENU (context_menu));
  gtk_widget_show_all (GTK_WIDGET (context_menu));
  window = gtk_widget_get_toplevel (GTK_WIDGET (buttons));
  thunar_window_redirect_menu_tooltips_to_statusbar (THUNAR_WINDOW (window), GTK_MENU (context_menu));

  thunar_gtk_menu_run (GTK_MENU (context_menu));
  return TRUE;
}
