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

source code of gtk/gtk/gtkplacesviewrow.c