1 /*
2  * Nautilus
3  *
4  * Copyright (C) 2011, Red Hat, Inc.
5  *
6  * Nautilus is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (at your option) any later version.
10  *
11  * Nautilus is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see <http://www.gnu.org/licenses/>.
18  *
19  * Author: Cosimo Cecchi <cosimoc@redhat.com>
20  *
21  */
22 
23 #include "nautilus-toolbar.h"
24 
25 #include <glib/gi18n.h>
26 #include <math.h>
27 
28 #include "animation/ide-box-theatric.h"
29 #include "animation/egg-animation.h"
30 
31 #include "nautilus-application.h"
32 #include "nautilus-bookmark.h"
33 #include "nautilus-file-operations.h"
34 #include "nautilus-file-undo-manager.h"
35 #include "nautilus-global-preferences.h"
36 #include "nautilus-location-entry.h"
37 #include "nautilus-pathbar.h"
38 #include "nautilus-progress-info-manager.h"
39 #include "nautilus-progress-info-widget.h"
40 #include "nautilus-toolbar-menu-sections.h"
41 #include "nautilus-ui-utilities.h"
42 #include "nautilus-window.h"
43 #include "nautilus-container-max-width.h"
44 
45 #define OPERATION_MINIMUM_TIME 2 /*s */
46 #define NEEDS_ATTENTION_ANIMATION_TIMEOUT 2000 /*ms */
47 #define REMOVE_FINISHED_OPERATIONS_TIEMOUT 3 /*s */
48 
49 #define ANIMATION_X_GROW 30
50 #define ANIMATION_Y_GROW 30
51 
52 /* Just design, context at https://gitlab.gnome.org/GNOME/nautilus/issues/548#note_274131 */
53 #define SWITCHER_MAX_WIDTH 840
54 
55 typedef enum
56 {
57     NAUTILUS_NAVIGATION_DIRECTION_NONE,
58     NAUTILUS_NAVIGATION_DIRECTION_BACK,
59     NAUTILUS_NAVIGATION_DIRECTION_FORWARD
60 } NautilusNavigationDirection;
61 
62 struct _NautilusToolbar
63 {
64     GtkHeaderBar parent_instance;
65 
66     NautilusWindow *window;
67 
68     GtkWidget *path_bar_container;
69     GtkWidget *location_entry_container;
70     GtkWidget *search_container;
71     GtkWidget *toolbar_switcher;
72     GtkWidget *toolbar_switcher_container;
73     NautilusContainerMaxWidth *toolbar_switcher_container_max_width;
74     GtkWidget *path_bar;
75     GtkWidget *location_entry;
76 
77     gboolean show_location_entry;
78     gboolean location_entry_should_auto_hide;
79 
80     guint start_operations_timeout_id;
81     guint remove_finished_operations_timeout_id;
82     guint operations_button_attention_timeout_id;
83 
84     GtkWidget *operations_button;
85     GtkWidget *view_button;
86     GtkWidget *view_menu_zoom_section;
87     GtkWidget *view_menu_undo_redo_section;
88     GtkWidget *view_menu_extended_section;
89     GtkWidget *undo_button;
90     GtkWidget *redo_button;
91     GtkWidget *view_toggle_button;
92     GtkWidget *view_toggle_icon;
93 
94     GtkWidget *app_menu;
95 
96     GtkWidget *operations_popover;
97     GtkWidget *operations_container;
98     GtkWidget *operations_revealer;
99     GtkWidget *operations_icon;
100 
101     GtkWidget *forward_button;
102     GtkGesture *forward_button_longpress_gesture;
103     GtkGesture *forward_button_multi_press_gesture;
104 
105     GtkWidget *back_button;
106     GtkGesture *back_button_longpress_gesture;
107     GtkGesture *back_button_multi_press_gesture;
108 
109     GtkWidget *search_button;
110 
111     GtkWidget *location_entry_close_button;
112 
113     NautilusProgressInfoManager *progress_manager;
114 
115     /* active slot & bindings */
116     NautilusWindowSlot *window_slot;
117     GBinding *icon_binding;
118     GBinding *search_binding;
119     GBinding *tooltip_binding;
120 };
121 
122 enum
123 {
124     PROP_WINDOW = 1,
125     PROP_SHOW_LOCATION_ENTRY,
126     PROP_WINDOW_SLOT,
127     PROP_SEARCHING,
128     NUM_PROPERTIES
129 };
130 
131 static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
132 
133 G_DEFINE_TYPE (NautilusToolbar, nautilus_toolbar, HDY_TYPE_HEADER_BAR);
134 
135 static void nautilus_toolbar_set_window_slot_real (NautilusToolbar    *self,
136                                                    NautilusWindowSlot *slot);
137 static void update_operations (NautilusToolbar *self);
138 
139 static void
toolbar_update_appearance(NautilusToolbar * self)140 toolbar_update_appearance (NautilusToolbar *self)
141 {
142     gboolean show_location_entry;
143 
144     show_location_entry = self->show_location_entry ||
145                           g_settings_get_boolean (nautilus_preferences,
146                                                   NAUTILUS_PREFERENCES_ALWAYS_USE_LOCATION_ENTRY);
147 
148     if (self->window_slot != NULL &&
149         nautilus_window_slot_get_searching (self->window_slot))
150     {
151         gtk_stack_set_visible_child_name (GTK_STACK (self->toolbar_switcher), "search");
152     }
153     else if (show_location_entry)
154     {
155         gtk_stack_set_visible_child_name (GTK_STACK (self->toolbar_switcher), "location");
156     }
157     else
158     {
159         gtk_stack_set_visible_child_name (GTK_STACK (self->toolbar_switcher), "pathbar");
160     }
161 }
162 
163 static void
activate_back_or_forward_menu_item(GtkMenuItem * menu_item,NautilusWindowSlot * window_slot,gboolean back)164 activate_back_or_forward_menu_item (GtkMenuItem        *menu_item,
165                                     NautilusWindowSlot *window_slot,
166                                     gboolean            back)
167 {
168     int index;
169 
170     g_assert (GTK_IS_MENU_ITEM (menu_item));
171 
172     index = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (menu_item), "user_data"));
173 
174     nautilus_window_slot_back_or_forward (window_slot, back, index);
175 }
176 
177 static void
activate_back_menu_item_callback(GtkMenuItem * menu_item,NautilusToolbar * self)178 activate_back_menu_item_callback (GtkMenuItem     *menu_item,
179                                   NautilusToolbar *self)
180 {
181     activate_back_or_forward_menu_item (menu_item, self->window_slot, TRUE);
182 }
183 
184 static void
activate_forward_menu_item_callback(GtkMenuItem * menu_item,NautilusToolbar * self)185 activate_forward_menu_item_callback (GtkMenuItem     *menu_item,
186                                      NautilusToolbar *self)
187 {
188     activate_back_or_forward_menu_item (menu_item, self->window_slot, FALSE);
189 }
190 
191 static void
fill_menu(NautilusToolbar * self,GtkWidget * menu,gboolean back)192 fill_menu (NautilusToolbar *self,
193            GtkWidget       *menu,
194            gboolean         back)
195 {
196     GtkWidget *menu_item;
197     int index;
198     GList *list;
199 
200     list = back ? nautilus_window_slot_get_back_history (self->window_slot) :
201            nautilus_window_slot_get_forward_history (self->window_slot);
202 
203     index = 0;
204     while (list != NULL)
205     {
206         menu_item = nautilus_bookmark_menu_item_new (NAUTILUS_BOOKMARK (list->data));
207         g_object_set_data (G_OBJECT (menu_item), "user_data", GINT_TO_POINTER (index));
208         gtk_widget_show (GTK_WIDGET (menu_item));
209         g_signal_connect_object (menu_item, "activate",
210                                  back
211                                  ? G_CALLBACK (activate_back_menu_item_callback)
212                                  : G_CALLBACK (activate_forward_menu_item_callback),
213                                  self, 0);
214 
215         gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
216         list = g_list_next (list);
217         ++index;
218     }
219 }
220 
221 static void
show_menu(NautilusToolbar * self,GtkWidget * widget,const GdkEvent * event)222 show_menu (NautilusToolbar *self,
223            GtkWidget       *widget,
224            const GdkEvent  *event)
225 {
226     GtkWidget *menu;
227     NautilusNavigationDirection direction;
228 
229     menu = gtk_menu_new ();
230 
231     direction = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (widget),
232                                                      "nav-direction"));
233 
234     switch (direction)
235     {
236         case NAUTILUS_NAVIGATION_DIRECTION_FORWARD:
237         {
238             fill_menu (self, menu, FALSE);
239         }
240         break;
241 
242         case NAUTILUS_NAVIGATION_DIRECTION_BACK:
243         {
244             fill_menu (self, menu, TRUE);
245         }
246         break;
247 
248         default:
249         {
250             g_assert_not_reached ();
251         }
252         break;
253     }
254 
255     gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (self->window), NULL);
256     gtk_menu_popup_at_widget (GTK_MENU (menu), widget,
257                               GDK_GRAVITY_SOUTH_WEST,
258                               GDK_GRAVITY_NORTH_WEST,
259                               event);
260 }
261 
262 static void
navigation_button_press_cb(GtkGestureMultiPress * gesture,gint n_press,gdouble x,gdouble y,gpointer user_data)263 navigation_button_press_cb (GtkGestureMultiPress *gesture,
264                             gint                  n_press,
265                             gdouble               x,
266                             gdouble               y,
267                             gpointer              user_data)
268 {
269     NautilusToolbar *self;
270     GtkWidget *widget;
271     GdkEventSequence *sequence;
272     const GdkEvent *event;
273 
274     self = NAUTILUS_TOOLBAR (user_data);
275     widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
276     sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
277     event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
278 
279     show_menu (self, widget, event);
280 }
281 
282 static void
back_button_longpress_cb(GtkGestureLongPress * gesture,double x,double y,gpointer user_data)283 back_button_longpress_cb (GtkGestureLongPress *gesture,
284                           double               x,
285                           double               y,
286                           gpointer             user_data)
287 {
288     NautilusToolbar *self = user_data;
289     GdkEventSequence *sequence;
290     const GdkEvent *event;
291 
292     sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
293     event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
294 
295     show_menu (self, self->back_button, event);
296 }
297 
298 static void
forward_button_longpress_cb(GtkGestureLongPress * gesture,double x,double y,gpointer user_data)299 forward_button_longpress_cb (GtkGestureLongPress *gesture,
300                              double               x,
301                              double               y,
302                              gpointer             user_data)
303 {
304     NautilusToolbar *self = user_data;
305     GdkEventSequence *sequence;
306     const GdkEvent *event;
307 
308     sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
309     event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
310 
311     show_menu (self, self->forward_button, event);
312 }
313 
314 static gboolean
should_show_progress_info(NautilusProgressInfo * info)315 should_show_progress_info (NautilusProgressInfo *info)
316 {
317     return nautilus_progress_info_get_total_elapsed_time (info) +
318            nautilus_progress_info_get_remaining_time (info) > OPERATION_MINIMUM_TIME;
319 }
320 
321 static GList *
get_filtered_progress_infos(NautilusToolbar * self)322 get_filtered_progress_infos (NautilusToolbar *self)
323 {
324     GList *l;
325     GList *filtered_progress_infos;
326     GList *progress_infos;
327 
328     progress_infos = nautilus_progress_info_manager_get_all_infos (self->progress_manager);
329     filtered_progress_infos = NULL;
330 
331     for (l = progress_infos; l != NULL; l = l->next)
332     {
333         if (should_show_progress_info (l->data))
334         {
335             filtered_progress_infos = g_list_append (filtered_progress_infos, l->data);
336         }
337     }
338 
339     return filtered_progress_infos;
340 }
341 
342 static gboolean
should_hide_operations_button(NautilusToolbar * self)343 should_hide_operations_button (NautilusToolbar *self)
344 {
345     GList *progress_infos;
346     GList *l;
347 
348     progress_infos = get_filtered_progress_infos (self);
349 
350     for (l = progress_infos; l != NULL; l = l->next)
351     {
352         if (nautilus_progress_info_get_total_elapsed_time (l->data) +
353             nautilus_progress_info_get_remaining_time (l->data) > OPERATION_MINIMUM_TIME &&
354             !nautilus_progress_info_get_is_cancelled (l->data) &&
355             !nautilus_progress_info_get_is_finished (l->data))
356         {
357             return FALSE;
358         }
359     }
360 
361     g_list_free (progress_infos);
362 
363     return TRUE;
364 }
365 
366 static gboolean
on_remove_finished_operations_timeout(NautilusToolbar * self)367 on_remove_finished_operations_timeout (NautilusToolbar *self)
368 {
369     nautilus_progress_info_manager_remove_finished_or_cancelled_infos (self->progress_manager);
370     if (should_hide_operations_button (self))
371     {
372         gtk_revealer_set_reveal_child (GTK_REVEALER (self->operations_revealer),
373                                        FALSE);
374     }
375     else
376     {
377         update_operations (self);
378     }
379 
380     self->remove_finished_operations_timeout_id = 0;
381 
382     return G_SOURCE_REMOVE;
383 }
384 
385 static void
unschedule_remove_finished_operations(NautilusToolbar * self)386 unschedule_remove_finished_operations (NautilusToolbar *self)
387 {
388     if (self->remove_finished_operations_timeout_id != 0)
389     {
390         g_source_remove (self->remove_finished_operations_timeout_id);
391         self->remove_finished_operations_timeout_id = 0;
392     }
393 }
394 
395 static void
schedule_remove_finished_operations(NautilusToolbar * self)396 schedule_remove_finished_operations (NautilusToolbar *self)
397 {
398     if (self->remove_finished_operations_timeout_id == 0)
399     {
400         self->remove_finished_operations_timeout_id =
401             g_timeout_add_seconds (REMOVE_FINISHED_OPERATIONS_TIEMOUT,
402                                    (GSourceFunc) on_remove_finished_operations_timeout,
403                                    self);
404     }
405 }
406 
407 static void
remove_operations_button_attention_style(NautilusToolbar * self)408 remove_operations_button_attention_style (NautilusToolbar *self)
409 {
410     GtkStyleContext *style_context;
411 
412     style_context = gtk_widget_get_style_context (self->operations_button);
413     gtk_style_context_remove_class (style_context,
414                                     "nautilus-operations-button-needs-attention");
415 }
416 
417 static gboolean
on_remove_operations_button_attention_style_timeout(NautilusToolbar * self)418 on_remove_operations_button_attention_style_timeout (NautilusToolbar *self)
419 {
420     remove_operations_button_attention_style (self);
421     self->operations_button_attention_timeout_id = 0;
422 
423     return G_SOURCE_REMOVE;
424 }
425 
426 static void
unschedule_operations_button_attention_style(NautilusToolbar * self)427 unschedule_operations_button_attention_style (NautilusToolbar *self)
428 {
429     if (self->operations_button_attention_timeout_id != 0)
430     {
431         g_source_remove (self->operations_button_attention_timeout_id);
432         self->operations_button_attention_timeout_id = 0;
433     }
434 }
435 
436 static void
add_operations_button_attention_style(NautilusToolbar * self)437 add_operations_button_attention_style (NautilusToolbar *self)
438 {
439     GtkStyleContext *style_context;
440 
441     style_context = gtk_widget_get_style_context (self->operations_button);
442 
443     unschedule_operations_button_attention_style (self);
444     remove_operations_button_attention_style (self);
445 
446     gtk_style_context_add_class (style_context,
447                                  "nautilus-operations-button-needs-attention");
448     self->operations_button_attention_timeout_id = g_timeout_add (NEEDS_ATTENTION_ANIMATION_TIMEOUT,
449                                                                   (GSourceFunc) on_remove_operations_button_attention_style_timeout,
450                                                                   self);
451 }
452 
453 static void
on_progress_info_cancelled(NautilusToolbar * self)454 on_progress_info_cancelled (NautilusToolbar *self)
455 {
456     /* Update the pie chart progress */
457     gtk_widget_queue_draw (self->operations_icon);
458 
459     if (!nautilus_progress_manager_has_viewers (self->progress_manager))
460     {
461         schedule_remove_finished_operations (self);
462     }
463 }
464 
465 static void
on_progress_info_progress_changed(NautilusToolbar * self)466 on_progress_info_progress_changed (NautilusToolbar *self)
467 {
468     /* Update the pie chart progress */
469     gtk_widget_queue_draw (self->operations_icon);
470 }
471 
472 static void
on_progress_info_finished(NautilusToolbar * self,NautilusProgressInfo * info)473 on_progress_info_finished (NautilusToolbar      *self,
474                            NautilusProgressInfo *info)
475 {
476     gchar *main_label;
477     GFile *folder_to_open;
478 
479     /* Update the pie chart progress */
480     gtk_widget_queue_draw (self->operations_icon);
481 
482     if (!nautilus_progress_manager_has_viewers (self->progress_manager))
483     {
484         schedule_remove_finished_operations (self);
485     }
486 
487     folder_to_open = nautilus_progress_info_get_destination (info);
488     /* If destination is null, don't show a notification. This happens when the
489      * operation is a trash operation, which we already show a diferent kind of
490      * notification */
491     if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->operations_button)) &&
492         folder_to_open != NULL)
493     {
494         add_operations_button_attention_style (self);
495         main_label = nautilus_progress_info_get_status (info);
496         nautilus_window_show_operation_notification (self->window,
497                                                      main_label,
498                                                      folder_to_open);
499         g_free (main_label);
500     }
501 
502     g_clear_object (&folder_to_open);
503 }
504 
505 static void
disconnect_progress_infos(NautilusToolbar * self)506 disconnect_progress_infos (NautilusToolbar *self)
507 {
508     GList *progress_infos;
509     GList *l;
510 
511     progress_infos = nautilus_progress_info_manager_get_all_infos (self->progress_manager);
512     for (l = progress_infos; l != NULL; l = l->next)
513     {
514         g_signal_handlers_disconnect_by_data (l->data, self);
515     }
516 }
517 
518 static void
update_operations(NautilusToolbar * self)519 update_operations (NautilusToolbar *self)
520 {
521     GList *progress_infos;
522     GList *l;
523     GtkWidget *progress;
524     gboolean should_show_progress_button = FALSE;
525 
526     gtk_container_foreach (GTK_CONTAINER (self->operations_container),
527                            (GtkCallback) gtk_widget_destroy,
528                            NULL);
529 
530     disconnect_progress_infos (self);
531 
532     progress_infos = get_filtered_progress_infos (self);
533     for (l = progress_infos; l != NULL; l = l->next)
534     {
535         should_show_progress_button = should_show_progress_button ||
536                                       should_show_progress_info (l->data);
537 
538         g_signal_connect_swapped (l->data, "finished",
539                                   G_CALLBACK (on_progress_info_finished), self);
540         g_signal_connect_swapped (l->data, "cancelled",
541                                   G_CALLBACK (on_progress_info_cancelled), self);
542         g_signal_connect_swapped (l->data, "progress-changed",
543                                   G_CALLBACK (on_progress_info_progress_changed), self);
544         progress = nautilus_progress_info_widget_new (l->data);
545         gtk_box_pack_start (GTK_BOX (self->operations_container),
546                             progress,
547                             FALSE, FALSE, 0);
548     }
549 
550     g_list_free (progress_infos);
551 
552     if (should_show_progress_button &&
553         !gtk_revealer_get_reveal_child (GTK_REVEALER (self->operations_revealer)))
554     {
555         add_operations_button_attention_style (self);
556         gtk_revealer_set_reveal_child (GTK_REVEALER (self->operations_revealer),
557                                        TRUE);
558         gtk_widget_queue_draw (self->operations_icon);
559 
560         /* Show the popover at start to increase visibility.
561          * Check whether the toolbar is visible or not before showing the
562          * popover. This can happens if the window has the disables-chrome
563          * property set. */
564         if (gtk_widget_is_visible (GTK_WIDGET (self)))
565         {
566             GtkAllocation rect;
567             IdeBoxTheatric *theatric;
568 
569             gtk_widget_get_allocation (GTK_WIDGET (self->operations_button), &rect);
570             theatric = g_object_new (IDE_TYPE_BOX_THEATRIC,
571                                      "alpha", 0.9,
572                                      "background", "#fdfdfd",
573                                      "target", self->operations_button,
574                                      "height", rect.height,
575                                      "width", rect.width,
576                                      "x", rect.x,
577                                      "y", rect.y,
578                                      NULL);
579 
580             egg_object_animate_full (theatric,
581                                      EGG_ANIMATION_EASE_IN_CUBIC,
582                                      250,
583                                      gtk_widget_get_frame_clock (GTK_WIDGET (self->operations_button)),
584                                      g_object_unref,
585                                      theatric,
586                                      "x", rect.x - ANIMATION_X_GROW,
587                                      "width", rect.width + (ANIMATION_X_GROW * 2),
588                                      "y", rect.y - ANIMATION_Y_GROW,
589                                      "height", rect.height + (ANIMATION_Y_GROW * 2),
590                                      "alpha", 0.0,
591                                      NULL);
592         }
593     }
594 
595     /* Since we removed the info widgets, we need to restore the focus */
596     if (gtk_widget_get_visible (self->operations_popover))
597     {
598         gtk_widget_grab_focus (self->operations_popover);
599     }
600 }
601 
602 static gboolean
on_progress_info_started_timeout(NautilusToolbar * self)603 on_progress_info_started_timeout (NautilusToolbar *self)
604 {
605     GList *progress_infos;
606     GList *filtered_progress_infos;
607 
608     update_operations (self);
609 
610     /* In case we didn't show the operations button because the operation total
611      * time stimation is not good enough, update again to make sure we don't miss
612      * a long time operation because of that */
613 
614     progress_infos = nautilus_progress_info_manager_get_all_infos (self->progress_manager);
615     filtered_progress_infos = get_filtered_progress_infos (self);
616     if (!nautilus_progress_manager_are_all_infos_finished_or_cancelled (self->progress_manager) &&
617         g_list_length (progress_infos) != g_list_length (filtered_progress_infos))
618     {
619         g_list_free (filtered_progress_infos);
620         return G_SOURCE_CONTINUE;
621     }
622     else
623     {
624         g_list_free (filtered_progress_infos);
625         self->start_operations_timeout_id = 0;
626         return G_SOURCE_REMOVE;
627     }
628 }
629 
630 static void
schedule_operations_start(NautilusToolbar * self)631 schedule_operations_start (NautilusToolbar *self)
632 {
633     if (self->start_operations_timeout_id == 0)
634     {
635         /* Timeout is a little more than what we require for a stimated operation
636          * total time, to make sure the stimated total time is correct */
637         self->start_operations_timeout_id =
638             g_timeout_add (SECONDS_NEEDED_FOR_APROXIMATE_TRANSFER_RATE * 1000 + 500,
639                            (GSourceFunc) on_progress_info_started_timeout,
640                            self);
641     }
642 }
643 
644 static void
unschedule_operations_start(NautilusToolbar * self)645 unschedule_operations_start (NautilusToolbar *self)
646 {
647     if (self->start_operations_timeout_id != 0)
648     {
649         g_source_remove (self->start_operations_timeout_id);
650         self->start_operations_timeout_id = 0;
651     }
652 }
653 
654 static void
on_progress_info_started(NautilusProgressInfo * info,NautilusToolbar * self)655 on_progress_info_started (NautilusProgressInfo *info,
656                           NautilusToolbar      *self)
657 {
658     g_signal_handlers_disconnect_by_data (info, self);
659     schedule_operations_start (self);
660 }
661 
662 static void
on_new_progress_info(NautilusProgressInfoManager * manager,NautilusProgressInfo * info,NautilusToolbar * self)663 on_new_progress_info (NautilusProgressInfoManager *manager,
664                       NautilusProgressInfo        *info,
665                       NautilusToolbar             *self)
666 {
667     g_signal_connect (info, "started",
668                       G_CALLBACK (on_progress_info_started), self);
669 }
670 
671 static void
on_operations_icon_draw(GtkWidget * widget,cairo_t * cr,NautilusToolbar * self)672 on_operations_icon_draw (GtkWidget       *widget,
673                          cairo_t         *cr,
674                          NautilusToolbar *self)
675 {
676     gfloat elapsed_progress = 0;
677     gint remaining_progress = 0;
678     gint total_progress;
679     gdouble ratio;
680     GList *progress_infos;
681     GList *l;
682     guint width;
683     guint height;
684     gboolean all_cancelled;
685     GdkRGBA background;
686     GdkRGBA foreground;
687     GtkStyleContext *style_context;
688 
689     style_context = gtk_widget_get_style_context (widget);
690     gtk_style_context_get_color (style_context, gtk_widget_get_state_flags (widget), &foreground);
691     background = foreground;
692     background.alpha *= 0.3;
693 
694     all_cancelled = TRUE;
695     progress_infos = get_filtered_progress_infos (self);
696     for (l = progress_infos; l != NULL; l = l->next)
697     {
698         if (!nautilus_progress_info_get_is_cancelled (l->data))
699         {
700             all_cancelled = FALSE;
701             remaining_progress += nautilus_progress_info_get_remaining_time (l->data);
702             elapsed_progress += nautilus_progress_info_get_elapsed_time (l->data);
703         }
704     }
705 
706     g_list_free (progress_infos);
707 
708     total_progress = remaining_progress + elapsed_progress;
709 
710     if (all_cancelled)
711     {
712         ratio = 1.0;
713     }
714     else
715     {
716         if (total_progress > 0)
717         {
718             ratio = MAX (0.05, elapsed_progress / total_progress);
719         }
720         else
721         {
722             ratio = 0.05;
723         }
724     }
725 
726 
727     width = gtk_widget_get_allocated_width (widget);
728     height = gtk_widget_get_allocated_height (widget);
729 
730     gdk_cairo_set_source_rgba (cr, &background);
731     cairo_arc (cr,
732                width / 2.0, height / 2.0,
733                MIN (width, height) / 2.0,
734                0, 2 * G_PI);
735     cairo_fill (cr);
736     cairo_move_to (cr, width / 2.0, height / 2.0);
737     gdk_cairo_set_source_rgba (cr, &foreground);
738     cairo_arc (cr,
739                width / 2.0, height / 2.0,
740                MIN (width, height) / 2.0,
741                -G_PI / 2.0, ratio * 2 * G_PI - G_PI / 2.0);
742 
743     cairo_fill (cr);
744 }
745 
746 static void
on_operations_button_toggled(NautilusToolbar * self,GtkToggleButton * button)747 on_operations_button_toggled (NautilusToolbar *self,
748                               GtkToggleButton *button)
749 {
750     if (gtk_toggle_button_get_active (button))
751     {
752         unschedule_remove_finished_operations (self);
753         nautilus_progress_manager_add_viewer (self->progress_manager,
754                                               G_OBJECT (self));
755     }
756     else
757     {
758         nautilus_progress_manager_remove_viewer (self->progress_manager,
759                                                  G_OBJECT (self));
760     }
761 }
762 
763 static void
on_progress_has_viewers_changed(NautilusProgressInfoManager * manager,NautilusToolbar * self)764 on_progress_has_viewers_changed (NautilusProgressInfoManager *manager,
765                                  NautilusToolbar             *self)
766 {
767     if (nautilus_progress_manager_has_viewers (manager))
768     {
769         unschedule_remove_finished_operations (self);
770         return;
771     }
772 
773     if (nautilus_progress_manager_are_all_infos_finished_or_cancelled (manager))
774     {
775         unschedule_remove_finished_operations (self);
776         schedule_remove_finished_operations (self);
777     }
778 }
779 
780 static void
update_menu_item(GtkWidget * menu_item,NautilusToolbar * self,const char * action_name,gboolean enabled,char * label)781 update_menu_item (GtkWidget       *menu_item,
782                   NautilusToolbar *self,
783                   const char      *action_name,
784                   gboolean         enabled,
785                   char            *label)
786 {
787     GAction *action;
788     GValue val = G_VALUE_INIT;
789 
790     /* Activate/deactivate */
791     action = g_action_map_lookup_action (G_ACTION_MAP (self->window), action_name);
792     g_simple_action_set_enabled (G_SIMPLE_ACTION (action), enabled);
793 
794     /* Set the text of the menu item. Can't use gtk_button_set_label here as
795      * we need to set the text property, not the label. There's no equivalent
796      * gtk_model_button_set_text function (refer to #766083 for discussion
797      * on adding a set_text function)
798      */
799     g_value_init (&val, G_TYPE_STRING);
800     g_value_set_string (&val, label);
801     g_object_set_property (G_OBJECT (menu_item), "text", &val);
802     g_value_unset (&val);
803 }
804 
805 static void
undo_manager_changed(NautilusToolbar * self)806 undo_manager_changed (NautilusToolbar *self)
807 {
808     NautilusFileUndoInfo *info;
809     NautilusFileUndoManagerState undo_state;
810     gboolean undo_active;
811     gboolean redo_active;
812     g_autofree gchar *undo_label = NULL;
813     g_autofree gchar *redo_label = NULL;
814     g_autofree gchar *undo_description = NULL;
815     g_autofree gchar *redo_description = NULL;
816     gboolean is_undo;
817 
818     /* Look up the last action from the undo manager, and get the text that
819      * describes it, e.g. "Undo Create Folder"/"Redo Create Folder"
820      */
821     info = nautilus_file_undo_manager_get_action ();
822     undo_state = nautilus_file_undo_manager_get_state ();
823     undo_active = redo_active = FALSE;
824     if (info != NULL && undo_state > NAUTILUS_FILE_UNDO_MANAGER_STATE_NONE)
825     {
826         is_undo = undo_state == NAUTILUS_FILE_UNDO_MANAGER_STATE_UNDO;
827 
828         /* The last action can either be undone/redone. Activate the corresponding
829          * menu item and deactivate the other
830          */
831         undo_active = is_undo;
832         redo_active = !is_undo;
833         nautilus_file_undo_info_get_strings (info, &undo_label, &undo_description,
834                                              &redo_label, &redo_description);
835     }
836 
837     /* Set the label of the undo and redo menu items, and activate them appropriately
838      */
839     if (!undo_active || undo_label == NULL)
840     {
841         g_free (undo_label);
842         undo_label = g_strdup (_("_Undo"));
843     }
844     update_menu_item (self->undo_button, self, "undo", undo_active, undo_label);
845 
846     if (!redo_active || redo_label == NULL)
847     {
848         g_free (redo_label);
849         redo_label = g_strdup (_("_Redo"));
850     }
851     update_menu_item (self->redo_button, self, "redo", redo_active, redo_label);
852 }
853 
854 static void
on_location_entry_close(GtkWidget * close_button,NautilusToolbar * self)855 on_location_entry_close (GtkWidget       *close_button,
856                          NautilusToolbar *self)
857 {
858     nautilus_toolbar_set_show_location_entry (self, FALSE);
859 }
860 
861 static gboolean
on_location_entry_populate_popup(GtkEntry * entry,GtkWidget * widget,gpointer user_data)862 on_location_entry_populate_popup (GtkEntry  *entry,
863                                   GtkWidget *widget,
864                                   gpointer   user_data)
865 {
866     NautilusToolbar *toolbar;
867 
868     toolbar = user_data;
869 
870     toolbar->location_entry_should_auto_hide = FALSE;
871 
872     return GDK_EVENT_PROPAGATE;
873 }
874 
875 static void
on_location_entry_focus_changed(GObject * object,GParamSpec * pspec,gpointer user_data)876 on_location_entry_focus_changed (GObject    *object,
877                                  GParamSpec *pspec,
878                                  gpointer    user_data)
879 {
880     NautilusToolbar *toolbar;
881 
882     toolbar = NAUTILUS_TOOLBAR (user_data);
883 
884     if (gtk_widget_has_focus (GTK_WIDGET (object)))
885     {
886         toolbar->location_entry_should_auto_hide = TRUE;
887     }
888     else if (toolbar->location_entry_should_auto_hide)
889     {
890         nautilus_toolbar_set_show_location_entry (toolbar, FALSE);
891     }
892 }
893 
894 static void
nautilus_toolbar_constructed(GObject * object)895 nautilus_toolbar_constructed (GObject *object)
896 {
897     g_autoptr (GtkBuilder) builder = NULL;
898     NautilusToolbar *self = NAUTILUS_TOOLBAR (object);
899 
900     builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-toolbar-switcher.ui");
901     self->toolbar_switcher = GTK_WIDGET (gtk_builder_get_object (builder, "toolbar_switcher"));
902     self->search_container = GTK_WIDGET (gtk_builder_get_object (builder, "search_container"));
903     self->path_bar_container = GTK_WIDGET (gtk_builder_get_object (builder, "path_bar_container"));
904     self->location_entry_container = GTK_WIDGET (gtk_builder_get_object (builder, "location_entry_container"));
905 
906     self->toolbar_switcher_container_max_width = nautilus_container_max_width_new ();
907     nautilus_container_max_width_set_max_width (self->toolbar_switcher_container_max_width,
908                                                 SWITCHER_MAX_WIDTH);
909     gtk_container_add (GTK_CONTAINER (self->toolbar_switcher_container_max_width),
910                        self->toolbar_switcher);
911     gtk_container_add (GTK_CONTAINER (self->toolbar_switcher_container),
912                        GTK_WIDGET (self->toolbar_switcher_container_max_width));
913 
914     self->path_bar = g_object_new (NAUTILUS_TYPE_PATH_BAR, NULL);
915     gtk_container_add (GTK_CONTAINER (self->path_bar_container),
916                        self->path_bar);
917 
918     self->location_entry = nautilus_location_entry_new ();
919     gtk_container_add (GTK_CONTAINER (self->location_entry_container),
920                        self->location_entry);
921     self->location_entry_close_button = gtk_button_new_from_icon_name ("window-close-symbolic",
922                                                                        GTK_ICON_SIZE_BUTTON);
923     gtk_container_add (GTK_CONTAINER (self->location_entry_container),
924                        self->location_entry_close_button);
925     g_signal_connect (self->location_entry_close_button, "clicked",
926                       G_CALLBACK (on_location_entry_close), self);
927 
928     self->progress_manager = nautilus_progress_info_manager_dup_singleton ();
929     g_signal_connect (self->progress_manager, "new-progress-info",
930                       G_CALLBACK (on_new_progress_info), self);
931     g_signal_connect (self->progress_manager, "has-viewers-changed",
932                       G_CALLBACK (on_progress_has_viewers_changed), self);
933 
934     update_operations (self);
935 
936     self->back_button_longpress_gesture = gtk_gesture_long_press_new (self->back_button);
937     g_signal_connect (self->back_button_longpress_gesture, "pressed",
938                       G_CALLBACK (back_button_longpress_cb), self);
939 
940     self->forward_button_longpress_gesture = gtk_gesture_long_press_new (self->forward_button);
941     g_signal_connect (self->forward_button_longpress_gesture, "pressed",
942                       G_CALLBACK (forward_button_longpress_cb), self);
943 
944     g_object_set_data (G_OBJECT (self->back_button), "nav-direction",
945                        GUINT_TO_POINTER (NAUTILUS_NAVIGATION_DIRECTION_BACK));
946     g_object_set_data (G_OBJECT (self->forward_button), "nav-direction",
947                        GUINT_TO_POINTER (NAUTILUS_NAVIGATION_DIRECTION_FORWARD));
948 
949 
950     self->back_button_multi_press_gesture = gtk_gesture_multi_press_new (self->back_button);
951     gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (self->back_button_multi_press_gesture),
952                                    GDK_BUTTON_SECONDARY);
953     g_signal_connect (self->back_button_multi_press_gesture, "pressed",
954                       G_CALLBACK (navigation_button_press_cb), self);
955 
956     self->forward_button_multi_press_gesture = gtk_gesture_multi_press_new (self->forward_button);
957     gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (self->forward_button_multi_press_gesture),
958                                    GDK_BUTTON_SECONDARY);
959     g_signal_connect (self->forward_button_multi_press_gesture, "pressed",
960                       G_CALLBACK (navigation_button_press_cb), self);
961 
962     g_signal_connect (self->operations_popover, "show",
963                       (GCallback) gtk_widget_grab_focus, NULL);
964     g_signal_connect_swapped (self->operations_popover, "closed",
965                               (GCallback) gtk_widget_grab_focus, self);
966     g_signal_connect (self->location_entry, "populate-popup",
967                       G_CALLBACK (on_location_entry_populate_popup), self);
968     g_signal_connect (self->location_entry, "notify::has-focus",
969                       G_CALLBACK (on_location_entry_focus_changed), self);
970 
971     gtk_widget_show_all (GTK_WIDGET (self));
972     toolbar_update_appearance (self);
973 }
974 
975 static void
nautilus_toolbar_init(NautilusToolbar * self)976 nautilus_toolbar_init (NautilusToolbar *self)
977 {
978     gtk_widget_init_template (GTK_WIDGET (self));
979 }
980 
981 void
nautilus_toolbar_on_window_constructed(NautilusToolbar * self)982 nautilus_toolbar_on_window_constructed (NautilusToolbar *self)
983 {
984     /* undo_manager_changed manipulates the window actions, so set it up
985      * after the window and it's actions have been constructed
986      */
987     g_signal_connect_object (nautilus_file_undo_manager_get (),
988                              "undo-changed",
989                              G_CALLBACK (undo_manager_changed),
990                              self,
991                              G_CONNECT_SWAPPED);
992 
993     undo_manager_changed (self);
994 }
995 
996 static void
nautilus_toolbar_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)997 nautilus_toolbar_get_property (GObject    *object,
998                                guint       property_id,
999                                GValue     *value,
1000                                GParamSpec *pspec)
1001 {
1002     NautilusToolbar *self = NAUTILUS_TOOLBAR (object);
1003 
1004     switch (property_id)
1005     {
1006         case PROP_SHOW_LOCATION_ENTRY:
1007         {
1008             g_value_set_boolean (value, self->show_location_entry);
1009         }
1010         break;
1011 
1012         case PROP_SEARCHING:
1013         {
1014             g_value_set_boolean (value, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->search_button)));
1015         }
1016         break;
1017 
1018         default:
1019         {
1020             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1021         }
1022         break;
1023     }
1024 }
1025 
1026 static void
on_window_slot_destroyed(gpointer data,GObject * where_the_object_was)1027 on_window_slot_destroyed (gpointer  data,
1028                           GObject  *where_the_object_was)
1029 {
1030     NautilusToolbar *self;
1031 
1032     self = NAUTILUS_TOOLBAR (data);
1033 
1034     /* The window slot was finalized, and the binding has already been removed.
1035      * Null it here, so that dispose() does not trip over itself when removing it.
1036      */
1037     self->icon_binding = NULL;
1038     self->search_binding = NULL;
1039 
1040     nautilus_toolbar_set_window_slot_real (self, NULL);
1041 }
1042 
1043 static void
on_window_focus_changed(GObject * object,GParamSpec * pspec,gpointer user_data)1044 on_window_focus_changed (GObject    *object,
1045                          GParamSpec *pspec,
1046                          gpointer    user_data)
1047 {
1048     GtkWidget *widget;
1049     NautilusToolbar *toolbar;
1050 
1051     widget = GTK_WIDGET (object);
1052     toolbar = NAUTILUS_TOOLBAR (user_data);
1053 
1054     if (g_settings_get_boolean (nautilus_preferences,
1055                                 NAUTILUS_PREFERENCES_ALWAYS_USE_LOCATION_ENTRY))
1056     {
1057         return;
1058     }
1059 
1060     /* The working assumption being made here is, if the location entry is visible,
1061      * the user must have switched windows while having keyboard focus on the entry
1062      * (because otherwise it would be invisible),
1063      * so we focus the entry explicitly to reset the “should auto-hide” flag.
1064      */
1065     if (gtk_widget_has_focus (widget) && toolbar->show_location_entry)
1066     {
1067         gtk_widget_grab_focus (toolbar->location_entry);
1068     }
1069     /* The location entry in general is hidden when it loses focus,
1070      * but hiding it when switching windows could be undesirable, as the user
1071      * might want to copy a path from somewhere. This here prevents that from happening.
1072      */
1073     else
1074     {
1075         toolbar->location_entry_should_auto_hide = FALSE;
1076     }
1077 }
1078 
1079 static void
nautilus_toolbar_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)1080 nautilus_toolbar_set_property (GObject      *object,
1081                                guint         property_id,
1082                                const GValue *value,
1083                                GParamSpec   *pspec)
1084 {
1085     NautilusToolbar *self = NAUTILUS_TOOLBAR (object);
1086 
1087     switch (property_id)
1088     {
1089         case PROP_WINDOW:
1090         {
1091             if (self->window != NULL)
1092             {
1093                 g_signal_handlers_disconnect_by_func (self->window,
1094                                                       on_window_focus_changed, self);
1095             }
1096             self->window = g_value_get_object (value);
1097             if (self->window != NULL)
1098             {
1099                 g_signal_connect (self->window, "notify::has-focus",
1100                                   G_CALLBACK (on_window_focus_changed), self);
1101             }
1102         }
1103         break;
1104 
1105         case PROP_SHOW_LOCATION_ENTRY:
1106         {
1107             nautilus_toolbar_set_show_location_entry (self, g_value_get_boolean (value));
1108         }
1109         break;
1110 
1111         case PROP_WINDOW_SLOT:
1112         {
1113             nautilus_toolbar_set_window_slot (self, g_value_get_object (value));
1114         }
1115         break;
1116 
1117         case PROP_SEARCHING:
1118         {
1119             gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->search_button),
1120                                           g_value_get_boolean (value));
1121         }
1122         break;
1123 
1124         default:
1125         {
1126             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1127         }
1128         break;
1129     }
1130 }
1131 
1132 static void
nautilus_toolbar_dispose(GObject * object)1133 nautilus_toolbar_dispose (GObject *object)
1134 {
1135     NautilusToolbar *self;
1136 
1137     self = NAUTILUS_TOOLBAR (object);
1138 
1139     g_clear_object (&self->forward_button_multi_press_gesture);
1140     g_clear_object (&self->back_button_multi_press_gesture);
1141     g_clear_pointer (&self->icon_binding, g_binding_unbind);
1142     g_clear_pointer (&self->search_binding, g_binding_unbind);
1143 
1144     G_OBJECT_CLASS (nautilus_toolbar_parent_class)->dispose (object);
1145 }
1146 
1147 static void
nautilus_toolbar_finalize(GObject * obj)1148 nautilus_toolbar_finalize (GObject *obj)
1149 {
1150     NautilusToolbar *self = NAUTILUS_TOOLBAR (obj);
1151 
1152     g_signal_handlers_disconnect_by_func (nautilus_preferences,
1153                                           toolbar_update_appearance, self);
1154 
1155     if (self->window_slot != NULL)
1156     {
1157         g_signal_handlers_disconnect_by_data (self->window_slot, self);
1158         g_object_weak_unref (G_OBJECT (self->window_slot),
1159                              on_window_slot_destroyed, self);
1160         self->window_slot = NULL;
1161     }
1162     disconnect_progress_infos (self);
1163     unschedule_remove_finished_operations (self);
1164     unschedule_operations_start (self);
1165     unschedule_operations_button_attention_style (self);
1166 
1167     g_signal_handlers_disconnect_by_data (self->progress_manager, self);
1168     g_clear_object (&self->progress_manager);
1169 
1170     g_signal_handlers_disconnect_by_func (self->window,
1171                                           on_window_focus_changed, self);
1172 
1173     g_clear_object (&self->back_button_longpress_gesture);
1174     g_clear_object (&self->forward_button_longpress_gesture);
1175 
1176     G_OBJECT_CLASS (nautilus_toolbar_parent_class)->finalize (obj);
1177 }
1178 
1179 static void
nautilus_toolbar_class_init(NautilusToolbarClass * klass)1180 nautilus_toolbar_class_init (NautilusToolbarClass *klass)
1181 {
1182     GObjectClass *oclass;
1183     GtkWidgetClass *widget_class;
1184 
1185     widget_class = GTK_WIDGET_CLASS (klass);
1186     oclass = G_OBJECT_CLASS (klass);
1187     oclass->get_property = nautilus_toolbar_get_property;
1188     oclass->set_property = nautilus_toolbar_set_property;
1189     oclass->dispose = nautilus_toolbar_dispose;
1190     oclass->finalize = nautilus_toolbar_finalize;
1191     oclass->constructed = nautilus_toolbar_constructed;
1192 
1193     properties[PROP_WINDOW] =
1194         g_param_spec_object ("window",
1195                              "The NautilusWindow",
1196                              "The NautilusWindow this toolbar is part of",
1197                              NAUTILUS_TYPE_WINDOW,
1198                              G_PARAM_WRITABLE |
1199                              G_PARAM_STATIC_STRINGS);
1200     properties[PROP_SHOW_LOCATION_ENTRY] =
1201         g_param_spec_boolean ("show-location-entry",
1202                               "Whether to show the location entry",
1203                               "Whether to show the location entry instead of the pathbar",
1204                               FALSE,
1205                               G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
1206 
1207     properties [PROP_WINDOW_SLOT] =
1208         g_param_spec_object ("window-slot",
1209                              "Window slot currently active",
1210                              "Window slot currently acive",
1211                              NAUTILUS_TYPE_WINDOW_SLOT,
1212                              (G_PARAM_READWRITE |
1213                               G_PARAM_STATIC_STRINGS));
1214 
1215     properties [PROP_SEARCHING] =
1216         g_param_spec_boolean ("searching",
1217                               "Current view is searching",
1218                               "Whether the current view is searching or not",
1219                               FALSE,
1220                               G_PARAM_READWRITE);
1221 
1222     g_object_class_install_properties (oclass, NUM_PROPERTIES, properties);
1223 
1224     gtk_widget_class_set_template_from_resource (widget_class,
1225                                                  "/org/gnome/nautilus/ui/nautilus-toolbar.ui");
1226 
1227     gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, operations_button);
1228     gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, operations_icon);
1229     gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, operations_popover);
1230     gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, operations_container);
1231     gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, operations_revealer);
1232     gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, view_button);
1233     gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, view_toggle_button);
1234     gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, view_toggle_icon);
1235     gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, app_menu);
1236     gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, back_button);
1237     gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, forward_button);
1238     gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, toolbar_switcher_container);
1239 
1240     gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, view_menu_zoom_section);
1241     gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, view_menu_undo_redo_section);
1242     gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, view_menu_extended_section);
1243     gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, undo_button);
1244     gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, redo_button);
1245 
1246     gtk_widget_class_bind_template_child (widget_class, NautilusToolbar, search_button);
1247 
1248     gtk_widget_class_bind_template_callback (widget_class, on_operations_icon_draw);
1249     gtk_widget_class_bind_template_callback (widget_class, on_operations_button_toggled);
1250 }
1251 
1252 GtkWidget *
nautilus_toolbar_new()1253 nautilus_toolbar_new ()
1254 {
1255     return g_object_new (NAUTILUS_TYPE_TOOLBAR,
1256                          NULL);
1257 }
1258 
1259 GtkWidget *
nautilus_toolbar_get_path_bar(NautilusToolbar * self)1260 nautilus_toolbar_get_path_bar (NautilusToolbar *self)
1261 {
1262     return self->path_bar;
1263 }
1264 
1265 GtkWidget *
nautilus_toolbar_get_location_entry(NautilusToolbar * self)1266 nautilus_toolbar_get_location_entry (NautilusToolbar *self)
1267 {
1268     return self->location_entry;
1269 }
1270 
1271 void
nautilus_toolbar_set_show_location_entry(NautilusToolbar * self,gboolean show_location_entry)1272 nautilus_toolbar_set_show_location_entry (NautilusToolbar *self,
1273                                           gboolean         show_location_entry)
1274 {
1275     if (show_location_entry != self->show_location_entry)
1276     {
1277         self->show_location_entry = show_location_entry;
1278         toolbar_update_appearance (self);
1279 
1280         g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SHOW_LOCATION_ENTRY]);
1281     }
1282 }
1283 
1284 static void
container_remove_all_children(GtkContainer * container)1285 container_remove_all_children (GtkContainer *container)
1286 {
1287     GList *children;
1288     GList *child;
1289 
1290     children = gtk_container_get_children (container);
1291     for (child = children; child != NULL; child = g_list_next (child))
1292     {
1293         gtk_container_remove (container, GTK_WIDGET (child->data));
1294     }
1295     g_list_free (children);
1296 }
1297 
1298 static void
slot_on_extensions_background_menu_changed(NautilusToolbar * self,GParamSpec * param,NautilusWindowSlot * slot)1299 slot_on_extensions_background_menu_changed (NautilusToolbar    *self,
1300                                             GParamSpec         *param,
1301                                             NautilusWindowSlot *slot)
1302 {
1303     g_autoptr (GMenuModel) menu = NULL;
1304 
1305     menu = nautilus_window_slot_get_extensions_background_menu (slot);
1306     nautilus_path_bar_set_extensions_background_menu (NAUTILUS_PATH_BAR (self->path_bar),
1307                                                       menu);
1308 }
1309 
1310 static void
slot_on_templates_menu_changed(NautilusToolbar * self,GParamSpec * param,NautilusWindowSlot * slot)1311 slot_on_templates_menu_changed (NautilusToolbar    *self,
1312                                 GParamSpec         *param,
1313                                 NautilusWindowSlot *slot)
1314 {
1315     g_autoptr (GMenuModel) menu = NULL;
1316 
1317     menu = nautilus_window_slot_get_templates_menu (slot);
1318     nautilus_path_bar_set_templates_menu (NAUTILUS_PATH_BAR (self->path_bar),
1319                                           menu);
1320 }
1321 
1322 static void
on_slot_toolbar_menu_sections_changed(NautilusToolbar * self,GParamSpec * param,NautilusWindowSlot * slot)1323 on_slot_toolbar_menu_sections_changed (NautilusToolbar    *self,
1324                                        GParamSpec         *param,
1325                                        NautilusWindowSlot *slot)
1326 {
1327     NautilusToolbarMenuSections *new_sections;
1328 
1329     container_remove_all_children (GTK_CONTAINER (self->view_menu_zoom_section));
1330     container_remove_all_children (GTK_CONTAINER (self->view_menu_extended_section));
1331 
1332     new_sections = nautilus_window_slot_get_toolbar_menu_sections (slot);
1333     if (new_sections == NULL)
1334     {
1335         return;
1336     }
1337 
1338     gtk_widget_set_visible (self->view_menu_undo_redo_section,
1339                             new_sections->supports_undo_redo);
1340 
1341     if (new_sections->zoom_section != NULL)
1342     {
1343         gtk_box_pack_start (GTK_BOX (self->view_menu_zoom_section),
1344                             new_sections->zoom_section, FALSE, FALSE, 0);
1345     }
1346 
1347     if (new_sections->extended_section != NULL)
1348     {
1349         gtk_box_pack_start (GTK_BOX (self->view_menu_extended_section),
1350                             new_sections->extended_section, FALSE, FALSE, 0);
1351     }
1352 
1353     gtk_widget_set_sensitive (self->view_button, (new_sections->extended_section != NULL ||
1354                                                   new_sections->zoom_section != NULL ||
1355                                                   new_sections->supports_undo_redo));
1356 }
1357 
1358 
1359 static void
disconnect_toolbar_menu_sections_change_handler(NautilusToolbar * self)1360 disconnect_toolbar_menu_sections_change_handler (NautilusToolbar *self)
1361 {
1362     if (self->window_slot == NULL)
1363     {
1364         return;
1365     }
1366 
1367     g_signal_handlers_disconnect_by_func (self->window_slot,
1368                                           G_CALLBACK (on_slot_toolbar_menu_sections_changed),
1369                                           self);
1370 }
1371 
1372 static gboolean
nautilus_toolbar_view_toggle_icon_transform_to(GBinding * binding,const GValue * from_value,GValue * to_value,gpointer user_data)1373 nautilus_toolbar_view_toggle_icon_transform_to (GBinding     *binding,
1374                                                 const GValue *from_value,
1375                                                 GValue       *to_value,
1376                                                 gpointer      user_data)
1377 {
1378     GIcon *icon;
1379 
1380     icon = g_value_get_object (from_value);
1381 
1382     /* As per design decision, we let the previous used icon if no
1383      * view menu is available */
1384     if (icon)
1385     {
1386         g_value_set_object (to_value, icon);
1387     }
1388 
1389     return TRUE;
1390 }
1391 
1392 static gboolean
nautilus_toolbar_view_toggle_tooltip_transform_to(GBinding * binding,const GValue * from_value,GValue * to_value,gpointer user_data)1393 nautilus_toolbar_view_toggle_tooltip_transform_to (GBinding     *binding,
1394                                                    const GValue *from_value,
1395                                                    GValue       *to_value,
1396                                                    gpointer      user_data)
1397 {
1398     const gchar *tooltip;
1399 
1400     tooltip = g_value_get_string (from_value);
1401 
1402     /* As per design decision, we let the previous used tooltip if no
1403      * view menu is available */
1404     if (tooltip)
1405     {
1406         g_value_set_string (to_value, tooltip);
1407     }
1408 
1409     return TRUE;
1410 }
1411 
1412 /* Called from on_window_slot_destroyed(), since bindings and signal handlers
1413  * are automatically removed once the slot goes away.
1414  */
1415 static void
nautilus_toolbar_set_window_slot_real(NautilusToolbar * self,NautilusWindowSlot * slot)1416 nautilus_toolbar_set_window_slot_real (NautilusToolbar    *self,
1417                                        NautilusWindowSlot *slot)
1418 {
1419     g_autoptr (GList) children = NULL;
1420 
1421     self->window_slot = slot;
1422 
1423     if (self->window_slot != NULL)
1424     {
1425         g_object_weak_ref (G_OBJECT (self->window_slot),
1426                            on_window_slot_destroyed,
1427                            self);
1428 
1429         self->icon_binding = g_object_bind_property_full (self->window_slot, "icon",
1430                                                           self->view_toggle_icon, "gicon",
1431                                                           G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE,
1432                                                           (GBindingTransformFunc) nautilus_toolbar_view_toggle_icon_transform_to,
1433                                                           NULL,
1434                                                           self,
1435                                                           NULL);
1436 
1437         self->tooltip_binding = g_object_bind_property_full (self->window_slot, "tooltip",
1438                                                              self->view_toggle_button, "tooltip-text",
1439                                                              G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE,
1440                                                              (GBindingTransformFunc) nautilus_toolbar_view_toggle_tooltip_transform_to,
1441                                                              NULL,
1442                                                              self,
1443                                                              NULL);
1444 
1445         self->search_binding = g_object_bind_property (self->window_slot, "searching",
1446                                                        self, "searching",
1447                                                        G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
1448 
1449         on_slot_toolbar_menu_sections_changed (self, NULL, self->window_slot);
1450         g_signal_connect_swapped (self->window_slot, "notify::toolbar-menu-sections",
1451                                   G_CALLBACK (on_slot_toolbar_menu_sections_changed), self);
1452         g_signal_connect_swapped (self->window_slot, "notify::extensions-background-menu",
1453                                   G_CALLBACK (slot_on_extensions_background_menu_changed), self);
1454         g_signal_connect_swapped (self->window_slot, "notify::templates-menu",
1455                                   G_CALLBACK (slot_on_templates_menu_changed), self);
1456         g_signal_connect_swapped (self->window_slot, "notify::searching",
1457                                   G_CALLBACK (toolbar_update_appearance), self);
1458     }
1459 
1460     children = gtk_container_get_children (GTK_CONTAINER (self->search_container));
1461     if (children != NULL)
1462     {
1463         gtk_container_remove (GTK_CONTAINER (self->search_container),
1464                               children->data);
1465     }
1466 
1467     if (self->window_slot != NULL)
1468     {
1469         gtk_container_add (GTK_CONTAINER (self->search_container),
1470                            GTK_WIDGET (nautilus_window_slot_get_query_editor (self->window_slot)));
1471     }
1472 
1473     toolbar_update_appearance (self);
1474 
1475     g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_WINDOW_SLOT]);
1476     g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SEARCHING]);
1477 }
1478 
1479 void
nautilus_toolbar_set_window_slot(NautilusToolbar * self,NautilusWindowSlot * window_slot)1480 nautilus_toolbar_set_window_slot (NautilusToolbar    *self,
1481                                   NautilusWindowSlot *window_slot)
1482 {
1483     g_return_if_fail (NAUTILUS_IS_TOOLBAR (self));
1484     g_return_if_fail (window_slot == NULL || NAUTILUS_IS_WINDOW_SLOT (window_slot));
1485 
1486     if (self->window_slot == window_slot)
1487     {
1488         return;
1489     }
1490 
1491     g_clear_pointer (&self->icon_binding, g_binding_unbind);
1492     g_clear_pointer (&self->search_binding, g_binding_unbind);
1493 
1494     disconnect_toolbar_menu_sections_change_handler (self);
1495     if (self->window_slot != NULL)
1496     {
1497         g_signal_handlers_disconnect_by_data (self->window_slot, self);
1498         g_object_weak_unref (G_OBJECT (self->window_slot),
1499                              on_window_slot_destroyed, self);
1500     }
1501 
1502     nautilus_toolbar_set_window_slot_real (self, window_slot);
1503 }
1504 
1505 gboolean
nautilus_toolbar_is_menu_visible(NautilusToolbar * self)1506 nautilus_toolbar_is_menu_visible (NautilusToolbar *self)
1507 {
1508     g_return_val_if_fail (NAUTILUS_IS_TOOLBAR (self), FALSE);
1509 
1510     return gtk_widget_is_visible (self->app_menu);
1511 }
1512 
1513 gboolean
nautilus_toolbar_is_operations_button_active(NautilusToolbar * self)1514 nautilus_toolbar_is_operations_button_active (NautilusToolbar *self)
1515 {
1516     return gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->operations_button));
1517 }
1518