1 /* gtksidebarrow.c
2  *
3  * Copyright (C) 2015 Carlos Soriano <csoriano@gnome.org>
4  *
5  * This file is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU Lesser General Public License as
7  * published by the Free Software Foundation; either version 2.1 of the
8  * License, or (at your option) any later version.
9  *
10  * This file is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "config.h"
20 
21 #include "gtksidebarrowprivate.h"
22 /* For section and place type enums */
23 #include "gtkplacessidebarprivate.h"
24 #include "gtkplacessidebar.h"
25 #include "gtkwidget.h"
26 #include "gtkimage.h"
27 #include "gtklabel.h"
28 #include "gtkstylecontext.h"
29 #include "gtkrevealer.h"
30 #include "gtkselection.h"
31 #include "gtkspinner.h"
32 
33 #ifdef HAVE_CLOUDPROVIDERS
34 #include <cloudproviders/cloudprovidersaccount.h>
35 #endif
36 
37 struct _GtkSidebarRow
38 {
39   GtkListBoxRow parent_instance;
40   GIcon *start_icon;
41   GIcon *end_icon;
42   GtkWidget *start_icon_widget;
43   GtkWidget *end_icon_widget;
44   gchar *label;
45   gchar *tooltip;
46   GtkWidget *label_widget;
47   gboolean ejectable;
48   GtkWidget *eject_button;
49   gint order_index;
50   GtkPlacesSidebarSectionType section_type;
51   GtkPlacesSidebarPlaceType place_type;
52   gchar *uri;
53   GDrive *drive;
54   GVolume *volume;
55   GMount *mount;
56   GObject *cloud_provider_account;
57   gboolean placeholder;
58   GtkPlacesSidebar *sidebar;
59   GtkWidget *event_box;
60   GtkWidget *revealer;
61   GtkWidget *busy_spinner;
62 };
63 
64 G_DEFINE_TYPE (GtkSidebarRow, gtk_sidebar_row, GTK_TYPE_LIST_BOX_ROW)
65 
66 enum
67 {
68   PROP_0,
69   PROP_START_ICON,
70   PROP_END_ICON,
71   PROP_LABEL,
72   PROP_TOOLTIP,
73   PROP_EJECTABLE,
74   PROP_SIDEBAR,
75   PROP_ORDER_INDEX,
76   PROP_SECTION_TYPE,
77   PROP_PLACE_TYPE,
78   PROP_URI,
79   PROP_DRIVE,
80   PROP_VOLUME,
81   PROP_MOUNT,
82   PROP_CLOUD_PROVIDER_ACCOUNT,
83   PROP_PLACEHOLDER,
84   LAST_PROP
85 };
86 
87 static GParamSpec *properties [LAST_PROP];
88 
89 #ifdef HAVE_CLOUDPROVIDERS
90 
91 static void
cloud_row_update(GtkSidebarRow * self)92 cloud_row_update (GtkSidebarRow *self)
93 {
94   CloudProvidersAccount *account;
95   GIcon *end_icon;
96   gint provider_status;
97 
98   account = CLOUD_PROVIDERS_ACCOUNT (self->cloud_provider_account);
99   provider_status = cloud_providers_account_get_status (account);
100   switch (provider_status)
101     {
102       case CLOUD_PROVIDERS_ACCOUNT_STATUS_IDLE:
103         end_icon = NULL;
104         break;
105 
106       case CLOUD_PROVIDERS_ACCOUNT_STATUS_SYNCING:
107         end_icon = g_themed_icon_new ("emblem-synchronizing-symbolic");
108         break;
109 
110       case CLOUD_PROVIDERS_ACCOUNT_STATUS_ERROR:
111         end_icon = g_themed_icon_new ("dialog-warning-symbolic");
112         break;
113 
114       default:
115         return;
116     }
117 
118   g_object_set (self,
119                 "label", cloud_providers_account_get_name (account),
120                 NULL);
121   g_object_set (self,
122                 "tooltip", cloud_providers_account_get_status_details (account),
123                 NULL);
124   g_object_set (self,
125                 "end-icon", end_icon,
126                 NULL);
127 
128   if (end_icon != NULL)
129     g_object_unref (end_icon);
130 }
131 
132 #endif
133 
134 static void
gtk_sidebar_row_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)135 gtk_sidebar_row_get_property (GObject    *object,
136                               guint       prop_id,
137                               GValue     *value,
138                               GParamSpec *pspec)
139 {
140   GtkSidebarRow *self = GTK_SIDEBAR_ROW (object);
141 
142   switch (prop_id)
143     {
144     case PROP_SIDEBAR:
145       g_value_set_object (value, self->sidebar);
146       break;
147 
148     case PROP_START_ICON:
149       g_value_set_object (value, self->start_icon);
150       break;
151 
152     case PROP_END_ICON:
153       g_value_set_object (value, self->end_icon);
154       break;
155 
156     case PROP_LABEL:
157       g_value_set_string (value, self->label);
158       break;
159 
160     case PROP_TOOLTIP:
161       g_value_set_string (value, self->tooltip);
162       break;
163 
164     case PROP_EJECTABLE:
165       g_value_set_boolean (value, self->ejectable);
166       break;
167 
168     case PROP_ORDER_INDEX:
169       g_value_set_int (value, self->order_index);
170       break;
171 
172     case PROP_SECTION_TYPE:
173       g_value_set_int (value, self->section_type);
174       break;
175 
176     case PROP_PLACE_TYPE:
177       g_value_set_int (value, self->place_type);
178       break;
179 
180     case PROP_URI:
181       g_value_set_string (value, self->uri);
182       break;
183 
184     case PROP_DRIVE:
185       g_value_set_object (value, self->drive);
186       break;
187 
188     case PROP_VOLUME:
189       g_value_set_object (value, self->volume);
190       break;
191 
192     case PROP_MOUNT:
193       g_value_set_object (value, self->mount);
194       break;
195 
196     case PROP_CLOUD_PROVIDER_ACCOUNT:
197       g_value_set_object (value, self->cloud_provider_account);
198       break;
199 
200     case PROP_PLACEHOLDER:
201       g_value_set_boolean (value, self->placeholder);
202       break;
203 
204     default:
205       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
206     }
207 }
208 
209 static void
gtk_sidebar_row_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)210 gtk_sidebar_row_set_property (GObject      *object,
211                               guint         prop_id,
212                               const GValue *value,
213                               GParamSpec   *pspec)
214 {
215   GtkSidebarRow *self = GTK_SIDEBAR_ROW (object);
216   GtkStyleContext *context;
217 
218   switch (prop_id)
219     {
220     case PROP_SIDEBAR:
221       self->sidebar = g_value_get_object (value);
222       break;
223 
224     case PROP_START_ICON:
225       {
226         g_clear_object (&self->start_icon);
227         object = g_value_get_object (value);
228         if (object != NULL)
229           {
230             self->start_icon = G_ICON (g_object_ref (object));
231             gtk_image_set_from_gicon (GTK_IMAGE (self->start_icon_widget),
232                                       self->start_icon,
233                                       GTK_ICON_SIZE_MENU);
234           }
235         else
236           {
237             gtk_image_clear (GTK_IMAGE (self->start_icon_widget));
238           }
239         break;
240       }
241 
242     case PROP_END_ICON:
243       {
244         g_clear_object (&self->end_icon);
245         object = g_value_get_object (value);
246         if (object != NULL)
247           {
248             self->end_icon = G_ICON (g_object_ref (object));
249             gtk_image_set_from_gicon (GTK_IMAGE (self->end_icon_widget),
250                                       self->end_icon,
251                                       GTK_ICON_SIZE_MENU);
252             gtk_widget_show (self->end_icon_widget);
253           }
254         else
255           {
256             gtk_image_clear (GTK_IMAGE (self->end_icon_widget));
257             gtk_widget_hide (self->end_icon_widget);
258           }
259         break;
260       }
261 
262     case PROP_LABEL:
263       g_free (self->label);
264       self->label = g_strdup (g_value_get_string (value));
265       gtk_label_set_text (GTK_LABEL (self->label_widget), self->label);
266       break;
267 
268     case PROP_TOOLTIP:
269       g_free (self->tooltip);
270       self->tooltip = g_strdup (g_value_get_string (value));
271       gtk_widget_set_tooltip_text (GTK_WIDGET (self), self->tooltip);
272       break;
273 
274     case PROP_EJECTABLE:
275       self->ejectable = g_value_get_boolean (value);
276       if (self->ejectable)
277         gtk_widget_show (self->eject_button);
278       else
279         gtk_widget_hide (self->eject_button);
280       break;
281 
282     case PROP_ORDER_INDEX:
283       self->order_index = g_value_get_int (value);
284       break;
285 
286     case PROP_SECTION_TYPE:
287       self->section_type = g_value_get_int (value);
288       if (self->section_type == SECTION_COMPUTER ||
289           self->section_type == SECTION_OTHER_LOCATIONS)
290         gtk_label_set_ellipsize (GTK_LABEL (self->label_widget), PANGO_ELLIPSIZE_NONE);
291       else
292         gtk_label_set_ellipsize (GTK_LABEL (self->label_widget), PANGO_ELLIPSIZE_END);
293       break;
294 
295     case PROP_PLACE_TYPE:
296       self->place_type = g_value_get_int (value);
297       break;
298 
299     case PROP_URI:
300       g_free (self->uri);
301       self->uri = g_strdup (g_value_get_string (value));
302       break;
303 
304     case PROP_DRIVE:
305       g_set_object (&self->drive, g_value_get_object (value));
306       break;
307 
308     case PROP_VOLUME:
309       g_set_object (&self->volume, g_value_get_object (value));
310       break;
311 
312     case PROP_MOUNT:
313       g_set_object (&self->mount, g_value_get_object (value));
314       break;
315 
316     case PROP_CLOUD_PROVIDER_ACCOUNT:
317 #ifdef HAVE_CLOUDPROVIDERS
318       if (self->cloud_provider_account != NULL)
319         g_signal_handlers_disconnect_by_data (self->cloud_provider_account, self);
320 
321       self->cloud_provider_account = g_value_dup_object (value);
322 
323       if (self->cloud_provider_account != NULL)
324         {
325           g_signal_connect_swapped (self->cloud_provider_account, "notify::name",
326                                     G_CALLBACK (cloud_row_update), self);
327           g_signal_connect_swapped (self->cloud_provider_account, "notify::status",
328                                     G_CALLBACK (cloud_row_update), self);
329           g_signal_connect_swapped (self->cloud_provider_account, "notify::status-details",
330                                     G_CALLBACK (cloud_row_update), self);
331         }
332 #endif
333       break;
334 
335     case PROP_PLACEHOLDER:
336       {
337         self->placeholder = g_value_get_boolean (value);
338         if (self->placeholder)
339           {
340             g_clear_object (&self->start_icon);
341             g_clear_object (&self->end_icon);
342             g_free (self->label);
343             self->label = NULL;
344             g_free (self->tooltip);
345             self->tooltip = NULL;
346             gtk_widget_set_tooltip_text (GTK_WIDGET (self), NULL);
347             self->ejectable = FALSE;
348             self->section_type = SECTION_BOOKMARKS;
349             self->place_type = PLACES_BOOKMARK_PLACEHOLDER;
350             g_free (self->uri);
351             self->uri = NULL;
352             g_clear_object (&self->drive);
353             g_clear_object (&self->volume);
354             g_clear_object (&self->mount);
355             g_clear_object (&self->cloud_provider_account);
356 
357             gtk_container_foreach (GTK_CONTAINER (self),
358                                    (GtkCallback) gtk_widget_destroy,
359                                    NULL);
360 
361             context = gtk_widget_get_style_context (GTK_WIDGET (self));
362             gtk_style_context_add_class (context, "sidebar-placeholder-row");
363           }
364 
365         break;
366       }
367 
368     default:
369       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
370     }
371 }
372 
373 static void
on_child_revealed(GObject * self,GParamSpec * pspec,gpointer user_data)374 on_child_revealed (GObject    *self,
375                    GParamSpec *pspec,
376                    gpointer    user_data)
377 {
378  /* We need to hide the actual widget because if not the GtkListBoxRow will
379   * still allocate the paddings, even if the revealer is not revealed, and
380   * therefore the row will be still somewhat visible. */
381   if (!gtk_revealer_get_reveal_child (GTK_REVEALER (self)))
382     gtk_widget_hide (GTK_WIDGET (GTK_SIDEBAR_ROW (user_data)));
383 }
384 
385 void
gtk_sidebar_row_reveal(GtkSidebarRow * self)386 gtk_sidebar_row_reveal (GtkSidebarRow *self)
387 {
388   gtk_widget_show_all (GTK_WIDGET (self));
389   gtk_revealer_set_reveal_child (GTK_REVEALER (self->revealer), TRUE);
390 }
391 
392 void
gtk_sidebar_row_hide(GtkSidebarRow * self,gboolean inmediate)393 gtk_sidebar_row_hide (GtkSidebarRow *self,
394                       gboolean       inmediate)
395 {
396   guint transition_duration;
397 
398   transition_duration = gtk_revealer_get_transition_duration (GTK_REVEALER (self->revealer));
399   if (inmediate)
400       gtk_revealer_set_transition_duration (GTK_REVEALER (self->revealer), 0);
401 
402   gtk_revealer_set_reveal_child (GTK_REVEALER (self->revealer), FALSE);
403 
404   gtk_revealer_set_transition_duration (GTK_REVEALER (self->revealer), transition_duration);
405 }
406 
407 void
gtk_sidebar_row_set_start_icon(GtkSidebarRow * self,GIcon * icon)408 gtk_sidebar_row_set_start_icon (GtkSidebarRow *self,
409                                 GIcon         *icon)
410 {
411   g_return_if_fail (GTK_IS_SIDEBAR_ROW (self));
412 
413   if (self->start_icon != icon)
414     {
415       g_set_object (&self->start_icon, icon);
416       if (self->start_icon != NULL)
417         gtk_image_set_from_gicon (GTK_IMAGE (self->start_icon_widget), self->start_icon,
418                                   GTK_ICON_SIZE_MENU);
419       else
420         gtk_image_clear (GTK_IMAGE (self->start_icon_widget));
421 
422       g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_START_ICON]);
423     }
424 }
425 
426 void
gtk_sidebar_row_set_end_icon(GtkSidebarRow * self,GIcon * icon)427 gtk_sidebar_row_set_end_icon (GtkSidebarRow *self,
428                               GIcon         *icon)
429 {
430   g_return_if_fail (GTK_IS_SIDEBAR_ROW (self));
431 
432   if (self->end_icon != icon)
433     {
434       g_set_object (&self->end_icon, icon);
435       if (self->end_icon != NULL)
436         gtk_image_set_from_gicon (GTK_IMAGE (self->end_icon_widget), self->end_icon,
437                                   GTK_ICON_SIZE_MENU);
438       else
439         if (self->end_icon_widget != NULL)
440           gtk_image_clear (GTK_IMAGE (self->end_icon_widget));
441 
442       g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_END_ICON]);
443     }
444 }
445 
446 static void
gtk_sidebar_row_finalize(GObject * object)447 gtk_sidebar_row_finalize (GObject *object)
448 {
449   GtkSidebarRow *self = GTK_SIDEBAR_ROW (object);
450 
451   g_clear_object (&self->start_icon);
452   g_clear_object (&self->end_icon);
453   g_free (self->label);
454   self->label = NULL;
455   g_free (self->tooltip);
456   self->tooltip = NULL;
457   g_free (self->uri);
458   self->uri = NULL;
459   g_clear_object (&self->drive);
460   g_clear_object (&self->volume);
461   g_clear_object (&self->mount);
462 #ifdef HAVE_CLOUDPROVIDERS
463   if (self->cloud_provider_account != NULL)
464     g_signal_handlers_disconnect_by_data (self->cloud_provider_account, self);
465   g_clear_object (&self->cloud_provider_account);
466 #endif
467 
468   G_OBJECT_CLASS (gtk_sidebar_row_parent_class)->finalize (object);
469 }
470 
471 static void
gtk_sidebar_row_init(GtkSidebarRow * self)472 gtk_sidebar_row_init (GtkSidebarRow *self)
473 {
474   gtk_widget_init_template (GTK_WIDGET (self));
475 }
476 
477 static void
gtk_sidebar_row_class_init(GtkSidebarRowClass * klass)478 gtk_sidebar_row_class_init (GtkSidebarRowClass *klass)
479 {
480   GObjectClass *object_class = G_OBJECT_CLASS (klass);
481   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
482 
483   object_class->get_property = gtk_sidebar_row_get_property;
484   object_class->set_property = gtk_sidebar_row_set_property;
485   object_class->finalize = gtk_sidebar_row_finalize;
486 
487   properties [PROP_SIDEBAR] =
488     g_param_spec_object ("sidebar",
489                          "Sidebar",
490                          "Sidebar",
491                          GTK_TYPE_PLACES_SIDEBAR,
492                          (G_PARAM_READWRITE |
493                           G_PARAM_CONSTRUCT_ONLY |
494                           G_PARAM_STATIC_STRINGS));
495 
496   properties [PROP_START_ICON] =
497     g_param_spec_object ("start-icon",
498                          "start-icon",
499                          "The start icon.",
500                          G_TYPE_ICON,
501                          (G_PARAM_READWRITE |
502                           G_PARAM_STATIC_STRINGS));
503 
504   properties [PROP_END_ICON] =
505     g_param_spec_object ("end-icon",
506                          "end-icon",
507                          "The end icon.",
508                          G_TYPE_ICON,
509                          (G_PARAM_READWRITE |
510                           G_PARAM_STATIC_STRINGS));
511 
512   properties [PROP_LABEL] =
513     g_param_spec_string ("label",
514                          "label",
515                          "The label text.",
516                          NULL,
517                          (G_PARAM_READWRITE |
518                           G_PARAM_STATIC_STRINGS));
519 
520   properties [PROP_TOOLTIP] =
521     g_param_spec_string ("tooltip",
522                          "Tooltip",
523                          "Tooltip",
524                          NULL,
525                          (G_PARAM_READWRITE |
526                           G_PARAM_STATIC_STRINGS));
527 
528   properties [PROP_EJECTABLE] =
529     g_param_spec_boolean ("ejectable",
530                           "Ejectable",
531                           "Ejectable",
532                           FALSE,
533                           (G_PARAM_READWRITE |
534                            G_PARAM_STATIC_STRINGS));
535 
536   properties [PROP_ORDER_INDEX] =
537     g_param_spec_int ("order-index",
538                       "OrderIndex",
539                       "Order Index",
540                       0, G_MAXINT, 0,
541                       (G_PARAM_READWRITE |
542                        G_PARAM_STATIC_STRINGS));
543 
544   properties [PROP_SECTION_TYPE] =
545     g_param_spec_int ("section-type",
546                       "section type",
547                       "The section type.",
548                       SECTION_INVALID, N_SECTIONS, SECTION_INVALID,
549                       (G_PARAM_READWRITE |
550                        G_PARAM_STATIC_STRINGS |
551                        G_PARAM_CONSTRUCT_ONLY));
552 
553   properties [PROP_PLACE_TYPE] =
554     g_param_spec_int ("place-type",
555                       "place type",
556                       "The place type.",
557                       PLACES_INVALID, N_PLACES, PLACES_INVALID,
558                       (G_PARAM_READWRITE |
559                        G_PARAM_STATIC_STRINGS |
560                        G_PARAM_CONSTRUCT_ONLY));
561 
562   properties [PROP_URI] =
563     g_param_spec_string ("uri",
564                          "Uri",
565                          "Uri",
566                          NULL,
567                          (G_PARAM_READWRITE |
568                           G_PARAM_CONSTRUCT_ONLY |
569                           G_PARAM_STATIC_STRINGS));
570 
571   properties [PROP_DRIVE] =
572     g_param_spec_object ("drive",
573                          "Drive",
574                          "Drive",
575                          G_TYPE_DRIVE,
576                          (G_PARAM_READWRITE |
577                           G_PARAM_CONSTRUCT_ONLY |
578                           G_PARAM_STATIC_STRINGS));
579 
580   properties [PROP_VOLUME] =
581     g_param_spec_object ("volume",
582                          "Volume",
583                          "Volume",
584                          G_TYPE_VOLUME,
585                          (G_PARAM_READWRITE |
586                           G_PARAM_CONSTRUCT_ONLY |
587                           G_PARAM_STATIC_STRINGS));
588 
589   properties [PROP_MOUNT] =
590     g_param_spec_object ("mount",
591                          "Mount",
592                          "Mount",
593                          G_TYPE_MOUNT,
594                          (G_PARAM_READWRITE |
595                           G_PARAM_CONSTRUCT_ONLY |
596                           G_PARAM_STATIC_STRINGS));
597 
598   properties [PROP_CLOUD_PROVIDER_ACCOUNT] =
599     g_param_spec_object ("cloud-provider-account",
600                          "CloudProvidersAccount",
601                          "CloudProvidersAccount",
602                          G_TYPE_OBJECT,
603                          (G_PARAM_READWRITE |
604                           G_PARAM_STATIC_STRINGS));
605 
606   properties [PROP_PLACEHOLDER] =
607     g_param_spec_boolean ("placeholder",
608                           "Placeholder",
609                           "Placeholder",
610                           FALSE,
611                           (G_PARAM_READWRITE |
612                            G_PARAM_CONSTRUCT_ONLY |
613                            G_PARAM_STATIC_STRINGS));
614 
615   g_object_class_install_properties (object_class, LAST_PROP, properties);
616 
617   gtk_widget_class_set_template_from_resource (widget_class,
618                                                "/org/gtk/libgtk/ui/gtksidebarrow.ui");
619 
620   gtk_widget_class_bind_template_child (widget_class, GtkSidebarRow, start_icon_widget);
621   gtk_widget_class_bind_template_child (widget_class, GtkSidebarRow, end_icon_widget);
622   gtk_widget_class_bind_template_child (widget_class, GtkSidebarRow, label_widget);
623   gtk_widget_class_bind_template_child (widget_class, GtkSidebarRow, eject_button);
624   gtk_widget_class_bind_template_child (widget_class, GtkSidebarRow, event_box);
625   gtk_widget_class_bind_template_child (widget_class, GtkSidebarRow, revealer);
626   gtk_widget_class_bind_template_child (widget_class, GtkSidebarRow, busy_spinner);
627 
628   gtk_widget_class_bind_template_callback (widget_class, on_child_revealed);
629   gtk_widget_class_set_css_name (widget_class, "row");
630 }
631 
632 GtkSidebarRow*
gtk_sidebar_row_clone(GtkSidebarRow * self)633 gtk_sidebar_row_clone (GtkSidebarRow *self)
634 {
635  return g_object_new (GTK_TYPE_SIDEBAR_ROW,
636                       "sidebar", self->sidebar,
637                       "start-icon", self->start_icon,
638                       "end-icon", self->end_icon,
639                       "label", self->label,
640                       "tooltip", self->tooltip,
641                       "ejectable", self->ejectable,
642                       "order-index", self->order_index,
643                       "section-type", self->section_type,
644                       "place-type", self->place_type,
645                       "uri", self->uri,
646                       "drive", self->drive,
647                       "volume", self->volume,
648                       "mount", self->mount,
649                       "cloud-provider-account", self->cloud_provider_account,
650                       NULL);
651 }
652 
653 GtkWidget*
gtk_sidebar_row_get_eject_button(GtkSidebarRow * self)654 gtk_sidebar_row_get_eject_button (GtkSidebarRow *self)
655 {
656   return self->eject_button;
657 }
658 
659 GtkWidget*
gtk_sidebar_row_get_event_box(GtkSidebarRow * self)660 gtk_sidebar_row_get_event_box (GtkSidebarRow *self)
661 {
662   return self->event_box;
663 }
664 
665 void
gtk_sidebar_row_set_busy(GtkSidebarRow * row,gboolean is_busy)666 gtk_sidebar_row_set_busy (GtkSidebarRow *row,
667                           gboolean       is_busy)
668 {
669   g_return_if_fail (GTK_IS_SIDEBAR_ROW (row));
670 
671   gtk_widget_set_visible (row->busy_spinner, is_busy);
672 }
673