gtkpathbar.c 27.4 KB
Newer Older
1
/* gtkpathbar.c
2
 *
3
 * Copyright (C) 2015 Red Hat
4
 *
5 6 7 8 9 10
 * 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,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 13 14 15 16
 * 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, see <http://www.gnu.org/licenses/>.
17
 *
18
 * Authors: Carlos Soriano <csoriano@gnome.org>
19 20
 */

21
#include "config.h"
22

23
#include "gtkmain.h"
24
#include "gtkpathbar.h"
25 26 27 28 29
#include "gtkstack.h"
#include "gtkentry.h"
#include "gtkbutton.h"
#include "gtktogglebutton.h"
#include "gtklabel.h"
30
#include "gtkbox.h"
31 32 33 34 35 36
#include "gtkmenubutton.h"
#include "gtkpopover.h"
#include "gtkbuilder.h"
#include "gtkstylecontext.h"
#include "gtkpathbarcontainerprivate.h"

37
#include "gtkintl.h"
38
#include "gtkmarshalers.h"
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
#include "gtktypebuiltins.h"
#include "gtkhidingboxprivate.h"

/**
 * SECTION:gtkpathbar
 * @Short_description: Widget that displays a path in UNIX format in a button-like manner
 * @Title: GtkPathBar
 * @See_also: #GtkFileChooser
 *
 * #GtkPathBar is a stock widget that displays a path in UNIX format in a way that
 * the user can interact with it, selecting part of it or providing menus for
 * every part of the path.
 *
 * Given the usual big lenght of paths, it conveniently manages the overflow of it,
 * hiding the parts of the path that doesn't have snouegh space to be displayed
 * in a overflow popover
 *
 * The #GtkPathBar does not perform any file handling.
 * If file handling is needed #GtkFilesPathBar is the correct widget.
 */
59

60 61
struct _GtkPathBarPrivate
{
62 63 64 65 66 67 68 69
  GtkWidget *path_bar_containers_stack;
  GtkWidget *path_bar_container_1;
  GtkWidget *path_bar_container_2;
  GtkWidget *path_box_1;
  GtkWidget *path_box_2;
  GtkWidget *overflow_button_1;
  GtkWidget *overflow_button_2;
  GtkWidget *path_chunk_popover_container;
70

71
  GIcon *root_icon;
72 73 74 75 76 77
  gchar *root_label;
  gchar *root_path;

  gchar *path;
  gchar *selected_path;
  gint inverted :1;
78
};
79

80 81
G_DEFINE_TYPE_WITH_PRIVATE (GtkPathBar, gtk_path_bar, GTK_TYPE_BIN)

82
enum {
83
  POPULATE_POPUP,
84 85 86
  LAST_SIGNAL
};

87 88 89 90 91 92 93
enum {
  PROP_0,
  PROP_PATH,
  PROP_SELECTED_PATH,
  PROP_INVERTED,
  LAST_PROP
};
94

95 96
static GParamSpec *path_bar_properties[LAST_PROP] = { NULL, };
static guint path_bar_signals[LAST_SIGNAL] = { 0 };
97

98
typedef struct {
99
  GtkWidget *button;
100 101 102 103 104
  GIcon *icon;
  gchar *label;
  gchar *path;
  GtkPathBar *path_bar;
} PathChunkData;
105

106
static void
107 108
emit_populate_popup (GtkPathBar  *self,
                     const gchar *path)
109
{
110
  GtkPathBarPrivate *priv = gtk_path_bar_get_instance_private (GTK_PATH_BAR (self));
111

112 113
  g_signal_emit (self, path_bar_signals[POPULATE_POPUP], 0,
                 priv->path_chunk_popover_container, path);
114 115 116
}

static void
117 118 119 120 121
get_path_bar_widgets (GtkPathBar *self,
                      GtkWidget  **path_bar,
                      GtkWidget  **overflow_button,
                      GtkWidget  **path_box,
                      gboolean    current)
