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