thunar-location-button.c 35 KB
Newer Older
1
/* vi:set et ai sw=2 sts=2 ts=2: */
2 3
/*-
 * Copyright (c) 2005-2006 Benedikt Meurer <benny@xfce.org>
4
 * Copyright (c) 2009 Jannis Pohlmann <jannis@xfce.org>
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
 *
 * 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
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifdef HAVE_MEMORY_H
#include <memory.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif

#include <thunar/thunar-application.h>
#include <thunar/thunar-dnd.h>
34
#include <thunar/thunar-gio-extensions.h>
35 36 37
#include <thunar/thunar-icon-factory.h>
#include <thunar/thunar-location-button.h>
#include <thunar/thunar-pango-extensions.h>
38
#include <thunar/thunar-preferences.h>
39 40 41 42
#include <thunar/thunar-private.h>



43 44 45 46
#define THUNAR_LOCATION_BUTTON_MAX_WIDTH 250



47 48 49 50 51 52 53 54 55 56 57
/* Property identifiers */
enum
{
  PROP_0,
  PROP_FILE,
};

/* Signal identifiers */
enum
{
  CLICKED,
58
  GONE,
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
  LAST_SIGNAL,
};



static void           thunar_location_button_finalize               (GObject                    *object);
static void           thunar_location_button_get_property           (GObject                    *object,
                                                                     guint                       prop_id,
                                                                     GValue                     *value,
                                                                     GParamSpec                 *pspec);
static void           thunar_location_button_set_property           (GObject                    *object,
                                                                     guint                       prop_id,
                                                                     const GValue               *value,
                                                                     GParamSpec                 *pspec);
static GdkDragAction  thunar_location_button_get_dest_actions       (ThunarLocationButton       *location_button,
                                                                     GdkDragContext             *context,
                                                                     GtkWidget                  *button,
76
                                                                     guint                       timestamp);
77
static void           thunar_location_button_active_changed         (ThunarLocationButton       *location_button);
78 79 80 81
static void           thunar_location_button_file_changed           (ThunarLocationButton       *location_button,
                                                                     ThunarFile                 *file);
static void           thunar_location_button_file_destroy           (ThunarLocationButton       *location_button,
                                                                     ThunarFile                 *file);
82
static void           thunar_location_button_apply_label_size       (ThunarLocationButton       *location_button);
83
static gboolean       thunar_location_button_button_press_event     (GtkWidget                  *button,
84
                                                                     GdkEventButton             *event);
85
static gboolean       thunar_location_button_button_release_event   (GtkWidget                  *button,
86
                                                                     GdkEventButton             *event);
87 88
static void           thunar_location_button_map                    (GtkWidget                  *button);
static void           thunar_location_button_style_updated          (GtkWidget                  *button);
89 90 91 92
static gboolean       thunar_location_button_drag_drop              (GtkWidget                  *button,
                                                                     GdkDragContext             *context,
                                                                     gint                        x,
                                                                     gint                        y,
93
                                                                     guint                       timestamp);
94 95 96 97
static void           thunar_location_button_drag_data_get          (GtkWidget                  *button,
                                                                     GdkDragContext             *context,
                                                                     GtkSelectionData           *selection_data,
                                                                     guint                       info,
98
                                                                     guint                       timestamp);
99 100 101 102 103 104
static void           thunar_location_button_drag_data_received     (GtkWidget                  *button,
                                                                     GdkDragContext             *context,
                                                                     gint                        x,
                                                                     gint                        y,
                                                                     GtkSelectionData           *selection_data,
                                                                     guint                       info,
105
                                                                     guint                       timestamp);
106 107
static void           thunar_location_button_drag_leave             (GtkWidget                  *button,
                                                                     GdkDragContext             *context,
108
                                                                     guint                       timestamp);
109 110 111 112
static gboolean       thunar_location_button_drag_motion            (GtkWidget                  *button,
                                                                     GdkDragContext             *context,
                                                                     gint                        x,
                                                                     gint                        y,
113
                                                                     guint                       timestamp);
114 115
static gboolean       thunar_location_button_enter_timeout          (gpointer                    user_data);
static void           thunar_location_button_enter_timeout_destroy  (gpointer                    user_data);
116
static void           thunar_location_button_clicked                (ThunarLocationButton       *button);
117 118 119 120 121



struct _ThunarLocationButtonClass
{
122
  GtkToggleButtonClass __parent__;
123 124 125 126
};