122
{
123 124
  GtkPathBarPrivate *priv = gtk_path_bar_get_instance_private (GTK_PATH_BAR (self));
  GtkWidget *current_path_bar;
125

126 127 128 129
  current_path_bar = gtk_stack_get_visible_child (GTK_STACK (priv->path_bar_containers_stack));
  if (current_path_bar == NULL ||
      (current_path_bar == priv->path_bar_container_1 && current) ||
      (current_path_bar == priv->path_bar_container_2 && !current))
130
    {
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
      if (path_bar)
        *path_bar = priv->path_bar_container_1;
      if (overflow_button)
        *overflow_button = priv->overflow_button_1;
      if (path_box)
        *path_box = priv->path_box_1;
    }
  else
    {
      if (path_bar)
        *path_bar = priv->path_bar_container_2;
      if (overflow_button)
        *overflow_button = priv->overflow_button_2;
      if (path_box)
        *path_box = priv->path_box_2;
146 147 148 149
    }
}

static void
150
on_path_chunk_popover_destroyed (GtkPathBar *self)
151
{
152
  GtkPathBarPrivate *priv;
153

154 155
  if (self == NULL)
    return;
156

157 158
  priv = gtk_path_bar_get_instance_private (GTK_PATH_BAR (self));
  priv->path_chunk_popover_container = NULL;
159 160
}

161
static void
162 163
show_path_chunk_popover (GtkPathBar *self,
                         GtkWidget  *path_chunk)
164
{
165 166
  GtkPathBarPrivate *priv = gtk_path_bar_get_instance_private (GTK_PATH_BAR (self));
  GtkWidget *popover;
167

168
  if (priv->path_chunk_popover_container != NULL)
169
    {
170 171 172
      popover = gtk_widget_get_ancestor (priv->path_chunk_popover_container,
                                         GTK_TYPE_POPOVER);
      gtk_widget_destroy (popover);
173 174
    }

175 176 177 178 179 180 181
  popover = gtk_popover_new (path_chunk);
  priv->path_chunk_popover_container = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
  gtk_container_add (GTK_CONTAINER (popover), priv->path_chunk_popover_container);
  /* Clean private pointer when its destroyed, most of the times due to its
   * relative_to associated widget being destroyed */
  g_signal_connect_swapped (popover, "destroy",
                            G_CALLBACK (on_path_chunk_popover_destroyed), self);
182

183
  gtk_widget_show (popover);
184
}
185

186
static void
187
hide_path_chunk_popover_if_empty (GtkPathBar *self)
188
{
189 190 191
  GtkPathBarPrivate *priv = gtk_path_bar_get_instance_private (GTK_PATH_BAR (self));
  GList *children;
  GtkWidget *popover;
192

193 194 195 196 197
  children = gtk_container_get_children (GTK_CONTAINER (priv->path_chunk_popover_container));
  popover = gtk_widget_get_ancestor (priv->path_chunk_popover_container,
                                         GTK_TYPE_POPOVER);
  if (g_list_length (children) == 0)
    gtk_widget_destroy (popover);
198

199 200
  g_list_free (children);
}
201

202 203 204 205 206 207
static gboolean
on_path_chunk_button_release_event (GtkWidget      *path_chunk,
                                    GdkEventButton *event)
{
  PathChunkData *data;
  GtkPathBarPrivate *priv;
208

209 210
  data = g_object_get_data (G_OBJECT (path_chunk), "data");
  g_assert (data != NULL);
211

212
  priv = gtk_path_bar_get_instance_private (GTK_PATH_BAR (data->path_bar));
213

214
  if (event->type == GDK_BUTTON_RELEASE)
215
    {
216 217 218 219
      switch (event->button)
        {
        case GDK_BUTTON_PRIMARY:
          gtk_path_bar_set_selected_path (data->path_bar, data->path);
220

221
          return TRUE;
222

223 224 225 226
        case GDK_BUTTON_SECONDARY:
          show_path_chunk_popover (data->path_bar, path_chunk);
          emit_populate_popup (data->path_bar, data->path);
          hide_path_chunk_popover_if_empty (data->path_bar);
227

228
          return TRUE;
229

230 231 232 233
        default:
          break;
        }
    }
234

235
  return FALSE;
236 237 238
}

static void
239
free_path_chunk_data (PathChunkData *data)
240
{
241 242 243 244 245
  if (data->label)
    g_free (data->label);
  g_free (data->path);
  g_clear_object (&data->icon);
  g_slice_free (PathChunkData, data);
246 247
}

