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 "gtkdragsource.h" |
27 | #include "gtkicontheme.h" |
28 | #include "gtkimage.h" |
29 | #include "gtkintl.h" |
30 | #include "gtklabel.h" |
31 | #include "gtkmain.h" |
32 | #include "gtkmarshalers.h" |
33 | #include "gtksettings.h" |
34 | #include "gtktogglebutton.h" |
35 | #include "gtkwidgetprivate.h" |
36 | #include "gtkeventcontrollerscroll.h" |
37 | #include "gtkdragsource.h" |
38 | |
39 | struct _GtkPathBar |
40 | { |
41 | GtkWidget parent_instance; |
42 | |
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 | GList *button_list; |
78 | GList *first_scrolled_button; |
79 | GList *fake_root; |
80 | GtkWidget *up_slider_button; |
81 | GtkWidget *down_slider_button; |
82 | gint16 slider_width; |
83 | }; |
84 | |
85 | typedef struct _GtkPathBarClass GtkPathBarClass; |
86 | |
87 | struct _GtkPathBarClass |
88 | { |
89 | GtkWidgetClass parent_class; |
90 | |
91 | void (* path_clicked) (GtkPathBar *path_bar, |
92 | GFile *file, |
93 | GFile *child_file, |
94 | gboolean child_is_hidden); |
95 | }; |
96 | |
97 | enum { |
98 | PATH_CLICKED, |
99 | LAST_SIGNAL |
100 | }; |
101 | |
102 | typedef enum { |
103 | NORMAL_BUTTON, |
104 | ROOT_BUTTON, |
105 | HOME_BUTTON, |
106 | DESKTOP_BUTTON |
107 | } ButtonType; |
108 | |
109 | #define BUTTON_DATA(x) ((ButtonData *)(x)) |
110 | |
111 | static guint path_bar_signals [LAST_SIGNAL] = { 0 }; |
112 | |
113 | typedef struct _ButtonData ButtonData; |
114 | |
115 | struct _ButtonData |
116 | { |
117 | GtkWidget *button; |
118 | ButtonType type; |
119 | char *dir_name; |
120 | GFile *file; |
121 | GtkWidget *image; |
122 | GtkWidget *label; |
123 | GCancellable *cancellable; |
124 | guint ignore_changes : 1; |
125 | guint file_is_hidden : 1; |
126 | }; |
127 | /* This macro is used to check if a button can be used as a fake root. |
128 | * All buttons in front of a fake root are automatically hidden when in a |
129 | * directory below a fake root and replaced with the "<" arrow button. |
130 | */ |
131 | #define BUTTON_IS_FAKE_ROOT(button) ((button)->type == HOME_BUTTON) |
132 | |
133 | G_DEFINE_TYPE (GtkPathBar, gtk_path_bar, GTK_TYPE_WIDGET) |
134 | |
135 | static void gtk_path_bar_finalize (GObject *object); |
136 | static void gtk_path_bar_dispose (GObject *object); |
137 | static void gtk_path_bar_measure (GtkWidget *widget, |
138 | GtkOrientation orientation, |
139 | int for_size, |
140 | int *minimum, |
141 | int *natural, |
142 | int *minimum_baseline, |
143 | int *natural_baseline); |
144 | static void gtk_path_bar_size_allocate (GtkWidget *widget, |
145 | int width, |
146 | int height, |
147 | int baseline); |
148 | static void gtk_path_bar_scroll_up (GtkPathBar *path_bar); |
149 | static void gtk_path_bar_scroll_down (GtkPathBar *path_bar); |
150 | static void gtk_path_bar_update_button_appearance (GtkPathBar *path_bar, |
151 | ButtonData *button_data, |
152 | gboolean current_dir); |
153 | |
154 | static gboolean gtk_path_bar_scroll_controller_scroll (GtkEventControllerScroll *scroll, |
155 | double dx, |
156 | double dy, |
157 | GtkPathBar *path_bar); |
158 | |
159 | static void |
160 | add_cancellable (GtkPathBar *path_bar, |
161 | GCancellable *cancellable) |
162 | { |
163 | g_assert (g_list_find (path_bar->cancellables, cancellable) == NULL); |
164 | path_bar->cancellables = g_list_prepend (list: path_bar->cancellables, data: cancellable); |
165 | } |
166 | |
167 | static void |
168 | drop_node_for_cancellable (GtkPathBar *path_bar, |
169 | GCancellable *cancellable) |
170 | { |
171 | GList *node; |
172 | |
173 | node = g_list_find (list: path_bar->cancellables, data: cancellable); |
174 | g_assert (node != NULL); |
175 | node->data = NULL; |
176 | path_bar->cancellables = g_list_delete_link (list: path_bar->cancellables, link_: node); |
177 | } |
178 | |
179 | static void |
180 | cancel_cancellable (GtkPathBar *path_bar, |
181 | GCancellable *cancellable) |
182 | { |
183 | drop_node_for_cancellable (path_bar, cancellable); |
184 | g_cancellable_cancel (cancellable); |
185 | } |
186 | |
187 | static void |
188 | cancellable_async_done (GtkPathBar *path_bar, |
189 | GCancellable *cancellable) |
190 | { |
191 | drop_node_for_cancellable (path_bar, cancellable); |
192 | g_object_unref (object: cancellable); |
193 | } |
194 | |
195 | static void |
196 | cancel_all_cancellables (GtkPathBar *path_bar) |
197 | { |
198 | while (path_bar->cancellables) |
199 | { |
200 | GCancellable *cancellable = path_bar->cancellables->data; |
201 | cancel_cancellable (path_bar, cancellable); |
202 | } |
203 | } |
204 | |
205 | static void |
206 | gtk_path_bar_init (GtkPathBar *path_bar) |
207 | { |
208 | GtkEventController *controller; |
209 | const char *home; |
210 | |
211 | path_bar->up_slider_button = gtk_button_new_from_icon_name (icon_name: "pan-start-symbolic" ); |
212 | gtk_widget_add_css_class (widget: path_bar->up_slider_button, css_class: "slider-button" ); |
213 | gtk_widget_set_parent (widget: path_bar->up_slider_button, GTK_WIDGET (path_bar)); |
214 | |
215 | path_bar->down_slider_button = gtk_button_new_from_icon_name (icon_name: "pan-end-symbolic" ); |
216 | gtk_widget_add_css_class (widget: path_bar->down_slider_button, css_class: "slider-button" ); |
217 | gtk_widget_set_parent (widget: path_bar->down_slider_button, GTK_WIDGET (path_bar)); |
218 | |
219 | /* GtkBuilder won't let us connect 'swapped' without specifying the signal's |
220 | * user data in the .ui file |
221 | */ |
222 | g_signal_connect_swapped (path_bar->up_slider_button, "clicked" , |
223 | G_CALLBACK (gtk_path_bar_scroll_up), path_bar); |
224 | g_signal_connect_swapped (path_bar->down_slider_button, "clicked" , |
225 | G_CALLBACK (gtk_path_bar_scroll_down), path_bar); |
226 | |
227 | gtk_widget_add_css_class (GTK_WIDGET (path_bar), css_class: "linked" ); |
228 | |
229 | path_bar->get_info_cancellable = NULL; |
230 | path_bar->cancellables = NULL; |
231 | |
232 | controller = gtk_event_controller_scroll_new (flags: GTK_EVENT_CONTROLLER_SCROLL_VERTICAL | |
233 | GTK_EVENT_CONTROLLER_SCROLL_DISCRETE); |
234 | g_signal_connect (controller, "scroll" , |
235 | G_CALLBACK (gtk_path_bar_scroll_controller_scroll), |
236 | path_bar); |
237 | gtk_widget_add_controller (GTK_WIDGET (path_bar), controller); |
238 | |
239 | home = g_get_home_dir (); |
240 | if (home != NULL) |
241 | { |
242 | const char *desktop; |
243 | |
244 | path_bar->home_file = g_file_new_for_path (path: home); |
245 | /* FIXME: Need file system backend specific way of getting the |
246 | * Desktop path. |
247 | */ |
248 | desktop = g_get_user_special_dir (directory: G_USER_DIRECTORY_DESKTOP); |
249 | if (desktop != NULL) |
250 | path_bar->desktop_file = g_file_new_for_path (path: desktop); |
251 | else |
252 | path_bar->desktop_file = NULL; |
253 | } |
254 | else |
255 | { |
256 | path_bar->home_file = NULL; |
257 | path_bar->desktop_file = NULL; |
258 | } |
259 | path_bar->root_file = g_file_new_for_path (path: "/" ); |
260 | } |
261 | |
262 | static void |
263 | gtk_path_bar_class_init (GtkPathBarClass *path_bar_class) |
264 | { |
265 | GObjectClass *gobject_class; |
266 | GtkWidgetClass *widget_class; |
267 | |
268 | gobject_class = (GObjectClass *) path_bar_class; |
269 | widget_class = (GtkWidgetClass *) path_bar_class; |
270 | |
271 | gobject_class->finalize = gtk_path_bar_finalize; |
272 | gobject_class->dispose = gtk_path_bar_dispose; |
273 | |
274 | widget_class->measure = gtk_path_bar_measure; |
275 | widget_class->size_allocate = gtk_path_bar_size_allocate; |
276 | |
277 | path_bar_signals [PATH_CLICKED] = |
278 | g_signal_new (I_("path-clicked" ), |
279 | G_OBJECT_CLASS_TYPE (gobject_class), |
280 | signal_flags: G_SIGNAL_RUN_FIRST, |
281 | G_STRUCT_OFFSET (GtkPathBarClass, path_clicked), |
282 | NULL, NULL, |
283 | c_marshaller: _gtk_marshal_VOID__OBJECT_OBJECT_BOOLEAN, |
284 | G_TYPE_NONE, n_params: 3, |
285 | G_TYPE_FILE, |
286 | G_TYPE_FILE, |
287 | G_TYPE_BOOLEAN); |
288 | |
289 | gtk_widget_class_set_css_name (widget_class, name: "pathbar" ); |
290 | } |
291 | |
292 | static void |
293 | gtk_path_bar_finalize (GObject *object) |
294 | { |
295 | GtkPathBar *path_bar = GTK_PATH_BAR (object); |
296 | |
297 | cancel_all_cancellables (path_bar); |
298 | |
299 | g_list_free (list: path_bar->button_list); |
300 | g_clear_object (&path_bar->root_file); |
301 | g_clear_object (&path_bar->home_file); |
302 | g_clear_object (&path_bar->desktop_file); |
303 | |
304 | g_clear_object (&path_bar->root_icon); |
305 | g_clear_object (&path_bar->home_icon); |
306 | g_clear_object (&path_bar->desktop_icon); |
307 | |
308 | G_OBJECT_CLASS (gtk_path_bar_parent_class)->finalize (object); |
309 | } |
310 | |
311 | static void |
312 | gtk_path_bar_dispose (GObject *object) |
313 | { |
314 | GtkPathBar *path_bar = GTK_PATH_BAR (object); |
315 | GtkWidget *w; |
316 | |
317 | while ((w = gtk_widget_get_first_child (GTK_WIDGET (path_bar))) != NULL) |
318 | gtk_widget_unparent (widget: w); |
319 | |
320 | path_bar->get_info_cancellable = NULL; |
321 | cancel_all_cancellables (path_bar); |
322 | |
323 | G_OBJECT_CLASS (gtk_path_bar_parent_class)->dispose (object); |
324 | } |
325 | |
326 | static void |
327 | gtk_path_bar_measure (GtkWidget *widget, |
328 | GtkOrientation orientation, |
329 | int for_size, |
330 | int *minimum, |
331 | int *natural, |
332 | int *minimum_baseline, |
333 | int *natural_baseline) |
334 | { |
335 | GtkPathBar *path_bar = GTK_PATH_BAR (widget); |
336 | ButtonData *button_data; |
337 | GList *list; |
338 | int child_size; |
339 | int size = 0; |
340 | int child_min, child_nat; |
341 | |
342 | *minimum = 0; |
343 | *natural = 0; |
344 | |
345 | if (orientation == GTK_ORIENTATION_HORIZONTAL) |
346 | { |
347 | for (list = path_bar->button_list; list; list = list->next) |
348 | { |
349 | button_data = BUTTON_DATA (list->data); |
350 | gtk_widget_measure (widget: button_data->button, orientation: GTK_ORIENTATION_HORIZONTAL, for_size: -1, |
351 | minimum: &child_min, natural: &child_nat, NULL, NULL); |
352 | gtk_widget_measure (widget: button_data->button, orientation: GTK_ORIENTATION_VERTICAL, for_size: -1, |
353 | minimum: &child_size, NULL, NULL, NULL); |
354 | size = MAX (size, child_size); |
355 | |
356 | if (button_data->type == NORMAL_BUTTON) |
357 | { |
358 | /* Use 2*Height as button width because of ellipsized label. */ |
359 | child_min = MAX (child_min, child_size * 2); |
360 | child_nat = MAX (child_min, child_size * 2); |
361 | } |
362 | |
363 | *minimum = MAX (*minimum, child_min); |
364 | *natural = *natural + child_nat; |
365 | } |
366 | |
367 | /* Add space for slider, if we have more than one path */ |
368 | /* Theoretically, the slider could be bigger than the other button. But we're |
369 | * not going to worry about that now. |
370 | */ |
371 | path_bar->slider_width = 0; |
372 | |
373 | gtk_widget_measure (widget: path_bar->up_slider_button, orientation: GTK_ORIENTATION_HORIZONTAL, for_size: -1, |
374 | minimum: &child_min, natural: &child_nat, NULL, NULL); |
375 | if (path_bar->button_list && path_bar->button_list->next != NULL) |
376 | { |
377 | *minimum += child_min; |
378 | *natural += child_nat; |
379 | } |
380 | path_bar->slider_width = MAX (path_bar->slider_width, child_min); |
381 | |
382 | gtk_widget_measure (widget: path_bar->down_slider_button, orientation: GTK_ORIENTATION_HORIZONTAL, for_size: -1, |
383 | minimum: &child_min, natural: &child_nat, NULL, NULL); |
384 | if (path_bar->button_list && path_bar->button_list->next != NULL) |
385 | { |
386 | *minimum += child_min; |
387 | *natural += child_nat; |
388 | } |
389 | path_bar->slider_width = MAX (path_bar->slider_width, child_min); |
390 | |
391 | } |
392 | else /* VERTICAL */ |
393 | { |
394 | for (list = path_bar->button_list; list; list = list->next) |
395 | { |
396 | button_data = BUTTON_DATA (list->data); |
397 | gtk_widget_measure (widget: button_data->button, orientation: GTK_ORIENTATION_VERTICAL, for_size: -1, |
398 | minimum: &child_min, natural: &child_nat, NULL, NULL); |
399 | |
400 | *minimum = MAX (*minimum, child_min); |
401 | *natural = MAX (*natural, child_nat); |
402 | } |
403 | |
404 | gtk_widget_measure (widget: path_bar->up_slider_button, orientation: GTK_ORIENTATION_VERTICAL, for_size: -1, |
405 | minimum: &child_min, natural: &child_nat, NULL, NULL); |
406 | *minimum = MAX (*minimum, child_min); |
407 | *natural = MAX (*natural, child_nat); |
408 | |
409 | gtk_widget_measure (widget: path_bar->up_slider_button, orientation: GTK_ORIENTATION_VERTICAL, for_size: -1, |
410 | minimum: &child_min, natural: &child_nat, NULL, NULL); |
411 | *minimum = MAX (*minimum, child_min); |
412 | *natural = MAX (*natural, child_nat); |
413 | } |
414 | } |
415 | |
416 | static void |
417 | gtk_path_bar_update_slider_buttons (GtkPathBar *path_bar) |
418 | { |
419 | if (path_bar->button_list) |
420 | { |
421 | GtkWidget *button; |
422 | |
423 | button = BUTTON_DATA (path_bar->button_list->data)->button; |
424 | if (gtk_widget_get_child_visible (widget: button)) |
425 | gtk_widget_set_sensitive (widget: path_bar->down_slider_button, FALSE); |
426 | else |
427 | gtk_widget_set_sensitive (widget: path_bar->down_slider_button, TRUE); |
428 | |
429 | button = BUTTON_DATA (g_list_last (path_bar->button_list)->data)->button; |
430 | if (gtk_widget_get_child_visible (widget: button)) |
431 | gtk_widget_set_sensitive (widget: path_bar->up_slider_button, FALSE); |
432 | else |
433 | gtk_widget_set_sensitive (widget: path_bar->up_slider_button, TRUE); |
434 | } |
435 | } |
436 | |
437 | /* This is a tad complicated |
438 | */ |
439 | static void |
440 | gtk_path_bar_size_allocate (GtkWidget *widget, |
441 | int widget_width, |
442 | int widget_height, |
443 | int baseline) |
444 | { |
445 | GtkPathBar *path_bar = GTK_PATH_BAR (widget); |
446 | GtkWidget *child; |
447 | GtkTextDirection direction; |
448 | GtkAllocation child_allocation; |
449 | GList *list, *first_button; |
450 | int width; |
451 | int allocation_width; |
452 | gboolean need_sliders = TRUE; |
453 | int up_slider_offset = 0; |
454 | int down_slider_offset = 0; |
455 | GtkRequisition child_requisition; |
456 | |
457 | /* No path is set; we don't have to allocate anything. */ |
458 | if (path_bar->button_list == NULL) |
459 | return; |
460 | |
461 | direction = gtk_widget_get_direction (widget); |
462 | allocation_width = widget_width; |
463 | |
464 | /* First, we check to see if we need the scrollbars. */ |
465 | if (path_bar->fake_root) |
466 | width = path_bar->slider_width; |
467 | else |
468 | width = 0; |
469 | |
470 | for (list = path_bar->button_list; list; list = list->next) |
471 | { |
472 | child = BUTTON_DATA (list->data)->button; |
473 | |
474 | gtk_widget_get_preferred_size (widget: child, minimum_size: &child_requisition, NULL); |
475 | |
476 | width += child_requisition.width; |
477 | if (list == path_bar->fake_root) |
478 | break; |
479 | } |
480 | |
481 | if (width <= allocation_width) |
482 | { |
483 | if (path_bar->fake_root) |
484 | first_button = path_bar->fake_root; |
485 | else |
486 | first_button = g_list_last (list: path_bar->button_list); |
487 | } |
488 | else |
489 | { |
490 | gboolean reached_end = FALSE; |
491 | int slider_space = 2 * path_bar->slider_width; |
492 | |
493 | if (path_bar->first_scrolled_button) |
494 | first_button = path_bar->first_scrolled_button; |
495 | else |
496 | first_button = path_bar->button_list; |
497 | need_sliders = TRUE; |
498 | |
499 | /* To see how much space we have, and how many buttons we can display. |
500 | * We start at the first button, count forward until hit the new |
501 | * button, then count backwards. |
502 | */ |
503 | /* Count down the path chain towards the end. */ |
504 | gtk_widget_get_preferred_size (BUTTON_DATA (first_button->data)->button, |
505 | minimum_size: &child_requisition, NULL); |
506 | |
507 | width = child_requisition.width; |
508 | list = first_button->prev; |
509 | while (list && !reached_end) |
510 | { |
511 | child = BUTTON_DATA (list->data)->button; |
512 | |
513 | gtk_widget_get_preferred_size (widget: child, minimum_size: &child_requisition, NULL); |
514 | |
515 | if (width + child_requisition.width + slider_space > allocation_width) |
516 | reached_end = TRUE; |
517 | else if (list == path_bar->fake_root) |
518 | break; |
519 | else |
520 | width += child_requisition.width; |
521 | |
522 | list = list->prev; |
523 | } |
524 | |
525 | /* Finally, we walk up, seeing how many of the previous buttons we can |
526 | * add */ |
527 | while (first_button->next && !reached_end) |
528 | { |
529 | child = BUTTON_DATA (first_button->next->data)->button; |
530 | |
531 | gtk_widget_get_preferred_size (widget: child, minimum_size: &child_requisition, NULL); |
532 | |
533 | if (width + child_requisition.width + slider_space > allocation_width) |
534 | { |
535 | reached_end = TRUE; |
536 | } |
537 | else |
538 | { |
539 | width += child_requisition.width; |
540 | if (first_button == path_bar->fake_root) |
541 | break; |
542 | first_button = first_button->next; |
543 | } |
544 | } |
545 | } |
546 | |
547 | /* Now, we allocate space to the buttons */ |
548 | child_allocation.y = 0; |
549 | child_allocation.height = widget_height; |
550 | |
551 | if (direction == GTK_TEXT_DIR_RTL) |
552 | { |
553 | child_allocation.x = widget_width; |
554 | if (need_sliders || path_bar->fake_root) |
555 | { |
556 | child_allocation.x -= path_bar->slider_width; |
557 | up_slider_offset = widget_width - path_bar->slider_width; |
558 | } |
559 | } |
560 | else |
561 | { |
562 | child_allocation.x = 0; |
563 | if (need_sliders || path_bar->fake_root) |
564 | { |
565 | up_slider_offset = 0; |
566 | child_allocation.x += path_bar->slider_width; |
567 | } |
568 | } |
569 | |
570 | for (list = first_button; list; list = list->prev) |
571 | { |
572 | GtkAllocation widget_allocation; |
573 | ButtonData *button_data; |
574 | |
575 | button_data = BUTTON_DATA (list->data); |
576 | child = button_data->button; |
577 | |
578 | gtk_widget_get_preferred_size (widget: child, minimum_size: &child_requisition, NULL); |
579 | |
580 | child_allocation.width = MIN (child_requisition.width, |
581 | allocation_width - 2 * path_bar->slider_width); |
582 | |
583 | if (direction == GTK_TEXT_DIR_RTL) |
584 | child_allocation.x -= child_allocation.width; |
585 | |
586 | /* Check to see if we've don't have any more space to allocate buttons */ |
587 | if (need_sliders && direction == GTK_TEXT_DIR_RTL) |
588 | { |
589 | gtk_widget_get_allocation (widget, allocation: &widget_allocation); |
590 | if (child_allocation.x - path_bar->slider_width < widget_allocation.x) |
591 | break; |
592 | } |
593 | else if (need_sliders && direction == GTK_TEXT_DIR_LTR) |
594 | { |
595 | gtk_widget_get_allocation (widget, allocation: &widget_allocation); |
596 | if (child_allocation.x + child_allocation.width + path_bar->slider_width > |
597 | widget_allocation.x + allocation_width) |
598 | break; |
599 | } |
600 | |
601 | if (child_allocation.width < child_requisition.width) |
602 | { |
603 | if (!gtk_widget_get_has_tooltip (widget: child)) |
604 | gtk_widget_set_tooltip_text (widget: child, text: button_data->dir_name); |
605 | } |
606 | else if (gtk_widget_get_has_tooltip (widget: child)) |
607 | gtk_widget_set_tooltip_text (widget: child, NULL); |
608 | |
609 | gtk_widget_set_child_visible (widget: child, TRUE); |
610 | gtk_widget_size_allocate (widget: child, allocation: &child_allocation, baseline); |
611 | |
612 | if (direction == GTK_TEXT_DIR_RTL) |
613 | { |
614 | down_slider_offset = child_allocation.x - path_bar->slider_width; |
615 | } |
616 | else |
617 | { |
618 | down_slider_offset += child_allocation.width; |
619 | child_allocation.x += child_allocation.width; |
620 | } |
621 | } |
622 | /* Now we go hide all the widgets that don't fit */ |
623 | while (list) |
624 | { |
625 | child = BUTTON_DATA (list->data)->button; |
626 | gtk_widget_set_child_visible (widget: child, FALSE); |
627 | list = list->prev; |
628 | } |
629 | for (list = first_button->next; list; list = list->next) |
630 | { |
631 | child = BUTTON_DATA (list->data)->button; |
632 | gtk_widget_set_child_visible (widget: child, FALSE); |
633 | } |
634 | |
635 | if (need_sliders || path_bar->fake_root) |
636 | { |
637 | child_allocation.width = path_bar->slider_width; |
638 | child_allocation.x = up_slider_offset; |
639 | gtk_widget_size_allocate (widget: path_bar->up_slider_button, |
640 | allocation: &child_allocation, |
641 | baseline: -1); |
642 | |
643 | gtk_widget_set_child_visible (widget: path_bar->up_slider_button, TRUE); |
644 | gtk_widget_show (widget: path_bar->up_slider_button); |
645 | |
646 | if (direction == GTK_TEXT_DIR_LTR) |
647 | down_slider_offset += path_bar->slider_width; |
648 | } |
649 | else |
650 | { |
651 | gtk_widget_set_child_visible (widget: path_bar->up_slider_button, FALSE); |
652 | } |
653 | |
654 | if (need_sliders) |
655 | { |
656 | child_allocation.width = path_bar->slider_width; |
657 | child_allocation.x = down_slider_offset; |
658 | |
659 | gtk_widget_size_allocate (widget: path_bar->down_slider_button, |
660 | allocation: &child_allocation, |
661 | baseline: -1); |
662 | |
663 | gtk_widget_set_child_visible (widget: path_bar->down_slider_button, TRUE); |
664 | gtk_widget_show (widget: path_bar->down_slider_button); |
665 | gtk_path_bar_update_slider_buttons (path_bar); |
666 | } |
667 | else |
668 | { |
669 | gtk_widget_set_child_visible (widget: path_bar->down_slider_button, FALSE); |
670 | } |
671 | } |
672 | |
673 | static gboolean |
674 | gtk_path_bar_scroll_controller_scroll (GtkEventControllerScroll *scroll, |
675 | double dx, |
676 | double dy, |
677 | GtkPathBar *path_bar) |
678 | { |
679 | if (dy > 0) |
680 | gtk_path_bar_scroll_down (path_bar); |
681 | else if (dy < 0) |
682 | gtk_path_bar_scroll_up (path_bar); |
683 | |
684 | return GDK_EVENT_STOP; |
685 | } |
686 | |
687 | static void |
688 | gtk_path_bar_scroll_down (GtkPathBar *path_bar) |
689 | { |
690 | GtkAllocation allocation, button_allocation; |
691 | GList *list; |
692 | GList *down_button = NULL; |
693 | int space_available; |
694 | |
695 | if (gtk_widget_get_child_visible (BUTTON_DATA (path_bar->button_list->data)->button)) |
696 | { |
697 | /* Return if the last button is already visible */ |
698 | return; |
699 | } |
700 | |
701 | gtk_widget_queue_resize (GTK_WIDGET (path_bar)); |
702 | |
703 | /* We find the button at the 'down' end that we have to make |
704 | * visible */ |
705 | for (list = path_bar->button_list; list; list = list->next) |
706 | { |
707 | if (list->next && gtk_widget_get_child_visible (BUTTON_DATA (list->next->data)->button)) |
708 | { |
709 | down_button = list; |
710 | break; |
711 | } |
712 | } |
713 | g_assert (down_button); |
714 | |
715 | gtk_widget_get_allocation (GTK_WIDGET (path_bar), allocation: &allocation); |
716 | gtk_widget_get_allocation (BUTTON_DATA (down_button->data)->button, allocation: &button_allocation); |
717 | |
718 | space_available = (allocation.width |
719 | - 2 * path_bar->slider_width |
720 | - button_allocation.width); |
721 | path_bar->first_scrolled_button = down_button; |
722 | |
723 | /* We have space_available free space that's not being used. |
724 | * So we walk down from the end, adding buttons until we use all free space. |
725 | */ |
726 | while (space_available > 0) |
727 | { |
728 | path_bar->first_scrolled_button = down_button; |
729 | down_button = down_button->next; |
730 | if (!down_button) |
731 | break; |
732 | space_available -= button_allocation.width; |
733 | } |
734 | } |
735 | |
736 | static void |
737 | gtk_path_bar_scroll_up (GtkPathBar *path_bar) |
738 | { |
739 | GList *list; |
740 | |
741 | list = g_list_last (list: path_bar->button_list); |
742 | |
743 | if (gtk_widget_get_child_visible (BUTTON_DATA (list->data)->button)) |
744 | { |
745 | /* Return if the first button is already visible */ |
746 | return; |
747 | } |
748 | |
749 | gtk_widget_queue_resize (GTK_WIDGET (path_bar)); |
750 | |
751 | for ( ; list; list = list->prev) |
752 | { |
753 | if (list->prev && gtk_widget_get_child_visible (BUTTON_DATA (list->prev->data)->button)) |
754 | { |
755 | if (list->prev == path_bar->fake_root) |
756 | path_bar->fake_root = NULL; |
757 | path_bar->first_scrolled_button = list; |
758 | return; |
759 | } |
760 | } |
761 | } |
762 | |
763 | static void |
764 | gtk_path_bar_clear_buttons (GtkPathBar *path_bar) |
765 | { |
766 | GtkWidget *w; |
767 | |
768 | w = gtk_widget_get_first_child (GTK_WIDGET (path_bar)); |
769 | while (w) |
770 | { |
771 | GtkWidget *next = gtk_widget_get_next_sibling (widget: w); |
772 | |
773 | if (w != path_bar->up_slider_button && w != path_bar->down_slider_button) |
774 | { |
775 | gtk_widget_unparent (widget: w); |
776 | } |
777 | |
778 | w = next; |
779 | } |
780 | |
781 | path_bar->first_scrolled_button = NULL; |
782 | path_bar->fake_root = NULL; |
783 | } |
784 | |
785 | static void |
786 | button_clicked_cb (GtkWidget *button, |
787 | gpointer data) |
788 | { |
789 | GtkPathBar *path_bar; |
790 | ButtonData *button_data; |
791 | GList *button_list; |
792 | gboolean child_is_hidden; |
793 | GFile *child_file; |
794 | |
795 | button_data = BUTTON_DATA (data); |
796 | if (button_data->ignore_changes) |
797 | return; |
798 | |
799 | path_bar = GTK_PATH_BAR (gtk_widget_get_parent (button)); |
800 | |
801 | button_list = g_list_find (list: path_bar->button_list, data: button_data); |
802 | g_assert (button_list != NULL); |
803 | |
804 | g_signal_handlers_block_by_func (button, |
805 | G_CALLBACK (button_clicked_cb), data); |
806 | gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE); |
807 | g_signal_handlers_unblock_by_func (button, |
808 | G_CALLBACK (button_clicked_cb), data); |
809 | |
810 | if (button_list->prev) |
811 | { |
812 | ButtonData *child_data; |
813 | |
814 | child_data = BUTTON_DATA (button_list->prev->data); |
815 | child_file = child_data->file; |
816 | child_is_hidden = child_data->file_is_hidden; |
817 | } |
818 | else |
819 | { |
820 | child_file = NULL; |
821 | child_is_hidden = FALSE; |
822 | } |
823 | |
824 | g_signal_emit (instance: path_bar, signal_id: path_bar_signals [PATH_CLICKED], detail: 0, |
825 | button_data->file, child_file, child_is_hidden); |
826 | } |
827 | |
828 | struct SetButtonImageData |
829 | { |
830 | GtkPathBar *path_bar; |
831 | ButtonData *button_data; |
832 | }; |
833 | |
834 | static void |
835 | set_button_image_get_info_cb (GObject *source, |
836 | GAsyncResult *result, |
837 | gpointer user_data) |
838 | { |
839 | GFile *file = G_FILE (source); |
840 | struct SetButtonImageData *data = user_data; |
841 | GFileInfo *info; |
842 | GIcon *icon; |
843 | |
844 | info = g_file_query_info_finish (file, res: result, NULL); |
845 | if (!info) |
846 | goto out; |
847 | |
848 | g_assert (GTK_IS_PATH_BAR (data->path_bar)); |
849 | g_assert (G_OBJECT (data->path_bar)->ref_count > 0); |
850 | |
851 | cancellable_async_done (path_bar: data->path_bar, cancellable: data->button_data->cancellable); |
852 | data->button_data->cancellable = NULL; |
853 | |
854 | icon = g_file_info_get_symbolic_icon (info); |
855 | gtk_image_set_from_gicon (GTK_IMAGE (data->button_data->image), icon); |
856 | |
857 | switch (data->button_data->type) |
858 | { |
859 | case HOME_BUTTON: |
860 | g_set_object (&data->path_bar->home_icon, icon); |
861 | break; |
862 | |
863 | case DESKTOP_BUTTON: |
864 | g_set_object (&data->path_bar->desktop_icon, icon); |
865 | break; |
866 | |
867 | case NORMAL_BUTTON: |
868 | case ROOT_BUTTON: |
869 | default: |
870 | break; |
871 | }; |
872 | |
873 | out: |
874 | g_free (mem: data); |
875 | } |
876 | |
877 | static void |
878 | set_button_image (GtkPathBar *path_bar, |
879 | ButtonData *button_data) |
880 | { |
881 | struct SetButtonImageData *data; |
882 | GMount *mount; |
883 | |
884 | switch (button_data->type) |
885 | { |
886 | case ROOT_BUTTON: |
887 | |
888 | if (path_bar->root_icon != NULL) |
889 | { |
890 | gtk_image_set_from_gicon (GTK_IMAGE (button_data->image), icon: path_bar->root_icon); |
891 | break; |
892 | } |
893 | |
894 | mount = g_file_find_enclosing_mount (file: button_data->file, NULL, NULL); |
895 | |
896 | if (!mount && g_file_is_native (file: button_data->file)) |
897 | path_bar->root_icon = g_themed_icon_new (iconname: "drive-harddisk-symbolic" ); |
898 | else if (mount) |
899 | path_bar->root_icon = g_mount_get_symbolic_icon (mount); |
900 | else |
901 | path_bar->root_icon = NULL; |
902 | |
903 | g_clear_object (&mount); |
904 | |
905 | gtk_image_set_from_gicon (GTK_IMAGE (button_data->image), icon: path_bar->root_icon); |
906 | |
907 | break; |
908 | |
909 | case HOME_BUTTON: |
910 | if (path_bar->home_icon != NULL) |
911 | { |
912 | gtk_image_set_from_gicon (GTK_IMAGE (button_data->image), icon: path_bar->home_icon); |
913 | break; |
914 | } |
915 | |
916 | data = g_new0 (struct SetButtonImageData, 1); |
917 | data->path_bar = path_bar; |
918 | data->button_data = button_data; |
919 | |
920 | if (button_data->cancellable) |
921 | { |
922 | cancel_cancellable (path_bar, cancellable: button_data->cancellable); |
923 | g_clear_object (&button_data->cancellable); |
924 | } |
925 | |
926 | button_data->cancellable = g_cancellable_new (); |
927 | g_file_query_info_async (file: path_bar->home_file, |
928 | attributes: "standard::symbolic-icon" , |
929 | flags: G_FILE_QUERY_INFO_NONE, |
930 | G_PRIORITY_DEFAULT, |
931 | cancellable: button_data->cancellable, |
932 | callback: set_button_image_get_info_cb, |
933 | user_data: data); |
934 | add_cancellable (path_bar, cancellable: button_data->cancellable); |
935 | break; |
936 | |
937 | case DESKTOP_BUTTON: |
938 | if (path_bar->desktop_icon != NULL) |
939 | { |
940 | gtk_image_set_from_gicon (GTK_IMAGE (button_data->image), icon: path_bar->desktop_icon); |
941 | break; |
942 | } |
943 | |
944 | data = g_new0 (struct SetButtonImageData, 1); |
945 | data->path_bar = path_bar; |
946 | data->button_data = button_data; |
947 | |
948 | if (button_data->cancellable) |
949 | { |
950 | cancel_cancellable (path_bar, cancellable: button_data->cancellable); |
951 | g_clear_object (&button_data->cancellable); |
952 | } |
953 | |
954 | button_data->cancellable = g_cancellable_new (); |
955 | g_file_query_info_async (file: path_bar->desktop_file, |
956 | attributes: "standard::symbolic-icon" , |
957 | flags: G_FILE_QUERY_INFO_NONE, |
958 | G_PRIORITY_DEFAULT, |
959 | cancellable: button_data->cancellable, |
960 | callback: set_button_image_get_info_cb, |
961 | user_data: data); |
962 | add_cancellable (path_bar, cancellable: button_data->cancellable); |
963 | break; |
964 | |
965 | case NORMAL_BUTTON: |
966 | default: |
967 | break; |
968 | } |
969 | } |
970 | |
971 | static void |
972 | button_data_free (ButtonData *button_data) |
973 | { |
974 | g_clear_object (&button_data->file); |
975 | g_free (mem: button_data->dir_name); |
976 | g_free (mem: button_data); |
977 | } |
978 | |
979 | static const char * |
980 | get_dir_name (ButtonData *button_data) |
981 | { |
982 | return button_data->dir_name; |
983 | } |
984 | |
985 | static void |
986 | gtk_path_bar_update_button_appearance (GtkPathBar *path_bar, |
987 | ButtonData *button_data, |
988 | gboolean current_dir) |
989 | { |
990 | const char *dir_name = get_dir_name (button_data); |
991 | |
992 | gtk_widget_remove_css_class (widget: button_data->button, css_class: "text-button" ); |
993 | gtk_widget_remove_css_class (widget: button_data->button, css_class: "image-button" ); |
994 | |
995 | if (button_data->label != NULL) |
996 | { |
997 | gtk_label_set_text (GTK_LABEL (button_data->label), str: dir_name); |
998 | if (button_data->image == NULL) |
999 | gtk_widget_add_css_class (widget: button_data->button, css_class: "text-button" ); |
1000 | } |
1001 | |
1002 | if (button_data->image != NULL) |
1003 | { |
1004 | set_button_image (path_bar, button_data); |
1005 | if (button_data->label == NULL) |
1006 | gtk_widget_add_css_class (widget: button_data->button, css_class: "image-button" ); |
1007 | } |
1008 | |
1009 | if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button_data->button)) != current_dir) |
1010 | { |
1011 | button_data->ignore_changes = TRUE; |
1012 | gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button_data->button), is_active: current_dir); |
1013 | button_data->ignore_changes = FALSE; |
1014 | } |
1015 | } |
1016 | |
1017 | static ButtonType |
1018 | find_button_type (GtkPathBar *path_bar, |
1019 | GFile *file) |
1020 | { |
1021 | if (path_bar->root_file != NULL && |
1022 | g_file_equal (file1: file, file2: path_bar->root_file)) |
1023 | return ROOT_BUTTON; |
1024 | if (path_bar->home_file != NULL && |
1025 | g_file_equal (file1: file, file2: path_bar->home_file)) |
1026 | return HOME_BUTTON; |
1027 | if (path_bar->desktop_file != NULL && |
1028 | g_file_equal (file1: file, file2: path_bar->desktop_file)) |
1029 | return DESKTOP_BUTTON; |
1030 | |
1031 | return NORMAL_BUTTON; |
1032 | } |
1033 | |
1034 | static ButtonData * |
1035 | make_directory_button (GtkPathBar *path_bar, |
1036 | const char *dir_name, |
1037 | GFile *file, |
1038 | gboolean current_dir, |
1039 | gboolean file_is_hidden) |
1040 | { |
1041 | GtkWidget *child = NULL; |
1042 | ButtonData *button_data; |
1043 | GdkContentProvider *content; |
1044 | GtkDragSource *source; |
1045 | |
1046 | file_is_hidden = !! file_is_hidden; |
1047 | /* Is it a special button? */ |
1048 | button_data = g_new0 (ButtonData, 1); |
1049 | button_data->type = find_button_type (path_bar, file); |
1050 | button_data->button = gtk_toggle_button_new (); |
1051 | gtk_widget_set_focus_on_click (widget: button_data->button, FALSE); |
1052 | |
1053 | switch (button_data->type) |
1054 | { |
1055 | case ROOT_BUTTON: |
1056 | button_data->image = gtk_image_new (); |
1057 | child = button_data->image; |
1058 | button_data->label = NULL; |
1059 | break; |
1060 | case HOME_BUTTON: |
1061 | case DESKTOP_BUTTON: |
1062 | button_data->image = gtk_image_new (); |
1063 | button_data->label = gtk_label_new (NULL); |
1064 | child = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 0); |
1065 | gtk_box_append (GTK_BOX (child), child: button_data->image); |
1066 | gtk_box_append (GTK_BOX (child), child: button_data->label); |
1067 | break; |
1068 | case NORMAL_BUTTON: |
1069 | default: |
1070 | button_data->label = gtk_label_new (NULL); |
1071 | child = button_data->label; |
1072 | button_data->image = NULL; |
1073 | } |
1074 | |
1075 | button_data->dir_name = g_strdup (str: dir_name); |
1076 | button_data->file = g_object_ref (file); |
1077 | button_data->file_is_hidden = file_is_hidden; |
1078 | |
1079 | gtk_button_set_child (GTK_BUTTON (button_data->button), child); |
1080 | |
1081 | gtk_path_bar_update_button_appearance (path_bar, button_data, current_dir); |
1082 | |
1083 | g_signal_connect (button_data->button, "clicked" , |
1084 | G_CALLBACK (button_clicked_cb), |
1085 | button_data); |
1086 | g_object_weak_ref (G_OBJECT (button_data->button), |
1087 | notify: (GWeakNotify) button_data_free, data: button_data); |
1088 | |
1089 | source = gtk_drag_source_new (); |
1090 | content = gdk_content_provider_new_typed (G_TYPE_FILE, button_data->file); |
1091 | gtk_drag_source_set_content (source, content); |
1092 | g_object_unref (object: content); |
1093 | gtk_widget_add_controller (widget: button_data->button, GTK_EVENT_CONTROLLER (source)); |
1094 | |
1095 | return button_data; |
1096 | } |
1097 | |
1098 | static gboolean |
1099 | gtk_path_bar_check_parent_path (GtkPathBar *path_bar, |
1100 | GFile *file) |
1101 | { |
1102 | GList *list; |
1103 | GList *current_path = NULL; |
1104 | gboolean need_new_fake_root = FALSE; |
1105 | |
1106 | for (list = path_bar->button_list; list; list = list->next) |
1107 | { |
1108 | ButtonData *button_data; |
1109 | |
1110 | button_data = list->data; |
1111 | if (g_file_equal (file1: file, file2: button_data->file)) |
1112 | { |
1113 | current_path = list; |
1114 | break; |
1115 | } |
1116 | if (list == path_bar->fake_root) |
1117 | need_new_fake_root = TRUE; |
1118 | } |
1119 | |
1120 | if (current_path) |
1121 | { |
1122 | if (need_new_fake_root) |
1123 | { |
1124 | path_bar->fake_root = NULL; |
1125 | for (list = current_path; list; list = list->next) |
1126 | { |
1127 | ButtonData *button_data; |
1128 | |
1129 | button_data = list->data; |
1130 | if (BUTTON_IS_FAKE_ROOT (button_data)) |
1131 | { |
1132 | path_bar->fake_root = list; |
1133 | break; |
1134 | } |
1135 | } |
1136 | } |
1137 | |
1138 | for (list = path_bar->button_list; list; list = list->next) |
1139 | { |
1140 | gtk_path_bar_update_button_appearance (path_bar, |
1141 | BUTTON_DATA (list->data), |
1142 | current_dir: (list == current_path) ? TRUE : FALSE); |
1143 | } |
1144 | |
1145 | if (!gtk_widget_get_child_visible (BUTTON_DATA (current_path->data)->button)) |
1146 | { |
1147 | path_bar->first_scrolled_button = current_path; |
1148 | gtk_widget_queue_resize (GTK_WIDGET (path_bar)); |
1149 | } |
1150 | |
1151 | return TRUE; |
1152 | } |
1153 | return FALSE; |
1154 | } |
1155 | |
1156 | |
1157 | struct SetFileInfo |
1158 | { |
1159 | GFile *file; |
1160 | GFile *parent_file; |
1161 | GtkPathBar *path_bar; |
1162 | GList *new_buttons; |
1163 | GList *fake_root; |
1164 | GCancellable *cancellable; |
1165 | gboolean first_directory; |
1166 | }; |
1167 | |
1168 | static void |
1169 | gtk_path_bar_set_file_finish (struct SetFileInfo *info, |
1170 | gboolean result) |
1171 | { |
1172 | if (result) |
1173 | { |
1174 | GList *l; |
1175 | |
1176 | gtk_path_bar_clear_buttons (path_bar: info->path_bar); |
1177 | info->path_bar->button_list = g_list_reverse (list: info->new_buttons); |
1178 | info->path_bar->fake_root = info->fake_root; |
1179 | |
1180 | for (l = info->path_bar->button_list; l; l = l->next) |
1181 | { |
1182 | GtkWidget *button = BUTTON_DATA (l->data)->button; |
1183 | |
1184 | gtk_widget_insert_after (widget: button, GTK_WIDGET (info->path_bar), previous_sibling: info->path_bar->up_slider_button); |
1185 | } |
1186 | } |
1187 | else |
1188 | { |
1189 | GList *l; |
1190 | |
1191 | for (l = info->new_buttons; l; l = l->next) |
1192 | { |
1193 | ButtonData *button_data = BUTTON_DATA (l->data); |
1194 | |
1195 | gtk_widget_unparent (widget: button_data->button); |
1196 | } |
1197 | |
1198 | g_list_free (list: info->new_buttons); |
1199 | } |
1200 | |
1201 | if (info->file) |
1202 | g_object_unref (object: info->file); |
1203 | if (info->parent_file) |
1204 | g_object_unref (object: info->parent_file); |
1205 | |
1206 | g_free (mem: info); |
1207 | } |
1208 | |
1209 | static void |
1210 | gtk_path_bar_get_info_callback (GObject *source, |
1211 | GAsyncResult *result, |
1212 | gpointer data) |
1213 | { |
1214 | GFile *file = G_FILE (source); |
1215 | struct SetFileInfo *file_info = data; |
1216 | GFileInfo *info; |
1217 | ButtonData *button_data; |
1218 | const char *display_name; |
1219 | gboolean is_hidden; |
1220 | |
1221 | info = g_file_query_info_finish (file, res: result, NULL); |
1222 | if (!info) |
1223 | { |
1224 | gtk_path_bar_set_file_finish (info: file_info, FALSE); |
1225 | return; |
1226 | } |
1227 | |
1228 | g_assert (GTK_IS_PATH_BAR (file_info->path_bar)); |
1229 | g_assert (G_OBJECT (file_info->path_bar)->ref_count > 0); |
1230 | |
1231 | cancellable_async_done (path_bar: file_info->path_bar, cancellable: file_info->cancellable); |
1232 | if (file_info->path_bar->get_info_cancellable == file_info->cancellable) |
1233 | file_info->path_bar->get_info_cancellable = NULL; |
1234 | file_info->cancellable = NULL; |
1235 | |
1236 | display_name = g_file_info_get_display_name (info); |
1237 | is_hidden = g_file_info_get_is_hidden (info) || g_file_info_get_is_backup (info); |
1238 | |
1239 | button_data = make_directory_button (path_bar: file_info->path_bar, dir_name: display_name, |
1240 | file: file_info->file, |
1241 | current_dir: file_info->first_directory, file_is_hidden: is_hidden); |
1242 | g_clear_object (&file_info->file); |
1243 | |
1244 | file_info->new_buttons = g_list_prepend (list: file_info->new_buttons, data: button_data); |
1245 | |
1246 | if (BUTTON_IS_FAKE_ROOT (button_data)) |
1247 | file_info->fake_root = file_info->new_buttons; |
1248 | |
1249 | /* We have assigned the info for the innermost button, i.e. the deepest directory. |
1250 | * Now, go on to fetch the info for this directory's parent. |
1251 | */ |
1252 | |
1253 | file_info->file = file_info->parent_file; |
1254 | file_info->first_directory = FALSE; |
1255 | |
1256 | if (!file_info->file) |
1257 | { |
1258 | /* No parent? Okay, we are done. */ |
1259 | gtk_path_bar_set_file_finish (info: file_info, TRUE); |
1260 | return; |
1261 | } |
1262 | |
1263 | file_info->parent_file = g_file_get_parent (file: file_info->file); |
1264 | |
1265 | /* Recurse asynchronously */ |
1266 | file_info->cancellable = g_cancellable_new (); |
1267 | file_info->path_bar->get_info_cancellable = file_info->cancellable; |
1268 | g_file_query_info_async (file: file_info->file, |
1269 | attributes: "standard::display-name," |
1270 | "standard::is-hidden," |
1271 | "standard::is-backup" , |
1272 | flags: G_FILE_QUERY_INFO_NONE, |
1273 | G_PRIORITY_DEFAULT, |
1274 | cancellable: file_info->cancellable, |
1275 | callback: gtk_path_bar_get_info_callback, |
1276 | user_data: file_info); |
1277 | add_cancellable (path_bar: file_info->path_bar, cancellable: file_info->cancellable); |
1278 | } |
1279 | |
1280 | void |
1281 | _gtk_path_bar_set_file (GtkPathBar *path_bar, |
1282 | GFile *file, |
1283 | gboolean keep_trail) |
1284 | { |
1285 | struct SetFileInfo *info; |
1286 | |
1287 | g_return_if_fail (GTK_IS_PATH_BAR (path_bar)); |
1288 | g_return_if_fail (G_IS_FILE (file)); |
1289 | |
1290 | /* Check whether the new path is already present in the pathbar as buttons. |
1291 | * This could be a parent directory or a previous selected subdirectory. |
1292 | */ |
1293 | if (keep_trail && gtk_path_bar_check_parent_path (path_bar, file)) |
1294 | return; |
1295 | |
1296 | info = g_new0 (struct SetFileInfo, 1); |
1297 | info->file = g_object_ref (file); |
1298 | info->path_bar = path_bar; |
1299 | info->first_directory = TRUE; |
1300 | info->parent_file = g_file_get_parent (file: info->file); |
1301 | |
1302 | if (path_bar->get_info_cancellable) |
1303 | cancel_cancellable (path_bar, cancellable: path_bar->get_info_cancellable); |
1304 | |
1305 | info->cancellable = g_cancellable_new (); |
1306 | path_bar->get_info_cancellable = info->cancellable; |
1307 | g_file_query_info_async (file: info->file, |
1308 | attributes: "standard::display-name," |
1309 | "standard::is-hidden," |
1310 | "standard::is-backup" , |
1311 | flags: G_FILE_QUERY_INFO_NONE, |
1312 | G_PRIORITY_DEFAULT, |
1313 | cancellable: info->cancellable, |
1314 | callback: gtk_path_bar_get_info_callback, |
1315 | user_data: info); |
1316 | add_cancellable (path_bar, cancellable: info->cancellable); |
1317 | } |
1318 | |
1319 | /** |
1320 | * _gtk_path_bar_up: |
1321 | * @path_bar: a `GtkPathBar` |
1322 | * |
1323 | * If the selected button in the pathbar is not the furthest button “up” (in the |
1324 | * root direction), act as if the user clicked on the next button up. |
1325 | **/ |
1326 | void |
1327 | _gtk_path_bar_up (GtkPathBar *path_bar) |
1328 | { |
1329 | GList *l; |
1330 | |
1331 | for (l = path_bar->button_list; l; l = l->next) |
1332 | { |
1333 | GtkWidget *button = BUTTON_DATA (l->data)->button; |
1334 | if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))) |
1335 | { |
1336 | if (l->next) |
1337 | { |
1338 | GtkWidget *next_button = BUTTON_DATA (l->next->data)->button; |
1339 | button_clicked_cb (button: next_button, data: l->next->data); |
1340 | } |
1341 | break; |
1342 | } |
1343 | } |
1344 | } |
1345 | |
1346 | /** |
1347 | * _gtk_path_bar_down: |
1348 | * @path_bar: a `GtkPathBar` |
1349 | * |
1350 | * If the selected button in the pathbar is not the furthest button “down” (in the |
1351 | * leaf direction), act as if the user clicked on the next button down. |
1352 | **/ |
1353 | void |
1354 | _gtk_path_bar_down (GtkPathBar *path_bar) |
1355 | { |
1356 | GList *l; |
1357 | |
1358 | for (l = path_bar->button_list; l; l = l->next) |
1359 | { |
1360 | GtkWidget *button = BUTTON_DATA (l->data)->button; |
1361 | if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))) |
1362 | { |
1363 | if (l->prev) |
1364 | { |
1365 | GtkWidget *prev_button = BUTTON_DATA (l->prev->data)->button; |
1366 | button_clicked_cb (button: prev_button, data: l->prev->data); |
1367 | } |
1368 | break; |
1369 | } |
1370 | } |
1371 | } |
1372 | |