struct _ThunarLocationButton
{
127
  GtkToggleButton __parent__;
128 129 130

  GtkWidget          *image;
  GtkWidget          *label;
131
  GtkWidget          *bold_label;
132 133 134

  /* the current icon state (i.e. accepting drops) */
  ThunarFileIconState file_icon_state;
Andre Miranda's avatar
Andre Miranda committed
135

136
  /* enter folders using DnD */
137
  guint               enter_timeout_id;
138 139

  /* drop support for the button */
140
  GList              *drop_file_list;
141 142 143 144 145 146 147 148 149 150 151 152 153 154
  guint               drop_data_ready : 1;
  guint               drop_occurred : 1;

  /* public properties */
  ThunarFile         *file;
};



static const GtkTargetEntry drag_targets[] =
{
  { "text/uri-list", 0, 0 },
};

155
static guint location_button_signals[LAST_SIGNAL];
156 157 158



159
G_DEFINE_TYPE (ThunarLocationButton, thunar_location_button, GTK_TYPE_TOGGLE_BUTTON)
160 161 162 163 164 165 166 167 168 169 170 171 172 173



static void
thunar_location_button_class_init (ThunarLocationButtonClass *klass)
{
  GtkWidgetClass *gtkwidget_class;
  GObjectClass   *gobject_class;

  gobject_class = G_OBJECT_CLASS (klass);
  gobject_class->finalize = thunar_location_button_finalize;
  gobject_class->get_property = thunar_location_button_get_property;
  gobject_class->set_property = thunar_location_button_set_property;

174 175 176 177
  gtkwidget_class = GTK_WIDGET_CLASS (klass);
  gtkwidget_class->map = thunar_location_button_map;
  gtkwidget_class->style_updated = thunar_location_button_style_updated;

178 179 180 181 182 183 184 185 186 187 188 189 190 191
  /**
   * ThunarLocationButton:file:
   *
   * The #ThunarFile represented by this location button.
   **/
  g_object_class_install_property (gobject_class,
                                   PROP_FILE,
                                   g_param_spec_object ("file",
                                                        "file",
                                                        "file",
                                                        THUNAR_TYPE_FILE,
                                                        EXO_PARAM_READWRITE));

  /**
192
   * ThunarLocationButton::location-button-clicked:
193 194 195
   * @location_button : a #ThunarLocationButton.
   *
   * Emitted by @location_button when the user clicks on the
196
   * @location_button or gtk_button_clicked() is called.
197 198
   **/
  location_button_signals[CLICKED] =
199
    g_signal_new (I_("location-button-clicked"),
200 201 202
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0, NULL, NULL,
203 204 205
                  g_cclosure_marshal_VOID__BOOLEAN,
                  G_TYPE_NONE, 1,
                  G_TYPE_BOOLEAN);
206

207 208 209 210 211 212 213 214 215 216 217 218 219 220
  /**
   * ThunarLocationButton::gone:
   * @location_button : a #ThunarLocationButton.
   *
   * Emitted by @location_button when the file associated with
   * the button is deleted.
   **/
  location_button_signals[GONE] =
    g_signal_new (I_("gone"),
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0, NULL, NULL,
                  g_cclosure_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);
221 222 223 224 225
}