248 249 250 251 252 253 254
static GtkWidget *
create_path_chunk (GtkPathBar  *self,
                   const gchar *path,
                   const gchar *label,
                   GIcon       *icon,
                   gboolean     add_separator,
                   gboolean     separator_after_button)
255
{
256 257 258 259 260 261 262
  GtkWidget *button;
  GtkWidget *separator;
  GtkWidget *path_chunk;
  GtkWidget *button_label;
  GtkWidget *image;
  GtkStyleContext *style;
  PathChunkData *path_chunk_data;
263

264 265
  path_chunk = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
  button = gtk_toggle_button_new ();
266

267
  if (icon)
268
    {
269 270
      image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_MENU);
      gtk_button_set_image (GTK_BUTTON (button), image);
271
    }
272
  else if (label)
273
    {
274 275 276 277 278 279 280
      button_label = gtk_label_new (label);
      gtk_label_set_ellipsize (GTK_LABEL (button_label), PANGO_ELLIPSIZE_MIDDLE);
      // FIXME: the GtkLabel requests more than the number of chars set here.
      // For visual testing for now substract 2 chars.
      gtk_label_set_width_chars (GTK_LABEL (button_label),
                                 MIN (strlen (label), 10));
      gtk_container_add (GTK_CONTAINER (button), button_label);
281 282 283
    }
  else
    {
284
      g_critical ("Path chunk doesn't provide either icon or label");
285 286
    }

287 288
  style = gtk_widget_get_style_context (button);
  gtk_style_context_add_class (style, "flat");
289

290 291
  g_signal_connect_swapped (button, "button-release-event",
                            G_CALLBACK (on_path_chunk_button_release_event), path_chunk);
292

293
  if (add_separator && !separator_after_button)
294
    {
295 296 297
      separator = gtk_label_new (G_DIR_SEPARATOR_S);
      gtk_widget_set_sensitive (separator, FALSE);
      gtk_container_add (GTK_CONTAINER (path_chunk), separator);
298
    }
299 300
  gtk_container_add (GTK_CONTAINER (path_chunk), button);
  if (add_separator && separator_after_button)
301
    {
302 303 304
      separator = gtk_label_new (G_DIR_SEPARATOR_S);
      gtk_widget_set_sensitive (separator, FALSE);
      gtk_container_add (GTK_CONTAINER (path_chunk), separator);
305 306
    }

307 308 309
  path_chunk_data = g_slice_new (PathChunkData);
  if (label)
    path_chunk_data->label = g_strdup (label);
310
  else
311
    path_chunk_data->label = NULL;
312

313 314
  if (icon)
    path_chunk_data->icon = g_object_ref (icon);
315
  else
316
    path_chunk_data->icon = NULL;
317

318 319 320 321 322
  path_chunk_data->path = g_strdup (path);
  path_chunk_data->path_bar = self;
  path_chunk_data->button = button;
  g_object_set_data_full (G_OBJECT (path_chunk), "data",
                          path_chunk_data, (GDestroyNotify) free_path_chunk_data);
323

324
  gtk_widget_show_all (path_chunk);
325

326
  return path_chunk;
327 328
}

329 330
static gchar**
get_splitted_path (const gchar* path)
331
{
332 333
  gchar *path_no_first_slash;
  gchar **splitted_path;
334

335 336
  path_no_first_slash = g_utf8_substring (path, 1, strlen (path));
  splitted_path = g_strsplit (path_no_first_slash, G_DIR_SEPARATOR_S, -1);
337

338
  g_free (path_no_first_slash);
339

340
  return splitted_path;
341 342
}

343 344
static gboolean
is_absolute_root (const gchar *path)
345
{
346
  return g_strcmp0 (path, G_DIR_SEPARATOR_S) == 0;
347 348
}

349 350
static gboolean
validate_path (const gchar *path)
351
{
352 353
  GFile *file = NULL;
  gboolean valid = FALSE;
354

355 356
  if (!path || strlen (path) == 0)
    goto out;
357

358 359
  if (!g_utf8_validate (path, -1, NULL))
    goto out;
360

361 362
  /* Special case absolute root which is always valid */
  if (is_absolute_root (path))
363
    {
364 365
      valid = TRUE;
      goto out;
366 367
    }

368 369 370
  file = g_file_new_for_uri (path);
  if (!file)
    goto out;
371

372
  valid = TRUE;
373

374 375
out:
  g_clear_object (&file);
376

377
  return valid;
378 379
}

