1 /* -*- Mode: C; c-file-style: "gnu"; tab-width: 8 -*- */
2 /* gtkpathbar.c
3 * Copyright (C) 2004 Red Hat, Inc., Jonathan Blandford <jrb@gnome.org>
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public
16 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "config.h"
20
21 #include "gtkpathbar.h"
22
23 #include <string.h>
24
25 #include "gtkbox.h"
26 #include "gtkcssnodeprivate.h"
27 #include "gtkdnd.h"
28 #include "gtkdragsource.h"
29 #include "gtkicontheme.h"
30 #include "gtkimage.h"
31 #include "gtkintl.h"
32 #include "gtklabel.h"
33 #include "gtkmain.h"
34 #include "gtkmarshalers.h"
35 #include "gtksettings.h"
36 #include "gtktogglebutton.h"
37 #include "gtkwidgetpath.h"
38 #include "gtkwidgetprivate.h"
39
40 struct _GtkPathBarPrivate
41 {
42 GtkFileSystem *file_system;
43 GFile *root_file;
44 GFile *home_file;
45 GFile *desktop_file;
46
47 /* List of running GCancellable. When we cancel one, we remove it from this list.
48 * The pathbar cancels all outstanding cancellables when it is disposed.
49 *
50 * In code that queues async I/O operations:
51 *
52 * - Obtain a cancellable from the async I/O APIs, and call add_cancellable().
53 *
54 * To cancel a cancellable:
55 *
56 * - Call cancel_cancellable().
57 *
58 * In async I/O callbacks:
59 *
60 * - Check right away if g_cancellable_is_cancelled(): if true, just
61 * g_object_unref() the cancellable and return early (also free your
62 * closure data if you have one).
63 *
64 * - If it was not cancelled, call cancellable_async_done(). This will
65 * unref the cancellable and unqueue it from the pathbar's outstanding
66 * cancellables. Do your normal work to process the async result and free
67 * your closure data if you have one.
68 */
69 GList *cancellables;
70
71 GCancellable *get_info_cancellable;
72
73 GIcon *root_icon;
74 GIcon *home_icon;
75 GIcon *desktop_icon;
76
77 GdkWindow *event_window;
78
79 GList *button_list;
80 GList *first_scrolled_button;
81 GList *fake_root;
82 GtkWidget *up_slider_button;
83 GtkWidget *down_slider_button;
84 guint settings_signal_id;
85 gint16 slider_width;
86 gint16 button_offset;
87 guint timer;
88 guint slider_visible : 1;
89 guint need_timer : 1;
90 guint ignore_click : 1;
91 guint scrolling_up : 1;
92 guint scrolling_down : 1;
93 };
94
95 enum {
96 PATH_CLICKED,
97 LAST_SIGNAL
98 };
99
100 typedef enum {
101 NORMAL_BUTTON,
102 ROOT_BUTTON,
103 HOME_BUTTON,
104 DESKTOP_BUTTON
105 } ButtonType;
106
107 #define BUTTON_DATA(x) ((ButtonData *)(x))
108
109 #define SCROLL_DELAY_FACTOR 5
110 #define TIMEOUT_INITIAL 500
111 #define TIMEOUT_REPEAT 50
112
113 static guint path_bar_signals [LAST_SIGNAL] = { 0 };
114
115 /* Icon size for if we can't get it from the theme */
116 #define FALLBACK_ICON_SIZE 16
117
118 typedef struct _ButtonData ButtonData;
119
120 struct _ButtonData
121 {
122 GtkWidget *button;
123 ButtonType type;
124 char *dir_name;
125 GFile *file;
126 GtkWidget *image;
127 GtkWidget *label;
128 GCancellable *cancellable;
129 guint ignore_changes : 1;
130 guint file_is_hidden : 1;
131 };
132 /* This macro is used to check if a button can be used as a fake root.
133 * All buttons in front of a fake root are automatically hidden when in a
134 * directory below a fake root and replaced with the "<" arrow button.
135 */
136 #define BUTTON_IS_FAKE_ROOT(button) ((button)->type == HOME_BUTTON)
137
138 G_DEFINE_TYPE_WITH_PRIVATE (GtkPathBar, gtk_path_bar, GTK_TYPE_CONTAINER)
139
140 static void gtk_path_bar_finalize (GObject *object);
141 static void gtk_path_bar_dispose (GObject *object);
142 static void gtk_path_bar_realize (GtkWidget *widget);
143 static void gtk_path_bar_unrealize (GtkWidget *widget);
144 static void gtk_path_bar_get_preferred_width (GtkWidget *widget,
145 gint *minimum,
146 gint *natural);
147 static void gtk_path_bar_get_preferred_height (GtkWidget *widget,
148 gint *minimum,
149 gint *natural);
150 static void gtk_path_bar_map (GtkWidget *widget);
151 static void gtk_path_bar_unmap (GtkWidget *widget);
152 static void gtk_path_bar_size_allocate (GtkWidget *widget,
153 GtkAllocation *allocation);
154 static void gtk_path_bar_add (GtkContainer *container,
155 GtkWidget *widget);
156 static void gtk_path_bar_remove (GtkContainer *container,
157 GtkWidget *widget);
158 static void gtk_path_bar_forall (GtkContainer *container,
159 gboolean include_internals,
160 GtkCallback callback,
161 gpointer callback_data);
162 static gboolean gtk_path_bar_scroll (GtkWidget *widget,
163 GdkEventScroll *event);
164 static void gtk_path_bar_scroll_up (GtkPathBar *path_bar);
165 static void gtk_path_bar_scroll_down (GtkPathBar *path_bar);
166 static void gtk_path_bar_stop_scrolling (GtkPathBar *path_bar);
167 static gboolean gtk_path_bar_slider_up_defocus (GtkWidget *widget,
168 GdkEventButton *event,
169 GtkPathBar *path_bar);
170 static gboolean gtk_path_bar_slider_down_defocus (GtkWidget *widget,
171 GdkEventButton *event,
172 GtkPathBar *path_bar);
173 static gboolean gtk_path_bar_slider_button_press (GtkWidget *widget,
174 GdkEventButton *event,
175 GtkPathBar *path_bar);
176 static gboolean gtk_path_bar_slider_button_release(GtkWidget *widget,
177 GdkEventButton *event,
178 GtkPathBar *path_bar);
179 static void gtk_path_bar_grab_notify (GtkWidget *widget,
180 gboolean was_grabbed);
181 static void gtk_path_bar_state_changed (GtkWidget *widget,
182 GtkStateType previous_state);
183 static void gtk_path_bar_style_updated (GtkWidget *widget);
184 static void gtk_path_bar_screen_changed (GtkWidget *widget,
185 GdkScreen *previous_screen);
186 static void gtk_path_bar_check_icon_theme (GtkPathBar *path_bar);
187 static void gtk_path_bar_update_button_appearance (GtkPathBar *path_bar,
188 ButtonData *button_data,
189 gboolean current_dir);
190
191 static void
add_cancellable(GtkPathBar * path_bar,GCancellable * cancellable)192 add_cancellable (GtkPathBar *path_bar,
193 GCancellable *cancellable)
194 {
195 g_assert (g_list_find (path_bar->priv->cancellables, cancellable) == NULL);
196 path_bar->priv->cancellables = g_list_prepend (path_bar->priv->cancellables, cancellable);
197 }
198
199 static void
drop_node_for_cancellable(GtkPathBar * path_bar,GCancellable * cancellable)200 drop_node_for_cancellable (GtkPathBar *path_bar,
201 GCancellable *cancellable)
202 {
203 GList *node;
204
205 node = g_list_find (path_bar->priv->cancellables, cancellable);
206 g_assert (node != NULL);
207 node->data = NULL;
208 path_bar->priv->cancellables = g_list_delete_link (path_bar->priv->cancellables, node);
209 }
210
211 static void
cancel_cancellable(GtkPathBar * path_bar,GCancellable * cancellable)212 cancel_cancellable (GtkPathBar *path_bar,
213 GCancellable *cancellable)
214 {
215 drop_node_for_cancellable (path_bar, cancellable);
216 g_cancellable_cancel (cancellable);
217 }
218
219 static void
cancellable_async_done(GtkPathBar * path_bar,GCancellable * cancellable)220 cancellable_async_done (GtkPathBar *path_bar,
221 GCancellable *cancellable)
222 {
223 drop_node_for_cancellable (path_bar, cancellable);
224 g_object_unref (cancellable);
225 }
226
227 static void
cancel_all_cancellables(GtkPathBar * path_bar)228 cancel_all_cancellables (GtkPathBar *path_bar)
229 {
230 while (path_bar->priv->cancellables)
231 {
232 GCancellable *cancellable = path_bar->priv->cancellables->data;
233 cancel_cancellable (path_bar, cancellable);
234 }
235 }
236
237 static void
on_slider_unmap(GtkWidget * widget,GtkPathBar * path_bar)238 on_slider_unmap (GtkWidget *widget,
239 GtkPathBar *path_bar)
240 {
241 if (path_bar->priv->timer &&
242 ((widget == path_bar->priv->up_slider_button && path_bar->priv->scrolling_up) ||
243 (widget == path_bar->priv->down_slider_button && path_bar->priv->scrolling_down)))
244 gtk_path_bar_stop_scrolling (path_bar);
245 }
246
247 static void
gtk_path_bar_init(GtkPathBar * path_bar)248 gtk_path_bar_init (GtkPathBar *path_bar)
249 {
250 GtkStyleContext *context;
251
252 path_bar->priv = gtk_path_bar_get_instance_private (path_bar);
253
254 gtk_widget_init_template (GTK_WIDGET (path_bar));
255
256 /* Add the children manually because GtkPathBar derives from an abstract class,
257 * Glade cannot edit a <template> in gtkpathbar.ui if it's only a GtkContainer.
258 */
259 gtk_container_add (GTK_CONTAINER (path_bar), path_bar->priv->up_slider_button);
260 gtk_container_add (GTK_CONTAINER (path_bar), path_bar->priv->down_slider_button);
261
262 /* GtkBuilder wont let us connect 'swapped' without specifying the signal's
263 * user data in the .ui file
264 */
265 g_signal_connect_swapped (path_bar->priv->up_slider_button, "clicked",
266 G_CALLBACK (gtk_path_bar_scroll_up), path_bar);
267 g_signal_connect_swapped (path_bar->priv->down_slider_button, "clicked",
268 G_CALLBACK (gtk_path_bar_scroll_down), path_bar);
269
270 gtk_widget_set_has_window (GTK_WIDGET (path_bar), FALSE);
271
272 context = gtk_widget_get_style_context (GTK_WIDGET (path_bar));
273 gtk_style_context_add_class (context, "path-bar");
274 gtk_style_context_add_class (context, GTK_STYLE_CLASS_LINKED);
275
276 path_bar->priv->get_info_cancellable = NULL;
277 path_bar->priv->cancellables = NULL;
278 }
279
280 static void
gtk_path_bar_class_init(GtkPathBarClass * path_bar_class)281 gtk_path_bar_class_init (GtkPathBarClass *path_bar_class)
282 {
283 GObjectClass *gobject_class;
284 GtkWidgetClass *widget_class;
285 GtkContainerClass *container_class;
286
287 gobject_class = (GObjectClass *) path_bar_class;
288 widget_class = (GtkWidgetClass *) path_bar_class;
289 container_class = (GtkContainerClass *) path_bar_class;
290
291 gobject_class->finalize = gtk_path_bar_finalize;
292 gobject_class->dispose = gtk_path_bar_dispose;
293
294 widget_class->get_preferred_width = gtk_path_bar_get_preferred_width;
295 widget_class->get_preferred_height = gtk_path_bar_get_preferred_height;
296 widget_class->realize = gtk_path_bar_realize;
297 widget_class->unrealize = gtk_path_bar_unrealize;
298 widget_class->map = gtk_path_bar_map;
299 widget_class->unmap = gtk_path_bar_unmap;
300 widget_class->size_allocate = gtk_path_bar_size_allocate;
301 widget_class->style_updated = gtk_path_bar_style_updated;
302 widget_class->screen_changed = gtk_path_bar_screen_changed;
303 widget_class->grab_notify = gtk_path_bar_grab_notify;
304 widget_class->state_changed = gtk_path_bar_state_changed;
305 widget_class->scroll_event = gtk_path_bar_scroll;
306
307 container_class->add = gtk_path_bar_add;
308 container_class->forall = gtk_path_bar_forall;
309 container_class->remove = gtk_path_bar_remove;
310 gtk_container_class_handle_border_width (container_class);
311 /* FIXME: */
312 /* container_class->child_type = gtk_path_bar_child_type;*/
313
314 path_bar_signals [PATH_CLICKED] =
315 g_signal_new (I_("path-clicked"),
316 G_OBJECT_CLASS_TYPE (gobject_class),
317 G_SIGNAL_RUN_FIRST,
318 G_STRUCT_OFFSET (GtkPathBarClass, path_clicked),
319 NULL, NULL,
320 _gtk_marshal_VOID__POINTER_POINTER_BOOLEAN,
321 G_TYPE_NONE, 3,
322 G_TYPE_POINTER,
323 G_TYPE_POINTER,
324 G_TYPE_BOOLEAN);
325
326 /* Bind class to template
327 */
328 gtk_widget_class_set_template_from_resource (widget_class,
329 "/org/gtk/libgtk/ui/gtkpathbar.ui");
330
331 gtk_widget_class_bind_template_child_private (widget_class, GtkPathBar, up_slider_button);
332 gtk_widget_class_bind_template_child_private (widget_class, GtkPathBar, down_slider_button);
333
334 gtk_widget_class_bind_template_callback (widget_class, gtk_path_bar_slider_button_press);
335 gtk_widget_class_bind_template_callback (widget_class, gtk_path_bar_slider_button_release);
336 gtk_widget_class_bind_template_callback (widget_class, gtk_path_bar_slider_up_defocus);
337 gtk_widget_class_bind_template_callback (widget_class, gtk_path_bar_slider_down_defocus);
338 gtk_widget_class_bind_template_callback (widget_class, gtk_path_bar_scroll_up);
339 gtk_widget_class_bind_template_callback (widget_class, gtk_path_bar_scroll_down);
340 gtk_widget_class_bind_template_callback (widget_class, on_slider_unmap);
341 }
342
343
344 static void
gtk_path_bar_finalize(GObject * object)345 gtk_path_bar_finalize (GObject *object)
346 {
347 GtkPathBar *path_bar;
348
349 path_bar = GTK_PATH_BAR (object);
350
351 cancel_all_cancellables (path_bar);
352 gtk_path_bar_stop_scrolling (path_bar);
353
354 g_list_free (path_bar->priv->button_list);
355 g_clear_object (&path_bar->priv->root_file);
356 g_clear_object (&path_bar->priv->home_file);
357 g_clear_object (&path_bar->priv->desktop_file);
358
359 g_clear_object (&path_bar->priv->root_icon);
360 g_clear_object (&path_bar->priv->home_icon);
361 g_clear_object (&path_bar->priv->desktop_icon);
362
363 g_clear_object (&path_bar->priv->file_system);
364
365 G_OBJECT_CLASS (gtk_path_bar_parent_class)->finalize (object);
366 }
367
368 /* Removes the settings signal handler. It's safe to call multiple times */
369 static void
remove_settings_signal(GtkPathBar * path_bar,GdkScreen * screen)370 remove_settings_signal (GtkPathBar *path_bar,
371 GdkScreen *screen)
372 {
373 if (path_bar->priv->settings_signal_id)
374 {
375 GtkSettings *settings;
376
377 settings = gtk_settings_get_for_screen (screen);
378 g_signal_handler_disconnect (settings,
379 path_bar->priv->settings_signal_id);
380 path_bar->priv->settings_signal_id = 0;
381 }
382 }
383
384 static void
gtk_path_bar_dispose(GObject * object)385 gtk_path_bar_dispose (GObject *object)
386 {
387 GtkPathBar *path_bar = GTK_PATH_BAR (object);
388
389 remove_settings_signal (path_bar, gtk_widget_get_screen (GTK_WIDGET (object)));
390
391 path_bar->priv->get_info_cancellable = NULL;
392 cancel_all_cancellables (path_bar);
393
394 G_OBJECT_CLASS (gtk_path_bar_parent_class)->dispose (object);
395 }
396
397 /* Size requisition:
398 *
399 * Ideally, our size is determined by another widget, and we are just filling
400 * available space.
401 */
402 static void
gtk_path_bar_get_preferred_width(GtkWidget * widget,gint * minimum,gint * natural)403 gtk_path_bar_get_preferred_width (GtkWidget *widget,
404 gint *minimum,
405 gint *natural)
406 {
407 ButtonData *button_data;
408 GtkPathBar *path_bar;
409 GList *list;
410 gint child_height;
411 gint height;
412 gint child_min, child_nat;
413
414 path_bar = GTK_PATH_BAR (widget);
415
416 *minimum = *natural = 0;
417 height = 0;
418
419 for (list = path_bar->priv->button_list; list; list = list->next)
420 {
421 button_data = BUTTON_DATA (list->data);
422 gtk_widget_get_preferred_width (button_data->button, &child_min, &child_nat);
423 gtk_widget_get_preferred_height (button_data->button, &child_height, NULL);
424 height = MAX (height, child_height);
425
426 if (button_data->type == NORMAL_BUTTON)
427 {
428 /* Use 2*Height as button width because of ellipsized label. */
429 child_min = MAX (child_min, child_height * 2);
430 child_nat = MAX (child_min, child_height * 2);
431 }
432
433 *minimum = MAX (*minimum, child_min);
434 *natural = *natural + child_nat;
435 }
436
437 /* Add space for slider, if we have more than one path */
438 /* Theoretically, the slider could be bigger than the other button. But we're
439 * not going to worry about that now.
440 */
441 path_bar->priv->slider_width = 0;
442
443 gtk_widget_get_preferred_width (path_bar->priv->up_slider_button, &child_min, &child_nat);
444 if (path_bar->priv->button_list && path_bar->priv->button_list->next != NULL)
445 {
446 *minimum += child_min;
447 *natural += child_nat;
448 }
449 path_bar->priv->slider_width = MAX (path_bar->priv->slider_width, child_min);
450
451 gtk_widget_get_preferred_width (path_bar->priv->down_slider_button, &child_min, &child_nat);
452 if (path_bar->priv->button_list && path_bar->priv->button_list->next != NULL)
453 {
454 *minimum += child_min;
455 *natural += child_nat;
456 }
457 path_bar->priv->slider_width = MAX (path_bar->priv->slider_width, child_min);
458 }
459
460 static void
gtk_path_bar_get_preferred_height(GtkWidget * widget,gint * minimum,gint * natural)461 gtk_path_bar_get_preferred_height (GtkWidget *widget,
462 gint *minimum,
463 gint *natural)
464 {
465 ButtonData *button_data;
466 GtkPathBar *path_bar;
467 GList *list;
468 gint child_min, child_nat;
469
470 path_bar = GTK_PATH_BAR (widget);
471
472 *minimum = *natural = 0;
473
474 for (list = path_bar->priv->button_list; list; list = list->next)
475 {
476 button_data = BUTTON_DATA (list->data);
477 gtk_widget_get_preferred_height (button_data->button, &child_min, &child_nat);
478
479 *minimum = MAX (*minimum, child_min);
480 *natural = MAX (*natural, child_nat);
481 }
482
483 gtk_widget_get_preferred_height (path_bar->priv->up_slider_button, &child_min, &child_nat);
484 *minimum = MAX (*minimum, child_min);
485 *natural = MAX (*natural, child_nat);
486
487 gtk_widget_get_preferred_height (path_bar->priv->down_slider_button, &child_min, &child_nat);
488 *minimum = MAX (*minimum, child_min);
489 *natural = MAX (*natural, child_nat);
490 }
491
492 static void
gtk_path_bar_update_slider_buttons(GtkPathBar * path_bar)493 gtk_path_bar_update_slider_buttons (GtkPathBar *path_bar)
494 {
495 if (path_bar->priv->button_list)
496 {
497 GtkWidget *button;
498
499 button = BUTTON_DATA (path_bar->priv->button_list->data)->button;
500 if (gtk_widget_get_child_visible (button))
501 {
502 gtk_path_bar_stop_scrolling (path_bar);
503 gtk_widget_set_sensitive (path_bar->priv->down_slider_button, FALSE);
504 }
505 else
506 gtk_widget_set_sensitive (path_bar->priv->down_slider_button, TRUE);
507
508 button = BUTTON_DATA (g_list_last (path_bar->priv->button_list)->data)->button;
509 if (gtk_widget_get_child_visible (button))
510 {
511 gtk_path_bar_stop_scrolling (path_bar);
512 gtk_widget_set_sensitive (path_bar->priv->up_slider_button, FALSE);
513 }
514 else
515 gtk_widget_set_sensitive (path_bar->priv->up_slider_button, TRUE);
516 }
517 }
518
519 static void
gtk_path_bar_map(GtkWidget * widget)520 gtk_path_bar_map (GtkWidget *widget)
521 {
522 gdk_window_show (GTK_PATH_BAR (widget)->priv->event_window);
523
524 GTK_WIDGET_CLASS (gtk_path_bar_parent_class)->map (widget);
525 }
526
527 static void
gtk_path_bar_unmap(GtkWidget * widget)528 gtk_path_bar_unmap (GtkWidget *widget)
529 {
530 gtk_path_bar_stop_scrolling (GTK_PATH_BAR (widget));
531 gdk_window_hide (GTK_PATH_BAR (widget)->priv->event_window);
532
533 GTK_WIDGET_CLASS (gtk_path_bar_parent_class)->unmap (widget);
534 }
535
536 static void
gtk_path_bar_realize(GtkWidget * widget)537 gtk_path_bar_realize (GtkWidget *widget)
538 {
539 GtkPathBar *path_bar;
540 GtkAllocation allocation;
541 GdkWindow *window;
542 GdkWindowAttr attributes;
543 gint attributes_mask;
544
545 gtk_widget_set_realized (widget, TRUE);
546
547 path_bar = GTK_PATH_BAR (widget);
548 window = gtk_widget_get_parent_window (widget);
549 gtk_widget_set_window (widget, window);
550 g_object_ref (window);
551
552 gtk_widget_get_allocation (widget, &allocation);
553
554 attributes.window_type = GDK_WINDOW_CHILD;
555 attributes.x = allocation.x;
556 attributes.y = allocation.y;
557 attributes.width = allocation.width;
558 attributes.height = allocation.height;
559 attributes.wclass = GDK_INPUT_ONLY;
560 attributes.event_mask = gtk_widget_get_events (widget);
561 attributes.event_mask |= GDK_SCROLL_MASK;
562 attributes_mask = GDK_WA_X | GDK_WA_Y;
563
564 path_bar->priv->event_window = gdk_window_new (gtk_widget_get_parent_window (widget),
565 &attributes, attributes_mask);
566 gtk_widget_register_window (widget, path_bar->priv->event_window);
567 }
568
569 static void
gtk_path_bar_unrealize(GtkWidget * widget)570 gtk_path_bar_unrealize (GtkWidget *widget)
571 {
572 GtkPathBar *path_bar;
573
574 path_bar = GTK_PATH_BAR (widget);
575
576 gtk_widget_unregister_window (widget, path_bar->priv->event_window);
577 gdk_window_destroy (path_bar->priv->event_window);
578 path_bar->priv->event_window = NULL;
579
580 GTK_WIDGET_CLASS (gtk_path_bar_parent_class)->unrealize (widget);
581 }
582
583 /* This is a tad complicated
584 */
585 static void
gtk_path_bar_size_allocate(GtkWidget * widget,GtkAllocation * allocation)586 gtk_path_bar_size_allocate (GtkWidget *widget,
587 GtkAllocation *allocation)
588 {
589 GtkWidget *child;
590 GtkPathBar *path_bar = GTK_PATH_BAR (widget);
591 GtkTextDirection direction;
592 GtkAllocation child_allocation;
593 GList *list, *first_button;
594 gint width;
595 gint allocation_width;
596 gboolean need_sliders = TRUE;
597 gint up_slider_offset = 0;
598 gint down_slider_offset = 0;
599 GtkRequisition child_requisition;
600
601 gtk_widget_set_allocation (widget, allocation);
602
603 if (gtk_widget_get_realized (widget))
604 gdk_window_move_resize (path_bar->priv->event_window,
605 allocation->x, allocation->y,
606 allocation->width, allocation->height);
607
608 /* No path is set; we don't have to allocate anything. */
609 if (path_bar->priv->button_list == NULL)
610 {
611 _gtk_widget_set_simple_clip (widget, NULL);
612
613 return;
614 }
615
616 direction = gtk_widget_get_direction (widget);
617 allocation_width = allocation->width;
618
619 /* First, we check to see if we need the scrollbars. */
620 if (path_bar->priv->fake_root)
621 width = path_bar->priv->slider_width;
622 else
623 width = 0;
624
625 for (list = path_bar->priv->button_list; list; list = list->next)
626 {
627 child = BUTTON_DATA (list->data)->button;
628
629 gtk_widget_get_preferred_size (child, &child_requisition, NULL);
630
631 width += child_requisition.width;
632 if (list == path_bar->priv->fake_root)
633 break;
634 }
635
636 if (width <= allocation_width)
637 {
638 if (path_bar->priv->fake_root)
639 first_button = path_bar->priv->fake_root;
640 else
641 first_button = g_list_last (path_bar->priv->button_list);
642 }
643 else
644 {
645 gboolean reached_end = FALSE;
646 gint slider_space = 2 * path_bar->priv->slider_width;
647
648 if (path_bar->priv->first_scrolled_button)
649 first_button = path_bar->priv->first_scrolled_button;
650 else
651 first_button = path_bar->priv->button_list;
652 need_sliders = TRUE;
653
654 /* To see how much space we have, and how many buttons we can display.
655 * We start at the first button, count forward until hit the new
656 * button, then count backwards.
657 */
658 /* Count down the path chain towards the end. */
659 gtk_widget_get_preferred_size (BUTTON_DATA (first_button->data)->button,
660 &child_requisition, NULL);
661
662 width = child_requisition.width;
663 list = first_button->prev;
664 while (list && !reached_end)
665 {
666 child = BUTTON_DATA (list->data)->button;
667
668 gtk_widget_get_preferred_size (child, &child_requisition, NULL);
669
670 if (width + child_requisition.width + slider_space > allocation_width)
671 reached_end = TRUE;
672 else if (list == path_bar->priv->fake_root)
673 break;
674 else
675 width += child_requisition.width;
676
677 list = list->prev;
678 }
679
680 /* Finally, we walk up, seeing how many of the previous buttons we can
681 * add */
682 while (first_button->next && !reached_end)
683 {
684 child = BUTTON_DATA (first_button->next->data)->button;
685
686 gtk_widget_get_preferred_size (child, &child_requisition, NULL);
687
688 if (width + child_requisition.width + slider_space > allocation_width)
689 {
690 reached_end = TRUE;
691 }
692 else
693 {
694 width += child_requisition.width;
695 if (first_button == path_bar->priv->fake_root)
696 break;
697 first_button = first_button->next;
698 }
699 }
700 }
701
702 /* Now, we allocate space to the buttons */
703 child_allocation.y = allocation->y;
704 child_allocation.height = allocation->height;
705
706 if (direction == GTK_TEXT_DIR_RTL)
707 {
708 child_allocation.x = allocation->x + allocation->width;
709 if (need_sliders || path_bar->priv->fake_root)
710 {
711 child_allocation.x -= path_bar->priv->slider_width;
712 up_slider_offset = allocation->width - path_bar->priv->slider_width;
713 }
714 }
715 else
716 {
717 child_allocation.x = allocation->x;
718 if (need_sliders || path_bar->priv->fake_root)
719 {
720 up_slider_offset = 0;
721 child_allocation.x += path_bar->priv->slider_width;
722 }
723 }
724
725 for (list = first_button; list; list = list->prev)
726 {
727 GtkAllocation widget_allocation;
728 ButtonData *button_data;
729
730 button_data = BUTTON_DATA (list->data);
731 child = button_data->button;
732
733 gtk_widget_get_preferred_size (child, &child_requisition, NULL);
734
735 child_allocation.width = MIN (child_requisition.width,
736 allocation_width - 2 * path_bar->priv->slider_width);
737
738 if (direction == GTK_TEXT_DIR_RTL)
739 child_allocation.x -= child_allocation.width;
740
741 /* Check to see if we've don't have any more space to allocate buttons */
742 if (need_sliders && direction == GTK_TEXT_DIR_RTL)
743 {
744 gtk_widget_get_allocation (widget, &widget_allocation);
745 if (child_allocation.x - path_bar->priv->slider_width < widget_allocation.x)
746 break;
747 }
748 else if (need_sliders && direction == GTK_TEXT_DIR_LTR)
749 {
750 gtk_widget_get_allocation (widget, &widget_allocation);
751 if (child_allocation.x + child_allocation.width + path_bar->priv->slider_width >
752 widget_allocation.x + allocation_width)
753 break;
754 }
755
756 if (child_allocation.width < child_requisition.width)
757 {
758 if (!gtk_widget_get_has_tooltip (child))
759 gtk_widget_set_tooltip_text (child, button_data->dir_name);
760 }
761 else if (gtk_widget_get_has_tooltip (child))
762 gtk_widget_set_tooltip_text (child, NULL);
763
764 gtk_widget_set_child_visible (child, TRUE);
765 gtk_widget_size_allocate (child, &child_allocation);
766
767 if (direction == GTK_TEXT_DIR_RTL)
768 {
769 down_slider_offset = child_allocation.x - allocation->x - path_bar->priv->slider_width;
770 }
771 else
772 {
773 down_slider_offset += child_allocation.width;
774 child_allocation.x += child_allocation.width;
775 }
776 }
777 /* Now we go hide all the widgets that don't fit */
778 while (list)
779 {
780 child = BUTTON_DATA (list->data)->button;
781 gtk_widget_set_child_visible (child, FALSE);
782 list = list->prev;
783 }
784 for (list = first_button->next; list; list = list->next)
785 {
786 child = BUTTON_DATA (list->data)->button;
787 gtk_widget_set_child_visible (child, FALSE);
788 }
789
790 if (need_sliders || path_bar->priv->fake_root)
791 {
792 child_allocation.width = path_bar->priv->slider_width;
793 child_allocation.x = up_slider_offset + allocation->x;
794 gtk_widget_size_allocate (path_bar->priv->up_slider_button, &child_allocation);
795
796 gtk_widget_set_child_visible (path_bar->priv->up_slider_button, TRUE);
797 gtk_widget_show_all (path_bar->priv->up_slider_button);
798
799 if (direction == GTK_TEXT_DIR_LTR)
800 down_slider_offset += path_bar->priv->slider_width;
801 }
802 else
803 {
804 gtk_widget_set_child_visible (path_bar->priv->up_slider_button, FALSE);
805 }
806
807 if (need_sliders)
808 {
809 child_allocation.width = path_bar->priv->slider_width;
810 child_allocation.x = down_slider_offset + allocation->x;
811
812 gtk_widget_size_allocate (path_bar->priv->down_slider_button, &child_allocation);
813
814 gtk_widget_set_child_visible (path_bar->priv->down_slider_button, TRUE);
815 gtk_widget_show_all (path_bar->priv->down_slider_button);
816 gtk_path_bar_update_slider_buttons (path_bar);
817 }
818 else
819 {
820 gtk_widget_set_child_visible (path_bar->priv->down_slider_button, FALSE);
821 }
822
823 _gtk_widget_set_simple_clip (widget, NULL);
824 }
825
826 static void
gtk_path_bar_style_updated(GtkWidget * widget)827 gtk_path_bar_style_updated (GtkWidget *widget)
828 {
829 GTK_WIDGET_CLASS (gtk_path_bar_parent_class)->style_updated (widget);
830
831 gtk_path_bar_check_icon_theme (GTK_PATH_BAR (widget));
832 }
833
834 static void
gtk_path_bar_screen_changed(GtkWidget * widget,GdkScreen * previous_screen)835 gtk_path_bar_screen_changed (GtkWidget *widget,
836 GdkScreen *previous_screen)
837 {
838 if (GTK_WIDGET_CLASS (gtk_path_bar_parent_class)->screen_changed)
839 GTK_WIDGET_CLASS (gtk_path_bar_parent_class)->screen_changed (widget, previous_screen);
840
841 /* We might nave a new settings, so we remove the old one */
842 if (previous_screen)
843 remove_settings_signal (GTK_PATH_BAR (widget), previous_screen);
844
845 gtk_path_bar_check_icon_theme (GTK_PATH_BAR (widget));
846 }
847
848 static gboolean
gtk_path_bar_scroll(GtkWidget * widget,GdkEventScroll * event)849 gtk_path_bar_scroll (GtkWidget *widget,
850 GdkEventScroll *event)
851 {
852 switch (event->direction)
853 {
854 case GDK_SCROLL_RIGHT:
855 case GDK_SCROLL_DOWN:
856 gtk_path_bar_scroll_down (GTK_PATH_BAR (widget));
857 break;
858 case GDK_SCROLL_LEFT:
859 case GDK_SCROLL_UP:
860 gtk_path_bar_scroll_up (GTK_PATH_BAR (widget));
861 break;
862 case GDK_SCROLL_SMOOTH:
863 break;
864 }
865
866 return TRUE;
867 }
868
869 static void
gtk_path_bar_add(GtkContainer * container,GtkWidget * widget)870 gtk_path_bar_add (GtkContainer *container,
871 GtkWidget *widget)
872
873 {
874 gtk_widget_set_parent (widget, GTK_WIDGET (container));
875 }
876
877 static void
gtk_path_bar_remove_1(GtkContainer * container,GtkWidget * widget)878 gtk_path_bar_remove_1 (GtkContainer *container,
879 GtkWidget *widget)
880 {
881 gboolean was_visible = gtk_widget_get_visible (widget);
882 gtk_widget_unparent (widget);
883 if (was_visible)
884 gtk_widget_queue_resize (GTK_WIDGET (container));
885 }
886
887 static void
gtk_path_bar_remove(GtkContainer * container,GtkWidget * widget)888 gtk_path_bar_remove (GtkContainer *container,
889 GtkWidget *widget)
890 {
891 GtkPathBar *path_bar;
892 GList *children;
893
894 path_bar = GTK_PATH_BAR (container);
895
896 if (widget == path_bar->priv->up_slider_button)
897 {
898 gtk_path_bar_remove_1 (container, widget);
899 path_bar->priv->up_slider_button = NULL;
900 return;
901 }
902
903 if (widget == path_bar->priv->down_slider_button)
904 {
905 gtk_path_bar_remove_1 (container, widget);
906 path_bar->priv->down_slider_button = NULL;
907 return;
908 }
909
910 children = path_bar->priv->button_list;
911 while (children)
912 {
913 if (widget == BUTTON_DATA (children->data)->button)
914 {
915 gtk_path_bar_remove_1 (container, widget);
916 path_bar->priv->button_list = g_list_remove_link (path_bar->priv->button_list, children);
917 g_list_free (children);
918 return;
919 }
920
921 children = children->next;
922 }
923 }
924
925 static void
gtk_path_bar_forall(GtkContainer * container,gboolean include_internals,GtkCallback callback,gpointer callback_data)926 gtk_path_bar_forall (GtkContainer *container,
927 gboolean include_internals,
928 GtkCallback callback,
929 gpointer callback_data)
930 {
931 GtkPathBar *path_bar;
932 GList *children;
933
934 g_return_if_fail (callback != NULL);
935 path_bar = GTK_PATH_BAR (container);
936
937 children = path_bar->priv->button_list;
938 while (children)
939 {
940 GtkWidget *child;
941 child = BUTTON_DATA (children->data)->button;
942 children = children->next;
943
944 (* callback) (child, callback_data);
945 }
946
947 if (path_bar->priv->up_slider_button)
948 (* callback) (path_bar->priv->up_slider_button, callback_data);
949
950 if (path_bar->priv->down_slider_button)
951 (* callback) (path_bar->priv->down_slider_button, callback_data);
952 }
953
954 static void
gtk_path_bar_scroll_down(GtkPathBar * path_bar)955 gtk_path_bar_scroll_down (GtkPathBar *path_bar)
956 {
957 GtkAllocation allocation, button_allocation;
958 GList *list;
959 GList *down_button = NULL;
960 gint space_available;
961
962 if (path_bar->priv->ignore_click)
963 {
964 path_bar->priv->ignore_click = FALSE;
965 return;
966 }
967
968 if (gtk_widget_get_child_visible (BUTTON_DATA (path_bar->priv->button_list->data)->button))
969 {
970 /* Return if the last button is already visible */
971 return;
972 }
973
974 gtk_widget_queue_resize (GTK_WIDGET (path_bar));
975
976 /* We find the button at the 'down' end that we have to make
977 * visible */
978 for (list = path_bar->priv->button_list; list; list = list->next)
979 {
980 if (list->next && gtk_widget_get_child_visible (BUTTON_DATA (list->next->data)->button))
981 {
982 down_button = list;
983 break;
984 }
985 }
986
987 gtk_widget_get_allocation (GTK_WIDGET (path_bar), &allocation);
988 gtk_widget_get_allocation (BUTTON_DATA (down_button->data)->button, &button_allocation);
989
990 space_available = (allocation.width
991 - 2 * path_bar->priv->slider_width
992 - button_allocation.width);
993 path_bar->priv->first_scrolled_button = down_button;
994
995 /* We have space_available free space that's not being used.
996 * So we walk down from the end, adding buttons until we use all free space.
997 */
998 while (space_available > 0)
999 {
1000 path_bar->priv->first_scrolled_button = down_button;
1001 down_button = down_button->next;
1002 if (!down_button)
1003 break;
1004 space_available -= button_allocation.width;
1005 }
1006 }
1007
1008 static void
gtk_path_bar_scroll_up(GtkPathBar * path_bar)1009 gtk_path_bar_scroll_up (GtkPathBar *path_bar)
1010 {
1011 GList *list;
1012
1013 if (path_bar->priv->ignore_click)
1014 {
1015 path_bar->priv->ignore_click = FALSE;
1016 return;
1017 }
1018
1019 list = g_list_last (path_bar->priv->button_list);
1020
1021 if (gtk_widget_get_child_visible (BUTTON_DATA (list->data)->button))
1022 {
1023 /* Return if the first button is already visible */
1024 return;
1025 }
1026
1027 gtk_widget_queue_resize (GTK_WIDGET (path_bar));
1028
1029 for ( ; list; list = list->prev)
1030 {
1031 if (list->prev && gtk_widget_get_child_visible (BUTTON_DATA (list->prev->data)->button))
1032 {
1033 if (list->prev == path_bar->priv->fake_root)
1034 path_bar->priv->fake_root = NULL;
1035 path_bar->priv->first_scrolled_button = list;
1036 return;
1037 }
1038 }
1039 }
1040
1041 static gboolean
gtk_path_bar_scroll_timeout(GtkPathBar * path_bar)1042 gtk_path_bar_scroll_timeout (GtkPathBar *path_bar)
1043 {
1044 gboolean retval = FALSE;
1045
1046 if (path_bar->priv->timer)
1047 {
1048 if (path_bar->priv->scrolling_up)
1049 gtk_path_bar_scroll_up (path_bar);
1050 else if (path_bar->priv->scrolling_down)
1051 gtk_path_bar_scroll_down (path_bar);
1052
1053 if (path_bar->priv->need_timer)
1054 {
1055 path_bar->priv->need_timer = FALSE;
1056
1057 path_bar->priv->timer = gdk_threads_add_timeout (TIMEOUT_REPEAT * SCROLL_DELAY_FACTOR,
1058 (GSourceFunc)gtk_path_bar_scroll_timeout,
1059 path_bar);
1060 g_source_set_name_by_id (path_bar->priv->timer, "[gtk+] gtk_path_bar_scroll_timeout");
1061 }
1062 else
1063 retval = TRUE;
1064 }
1065
1066 return retval;
1067 }
1068
1069 static void
gtk_path_bar_stop_scrolling(GtkPathBar * path_bar)1070 gtk_path_bar_stop_scrolling (GtkPathBar *path_bar)
1071 {
1072 if (path_bar->priv->timer)
1073 {
1074 g_source_remove (path_bar->priv->timer);
1075 path_bar->priv->timer = 0;
1076 path_bar->priv->need_timer = FALSE;
1077 }
1078 }
1079
1080 static gboolean
gtk_path_bar_slider_up_defocus(GtkWidget * widget,GdkEventButton * event,GtkPathBar * path_bar)1081 gtk_path_bar_slider_up_defocus (GtkWidget *widget,
1082 GdkEventButton *event,
1083 GtkPathBar *path_bar)
1084 {
1085 GList *list;
1086 GList *up_button = NULL;
1087
1088 if (event->type != GDK_FOCUS_CHANGE)
1089 return FALSE;
1090
1091 for (list = g_list_last (path_bar->priv->button_list); list; list = list->prev)
1092 {
1093 if (gtk_widget_get_child_visible (BUTTON_DATA (list->data)->button))
1094 {
1095 up_button = list;
1096 break;
1097 }
1098 }
1099
1100 /* don't let the focus vanish */
1101 if ((!gtk_widget_is_sensitive (path_bar->priv->up_slider_button)) ||
1102 (!gtk_widget_get_child_visible (path_bar->priv->up_slider_button)))
1103 gtk_widget_grab_focus (BUTTON_DATA (up_button->data)->button);
1104
1105 return FALSE;
1106 }
1107
1108 static gboolean
gtk_path_bar_slider_down_defocus(GtkWidget * widget,GdkEventButton * event,GtkPathBar * path_bar)1109 gtk_path_bar_slider_down_defocus (GtkWidget *widget,
1110 GdkEventButton *event,
1111 GtkPathBar *path_bar)
1112 {
1113 GList *list;
1114 GList *down_button = NULL;
1115
1116 if (event->type != GDK_FOCUS_CHANGE)
1117 return FALSE;
1118
1119 for (list = path_bar->priv->button_list; list; list = list->next)
1120 {
1121 if (gtk_widget_get_child_visible (BUTTON_DATA (list->data)->button))
1122 {
1123 down_button = list;
1124 break;
1125 }
1126 }
1127
1128 /* don't let the focus vanish */
1129 if ((!gtk_widget_is_sensitive (path_bar->priv->down_slider_button)) ||
1130 (!gtk_widget_get_child_visible (path_bar->priv->down_slider_button)))
1131 gtk_widget_grab_focus (BUTTON_DATA (down_button->data)->button);
1132
1133 return FALSE;
1134 }
1135
1136 static gboolean
gtk_path_bar_slider_button_press(GtkWidget * widget,GdkEventButton * event,GtkPathBar * path_bar)1137 gtk_path_bar_slider_button_press (GtkWidget *widget,
1138 GdkEventButton *event,
1139 GtkPathBar *path_bar)
1140 {
1141 if (event->type != GDK_BUTTON_PRESS || event->button != GDK_BUTTON_PRIMARY)
1142 return FALSE;
1143
1144 path_bar->priv->ignore_click = FALSE;
1145
1146 if (widget == path_bar->priv->up_slider_button)
1147 {
1148 path_bar->priv->scrolling_down = FALSE;
1149 path_bar->priv->scrolling_up = TRUE;
1150 gtk_path_bar_scroll_up (path_bar);
1151 }
1152 else if (widget == path_bar->priv->down_slider_button)
1153 {
1154 path_bar->priv->scrolling_up = FALSE;
1155 path_bar->priv->scrolling_down = TRUE;
1156 gtk_path_bar_scroll_down (path_bar);
1157 }
1158
1159 if (!path_bar->priv->timer)
1160 {
1161 path_bar->priv->need_timer = TRUE;
1162 path_bar->priv->timer = gdk_threads_add_timeout (TIMEOUT_INITIAL,
1163 (GSourceFunc)gtk_path_bar_scroll_timeout,
1164 path_bar);
1165 g_source_set_name_by_id (path_bar->priv->timer, "[gtk+] gtk_path_bar_scroll_timeout");
1166 }
1167
1168 return FALSE;
1169 }
1170
1171 static gboolean
gtk_path_bar_slider_button_release(GtkWidget * widget,GdkEventButton * event,GtkPathBar * path_bar)1172 gtk_path_bar_slider_button_release (GtkWidget *widget,
1173 GdkEventButton *event,
1174 GtkPathBar *path_bar)
1175 {
1176 if (event->type != GDK_BUTTON_RELEASE)
1177 return FALSE;
1178
1179 path_bar->priv->ignore_click = TRUE;
1180 gtk_path_bar_stop_scrolling (path_bar);
1181
1182 return FALSE;
1183 }
1184
1185 static void
gtk_path_bar_grab_notify(GtkWidget * widget,gboolean was_grabbed)1186 gtk_path_bar_grab_notify (GtkWidget *widget,
1187 gboolean was_grabbed)
1188 {
1189 if (!was_grabbed)
1190 gtk_path_bar_stop_scrolling (GTK_PATH_BAR (widget));
1191 }
1192
1193 static void
gtk_path_bar_state_changed(GtkWidget * widget,GtkStateType previous_state)1194 gtk_path_bar_state_changed (GtkWidget *widget,
1195 GtkStateType previous_state)
1196 {
1197 if (!gtk_widget_is_sensitive (widget))
1198 gtk_path_bar_stop_scrolling (GTK_PATH_BAR (widget));
1199 }
1200
1201
1202 /* Changes the icons wherever it is needed */
1203 static void
reload_icons(GtkPathBar * path_bar)1204 reload_icons (GtkPathBar *path_bar)
1205 {
1206 GList *list;
1207
1208 g_clear_object (&path_bar->priv->root_icon);
1209 g_clear_object (&path_bar->priv->home_icon);
1210 g_clear_object (&path_bar->priv->desktop_icon);
1211
1212 for (list = path_bar->priv->button_list; list; list = list->next)
1213 {
1214 ButtonData *button_data;
1215 gboolean current_dir;
1216
1217 button_data = BUTTON_DATA (list->data);
1218 if (button_data->type != NORMAL_BUTTON)
1219 {
1220 current_dir = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button_data->button));
1221 gtk_path_bar_update_button_appearance (path_bar, button_data, current_dir);
1222 }
1223 }
1224 }
1225
1226 static void
change_icon_theme(GtkPathBar * path_bar)1227 change_icon_theme (GtkPathBar *path_bar)
1228 {
1229 reload_icons (path_bar);
1230 }
1231
1232 /* Callback used when a GtkSettings value changes */
1233 static void
settings_notify_cb(GObject * object,GParamSpec * pspec,GtkPathBar * path_bar)1234 settings_notify_cb (GObject *object,
1235 GParamSpec *pspec,
1236 GtkPathBar *path_bar)
1237 {
1238 const char *name;
1239
1240 name = g_param_spec_get_name (pspec);
1241
1242 if (strcmp (name, "gtk-icon-theme-name") == 0)
1243 change_icon_theme (path_bar);
1244 }
1245
1246 static void
gtk_path_bar_check_icon_theme(GtkPathBar * path_bar)1247 gtk_path_bar_check_icon_theme (GtkPathBar *path_bar)
1248 {
1249 if (path_bar->priv->settings_signal_id == 0)
1250 {
1251 GtkSettings *settings;
1252
1253 settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (path_bar)));
1254 path_bar->priv->settings_signal_id = g_signal_connect (settings, "notify",
1255 G_CALLBACK (settings_notify_cb), path_bar);
1256 }
1257
1258 change_icon_theme (path_bar);
1259 }
1260
1261 /* Public functions and their helpers */
1262 static void
gtk_path_bar_clear_buttons(GtkPathBar * path_bar)1263 gtk_path_bar_clear_buttons (GtkPathBar *path_bar)
1264 {
1265 while (path_bar->priv->button_list != NULL)
1266 {
1267 gtk_container_remove (GTK_CONTAINER (path_bar), BUTTON_DATA (path_bar->priv->button_list->data)->button);
1268 }
1269 path_bar->priv->first_scrolled_button = NULL;
1270 path_bar->priv->fake_root = NULL;
1271 }
1272
1273 static void
button_clicked_cb(GtkWidget * button,gpointer data)1274 button_clicked_cb (GtkWidget *button,
1275 gpointer data)
1276 {
1277 ButtonData *button_data;
1278 GtkPathBar *path_bar;
1279 GList *button_list;
1280 gboolean child_is_hidden;
1281 GFile *child_file;
1282
1283 button_data = BUTTON_DATA (data);
1284 if (button_data->ignore_changes)
1285 return;
1286
1287 path_bar = GTK_PATH_BAR (gtk_widget_get_parent (button));
1288
1289 button_list = g_list_find (path_bar->priv->button_list, button_data);
1290 g_assert (button_list != NULL);
1291
1292 g_signal_handlers_block_by_func (button,
1293 G_CALLBACK (button_clicked_cb), data);
1294 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
1295 g_signal_handlers_unblock_by_func (button,
1296 G_CALLBACK (button_clicked_cb), data);
1297
1298 if (button_list->prev)
1299 {
1300 ButtonData *child_data;
1301
1302 child_data = BUTTON_DATA (button_list->prev->data);
1303 child_file = child_data->file;
1304 child_is_hidden = child_data->file_is_hidden;
1305 }
1306 else
1307 {
1308 child_file = NULL;
1309 child_is_hidden = FALSE;
1310 }
1311
1312 g_signal_emit (path_bar, path_bar_signals [PATH_CLICKED], 0,
1313 button_data->file, child_file, child_is_hidden);
1314 }
1315
1316 struct SetButtonImageData
1317 {
1318 GtkPathBar *path_bar;
1319 ButtonData *button_data;
1320 };
1321
1322 static void
set_button_image_get_info_cb(GCancellable * cancellable,GFileInfo * info,const GError * error,gpointer user_data)1323 set_button_image_get_info_cb (GCancellable *cancellable,
1324 GFileInfo *info,
1325 const GError *error,
1326 gpointer user_data)
1327 {
1328 gboolean cancelled = g_cancellable_is_cancelled (cancellable);
1329 GIcon *icon;
1330 struct SetButtonImageData *data = user_data;
1331
1332 if (cancelled)
1333 {
1334 g_free (data);
1335 g_object_unref (cancellable);
1336 return;
1337 }
1338
1339 g_assert (GTK_IS_PATH_BAR (data->path_bar));
1340 g_assert (G_OBJECT (data->path_bar)->ref_count > 0);
1341
1342 g_assert (cancellable == data->button_data->cancellable);
1343 cancellable_async_done (data->path_bar, cancellable);
1344 data->button_data->cancellable = NULL;
1345
1346 if (error)
1347 goto out;
1348
1349 icon = g_file_info_get_symbolic_icon (info);
1350 gtk_image_set_from_gicon (GTK_IMAGE (data->button_data->image), icon, GTK_ICON_SIZE_BUTTON);
1351
1352 switch (data->button_data->type)
1353 {
1354 case HOME_BUTTON:
1355 g_set_object (&data->path_bar->priv->home_icon, icon);
1356 break;
1357
1358 case DESKTOP_BUTTON:
1359 g_set_object (&data->path_bar->priv->desktop_icon, icon);
1360 break;
1361
1362 default:
1363 break;
1364 };
1365
1366 out:
1367 g_free (data);
1368 }
1369
1370 static void
set_button_image(GtkPathBar * path_bar,ButtonData * button_data)1371 set_button_image (GtkPathBar *path_bar,
1372 ButtonData *button_data)
1373 {
1374 GtkFileSystemVolume *volume;
1375 struct SetButtonImageData *data;
1376
1377 switch (button_data->type)
1378 {
1379 case ROOT_BUTTON:
1380
1381 if (path_bar->priv->root_icon != NULL)
1382 {
1383 gtk_image_set_from_gicon (GTK_IMAGE (button_data->image), path_bar->priv->root_icon, GTK_ICON_SIZE_BUTTON);
1384 break;
1385 }
1386
1387 volume = _gtk_file_system_get_volume_for_file (path_bar->priv->file_system, path_bar->priv->root_file);
1388 if (volume == NULL)
1389 return;
1390
1391 path_bar->priv->root_icon = _gtk_file_system_volume_get_symbolic_icon (volume);
1392 _gtk_file_system_volume_unref (volume);
1393 gtk_image_set_from_gicon (GTK_IMAGE (button_data->image), path_bar->priv->root_icon, GTK_ICON_SIZE_BUTTON);
1394
1395 break;
1396
1397 case HOME_BUTTON:
1398 if (path_bar->priv->home_icon != NULL)
1399 {
1400 gtk_image_set_from_gicon (GTK_IMAGE (button_data->image), path_bar->priv->home_icon, GTK_ICON_SIZE_BUTTON);
1401 break;
1402 }
1403
1404 data = g_new0 (struct SetButtonImageData, 1);
1405 data->path_bar = path_bar;
1406 data->button_data = button_data;
1407
1408 if (button_data->cancellable)
1409 {
1410 cancel_cancellable (path_bar, button_data->cancellable);
1411 }
1412
1413 button_data->cancellable =
1414 _gtk_file_system_get_info (path_bar->priv->file_system,
1415 path_bar->priv->home_file,
1416 "standard::symbolic-icon",
1417 set_button_image_get_info_cb,
1418 data);
1419 add_cancellable (path_bar, button_data->cancellable);
1420 break;
1421
1422 case DESKTOP_BUTTON:
1423 if (path_bar->priv->desktop_icon != NULL)
1424 {
1425 gtk_image_set_from_gicon (GTK_IMAGE (button_data->image), path_bar->priv->desktop_icon, GTK_ICON_SIZE_BUTTON);
1426 break;
1427 }
1428
1429 data = g_new0 (struct SetButtonImageData, 1);
1430 data->path_bar = path_bar;
1431 data->button_data = button_data;
1432
1433 if (button_data->cancellable)
1434 {
1435 cancel_cancellable (path_bar, button_data->cancellable);
1436 }
1437
1438 button_data->cancellable =
1439 _gtk_file_system_get_info (path_bar->priv->file_system,
1440 path_bar->priv->desktop_file,
1441 "standard::symbolic-icon",
1442 set_button_image_get_info_cb,
1443 data);
1444 add_cancellable (path_bar, button_data->cancellable);
1445 break;
1446 default:
1447 break;
1448 }
1449 }
1450
1451 static void
button_data_free(ButtonData * button_data)1452 button_data_free (ButtonData *button_data)
1453 {
1454 if (button_data->file)
1455 g_object_unref (button_data->file);
1456 button_data->file = NULL;
1457
1458 g_free (button_data->dir_name);
1459 button_data->dir_name = NULL;
1460
1461 button_data->button = NULL;
1462
1463 g_free (button_data);
1464 }
1465
1466 static const char *
get_dir_name(ButtonData * button_data)1467 get_dir_name (ButtonData *button_data)
1468 {
1469 return button_data->dir_name;
1470 }
1471
1472 static void
gtk_path_bar_update_button_appearance(GtkPathBar * path_bar,ButtonData * button_data,gboolean current_dir)1473 gtk_path_bar_update_button_appearance (GtkPathBar *path_bar,
1474 ButtonData *button_data,
1475 gboolean current_dir)
1476 {
1477 const gchar *dir_name = get_dir_name (button_data);
1478 GtkStyleContext *context;
1479
1480 context = gtk_widget_get_style_context (button_data->button);
1481
1482 gtk_style_context_remove_class (context, "text-button");
1483 gtk_style_context_remove_class (context, "image-button");
1484
1485 if (button_data->label != NULL)
1486 {
1487 gtk_label_set_text (GTK_LABEL (button_data->label), dir_name);
1488 if (button_data->image == NULL)
1489 gtk_style_context_add_class (context, "text-button");
1490 }
1491
1492 if (button_data->image != NULL)
1493 {
1494 set_button_image (path_bar, button_data);
1495 if (button_data->label == NULL)
1496 gtk_style_context_add_class (context, "image-button");
1497 }
1498
1499 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button_data->button)) != current_dir)
1500 {
1501 button_data->ignore_changes = TRUE;
1502 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button_data->button), current_dir);
1503 button_data->ignore_changes = FALSE;
1504 }
1505 }
1506
1507 static ButtonType
find_button_type(GtkPathBar * path_bar,GFile * file)1508 find_button_type (GtkPathBar *path_bar,
1509 GFile *file)
1510 {
1511 if (path_bar->priv->root_file != NULL &&
1512 g_file_equal (file, path_bar->priv->root_file))
1513 return ROOT_BUTTON;
1514 if (path_bar->priv->home_file != NULL &&
1515 g_file_equal (file, path_bar->priv->home_file))
1516 return HOME_BUTTON;
1517 if (path_bar->priv->desktop_file != NULL &&
1518 g_file_equal (file, path_bar->priv->desktop_file))
1519 return DESKTOP_BUTTON;
1520
1521 return NORMAL_BUTTON;
1522 }
1523
1524 static void
button_drag_data_get_cb(GtkWidget * widget,GdkDragContext * context,GtkSelectionData * selection_data,guint info,guint time_,gpointer data)1525 button_drag_data_get_cb (GtkWidget *widget,
1526 GdkDragContext *context,
1527 GtkSelectionData *selection_data,
1528 guint info,
1529 guint time_,
1530 gpointer data)
1531 {
1532 ButtonData *button_data;
1533 char *uris[2];
1534
1535 button_data = data;
1536
1537 uris[0] = g_file_get_uri (button_data->file);
1538 uris[1] = NULL;
1539
1540 gtk_selection_data_set_uris (selection_data, uris);
1541
1542 g_free (uris[0]);
1543 }
1544
1545 static ButtonData *
make_directory_button(GtkPathBar * path_bar,const char * dir_name,GFile * file,gboolean current_dir,gboolean file_is_hidden)1546 make_directory_button (GtkPathBar *path_bar,
1547 const char *dir_name,
1548 GFile *file,
1549 gboolean current_dir,
1550 gboolean file_is_hidden)
1551 {
1552 AtkObject *atk_obj;
1553 GtkWidget *child = NULL;
1554 ButtonData *button_data;
1555
1556 file_is_hidden = !! file_is_hidden;
1557 /* Is it a special button? */
1558 button_data = g_new0 (ButtonData, 1);
1559 button_data->type = find_button_type (path_bar, file);
1560 button_data->button = gtk_toggle_button_new ();
1561 atk_obj = gtk_widget_get_accessible (button_data->button);
1562 gtk_widget_set_focus_on_click (button_data->button, FALSE);
1563 gtk_widget_add_events (button_data->button, GDK_SCROLL_MASK);
1564
1565 switch (button_data->type)
1566 {
1567 case ROOT_BUTTON:
1568 button_data->image = gtk_image_new ();
1569 child = button_data->image;
1570 button_data->label = NULL;
1571 atk_object_set_name (atk_obj, _("File System Root"));
1572 break;
1573 case HOME_BUTTON:
1574 case DESKTOP_BUTTON:
1575 button_data->image = gtk_image_new ();
1576 button_data->label = gtk_label_new (NULL);
1577 child = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
1578 gtk_box_pack_start (GTK_BOX (child), button_data->image, FALSE, FALSE, 0);
1579 gtk_box_pack_start (GTK_BOX (child), button_data->label, FALSE, FALSE, 0);
1580 break;
1581 case NORMAL_BUTTON:
1582 default:
1583 button_data->label = gtk_label_new (NULL);
1584 child = button_data->label;
1585 button_data->image = NULL;
1586 }
1587
1588 button_data->dir_name = g_strdup (dir_name);
1589 button_data->file = g_object_ref (file);
1590 button_data->file_is_hidden = file_is_hidden;
1591
1592 gtk_container_add (GTK_CONTAINER (button_data->button), child);
1593 gtk_widget_show_all (button_data->button);
1594
1595 gtk_path_bar_update_button_appearance (path_bar, button_data, current_dir);
1596
1597 g_signal_connect (button_data->button, "clicked",
1598 G_CALLBACK (button_clicked_cb),
1599 button_data);
1600 g_object_weak_ref (G_OBJECT (button_data->button),
1601 (GWeakNotify) button_data_free, button_data);
1602
1603 gtk_drag_source_set (button_data->button,
1604 GDK_BUTTON1_MASK,
1605 NULL, 0,
1606 GDK_ACTION_COPY);
1607 gtk_drag_source_add_uri_targets (button_data->button);
1608 g_signal_connect (button_data->button, "drag-data-get",
1609 G_CALLBACK (button_drag_data_get_cb), button_data);
1610
1611 return button_data;
1612 }
1613
1614 static gboolean
gtk_path_bar_check_parent_path(GtkPathBar * path_bar,GFile * file)1615 gtk_path_bar_check_parent_path (GtkPathBar *path_bar,
1616 GFile *file)
1617 {
1618 GList *list;
1619 GList *current_path = NULL;
1620 gboolean need_new_fake_root = FALSE;
1621
1622 for (list = path_bar->priv->button_list; list; list = list->next)
1623 {
1624 ButtonData *button_data;
1625
1626 button_data = list->data;
1627 if (g_file_equal (file, button_data->file))
1628 {
1629 current_path = list;
1630 break;
1631 }
1632 if (list == path_bar->priv->fake_root)
1633 need_new_fake_root = TRUE;
1634 }
1635
1636 if (current_path)
1637 {
1638 if (need_new_fake_root)
1639 {
1640 path_bar->priv->fake_root = NULL;
1641 for (list = current_path; list; list = list->next)
1642 {
1643 ButtonData *button_data;
1644
1645 button_data = list->data;
1646 if (BUTTON_IS_FAKE_ROOT (button_data))
1647 {
1648 path_bar->priv->fake_root = list;
1649 break;
1650 }
1651 }
1652 }
1653
1654 for (list = path_bar->priv->button_list; list; list = list->next)
1655 {
1656 gtk_path_bar_update_button_appearance (path_bar,
1657 BUTTON_DATA (list->data),
1658 (list == current_path) ? TRUE : FALSE);
1659 }
1660
1661 if (!gtk_widget_get_child_visible (BUTTON_DATA (current_path->data)->button))
1662 {
1663 path_bar->priv->first_scrolled_button = current_path;
1664 gtk_widget_queue_resize (GTK_WIDGET (path_bar));
1665 }
1666
1667 return TRUE;
1668 }
1669 return FALSE;
1670 }
1671
1672
1673 struct SetFileInfo
1674 {
1675 GFile *file;
1676 GFile *parent_file;
1677 GtkPathBar *path_bar;
1678 GList *new_buttons;
1679 GList *fake_root;
1680 gboolean first_directory;
1681 };
1682
1683 static void
gtk_path_bar_set_file_finish(struct SetFileInfo * info,gboolean result)1684 gtk_path_bar_set_file_finish (struct SetFileInfo *info,
1685 gboolean result)
1686 {
1687 if (result)
1688 {
1689 GList *l;
1690 GtkCssNode *prev;
1691
1692 gtk_path_bar_clear_buttons (info->path_bar);
1693 info->path_bar->priv->button_list = g_list_reverse (info->new_buttons);
1694 info->path_bar->priv->fake_root = info->fake_root;
1695 prev = gtk_widget_get_css_node (info->path_bar->priv->down_slider_button);
1696
1697 for (l = info->path_bar->priv->button_list; l; l = l->next)
1698 {
1699 GtkWidget *button = BUTTON_DATA (l->data)->button;
1700 GtkCssNode *node = gtk_widget_get_css_node (button);
1701
1702 gtk_css_node_insert_before (gtk_widget_get_css_node (GTK_WIDGET (info->path_bar)),
1703 node,
1704 prev);
1705 gtk_container_add (GTK_CONTAINER (info->path_bar), button);
1706 prev = node;
1707 }
1708 }
1709 else
1710 {
1711 GList *l;
1712
1713 for (l = info->new_buttons; l; l = l->next)
1714 {
1715 ButtonData *button_data;
1716
1717 button_data = BUTTON_DATA (l->data);
1718 gtk_widget_destroy (button_data->button);
1719 }
1720
1721 g_list_free (info->new_buttons);
1722 }
1723
1724 if (info->file)
1725 g_object_unref (info->file);
1726 if (info->parent_file)
1727 g_object_unref (info->parent_file);
1728
1729 g_free (info);
1730 }
1731
1732 static void
gtk_path_bar_get_info_callback(GCancellable * cancellable,GFileInfo * info,const GError * error,gpointer data)1733 gtk_path_bar_get_info_callback (GCancellable *cancellable,
1734 GFileInfo *info,
1735 const GError *error,
1736 gpointer data)
1737 {
1738 gboolean cancelled = g_cancellable_is_cancelled (cancellable);
1739 struct SetFileInfo *file_info = data;
1740 ButtonData *button_data;
1741 const gchar *display_name;
1742 gboolean is_hidden;
1743
1744 if (cancelled)
1745 {
1746 gtk_path_bar_set_file_finish (file_info, FALSE);
1747 g_object_unref (cancellable);
1748 return;
1749 }
1750
1751 g_assert (GTK_IS_PATH_BAR (file_info->path_bar));
1752 g_assert (G_OBJECT (file_info->path_bar)->ref_count > 0);
1753
1754 g_assert (cancellable == file_info->path_bar->priv->get_info_cancellable);
1755 cancellable_async_done (file_info->path_bar, cancellable);
1756 file_info->path_bar->priv->get_info_cancellable = NULL;
1757
1758 if (!info)
1759 {
1760 gtk_path_bar_set_file_finish (file_info, FALSE);
1761 return;
1762 }
1763
1764 display_name = g_file_info_get_display_name (info);
1765 is_hidden = g_file_info_get_is_hidden (info) || g_file_info_get_is_backup (info);
1766
1767 button_data = make_directory_button (file_info->path_bar, display_name,
1768 file_info->file,
1769 file_info->first_directory, is_hidden);
1770 g_clear_object (&file_info->file);
1771
1772 file_info->new_buttons = g_list_prepend (file_info->new_buttons, button_data);
1773
1774 if (BUTTON_IS_FAKE_ROOT (button_data))
1775 file_info->fake_root = file_info->new_buttons;
1776
1777 /* We have assigned the info for the innermost button, i.e. the deepest directory.
1778 * Now, go on to fetch the info for this directory's parent.
1779 */
1780
1781 file_info->file = file_info->parent_file;
1782 file_info->first_directory = FALSE;
1783
1784 if (!file_info->file)
1785 {
1786 /* No parent? Okay, we are done. */
1787 gtk_path_bar_set_file_finish (file_info, TRUE);
1788 return;
1789 }
1790
1791 file_info->parent_file = g_file_get_parent (file_info->file);
1792
1793 /* Recurse asynchronously */
1794 file_info->path_bar->priv->get_info_cancellable =
1795 _gtk_file_system_get_info (file_info->path_bar->priv->file_system,
1796 file_info->file,
1797 "standard::display-name,standard::is-hidden,standard::is-backup",
1798 gtk_path_bar_get_info_callback,
1799 file_info);
1800 add_cancellable (file_info->path_bar, file_info->path_bar->priv->get_info_cancellable);
1801 }
1802
1803 void
_gtk_path_bar_set_file(GtkPathBar * path_bar,GFile * file,gboolean keep_trail)1804 _gtk_path_bar_set_file (GtkPathBar *path_bar,
1805 GFile *file,
1806 gboolean keep_trail)
1807 {
1808 struct SetFileInfo *info;
1809
1810 g_return_if_fail (GTK_IS_PATH_BAR (path_bar));
1811 g_return_if_fail (G_IS_FILE (file));
1812
1813 /* Check whether the new path is already present in the pathbar as buttons.
1814 * This could be a parent directory or a previous selected subdirectory.
1815 */
1816 if (keep_trail && gtk_path_bar_check_parent_path (path_bar, file))
1817 return;
1818
1819 info = g_new0 (struct SetFileInfo, 1);
1820 info->file = g_object_ref (file);
1821 info->path_bar = path_bar;
1822 info->first_directory = TRUE;
1823 info->parent_file = g_file_get_parent (info->file);
1824
1825 if (path_bar->priv->get_info_cancellable)
1826 {
1827 cancel_cancellable (path_bar, path_bar->priv->get_info_cancellable);
1828 }
1829
1830 path_bar->priv->get_info_cancellable =
1831 _gtk_file_system_get_info (path_bar->priv->file_system,
1832 info->file,
1833 "standard::display-name,standard::is-hidden,standard::is-backup",
1834 gtk_path_bar_get_info_callback,
1835 info);
1836 add_cancellable (path_bar, path_bar->priv->get_info_cancellable);
1837 }
1838
1839 /* FIXME: This should be a construct-only property */
1840 void
_gtk_path_bar_set_file_system(GtkPathBar * path_bar,GtkFileSystem * file_system)1841 _gtk_path_bar_set_file_system (GtkPathBar *path_bar,
1842 GtkFileSystem *file_system)
1843 {
1844 const char *home;
1845
1846 g_return_if_fail (GTK_IS_PATH_BAR (path_bar));
1847
1848 g_assert (path_bar->priv->file_system == NULL);
1849
1850 path_bar->priv->file_system = g_object_ref (file_system);
1851
1852 home = g_get_home_dir ();
1853 if (home != NULL)
1854 {
1855 const gchar *desktop;
1856
1857 path_bar->priv->home_file = g_file_new_for_path (home);
1858 /* FIXME: Need file system backend specific way of getting the
1859 * Desktop path.
1860 */
1861 desktop = g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP);
1862 if (desktop != NULL)
1863 path_bar->priv->desktop_file = g_file_new_for_path (desktop);
1864 else
1865 path_bar->priv->desktop_file = NULL;
1866 }
1867 else
1868 {
1869 path_bar->priv->home_file = NULL;
1870 path_bar->priv->desktop_file = NULL;
1871 }
1872 path_bar->priv->root_file = g_file_new_for_path ("/");
1873 }
1874
1875 /**
1876 * _gtk_path_bar_up:
1877 * @path_bar: a #GtkPathBar
1878 *
1879 * If the selected button in the pathbar is not the furthest button “up” (in the
1880 * root direction), act as if the user clicked on the next button up.
1881 **/
1882 void
_gtk_path_bar_up(GtkPathBar * path_bar)1883 _gtk_path_bar_up (GtkPathBar *path_bar)
1884 {
1885 GList *l;
1886
1887 for (l = path_bar->priv->button_list; l; l = l->next)
1888 {
1889 GtkWidget *button = BUTTON_DATA (l->data)->button;
1890 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
1891 {
1892 if (l->next)
1893 {
1894 GtkWidget *next_button = BUTTON_DATA (l->next->data)->button;
1895 button_clicked_cb (next_button, l->next->data);
1896 }
1897 break;
1898 }
1899 }
1900 }
1901
1902 /**
1903 * _gtk_path_bar_down:
1904 * @path_bar: a #GtkPathBar
1905 *
1906 * If the selected button in the pathbar is not the furthest button “down” (in the
1907 * leaf direction), act as if the user clicked on the next button down.
1908 **/
1909 void
_gtk_path_bar_down(GtkPathBar * path_bar)1910 _gtk_path_bar_down (GtkPathBar *path_bar)
1911 {
1912 GList *l;
1913
1914 for (l = path_bar->priv->button_list; l; l = l->next)
1915 {
1916 GtkWidget *button = BUTTON_DATA (l->data)->button;
1917 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
1918 {
1919 if (l->prev)
1920 {
1921 GtkWidget *prev_button = BUTTON_DATA (l->prev->data)->button;
1922 button_clicked_cb (prev_button, l->prev->data);
1923 }
1924 break;
1925 }
1926 }
1927 }
1928