1 /* gtkplacesviewrow.c
2  *
3  * Copyright (C) 2015 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU Lesser General Public License as published by
7  * the Free Software Foundation, either version 2.1 of the License, or
8  * (at your option) any later version.
9  *
10  * This program 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
13  * GNU 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 <gio/gio.h>
22 
23 #include "gtkplacesviewrowprivate.h"
24 
25 /* As this widget is shared with Nautilus, we use this guard to
26  * ensure that internally we only include the files that we need
27  * instead of including gtk.h
28  */
29 #ifdef GTK_COMPILATION
30 #include "gtkbutton.h"
31 #include "gtkeventbox.h"
32 #include "gtkimage.h"
33 #include "gtkintl.h"
34 #include "gtklabel.h"
35 #include "gtkspinner.h"
36 #include "gtkstack.h"
37 #include "gtktypebuiltins.h"
38 #else
39 #include <gtk/gtk.h>
40 #endif
41 
42 struct _GtkPlacesViewRow
43 {
44   GtkListBoxRow  parent_instance;
45 
46   GtkLabel      *available_space_label;
47   GtkStack      *mount_stack;
48   GtkSpinner    *busy_spinner;
49   GtkButton     *eject_button;
50   GtkImage      *eject_icon;
51   GtkEventBox   *event_box;
52   GtkImage      *icon_image;
53   GtkLabel      *name_label;
54   GtkLabel      *path_label;
55 
56   GVolume       *volume;
57   GMount        *mount;
58   GFile         *file;
59 
60   GCancellable  *cancellable;
61 
62   gint           is_network : 1;
63 };
64 
65 G_DEFINE_TYPE (GtkPlacesViewRow, gtk_places_view_row, GTK_TYPE_LIST_BOX_ROW)
66 
67 enum {
68   PROP_0,
69   PROP_ICON,
70   PROP_NAME,
71   PROP_PATH,
72   PROP_VOLUME,
73   PROP_MOUNT,
74   PROP_FILE,
75   PROP_IS_NETWORK,
76   LAST_PROP
77 };
78 
79 static GParamSpec *properties [LAST_PROP];
80 
81 static void
measure_available_space_finished(GObject * object,GAsyncResult * res,gpointer user_data)82 measure_available_space_finished (GObject      *object,
83                                   GAsyncResult *res,
84                                   gpointer      user_data)
85 {
86   GtkPlacesViewRow *row = user_data;
87   GFileInfo *info;
88   GError *error;
89   guint64 free_space;
90   guint64 total_space;
91   gchar *formatted_free_size;
92   gchar *formatted_total_size;
93   gchar *label;
94   guint plural_form;
95 
96   error = NULL;
97 
98   info = g_file_query_filesystem_info_finish (G_FILE (object),
99                                               res,
100                                               &error);
101 
102   if (error)
103     {
104       if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) &&
105           !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_MOUNTED))
106         {
107           g_warning ("Failed to measure available space: %s", error->message);
108         }
109 
110       g_clear_error (&error);
111       goto out;
112     }
113 
114   if (!g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE) ||
115       !g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE))
116     {
117       g_object_unref (info);
118       goto out;
119     }
120 
121   free_space = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
122   total_space = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE);
123 
124   formatted_free_size = g_format_size (free_space);
125   formatted_total_size = g_format_size (total_space);
126 
127   /* read g_format_size code in glib for further understanding */
128   plural_form = free_space < 1000 ? free_space : free_space % 1000 + 1000;
129 
130   /* Translators: respectively, free and total space of the drive. The plural form
131    * should be based on the free space available.
132    * i.e. 1 GB / 24 GB available.
133    */
134   label = g_strdup_printf (dngettext (GETTEXT_PACKAGE, "%s / %s available", "%s / %s available", plural_form),
135                            formatted_free_size, formatted_total_size);
136 
137   gtk_label_set_label (row->available_space_label, label);
138 
139   g_object_unref (info);
140   g_free (formatted_total_size);
141   g_free (formatted_free_size);
142   g_free (label);
143 out:
144   g_object_unref (object);
145 }
146 
147 static void
measure_available_space(GtkPlacesViewRow * row)148 measure_available_space (GtkPlacesViewRow *row)
149 {
150   gboolean should_measure;
151 
152   should_measure = (!row->is_network && (row->volume || row->mount || row->file));
153 
154   gtk_label_set_label (row->available_space_label, "");
155   gtk_widget_set_visible (GTK_WIDGET (row->available_space_label), should_measure);
156 
157   if (should_measure)
158     {
159       GFile *file = NULL;
160 
161       if (row->file)
162         {
163           file = g_object_ref (row->file);
164         }
165       else if (row->mount)
166         {
167           file = g_mount_get_root (row->mount);
168         }
169       else if (row->volume)
170         {
171           GMount *mount;
172 
173           mount = g_volume_get_mount (row->volume);
174 
175           if (mount)
176             file = g_mount_get_root (row->mount);
177 
178           g_clear_object (&mount);
179         }
180 
181       if (file)
182         {
183           g_cancellable_cancel (row->cancellable);
184           g_clear_object (&row->cancellable);
185           row->cancellable = g_cancellable_new ();
186 
187           g_file_query_filesystem_info_async (file,
188                                               G_FILE_ATTRIBUTE_FILESYSTEM_FREE "," G_FILE_ATTRIBUTE_FILESYSTEM_SIZE,
189                                               G_PRIORITY_DEFAULT,
190                                               row->cancellable,
191                                               measure_available_space_finished,
192                                               row);
193         }
194     }
195 }
196 
197 static void
gtk_places_view_row_finalize(GObject * object)198 gtk_places_view_row_finalize (GObject *object)
199 {
200   GtkPlacesViewRow *self = GTK_PLACES_VIEW_ROW (object);
201 
202   g_cancellable_cancel (self->cancellable);
203 
204   g_clear_object (&self->volume);
205   g_clear_object (&self->mount);
206   g_clear_object (&self->file);
207   g_clear_object (&self->cancellable);
208 
209   G_OBJECT_CLASS (gtk_places_view_row_parent_class)->finalize (object);
210 }
211 
212 static void
gtk_places_view_row_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)213 gtk_places_view_row_get_property (GObject    *object,
214                                   guint       prop_id,
215                                   GValue     *value,
216                                   GParamSpec *pspec)
217 {
218   GtkPlacesViewRow *self;
219   GIcon *icon;
220 
221   self = GTK_PLACES_VIEW_ROW (object);
222   icon = NULL;
223 
224   switch (prop_id)
225     {
226     case PROP_ICON:
227       gtk_image_get_gicon (self->icon_image, &icon, NULL);
228       g_value_set_object (value, icon);
229       break;
230 
231     case PROP_NAME:
232       g_value_set_string (value, gtk_label_get_label (self->name_label));
233       break;
234 
235     case PROP_PATH:
236       g_value_set_string (value, gtk_label_get_label (self->path_label));
237       break;
238 
239     case PROP_VOLUME:
240       g_value_set_object (value, self->volume);
241       break;
242 
243     case PROP_MOUNT:
244       g_value_set_object (value, self->mount);
245       break;
246 
247     case PROP_FILE:
248       g_value_set_object (value, self->file);
249       break;
250 
251     case PROP_IS_NETWORK:
252       g_value_set_boolean (value, self->is_network);
253       break;
254 
255     default:
256       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
257     }
258 }
259 
260 static void
gtk_places_view_row_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)261 gtk_places_view_row_set_property (GObject      *object,
262                                   guint         prop_id,
263                                   const GValue *value,
264                                   GParamSpec   *pspec)
265 {
266   GtkPlacesViewRow *self = GTK_PLACES_VIEW_ROW (object);
267 
268   switch (prop_id)
269     {
270     case PROP_ICON:
271       gtk_image_set_from_gicon (self->icon_image,
272                                 g_value_get_object (value),
273                                 GTK_ICON_SIZE_LARGE_TOOLBAR);
274       break;
275 
276     case PROP_NAME:
277       gtk_label_set_label (self->name_label, g_value_get_string (value));
278       break;
279 
280     case PROP_PATH:
281       gtk_label_set_label (self->path_label, g_value_get_string (value));
282       break;
283 
284     case PROP_VOLUME:
285       g_set_object (&self->volume, g_value_get_object (value));
286       break;
287 
288     case PROP_MOUNT:
289       g_set_object (&self->mount, g_value_get_object (value));
290       if (self->mount != NULL)
291         {
292           gtk_stack_set_visible_child (self->mount_stack, GTK_WIDGET (self->eject_button));
293           gtk_widget_set_child_visible (GTK_WIDGET (self->mount_stack), TRUE);
294         }
295       else
296         {
297           gtk_widget_set_child_visible (GTK_WIDGET (self->mount_stack), FALSE);
298         }
299       measure_available_space (self);
300       break;
301 
302     case PROP_FILE:
303       g_set_object (&self->file, g_value_get_object (value));
304       measure_available_space (self);
305       break;
306 
307     case PROP_IS_NETWORK:
308       gtk_places_view_row_set_is_network (self, g_value_get_boolean (value));
309       measure_available_space (self);
310       break;
311 
312     default:
313       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
314     }
315 }
316 
317 static void
gtk_places_view_row_class_init(GtkPlacesViewRowClass * klass)318 gtk_places_view_row_class_init (GtkPlacesViewRowClass *klass)
319 {
320   GObjectClass *object_class = G_OBJECT_CLASS (klass);
321   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
322 
323   object_class->finalize = gtk_places_view_row_finalize;
324   object_class->get_property = gtk_places_view_row_get_property;
325   object_class->set_property = gtk_places_view_row_set_property;
326 
327   properties[PROP_ICON] =
328           g_param_spec_object ("icon",
329                                P_("Icon of the row"),
330                                P_("The icon representing the volume"),
331                                G_TYPE_ICON,
332                                G_PARAM_READWRITE);
333 
334   properties[PROP_NAME] =
335           g_param_spec_string ("name",
336                                P_("Name of the volume"),
337                                P_("The name of the volume"),
338                                "",
339                                G_PARAM_READWRITE);
340 
341   properties[PROP_PATH] =
342           g_param_spec_string ("path",
343                                P_("Path of the volume"),
344                                P_("The path of the volume"),
345                                "",
346                                G_PARAM_READWRITE);
347 
348   properties[PROP_VOLUME] =
349           g_param_spec_object ("volume",
350                                P_("Volume represented by the row"),
351                                P_("The volume represented by the row"),
352                                G_TYPE_VOLUME,
353                                G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
354 
355   properties[PROP_MOUNT] =
356           g_param_spec_object ("mount",
357                                P_("Mount represented by the row"),
358                                P_("The mount point represented by the row, if any"),
359                                G_TYPE_MOUNT,
360                                G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
361 
362   properties[PROP_FILE] =
363           g_param_spec_object ("file",
364                                P_("File represented by the row"),
365                                P_("The file represented by the row, if any"),
366                                G_TYPE_FILE,
367                                G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
368 
369   properties[PROP_IS_NETWORK] =
370           g_param_spec_boolean ("is-network",
371                                 P_("Whether the row represents a network location"),
372                                 P_("Whether the row represents a network location"),
373                                 FALSE,
374                                 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
375 
376   g_object_class_install_properties (object_class, LAST_PROP, properties);
377 
378   gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/ui/gtkplacesviewrow.ui");
379 
380   gtk_widget_class_bind_template_child (widget_class, GtkPlacesViewRow, available_space_label);
381   gtk_widget_class_bind_template_child (widget_class, GtkPlacesViewRow, mount_stack);
382   gtk_widget_class_bind_template_child (widget_class, GtkPlacesViewRow, busy_spinner);
383   gtk_widget_class_bind_template_child (widget_class, GtkPlacesViewRow, eject_button);
384   gtk_widget_class_bind_template_child (widget_class, GtkPlacesViewRow, eject_icon);
385   gtk_widget_class_bind_template_child (widget_class, GtkPlacesViewRow, event_box);
386   gtk_widget_class_bind_template_child (widget_class, GtkPlacesViewRow, icon_image);
387   gtk_widget_class_bind_template_child (widget_class, GtkPlacesViewRow, name_label);
388   gtk_widget_class_bind_template_child (widget_class, GtkPlacesViewRow, path_label);
389 }
390 
391 static void
gtk_places_view_row_init(GtkPlacesViewRow * self)392 gtk_places_view_row_init (GtkPlacesViewRow *self)
393 {
394   gtk_widget_init_template (GTK_WIDGET (self));
395 }
396 
397 GtkWidget*
gtk_places_view_row_new(GVolume * volume,GMount * mount)398 gtk_places_view_row_new (GVolume *volume,
399                          GMount  *mount)
400 {
401   return g_object_new (GTK_TYPE_PLACES_VIEW_ROW,
402                        "volume", volume,
403                        "mount", mount,
404                        NULL);
405 }
406 
407 GMount*
gtk_places_view_row_get_mount(GtkPlacesViewRow * row)408 gtk_places_view_row_get_mount (GtkPlacesViewRow *row)
409 {
410   g_return_val_if_fail (GTK_IS_PLACES_VIEW_ROW (row), NULL);
411 
412   return row->mount;
413 }
414 
415 GVolume*
gtk_places_view_row_get_volume(GtkPlacesViewRow * row)416 gtk_places_view_row_get_volume (GtkPlacesViewRow *row)
417 {
418   g_return_val_if_fail (GTK_IS_PLACES_VIEW_ROW (row), NULL);
419 
420   return row->volume;
421 }
422 
423 GFile*
gtk_places_view_row_get_file(GtkPlacesViewRow * row)424 gtk_places_view_row_get_file (GtkPlacesViewRow *row)
425 {
426   g_return_val_if_fail (GTK_IS_PLACES_VIEW_ROW (row), NULL);
427 
428   return row->file;
429 }
430 
431 GtkWidget*
gtk_places_view_row_get_eject_button(GtkPlacesViewRow * row)432 gtk_places_view_row_get_eject_button (GtkPlacesViewRow *row)
433 {
434   g_return_val_if_fail (GTK_IS_PLACES_VIEW_ROW (row), NULL);
435 
436   return GTK_WIDGET (row->eject_button);
437 }
438 
439 GtkWidget*
gtk_places_view_row_get_event_box(GtkPlacesViewRow * row)440 gtk_places_view_row_get_event_box (GtkPlacesViewRow *row)
441 {
442   g_return_val_if_fail (GTK_IS_PLACES_VIEW_ROW (row), NULL);
443 
444   return GTK_WIDGET (row->event_box);
445 }
446 
447 void
gtk_places_view_row_set_busy(GtkPlacesViewRow * row,gboolean is_busy)448 gtk_places_view_row_set_busy (GtkPlacesViewRow *row,
449                               gboolean          is_busy)
450 {
451   g_return_if_fail (GTK_IS_PLACES_VIEW_ROW (row));
452 
453   if (is_busy)
454     {
455       gtk_stack_set_visible_child (row->mount_stack, GTK_WIDGET (row->busy_spinner));
456       gtk_widget_set_child_visible (GTK_WIDGET (row->mount_stack), TRUE);
457     }
458   else
459     {
460       gtk_widget_set_child_visible (GTK_WIDGET (row->mount_stack), FALSE);
461     }
462 }
463 
464 gboolean
gtk_places_view_row_get_is_network(GtkPlacesViewRow * row)465 gtk_places_view_row_get_is_network (GtkPlacesViewRow *row)
466 {
467   g_return_val_if_fail (GTK_IS_PLACES_VIEW_ROW (row), FALSE);
468 
469   return row->is_network;
470 }
471 
472 void
gtk_places_view_row_set_is_network(GtkPlacesViewRow * row,gboolean is_network)473 gtk_places_view_row_set_is_network (GtkPlacesViewRow *row,
474                                     gboolean          is_network)
475 {
476   if (row->is_network != is_network)
477     {
478       row->is_network = is_network;
479 
480       gtk_image_set_from_icon_name (row->eject_icon, "media-eject-symbolic", GTK_ICON_SIZE_BUTTON);
481       gtk_widget_set_tooltip_text (GTK_WIDGET (row->eject_button), is_network ? _("Disconnect") : _("Unmount"));
482     }
483 }
484 
485 void
gtk_places_view_row_set_path_size_group(GtkPlacesViewRow * row,GtkSizeGroup * group)486 gtk_places_view_row_set_path_size_group (GtkPlacesViewRow *row,
487                                          GtkSizeGroup     *group)
488 {
489   if (group)
490     gtk_size_group_add_widget (group, GTK_WIDGET (row->path_label));
491 }
492 
493 void
gtk_places_view_row_set_space_size_group(GtkPlacesViewRow * row,GtkSizeGroup * group)494 gtk_places_view_row_set_space_size_group (GtkPlacesViewRow *row,
495                                           GtkSizeGroup     *group)
496 {
497   if (group)
498     gtk_size_group_add_widget (group, GTK_WIDGET (row->available_space_label));
499 }
500