380 381 382
static gboolean
validate_root_path (const gchar *path,
                    const gchar *root_path)
383
{
384 385
  return validate_path (root_path) &&
         (g_str_has_prefix (path, root_path) || g_strcmp0 (path, root_path) == 0);
386 387 388
}

static void
389
update_path_bar (GtkPathBar  *self)
390
{
391 392 393 394 395 396 397
  GtkPathBarPrivate *priv = gtk_path_bar_get_instance_private (self);
  GtkWidget *path_chunk;
  GtkWidget *path_box;
  GtkWidget *overflow_button;
  GtkWidget *path_bar;
  GtkWidget *root_chunk;
  gchar *unprefixed_path;
398

399
  get_path_bar_widgets (GTK_PATH_BAR (self), &path_bar, &overflow_button, &path_box, FALSE);
400

401 402 403
  /* Make sure we dismiss all popovers */
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->overflow_button_1), FALSE);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->overflow_button_2), FALSE);
404

405
  gtk_container_foreach (GTK_CONTAINER (path_box), (GtkCallback) gtk_widget_destroy, NULL);
406

407
  if (priv->root_path)
408
    {
409 410
      root_chunk = create_path_chunk (self, priv->root_path, priv->root_label,
                                      priv->root_icon, TRUE, TRUE);
411

412
      gtk_container_add (GTK_CONTAINER (path_box), root_chunk);
413 414
    }

415 416
  if (g_strcmp0 (priv->root_path, priv->path) == 0)
    goto done;
417

418 419 420 421 422
  /* We always expect a path in the format /path/path in UNIX or \path\path in Windows.
   * However, the OS separator alone is a valid path, so we need to handle it
   * ourselves if the client didn't set a root label or icon for it.
   */
  if (!is_absolute_root (priv->path) || priv->root_path)
423
    {
424 425 426 427 428 429
      GString *current_path = NULL;
      gchar **splitted_path;
      gboolean add_separator;
      gboolean separator_after_button;
      gint length;
      gint i;
430

431 432
      current_path = g_string_new ("");
      if (priv->root_path && !is_absolute_root (priv->root_path))
433
        {
434 435 436
          g_string_append (current_path, priv->root_path);
          unprefixed_path = g_utf8_substring (priv->path, strlen (priv->root_path),
                                              strlen (priv->path));
437
        }
438
      else
439
        {
440
          unprefixed_path = g_strdup (priv->path);
441
        }
442

443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461
      splitted_path = get_splitted_path (unprefixed_path);
      length = g_strv_length (splitted_path);
      for (i = 0; i < length; i++)
        {
          g_string_append (current_path, G_DIR_SEPARATOR_S);
          g_string_append (current_path, splitted_path[i]);

          /* We add a separator for all items except the last one, which will result
           * in a pathbar in the form of "Home/Documents/Example".
           * However, if only one item is present, add a separator at the end since
           * is visually more pleasant. The result will be in the form of "Home/" */
          add_separator = length == 1 || (i != length - 1 && priv->inverted) ||
                          (i != 0 && !priv->inverted);
          separator_after_button = length == 1 || priv->inverted;

          path_chunk = create_path_chunk (self, current_path->str, splitted_path[i],
                                          NULL, add_separator, separator_after_button);
          gtk_container_add (GTK_CONTAINER (path_box), path_chunk);
        }
462

463 464 465
      g_strfreev (splitted_path);
      g_string_free (current_path, TRUE);
      g_free (unprefixed_path);
466
    }
467
  else
468
    {
469 470 471
      path_chunk = create_path_chunk (self, G_DIR_SEPARATOR_S, G_DIR_SEPARATOR_S,
                                      NULL, FALSE, FALSE);
      gtk_container_add (GTK_CONTAINER (path_box), path_chunk);
472 473
    }

474 475
done:
  gtk_stack_set_visible_child (GTK_STACK (priv->path_bar_containers_stack), path_bar);
476 477 478
}