static void
226
thunar_location_button_init (ThunarLocationButton *button)
227 228 229
{
  GtkWidget *hbox;

230 231 232
  /* initialize the toggle button */
  g_signal_connect (button, "notify::active", G_CALLBACK (thunar_location_button_active_changed), NULL);
  g_signal_connect (button, "clicked", G_CALLBACK (thunar_location_button_clicked), NULL);
233 234 235 236 237

  /* setup drag support for the button */
  gtk_drag_source_set (GTK_WIDGET (button), GDK_BUTTON1_MASK, drag_targets, G_N_ELEMENTS (drag_targets), GDK_ACTION_LINK);
  gtk_drag_dest_set (GTK_WIDGET (button), GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_MOTION, drag_targets,
                     G_N_ELEMENTS (drag_targets), GDK_ACTION_ASK | GDK_ACTION_COPY | GDK_ACTION_LINK | GDK_ACTION_MOVE);
238 239 240 241 242 243 244
  g_signal_connect (G_OBJECT (button), "button-press-event", G_CALLBACK (thunar_location_button_button_press_event), NULL);
  g_signal_connect (G_OBJECT (button), "button-release-event", G_CALLBACK (thunar_location_button_button_release_event), NULL);
  g_signal_connect (G_OBJECT (button), "drag-drop", G_CALLBACK (thunar_location_button_drag_drop), NULL);
  g_signal_connect (G_OBJECT (button), "drag-data-get", G_CALLBACK (thunar_location_button_drag_data_get), NULL);
  g_signal_connect (G_OBJECT (button), "drag-data-received", G_CALLBACK (thunar_location_button_drag_data_received), NULL);
  g_signal_connect (G_OBJECT (button), "drag-leave", G_CALLBACK (thunar_location_button_drag_leave), NULL);
  g_signal_connect (G_OBJECT (button), "drag-motion", G_CALLBACK (thunar_location_button_drag_motion), NULL);
245 246

  /* create the horizontal box */
247
  hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
248 249 250 251
  gtk_container_add (GTK_CONTAINER (button), hbox);
  gtk_widget_show (hbox);

  /* create the button image */
252 253 254
  button->image = gtk_image_new ();
  gtk_box_pack_start (GTK_BOX (hbox), button->image, TRUE, FALSE, 0);
  gtk_widget_show (button->image);
255 256

  /* create the button label */
257 258 259
  button->label = g_object_new (GTK_TYPE_LABEL, "xalign", 0.5f, "yalign", 0.5f, NULL);
  gtk_box_pack_start (GTK_BOX (hbox), button->label, TRUE, TRUE, 0);
  gtk_widget_show (button->label);
260 261 262 263 264 265

  /* create the bold label */
  button->bold_label = g_object_new (GTK_TYPE_LABEL, "xalign", 0.5f, "yalign", 0.5f, NULL);
  gtk_box_pack_start (GTK_BOX (hbox), button->bold_label, TRUE, TRUE, 0);
  gtk_label_set_attributes (GTK_LABEL (button->bold_label), thunar_pango_attr_list_bold ());
  /* but don't show it, as it is only a fake to retrieve the bold size */
266 267 268

  /* add widget to css class which matches all buttons in the path-bar */
  gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (button)), "path-bar-button");
269 270 271 272 273 274 275 276 277 278
}



static void
thunar_location_button_finalize (GObject *object)
{
  ThunarLocationButton *location_button = THUNAR_LOCATION_BUTTON (object);

  /* release the drop path list (just in case the drag-leave wasn't fired before) */
279
  thunar_g_file_list_free (location_button->drop_file_list);
280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336

  /* be sure to cancel any pending enter timeout */
  if (G_UNLIKELY (location_button->enter_timeout_id != 0))
    g_source_remove (location_button->enter_timeout_id);

  /* disconnect from the file */
  thunar_location_button_set_file (location_button, NULL);

  (*G_OBJECT_CLASS (thunar_location_button_parent_class)->finalize) (object);
}



