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 | |
44 | struct _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 | |
66 | G_DEFINE_TYPE (GtkPlacesViewRow, gtk_places_view_row, GTK_TYPE_LIST_BOX_ROW) |
67 | |
68 | enum { |
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 | |
80 | static GParamSpec *properties [LAST_PROP]; |
81 | |
82 | static void |
83 | measure_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); |
144 | out: |
145 | g_object_unref (object); |
146 | } |
147 | |
148 | static void |
149 | measure_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 | |
198 | static void |
199 | gtk_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 | |
213 | static void |
214 | gtk_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 | |
258 | static void |
259 | gtk_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 | |
313 | static void |
314 | gtk_places_view_row_size_allocate (GtkWidget *widget, |
315 | int width, |
316 | int height, |
317 | int baseline) |
318 | { |
319 | GtkWidget * = 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 | |
326 | static void |
327 | gtk_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 | |
401 | static void |
402 | gtk_places_view_row_init (GtkPlacesViewRow *self) |
403 | { |
404 | gtk_widget_init_template (GTK_WIDGET (self)); |
405 | } |
406 | |
407 | GtkWidget* |
408 | gtk_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 | |
417 | GMount* |
418 | gtk_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 | |
425 | GVolume* |
426 | gtk_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 | |
433 | GFile* |
434 | gtk_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 | |
441 | GtkWidget* |
442 | gtk_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 | |
449 | void |
450 | gtk_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 | |
468 | gboolean |
469 | gtk_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 | |
476 | void |
477 | gtk_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 | |
489 | void |
490 | gtk_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 | |
497 | void |
498 | gtk_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 | |