static void
479
update_selected_path (GtkPathBar  *self)
480
{
481 482 483 484 485 486 487 488 489
  GtkPathBarPrivate *priv = gtk_path_bar_get_instance_private (self);
  PathChunkData *data;
  GList *children;
  GList *overflow_children;
  GList *l;
  GtkWidget *path_box;
  GtkWidget *overflow_button;
  GtkPopover *overflow_popover;
  GtkWidget *overflow_container;
490

491 492 493 494 495 496
  get_path_bar_widgets (GTK_PATH_BAR (self), NULL, &overflow_button, &path_box, TRUE);
  overflow_popover = gtk_menu_button_get_popover (GTK_MENU_BUTTON (overflow_button));
  overflow_container = gtk_bin_get_child (GTK_BIN (overflow_popover));
  children = gtk_container_get_children (GTK_CONTAINER (path_box));
  overflow_children = gtk_container_get_children (GTK_CONTAINER (overflow_container));
  for (l = children; l != NULL; l = l->next)
497
    {
498 499 500 501 502 503 504 505 506
      data = g_object_get_data (G_OBJECT (l->data), "data");
      g_signal_handlers_block_by_func (data->button,
                                       G_CALLBACK (on_path_chunk_button_release_event),
                                       data);
      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (data->button),
                                    g_strcmp0 (data->path, priv->selected_path) == 0);
      g_signal_handlers_unblock_by_func (data->button,
                                         G_CALLBACK (on_path_chunk_button_release_event),
                                         data);
507 508
    }

509
  for (l = overflow_children; l != NULL; l = l->next)
510
    {
511 512 513 514 515 516 517 518 519
      data = g_object_get_data (G_OBJECT (l->data), "data");
      g_signal_handlers_block_by_func (data->button,
                                       G_CALLBACK (on_path_chunk_button_release_event),
                                       data);
      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (data->button),
                                    g_strcmp0 (data->path, priv->selected_path) == 0);
      g_signal_handlers_unblock_by_func (data->button,
                                         G_CALLBACK (on_path_chunk_button_release_event),
                                         data);
520
    }
521

522 523
  g_list_free (children);
  g_list_free (overflow_children);
524 525
}

526
static void
527
populate_overflow_popover (GtkPathBar *self)
528
{
529 530 531 532 533 534 535 536
  GList *overflow_children;
  GList *l;
  PathChunkData *data;
  GtkWidget *path_chunk;
  GtkWidget *path_box;
  GtkWidget *overflow_button;
  GtkPopover *overflow_popover;
  GtkWidget *overflow_container;
Federico Mena Quintero's avatar
Federico Mena Quintero committed
537

538 539 540
  get_path_bar_widgets (self, NULL, &overflow_button, &path_box, TRUE);
  overflow_popover = gtk_menu_button_get_popover (GTK_MENU_BUTTON (overflow_button));
  overflow_container = gtk_bin_get_child (GTK_BIN (overflow_popover));
Federico Mena Quintero's avatar
Federico Mena Quintero committed
541

542 543
  gtk_container_foreach (GTK_CONTAINER (overflow_container),
                         (GtkCallback) gtk_widget_destroy, NULL);
544

545 546
  overflow_children = gtk_hiding_box_get_overflow_children (GTK_HIDING_BOX (path_box));
  for (l = overflow_children; l != NULL; l = l->next)
547
    {
548 549 550
      data = g_object_get_data (l->data, "data");
      path_chunk = create_path_chunk (self, data->path, data->label, data->icon, FALSE, FALSE);
      gtk_container_add (GTK_CONTAINER (overflow_container), path_chunk);
551
    }
Federico Mena Quintero's avatar
Federico Mena Quintero committed
552

553
  update_selected_path (self);
554

555 556
  g_list_free (overflow_children);
}
557 558

static void
559
gtk_path_bar_finalize (GObject *object)
560
{
561 562
  GtkPathBar *self = (GtkPathBar *)object;
  GtkPathBarPrivate *priv = gtk_path_bar_get_instance_private (self);
563

564 565 566 567
  if (priv->path)
    g_free (priv->path);
  if (priv->selected_path)
    g_free (priv->selected_path);
568

569
  G_OBJECT_CLASS (gtk_path_bar_parent_class)->finalize (object);
570 571 572
}