static void
thunar_location_button_get_property (GObject    *object,
                                     guint       prop_id,
                                     GValue     *value,
                                     GParamSpec *pspec)
{
  ThunarLocationButton *location_button = THUNAR_LOCATION_BUTTON (object);

  switch (prop_id)
    {
    case PROP_FILE:
      g_value_set_object (value, thunar_location_button_get_file (location_button));
      break;

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



static void
thunar_location_button_set_property (GObject      *object,
                                     guint         prop_id,
                                     const GValue *value,
                                     GParamSpec   *pspec)
{
  ThunarLocationButton *location_button = THUNAR_LOCATION_BUTTON (object);

  switch (prop_id)
    {
    case PROP_FILE:
      thunar_location_button_set_file (location_button, g_value_get_object (value));
      break;

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



337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356
static void
thunar_location_button_map (GtkWidget *button)
{
  GTK_WIDGET_CLASS (thunar_location_button_parent_class)->map (button);

  thunar_location_button_apply_label_size (THUNAR_LOCATION_BUTTON (button));
}



static void
thunar_location_button_style_updated (GtkWidget *button)
{
  GTK_WIDGET_CLASS (thunar_location_button_parent_class)->style_updated (button);

  thunar_location_button_apply_label_size (THUNAR_LOCATION_BUTTON (button));
}



357 358 359 360
static GdkDragAction
thunar_location_button_get_dest_actions (ThunarLocationButton *location_button,
                                         GdkDragContext       *context,
                                         GtkWidget            *button,
361
                                         guint                 timestamp)
362 363 364 365 366 367 368 369
{
  GdkDragAction actions = 0;
  GdkDragAction action = 0;

  /* check if we can drop here */
  if (G_LIKELY (location_button->file != NULL))
    {
      /* determine the possible drop actions for the file (and the suggested action if any) */
370
      actions = thunar_file_accepts_drop (location_button->file, location_button->drop_file_list, context, &action);
371 372 373
    }

  /* tell Gdk whether we can drop here */
374
  gdk_drag_status (context, action, timestamp);
375 376 377 378 379 380 381 382 383 384 385

  return actions;
}



static void
thunar_location_button_file_changed (ThunarLocationButton *location_button,
                                     ThunarFile           *file)
{
  GtkIconTheme      *icon_theme;
386 387
  gchar             *icon_name;
  const gchar       *dnd_icon_name;
388 389 390 391 392

  _thunar_return_if_fail (THUNAR_IS_LOCATION_BUTTON (location_button));
  _thunar_return_if_fail (location_button->file == file);
  _thunar_return_if_fail (THUNAR_IS_FILE (file));

393
  /* Retrieve the icon theme */
394
  icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (location_button)));
395
  /* TODO: listen for icon theme changes */
396

397
  /* update and show the label widget (hide for the local root folder) */
Andre Miranda's avatar
Andre Miranda committed
398
  if (thunar_file_is_local (file) && thunar_file_is_root (file))
399
    {
400 401
      /* hide the label would otherwise show up */
      gtk_widget_hide (location_button->label);
402 403 404
    }
  else
    {
405
      /* set label to the file's display name and show the label */
406
      gtk_label_set_text (GTK_LABEL (location_button->label), thunar_file_get_display_name (file));
407 408

      /* set the label's size request in such a way that a bold label will not change the button size */
409 410
      if (gtk_widget_get_mapped (GTK_WIDGET (location_button)))
        thunar_location_button_apply_label_size (location_button);
411 412

      gtk_widget_show (location_button->label);
413
    }
414 415

  /* the image is only visible for certain special paths */
416
  if (thunar_file_is_home (file) || thunar_file_is_desktop (file) || thunar_file_is_root (file))
417
    {
418 419 420
      icon_name = g_strdup_printf ("%s-symbolic", thunar_file_get_icon_name (file,
                                                                             location_button->file_icon_state,
                                                                             icon_theme));
421 422

      /* update the icon for the image */
423
      gtk_image_set_from_icon_name (GTK_IMAGE (location_button->image), icon_name, GTK_ICON_SIZE_BUTTON);
424 425 426

      /* show the image widget */
      gtk_widget_show (location_button->image);
427 428

      g_free (icon_name);
429 430 431 432 433 434 435 436
    }
  else
    {
      /* hide the image widget */
      gtk_widget_hide (location_button->image);
    }

  /* setup the DnD icon for the button */
437 438
  dnd_icon_name = thunar_file_get_custom_icon (file);
  if (dnd_icon_name != NULL)
439
    {
440
      gtk_drag_source_set_icon_name (GTK_WIDGET (location_button), dnd_icon_name);
441 442 443
    }
  else
    {
444 445
      dnd_icon_name = thunar_file_get_icon_name (file, location_button->file_icon_state, icon_theme);
      gtk_drag_source_set_icon_name (GTK_WIDGET (location_button), dnd_icon_name);
446
    }
447 448 449 450

  /* recalculate the required size in case the filename changed */
  gtk_widget_set_size_request (GTK_WIDGET (location_button->label), -1, -1);

451 452 453 454 455 456 457 458 459 460 461 462
}



static void
thunar_location_button_file_destroy (ThunarLocationButton *location_button,
                                     ThunarFile           *file)
{
  _thunar_return_if_fail (THUNAR_IS_LOCATION_BUTTON (location_button));
  _thunar_return_if_fail (location_button->file == file);
  _thunar_return_if_fail (THUNAR_IS_FILE (file));

463 464
  /* the file is gone, emit the "gone" signal */
  g_signal_emit (G_OBJECT (location_button), location_button_signals[GONE], 0);
465 466 467 468 469
}



static void
470
thunar_location_button_apply_label_size (ThunarLocationButton *location_button)
471
{
472 473 474
  GtkRequisition normal_size;
  GtkRequisition bold_size;
  gint           width, height;
475
  const gchar   *text;
476

477
  text = gtk_label_get_text (GTK_LABEL (location_button->label));
478

479 480
  /* determine the normal size */
  gtk_widget_get_preferred_size (location_button->label, NULL, &normal_size);
481

482
  /* determine the bold size */
483 484
  gtk_label_set_text (GTK_LABEL (location_button->bold_label), text);
  gtk_widget_get_preferred_size (location_button->bold_label, NULL, &bold_size);
485 486 487 488 489

  /* request the maximum of the pixel sizes, to make sure
   * the button always requests the same size, no matter if
   * the label is bold or not.
   */
490 491
  width = MAX (normal_size.width, bold_size.width);
  height = MAX (normal_size.height, bold_size.height);
492

493 494 495 496 497
  if (width >= THUNAR_LOCATION_BUTTON_MAX_WIDTH)
    {
      /* if the size is too big, enable ellipsizing */
      gtk_label_set_ellipsize (GTK_LABEL (location_button->label), PANGO_ELLIPSIZE_MIDDLE);
      gtk_widget_set_size_request (GTK_WIDGET (location_button->label), THUNAR_LOCATION_BUTTON_MAX_WIDTH, height);
498 499 500

      /* and set a tooltip */
      gtk_widget_set_tooltip_text (location_button->label, text);
501 502 503 504 505 506 507
    }
  else
    {
      /* don't enable ellipsizing: In some borderline cases, the size calculated
       * is actually off by 1 or 2 pixels and the label will ellipsize unnecessarily
       * TODO: check what we did wrong */
      gtk_widget_set_size_request (GTK_WIDGET (location_button->label), width, height);
508
      gtk_widget_set_tooltip_text (location_button->label, NULL);
509
    }
510 511 512 513 514 515
}



static gboolean
thunar_location_button_button_press_event (GtkWidget            *button,
516
                                           GdkEventButton       *event)
517
{
518
  gboolean popup_menu_return;
519

520 521
  _thunar_return_val_if_fail (THUNAR_IS_LOCATION_BUTTON (button), FALSE);

522 523 524 525 526 527 528 529
  /* check if we can handle the button event */
  if (G_UNLIKELY (event->button == 2))
    {
      /* set button state to inconsistent while holding down the middle-mouse-button */
      gtk_toggle_button_set_inconsistent (GTK_TOGGLE_BUTTON (button), TRUE);
    }
  else if (event->button == 3)
    {
530 531
      /* emit the "popup-menu" signal and let the surrounding ThunarLocationButtons popup a menu */
      g_signal_emit_by_name (G_OBJECT (button), "popup-menu", &popup_menu_return);
532 533 534 535 536 537 538 539 540 541
      return TRUE;
    }

  return FALSE;
}



static gboolean
thunar_location_button_button_release_event (GtkWidget            *button,
542
                                             GdkEventButton       *event)
543 544
{
  ThunarApplication *application;
545 546
  ThunarPreferences *preferences;
  gboolean           open_in_tab;
547

548
  _thunar_return_val_if_fail (THUNAR_IS_LOCATION_BUTTON (button), FALSE);
549 550 551 552 553 554 555 556

  /* reset inconsistent button state after releasing the middle-mouse-button */
  if (G_UNLIKELY (event->button == 2))
    {
      /* reset button state */
      gtk_toggle_button_set_inconsistent (GTK_TOGGLE_BUTTON (button), FALSE);

      /* verify that we still have a valid file */
557
      if (G_LIKELY (THUNAR_LOCATION_BUTTON (button)->file != NULL))
558
        {
559 560 561 562 563 564 565
          preferences = thunar_preferences_get ();
          g_object_get (preferences, "misc-middle-click-in-tab", &open_in_tab, NULL);
          g_object_unref (preferences);

          if (open_in_tab)
            {
              /* open in tab */
566
              g_signal_emit_by_name (G_OBJECT (button), "location-button-clicked", TRUE);
567 568 569 570 571
            }
          else
            {
              /* open a new window for the folder */
              application = thunar_application_get ();
572
              thunar_application_open_window (application, THUNAR_LOCATION_BUTTON (button)->file, gtk_widget_get_screen (button), NULL, TRUE);
573 574
              g_object_unref (G_OBJECT (application));
            }
575 576 577 578 579 580 581 582 583 584 585 586 587
        }
    }

  return FALSE;
}



static gboolean
thunar_location_button_drag_drop (GtkWidget            *button,
                                  GdkDragContext       *context,
                                  gint                  x,
                                  gint                  y,
588
                                  guint                 timestamp)
589 590 591 592
{
  GdkAtom target;

  _thunar_return_val_if_fail (GTK_IS_WIDGET (button), FALSE);
593
  _thunar_return_val_if_fail (THUNAR_IS_LOCATION_BUTTON (button), FALSE);
594 595 596

  /* determine the DnD target and see if we can handle it */
  target = gtk_drag_dest_find_target (button, context, NULL);
597
  if (G_UNLIKELY (target != gdk_atom_intern_static_string ("text/uri-list")))
598 599 600 601 602
    return FALSE;

  /* set state so drag-data-received knows that
   * this is really a drop this time.
   */
603
  THUNAR_LOCATION_BUTTON (button)->drop_occurred = TRUE;
604 605

  /* request the drag data from the source */
606
  gtk_drag_get_data (button, context, target, timestamp);
607 608 609 610 611 612 613 614 615 616 617 618

  /* we'll call gtk_drag_finish() later */
  return TRUE;
}



static void
thunar_location_button_drag_data_get (GtkWidget            *button,
                                      GdkDragContext       *context,
                                      GtkSelectionData     *selection_data,
                                      guint                 info,
619
                                      guint                 timestamp)
620
{
621 622
  gchar **uris;
  GList   path_list;
623 624

  _thunar_return_if_fail (GTK_IS_WIDGET (button));
625
  _thunar_return_if_fail (THUNAR_IS_LOCATION_BUTTON (button));
626 627

  /* verify that we have a valid file */
628
  if (G_LIKELY (THUNAR_LOCATION_BUTTON (button)->file != NULL))
629 630
    {
      /* transform the path into an uri list string */
631
      path_list.next = path_list.prev = NULL;
632
      path_list.data = thunar_file_get_file (THUNAR_LOCATION_BUTTON (button)->file);
633 634

      /* set the uri list for the drag selection */
635 636 637
      uris = thunar_g_file_list_to_stringv (&path_list);
      gtk_selection_data_set_uris (selection_data, uris);
      g_strfreev (uris);
638 639 640 641 642 643 644 645 646 647 648 649
    }
}



static void
thunar_location_button_drag_data_received (GtkWidget            *button,
                                           GdkDragContext       *context,
                                           gint                  x,
                                           gint                  y,
                                           GtkSelectionData     *selection_data,
                                           guint                 info,
650
                                           guint                 timestamp)
651
{
652 653 654 655
  GdkDragAction         actions;
  GdkDragAction         action;
  gboolean              succeed = FALSE;
  ThunarLocationButton *location_button = THUNAR_LOCATION_BUTTON (button);
656 657 658 659 660

  /* check if we don't already know the drop data */
  if (G_LIKELY (!location_button->drop_data_ready))
    {
      /* extract the URI list from the selection data (if valid) */
Jonas Kümmerlin's avatar
Jonas Kümmerlin committed
661 662
      if (gtk_selection_data_get_format (selection_data) == 8 && gtk_selection_data_get_length (selection_data) > 0)
        location_button->drop_file_list = thunar_g_file_list_new_from_string ((const gchar *) gtk_selection_data_get_data (selection_data));
663 664 665 666 667 668 669 670 671 672 673 674

      /* reset the state */
      location_button->drop_data_ready = TRUE;
    }

  /* check if the data was dropped */
  if (G_UNLIKELY (location_button->drop_occurred))
    {
      /* reset the state */
      location_button->drop_occurred = FALSE;

      /* determine the drop dest actions */
675
      actions = thunar_location_button_get_dest_actions (location_button, context, button, timestamp);
676 677 678
      if (G_LIKELY ((actions & (GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK)) != 0))
        {
          /* as the user what to do with the drop data */
Jonas Kümmerlin's avatar
Jonas Kümmerlin committed
679
          action = (gdk_drag_context_get_selected_action (context) == GDK_ACTION_ASK)
680
                 ? thunar_dnd_ask (button, location_button->file, location_button->drop_file_list, actions)
Jonas Kümmerlin's avatar
Jonas Kümmerlin committed
681
                 : gdk_drag_context_get_selected_action (context);
682 683 684

          /* perform the requested action */
          if (G_LIKELY (action != 0))
685
            succeed = thunar_dnd_perform (button, location_button->file, location_button->drop_file_list, action, NULL);
686 687 688
        }

      /* tell the peer that we handled the drop */
689
      gtk_drag_finish (context, succeed, FALSE, timestamp);
690 691

      /* disable the highlighting and release the drag data */
692
      thunar_location_button_drag_leave (button, context, timestamp);
693 694 695 696 697 698 699 700
    }
}



static void
thunar_location_button_drag_leave (GtkWidget            *button,
                                   GdkDragContext       *context,
701
                                   guint                 timestamp)
702
{
703 704
  ThunarLocationButton *location_button;

705
  _thunar_return_if_fail (GTK_IS_BUTTON (button));
706 707
  _thunar_return_if_fail (THUNAR_IS_LOCATION_BUTTON (button));

708
  location_button = THUNAR_LOCATION_BUTTON (button);
709 710 711 712 713 714 715 716 717 718 719 720 721 722 723

  /* reset the file icon state to default appearance */
  if (G_LIKELY (location_button->file_icon_state != THUNAR_FILE_ICON_STATE_DEFAULT))
    {
      /* we may switch to "drop icon state" during drag motion */
      location_button->file_icon_state = THUNAR_FILE_ICON_STATE_DEFAULT;

      /* update the user interface if we still have a file */
      if (G_LIKELY (location_button->file != NULL))
        thunar_location_button_file_changed (location_button, location_button->file);
    }

  /* reset the "drop data ready" status and free the path list */
  if (G_LIKELY (location_button->drop_data_ready))
    {
724
      thunar_g_file_list_free (location_button->drop_file_list);
725
      location_button->drop_data_ready = FALSE;
726
      location_button->drop_file_list = NULL;
727 728 729 730 731 732 733 734 735 736 737 738 739 740
    }

  /* be sure to cancel any running enter timeout */
  if (G_LIKELY (location_button->enter_timeout_id != 0))
    g_source_remove (location_button->enter_timeout_id);
}



static gboolean
thunar_location_button_drag_motion (GtkWidget            *button,
                                    GdkDragContext       *context,
                                    gint                  x,
                                    gint                  y,
741
                                    guint                 timestamp)
742
{
743 744 745 746 747
  ThunarFileIconState    file_icon_state;
  GdkDragAction          actions;
  GdkAtom                target;
  gint                   delay;
  ThunarLocationButton  *location_button;
748 749

  _thunar_return_val_if_fail (GTK_IS_BUTTON (button), FALSE);
750 751 752
  _thunar_return_val_if_fail (THUNAR_IS_LOCATION_BUTTON (button), FALSE);

  location_button = THUNAR_LOCATION_BUTTON (button);
753 754 755 756

  /* schedule the enter timeout if not already done */
  if (G_UNLIKELY (location_button->enter_timeout_id == 0))
    {
757 758 759
      /* we used to use gtk-menu-popdown-delay here, but this was never right and is now deprecated */
      /* therefore, we hardcode the value instead. */
      delay = 1000;
760 761

      /* schedule the timeout */
762 763 764 765
      location_button->enter_timeout_id = gdk_threads_add_timeout_full (G_PRIORITY_DEFAULT, delay,
                                                                        thunar_location_button_enter_timeout,
                                                                        location_button,
                                                                        thunar_location_button_enter_timeout_destroy);
766 767 768 769 770 771 772
    }

  /* request the drop on-demand (if we don't have it already) */
  if (G_UNLIKELY (!location_button->drop_data_ready))
    {
      /* check if we can handle that drag data (can only drop text/uri-list) */
      target = gtk_drag_dest_find_target (button, context, NULL);
773
      if (G_LIKELY (target == gdk_atom_intern_static_string ("text/uri-list")))
774 775
        {
          /* request the drop data from the source */
776
          gtk_drag_get_data (button, context, target, timestamp);
777 778 779
        }

      /* tell GDK that it cannot drop here (yet!) */
780
      gdk_drag_status (context, 0, timestamp);
781 782 783 784
    }
  else
    {
      /* check whether we can drop into the buttons' folder */
785
      actions = thunar_location_button_get_dest_actions (location_button, context, button, timestamp);
786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819

      /* determine the new file icon state */
      file_icon_state = (actions == 0) ? THUNAR_FILE_ICON_STATE_DEFAULT : THUNAR_FILE_ICON_STATE_DROP;

      /* check if we need to update the user interface */
      if (G_UNLIKELY (location_button->file_icon_state != file_icon_state))
        {
          /* apply the new icon state */
          location_button->file_icon_state = file_icon_state;

          /* update the user interface if we have a file */
          if (G_LIKELY (location_button->file != NULL))
            thunar_location_button_file_changed (location_button, location_button->file);
        }
    }

  return FALSE;
}



static gboolean
thunar_location_button_enter_timeout (gpointer user_data)
{
  ThunarLocationButton *location_button = THUNAR_LOCATION_BUTTON (user_data);

  /* We emulate a "clicked" event here, because else the buttons
   * would be destroyed and replaced by new buttons, which causes
   * the Gtk DND code to dump core once the mouse leaves the area
   * of the new button.
   *
   * Besides that, handling this as "clicked" event allows the user
   * to go back to the initial directory.
   */
820
  gtk_button_clicked (GTK_BUTTON (location_button));
821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849

  return FALSE;
}



static void
thunar_location_button_enter_timeout_destroy (gpointer user_data)
{
  THUNAR_LOCATION_BUTTON (user_data)->enter_timeout_id = 0;
}



/**
 * thunar_location_button_new:
 *
 * Allocates a new #ThunarLocationButton instance.
 *
 * Return value: the newly allocated #ThunarLocationButton.
 **/
GtkWidget*
thunar_location_button_new (void)
{
  return g_object_new (THUNAR_TYPE_LOCATION_BUTTON, NULL);
}



850 851
static void
thunar_location_button_active_changed (ThunarLocationButton *location_button)
852
{
853 854
  gboolean active;

855 856
  _thunar_return_if_fail (THUNAR_IS_LOCATION_BUTTON (location_button));

857
  active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (location_button));
858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927

  /* use a bold label for active location buttons */
  gtk_label_set_attributes (GTK_LABEL (location_button->label), active ? thunar_pango_attr_list_bold () : NULL);
}



/**
 * thunar_location_button_get_file:
 * @location_button : a #ThunarLocationButton.
 *
 * Returns the #ThunarFile for @location_button.
 *
 * Return value: the #ThunarFile for @location_button.
 **/
ThunarFile*
thunar_location_button_get_file (ThunarLocationButton *location_button)
{
  _thunar_return_val_if_fail (THUNAR_IS_LOCATION_BUTTON (location_button), NULL);
  return location_button->file;
}



/**
 * thunar_location_button_set_file:
 * @location_button : a #ThunarLocationButton.
 * @file            : a #ThunarFile or %NULL.
 *
 * Sets the file for @location_button to @file.
 **/
void
thunar_location_button_set_file (ThunarLocationButton *location_button,
                                 ThunarFile           *file)
{
  _thunar_return_if_fail (THUNAR_IS_LOCATION_BUTTON (location_button));
  _thunar_return_if_fail (file == NULL || THUNAR_IS_FILE (file));

  /* check if we already use that file */
  if (G_UNLIKELY (location_button->file == file))
    return;

  /* disconnect from the previous file */
  if (location_button->file != NULL)
    {
      /* stop watching the file */
      thunar_file_unwatch (location_button->file);

      /* disconnect signals and release reference */
      g_signal_handlers_disconnect_by_func (G_OBJECT (location_button->file), thunar_location_button_file_destroy, location_button);
      g_signal_handlers_disconnect_by_func (G_OBJECT (location_button->file), thunar_location_button_file_changed, location_button);
      g_object_unref (G_OBJECT (location_button->file));
    }

  /* activate the new file */
  location_button->file = file;

  /* connect to the new file */
  if (G_LIKELY (file != NULL))
    {
      /* take a reference on the new file */
      g_object_ref (G_OBJECT (file));

      /* watch the file for changes */
      thunar_file_watch (file);

      /* stay informed about changes to the file */
      g_signal_connect_swapped (G_OBJECT (file), "changed", G_CALLBACK (thunar_location_button_file_changed), location_button);
      g_signal_connect_swapped (G_OBJECT (file), "destroy", G_CALLBACK (thunar_location_button_file_destroy), location_button);

928 929
      /* update our internal state for the new file */
      thunar_location_button_file_changed (location_button, file);
930 931 932 933 934 935 936 937 938 939 940 941
    }

  /* notify listeners */
  g_object_notify (G_OBJECT (location_button), "file");
}



/**
 * thunar_location_button_clicked:
 * @location_button : a #ThunarLocationButton.
 *
942
 * Emits the ::location-button-clicked signal on @location_button.
943 944 945 946 947
 **/
void
thunar_location_button_clicked (ThunarLocationButton *location_button)
{
  _thunar_return_if_fail (THUNAR_IS_LOCATION_BUTTON (location_button));
948
  g_signal_emit (G_OBJECT (location_button), location_button_signals[CLICKED], 0, FALSE);
949 950 951
}