static void
573 574 575 576
gtk_path_bar_get_property (GObject    *object,
                           guint       prop_id,
                           GValue     *value,
                           GParamSpec *pspec)
577
{
578 579
  GtkPathBar *self = GTK_PATH_BAR (object);
  GtkPathBarPrivate *priv = gtk_path_bar_get_instance_private (self);
580

581
  switch (prop_id)
582
    {
583 584
    case PROP_PATH:
      g_value_set_string (value, priv->path);
585
      break;
586 587
    case PROP_SELECTED_PATH:
      g_value_set_string (value, priv->selected_path);
588
      break;
589 590
    case PROP_INVERTED:
      g_value_set_boolean (value, priv->inverted);
591
      break;
592
    default:
593
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
594 595 596
    }
}

597
static void
598 599 600 601
gtk_path_bar_set_property (GObject      *object,
                           guint         prop_id,
                           const GValue *value,
                           GParamSpec   *pspec)
602
{
603
  GtkPathBar *self = GTK_PATH_BAR (object);
604

605
  switch (prop_id)
606
    {
607 608 609 610 611 612 613 614 615 616 617
    case PROP_PATH:
      gtk_path_bar_set_path (self, g_value_get_string (value));
      break;
    case PROP_SELECTED_PATH:
      gtk_path_bar_set_selected_path (self, g_value_get_string (value));
      break;
    case PROP_INVERTED:
      gtk_path_bar_set_inverted (self, g_value_get_boolean (value));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
618 619 620
    }
}

621 622
static GtkSizeRequestMode
get_request_mode (GtkWidget *self)
623
{
624
  return GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT;
625 626
}

627
static void
628
gtk_path_bar_class_init (GtkPathBarClass *klass)
629
{
630 631
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
632

633 634 635
  object_class->finalize = gtk_path_bar_finalize;
  object_class->get_property = gtk_path_bar_get_property;
  object_class->set_property = gtk_path_bar_set_property;
636

637
  widget_class->get_request_mode = get_request_mode;
638

639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665
  /**
   * GtkPathBar::populate-popup:
   * @path_bar: the object which received the signal.
   * @container: (type Gtk.Widget): a #GtkContainer
   * @path: (type const gchar*): string of the path where the user performed a right click.
   *
   * The path bar emits this signal when the user invokes a contextual
   * popup on one of its items. In the signal handler, the application may
   * add extra items to the menu as appropriate. For example, a file manager
   * may want to add a "Properties" command to the menu.
   *
   * The @container and all its contents are destroyed after the user
   * dismisses the popup. The popup is re-created (and thus, this signal is
   * emitted) every time the user activates the contextual menu.
   *
   * Since: 3.20
   */
  path_bar_signals [POPULATE_POPUP] =
          g_signal_new (I_("populate-popup"),
                        G_OBJECT_CLASS_TYPE (object_class),
                        G_SIGNAL_RUN_FIRST,
                        G_STRUCT_OFFSET (GtkPathBarClass, populate_popup),
                        NULL, NULL,
                        _gtk_marshal_VOID__OBJECT_STRING,
                        G_TYPE_NONE, 2,
                        GTK_TYPE_WIDGET,
                        G_TYPE_STRING);
666

667 668 669 670 671 672
  path_bar_properties[PROP_PATH] =
          g_param_spec_string ("path",
                               P_("Path"),
                               P_("The path set in the path bar. Should use UNIX path specs"),
                               NULL,
                               G_PARAM_READWRITE);
673

674 675 676 677 678 679
  path_bar_properties[PROP_SELECTED_PATH] =
          g_param_spec_string ("selected-path",
                               P_("Selected path"),
                               P_("The path selected. Should be a sufix of the path currently set"),
                               NULL,
                               G_PARAM_READWRITE);
680

681 682 683 684 685 686
  path_bar_properties[PROP_INVERTED] =
          g_param_spec_boolean ("inverted",
                                P_("Direction of hiding children inverted"),
                                P_("If false the container will start hiding widgets from the end when there is not enough space, and the oposite in case inverted is true."),
                                FALSE,
                                G_PARAM_READWRITE);
687

688
  g_object_class_install_properties (object_class, LAST_PROP, path_bar_properties);
689

690
  gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/ui/gtkpathbar.ui");
691

692 693 694 695 696 697 698
  gtk_widget_class_bind_template_child_private (widget_class, GtkPathBar, path_bar_containers_stack);
  gtk_widget_class_bind_template_child_private (widget_class, GtkPathBar, path_bar_container_1);
  gtk_widget_class_bind_template_child_private (widget_class, GtkPathBar, path_bar_container_2);
  gtk_widget_class_bind_template_child_private (widget_class, GtkPathBar, overflow_button_1);
  gtk_widget_class_bind_template_child_private (widget_class, GtkPathBar, overflow_button_2);
  gtk_widget_class_bind_template_child_private (widget_class, GtkPathBar, path_box_1);
  gtk_widget_class_bind_template_child_private (widget_class, GtkPathBar, path_box_2);
699

700
  gtk_widget_class_bind_template_callback (widget_class, populate_overflow_popover);
701

702
  gtk_widget_class_set_css_name (widget_class, "path-bar");
703 704
}

705 706
static void
gtk_path_bar_init (GtkPathBar *self)
707
{
708
  GtkPathBarPrivate *priv = gtk_path_bar_get_instance_private (self);
709

710 711
  g_type_ensure (GTK_TYPE_PATH_BAR_CONTAINER);
  gtk_widget_init_template (GTK_WIDGET (self));
712

713
  priv->inverted = FALSE;
714
}
715

716

717 718 719 720 721 722
void
gtk_path_bar_set_path_extended (GtkPathBar  *self,
                                const gchar *path,
                                const gchar *root_path,
                                const gchar *root_label,
                                GIcon       *root_icon)
723
{
724 725
  GtkPathBarPrivate *priv;
  gchar *old_path;
726

727 728 729
  g_return_if_fail (GTK_IS_PATH_BAR (self));
  g_return_if_fail (validate_path (path));
  g_return_if_fail (!root_path || validate_root_path (path, root_path));
730

731
  priv = gtk_path_bar_get_instance_private (GTK_PATH_BAR (self));
732

733 734 735 736
  if (priv->root_icon)
    {
      g_object_unref (priv->root_icon);
      priv->root_icon = NULL;
737
    }
738
  if (priv->root_path)
739
    {
740 741
      g_free (priv->root_path);
      priv->root_path = NULL;
742
    }
743
  if (priv->root_label)
744
    {
745 746
      g_free (priv->root_label);
      priv->root_label = NULL;
747
    }
748

749 750 751 752 753 754
  if (root_icon)
    priv->root_icon = g_object_ref (root_icon);
  if (root_path)
    priv->root_path = g_strdup (root_path);
  if (root_label)
    priv->root_label = g_strdup (root_label);
755

756 757
  old_path = priv->path;
  priv->path = g_strdup (path);
758

759
  update_path_bar (self);
760

761 762 763 764 765 766
  if (old_path)
    {
      if (g_strcmp0 (old_path, path) != 0)
        g_object_notify_by_pspec (G_OBJECT (self), path_bar_properties[PROP_PATH]);
      g_free (old_path);
    }
767

768 769
  gtk_path_bar_set_selected_path (self, priv->path);
}
770

771 772 773 774 775 776 777 778 779 780 781 782 783 784
/**
 * gtk_path_bar_get_selected_path:
 * @path_bar: a #GtkPathBar
 *
 * Get the path represented by the path bar
 *
 * Returns: (transfer none): a string that represents the current path.
 *
 * Since: 3.20
 */
const gchar*
gtk_path_bar_get_path (GtkPathBar *self)
{
  GtkPathBarPrivate *priv;
785

786
  g_return_val_if_fail (GTK_IS_PATH_BAR (self), NULL);
787

788
  priv = gtk_path_bar_get_instance_private (GTK_PATH_BAR (self));
789

790
  return priv->path;
791
}
792

793 794 795 796 797 798 799 800 801 802 803 804 805
/**
 * gtk_path_bar_set_path:
 * @path_bar: a #GtkPathBar
 * @path: a string representing a path in UNIX style.
 *
 * Set the path represented by the path bar.
 *
 * Note that if the path is not in the UNIX format behaviour is undefined,
 * however the path bar will try to represent it, and therefore the path set
 * by this function could be different than the actual one.
 *
 * Since: 3.20
 */
806
void
807 808
gtk_path_bar_set_path (GtkPathBar  *self,
                       const gchar *path)
809
{
810 811
  gtk_path_bar_set_path_extended (self, path, NULL, NULL, NULL);
}
812

813 814 815 816 817 818 819 820 821 822 823 824 825 826
/**
 * gtk_path_bar_get_selected_path:
 * @path_bar: a #GtkPathBar
 *
 * Get the selected path
 *
 * Returns: (transfer none): a string that represents the selected path.
 *
 * Since: 3.20
 */
const gchar*
gtk_path_bar_get_selected_path (GtkPathBar *self)
{
  GtkPathBarPrivate *priv;
827

828
  g_return_val_if_fail (GTK_IS_PATH_BAR (self), NULL);
829

830
  priv = gtk_path_bar_get_instance_private (GTK_PATH_BAR (self));
831

832
  return priv->selected_path;
833
}
834

835 836 837 838 839 840 841 842 843 844 845
/**
 * gtk_path_bar_set_selected_path:
 * @path_bar: a #GtkPathBar
 * @path: a string representing a path in UNIX style.
 *
 * Set the path selected by the path bar.
 *
 * The path must be a prefix of the current #path.
 *
 * Since: 3.20
 */
846
void
847 848
gtk_path_bar_set_selected_path (GtkPathBar  *self,
                                const gchar *path)
849
{
850
  GtkPathBarPrivate *priv ;
851

852
  g_return_if_fail (GTK_IS_PATH_BAR (self));
853

854
  priv = gtk_path_bar_get_instance_private (GTK_PATH_BAR (self));
855

856 857
  g_return_if_fail (g_str_has_prefix (priv->path, path) ||
                    g_strcmp0 (priv->path, path) == 0);
858

859
  if (g_strcmp0 (priv->selected_path, path) != 0)
860
    {
861 862 863 864 865 866
      if (priv->selected_path)
        g_free (priv->selected_path);

      priv->selected_path = g_strdup (path);
      update_selected_path (self);
      g_object_notify_by_pspec (G_OBJECT (self), path_bar_properties[PROP_SELECTED_PATH]);
867 868 869
    }
  else
    {
870 871
      /* Update the style in any case */
      update_selected_path (self);
872
    }
873
}
874 875

/**
876
 * gtk_path_bar_get_inverted:
877
 * @path_bar: a #GtkPathBar
878 879 880 881 882 883 884
 *
 * Returns a wheter the path bar hides children in the inverted direction.
 *
 * Since: 3.20
 */
gboolean
gtk_path_bar_get_inverted (GtkPathBar *self)
885
{
886
  GtkPathBarPrivate *priv ;
887

888 889 890 891 892
  g_return_val_if_fail (GTK_IS_PATH_BAR (self), 0);

  priv = gtk_path_bar_get_instance_private (GTK_PATH_BAR (self));

  return priv->inverted;
893 894 895
}

/**
896
 * gtk_path_bar_set_inverted:
897
 * @path_bar: a #GtkPathBar
898 899 900 901 902 903 904
 * @inverted: Wheter the path bar will start hiding widgets in the oposite direction.
 *
 * If %FALSE, the path bar will start hiding widgets from the end of it, and from
 * the start in the oposite case.
 *
 * Since: 3.20
 */
905
void
906 907
gtk_path_bar_set_inverted (GtkPathBar *self,
                           gboolean    inverted)
908
{
909 910 911
  GtkPathBarPrivate *priv ;

  g_return_if_fail (GTK_IS_PATH_BAR (self));
912

913 914 915
  priv = gtk_path_bar_get_instance_private (GTK_PATH_BAR (self));

  if (priv->inverted != inverted)
916
    {
917 918 919 920 921
      priv->inverted = inverted != FALSE;
      gtk_hiding_box_set_inverted (GTK_HIDING_BOX (priv->path_box_1), inverted);
      gtk_hiding_box_set_inverted (GTK_HIDING_BOX (priv->path_box_2), inverted);

      g_object_notify (G_OBJECT (self), "inverted");
922 923
    }
}
924 925 926 927 928 929

GtkWidget *
gtk_path_bar_new (void)
{
  return g_object_new (GTK_TYPE_PATH_BAR, NULL);
}