forked from AuroraMiddleware/gtk
67125ae4e0
GtkPlacesView is a widget to display locations in the computer, such as root ("/") and volumes, separating the persistent devices from removable ones. From the latest mockups[1], GtkPlacesView would display the available space of local drives like partitions. This, however, is not implemented in the current codebase. Fix that by implementing the measurement of disk space, and adding a new property GtkPlacesView::show-disk-usage which controls the visibility of measured disks. [1] https://raw.githubusercontent.com/gnome-design-team/gnome-mockups/master/nautilus/nautilus-next/other-locations.png https://bugzilla.gnome.org/show_bug.cgi?id=759225
480 lines
14 KiB
C
480 lines
14 KiB
C
/* gtkplacesviewrow.c
|
|
*
|
|
* Copyright (C) 2015 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation, either version 2.1 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <gio/gio.h>
|
|
|
|
#include "gtkplacesviewrowprivate.h"
|
|
|
|
/* As this widget is shared with Nautilus, we use this guard to
|
|
* ensure that internally we only include the files that we need
|
|
* instead of including gtk.h
|
|
*/
|
|
#ifdef GTK_COMPILATION
|
|
#include "gtkbutton.h"
|
|
#include "gtkeventbox.h"
|
|
#include "gtkimage.h"
|
|
#include "gtkintl.h"
|
|
#include "gtklabel.h"
|
|
#include "gtkspinner.h"
|
|
#include "gtktypebuiltins.h"
|
|
#else
|
|
#include <gtk/gtk.h>
|
|
#endif
|
|
|
|
struct _GtkPlacesViewRow
|
|
{
|
|
GtkListBoxRow parent_instance;
|
|
|
|
GtkLabel *available_space_label;
|
|
GtkSpinner *busy_spinner;
|
|
GtkButton *eject_button;
|
|
GtkImage *eject_icon;
|
|
GtkEventBox *event_box;
|
|
GtkImage *icon_image;
|
|
GtkLabel *name_label;
|
|
GtkLabel *path_label;
|
|
|
|
GVolume *volume;
|
|
GMount *mount;
|
|
GFile *file;
|
|
|
|
GCancellable *cancellable;
|
|
|
|
gint is_network : 1;
|
|
};
|
|
|
|
G_DEFINE_TYPE (GtkPlacesViewRow, gtk_places_view_row, GTK_TYPE_LIST_BOX_ROW)
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_ICON,
|
|
PROP_NAME,
|
|
PROP_PATH,
|
|
PROP_VOLUME,
|
|
PROP_MOUNT,
|
|
PROP_FILE,
|
|
PROP_IS_NETWORK,
|
|
LAST_PROP
|
|
};
|
|
|
|
static GParamSpec *properties [LAST_PROP];
|
|
|
|
static void
|
|
measure_available_space_finished (GObject *object,
|
|
GAsyncResult *res,
|
|
gpointer user_data)
|
|
{
|
|
GtkPlacesViewRow *row = user_data;
|
|
GFileInfo *info;
|
|
GError *error;
|
|
guint64 free_space;
|
|
guint64 total_space;
|
|
gchar *formatted_free_size;
|
|
gchar *formatted_total_size;
|
|
gchar *label;
|
|
|
|
error = NULL;
|
|
|
|
info = g_file_query_filesystem_info_finish (G_FILE (object),
|
|
res,
|
|
&error);
|
|
|
|
if (error)
|
|
{
|
|
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) &&
|
|
!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_MOUNTED))
|
|
{
|
|
g_warning ("Failed to measure available space: %s", error->message);
|
|
}
|
|
|
|
g_clear_error (&error);
|
|
goto out;
|
|
}
|
|
|
|
if (!g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE) ||
|
|
!g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE))
|
|
{
|
|
g_object_unref (info);
|
|
goto out;
|
|
}
|
|
|
|
free_space = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE);
|
|
total_space = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE);
|
|
|
|
formatted_free_size = g_format_size (free_space);
|
|
formatted_total_size = g_format_size (total_space);
|
|
/* Translators: respectively, free and total space of the drive */
|
|
label = g_strdup_printf (_("%s / %s available"), formatted_free_size, formatted_total_size);
|
|
|
|
gtk_label_set_label (row->available_space_label, label);
|
|
|
|
g_object_unref (info);
|
|
g_free (formatted_total_size);
|
|
g_free (formatted_free_size);
|
|
g_free (label);
|
|
out:
|
|
g_object_unref (object);
|
|
}
|
|
|
|
static void
|
|
measure_available_space (GtkPlacesViewRow *row)
|
|
{
|
|
gboolean should_measure;
|
|
|
|
should_measure = (!row->is_network && (row->volume || row->mount || row->file));
|
|
|
|
gtk_label_set_label (row->available_space_label, "");
|
|
gtk_widget_set_visible (GTK_WIDGET (row->available_space_label), should_measure);
|
|
|
|
if (should_measure)
|
|
{
|
|
GFile *file = NULL;
|
|
|
|
if (row->file)
|
|
{
|
|
file = g_object_ref (row->file);
|
|
}
|
|
else if (row->mount)
|
|
{
|
|
file = g_mount_get_root (row->mount);
|
|
}
|
|
else if (row->volume)
|
|
{
|
|
GMount *mount;
|
|
|
|
mount = g_volume_get_mount (row->volume);
|
|
|
|
if (mount)
|
|
file = g_mount_get_root (row->mount);
|
|
|
|
g_clear_object (&mount);
|
|
}
|
|
|
|
if (file)
|
|
{
|
|
g_cancellable_cancel (row->cancellable);
|
|
g_clear_object (&row->cancellable);
|
|
row->cancellable = g_cancellable_new ();
|
|
|
|
g_file_query_filesystem_info_async (file,
|
|
G_FILE_ATTRIBUTE_FILESYSTEM_FREE "," G_FILE_ATTRIBUTE_FILESYSTEM_SIZE,
|
|
G_PRIORITY_DEFAULT,
|
|
row->cancellable,
|
|
measure_available_space_finished,
|
|
row);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
gtk_places_view_row_finalize (GObject *object)
|
|
{
|
|
GtkPlacesViewRow *self = GTK_PLACES_VIEW_ROW (object);
|
|
|
|
g_cancellable_cancel (self->cancellable);
|
|
|
|
g_clear_object (&self->volume);
|
|
g_clear_object (&self->mount);
|
|
g_clear_object (&self->file);
|
|
g_clear_object (&self->cancellable);
|
|
|
|
G_OBJECT_CLASS (gtk_places_view_row_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
gtk_places_view_row_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GtkPlacesViewRow *self;
|
|
GIcon *icon;
|
|
|
|
self = GTK_PLACES_VIEW_ROW (object);
|
|
icon = NULL;
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_ICON:
|
|
gtk_image_get_gicon (self->icon_image, &icon, NULL);
|
|
g_value_set_object (value, icon);
|
|
break;
|
|
|
|
case PROP_NAME:
|
|
g_value_set_string (value, gtk_label_get_label (self->name_label));
|
|
break;
|
|
|
|
case PROP_PATH:
|
|
g_value_set_string (value, gtk_label_get_label (self->path_label));
|
|
break;
|
|
|
|
case PROP_VOLUME:
|
|
g_value_set_object (value, self->volume);
|
|
break;
|
|
|
|
case PROP_MOUNT:
|
|
g_value_set_object (value, self->mount);
|
|
break;
|
|
|
|
case PROP_FILE:
|
|
g_value_set_object (value, self->file);
|
|
break;
|
|
|
|
case PROP_IS_NETWORK:
|
|
g_value_set_boolean (value, self->is_network);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gtk_places_view_row_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GtkPlacesViewRow *self = GTK_PLACES_VIEW_ROW (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_ICON:
|
|
gtk_image_set_from_gicon (self->icon_image,
|
|
g_value_get_object (value),
|
|
GTK_ICON_SIZE_LARGE_TOOLBAR);
|
|
break;
|
|
|
|
case PROP_NAME:
|
|
gtk_label_set_label (self->name_label, g_value_get_string (value));
|
|
break;
|
|
|
|
case PROP_PATH:
|
|
gtk_label_set_label (self->path_label, g_value_get_string (value));
|
|
break;
|
|
|
|
case PROP_VOLUME:
|
|
g_set_object (&self->volume, g_value_get_object (value));
|
|
break;
|
|
|
|
case PROP_MOUNT:
|
|
g_set_object (&self->mount, g_value_get_object (value));
|
|
|
|
/*
|
|
* When we hide the eject button, no size is allocated for it. Since
|
|
* we want to have alignment between rows, it needs an empty space
|
|
* when the eject button is not available. So, call then
|
|
* gtk_widget_set_child_visible(), which makes the button allocate the
|
|
* size but it stays hidden when needed.
|
|
*/
|
|
gtk_widget_set_child_visible (GTK_WIDGET (self->eject_button), self->mount != NULL);
|
|
measure_available_space (self);
|
|
break;
|
|
|
|
case PROP_FILE:
|
|
g_set_object (&self->file, g_value_get_object (value));
|
|
measure_available_space (self);
|
|
break;
|
|
|
|
case PROP_IS_NETWORK:
|
|
gtk_places_view_row_set_is_network (self, g_value_get_boolean (value));
|
|
measure_available_space (self);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gtk_places_view_row_class_init (GtkPlacesViewRowClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
|
|
|
object_class->finalize = gtk_places_view_row_finalize;
|
|
object_class->get_property = gtk_places_view_row_get_property;
|
|
object_class->set_property = gtk_places_view_row_set_property;
|
|
|
|
properties[PROP_ICON] =
|
|
g_param_spec_object ("icon",
|
|
P_("Icon of the row"),
|
|
P_("The icon representing the volume"),
|
|
G_TYPE_ICON,
|
|
G_PARAM_READWRITE);
|
|
|
|
properties[PROP_NAME] =
|
|
g_param_spec_string ("name",
|
|
P_("Name of the volume"),
|
|
P_("The name of the volume"),
|
|
"",
|
|
G_PARAM_READWRITE);
|
|
|
|
properties[PROP_PATH] =
|
|
g_param_spec_string ("path",
|
|
P_("Path of the volume"),
|
|
P_("The path of the volume"),
|
|
"",
|
|
G_PARAM_READWRITE);
|
|
|
|
properties[PROP_VOLUME] =
|
|
g_param_spec_object ("volume",
|
|
P_("Volume represented by the row"),
|
|
P_("The volume represented by the row"),
|
|
G_TYPE_VOLUME,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
|
|
|
|
properties[PROP_MOUNT] =
|
|
g_param_spec_object ("mount",
|
|
P_("Mount represented by the row"),
|
|
P_("The mount point represented by the row, if any"),
|
|
G_TYPE_MOUNT,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
|
|
|
|
properties[PROP_FILE] =
|
|
g_param_spec_object ("file",
|
|
P_("File represented by the row"),
|
|
P_("The file represented by the row, if any"),
|
|
G_TYPE_FILE,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
|
|
|
|
properties[PROP_IS_NETWORK] =
|
|
g_param_spec_boolean ("is-network",
|
|
P_("Whether the row represents a network location"),
|
|
P_("Whether the row represents a network location"),
|
|
FALSE,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
|
|
|
|
g_object_class_install_properties (object_class, LAST_PROP, properties);
|
|
|
|
gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/ui/gtkplacesviewrow.ui");
|
|
|
|
gtk_widget_class_bind_template_child (widget_class, GtkPlacesViewRow, available_space_label);
|
|
gtk_widget_class_bind_template_child (widget_class, GtkPlacesViewRow, busy_spinner);
|
|
gtk_widget_class_bind_template_child (widget_class, GtkPlacesViewRow, eject_button);
|
|
gtk_widget_class_bind_template_child (widget_class, GtkPlacesViewRow, eject_icon);
|
|
gtk_widget_class_bind_template_child (widget_class, GtkPlacesViewRow, event_box);
|
|
gtk_widget_class_bind_template_child (widget_class, GtkPlacesViewRow, icon_image);
|
|
gtk_widget_class_bind_template_child (widget_class, GtkPlacesViewRow, name_label);
|
|
gtk_widget_class_bind_template_child (widget_class, GtkPlacesViewRow, path_label);
|
|
}
|
|
|
|
static void
|
|
gtk_places_view_row_init (GtkPlacesViewRow *self)
|
|
{
|
|
gtk_widget_init_template (GTK_WIDGET (self));
|
|
}
|
|
|
|
GtkWidget*
|
|
gtk_places_view_row_new (GVolume *volume,
|
|
GMount *mount)
|
|
{
|
|
return g_object_new (GTK_TYPE_PLACES_VIEW_ROW,
|
|
"volume", volume,
|
|
"mount", mount,
|
|
NULL);
|
|
}
|
|
|
|
GMount*
|
|
gtk_places_view_row_get_mount (GtkPlacesViewRow *row)
|
|
{
|
|
g_return_val_if_fail (GTK_IS_PLACES_VIEW_ROW (row), NULL);
|
|
|
|
return row->mount;
|
|
}
|
|
|
|
GVolume*
|
|
gtk_places_view_row_get_volume (GtkPlacesViewRow *row)
|
|
{
|
|
g_return_val_if_fail (GTK_IS_PLACES_VIEW_ROW (row), NULL);
|
|
|
|
return row->volume;
|
|
}
|
|
|
|
GFile*
|
|
gtk_places_view_row_get_file (GtkPlacesViewRow *row)
|
|
{
|
|
g_return_val_if_fail (GTK_IS_PLACES_VIEW_ROW (row), NULL);
|
|
|
|
return row->file;
|
|
}
|
|
|
|
GtkWidget*
|
|
gtk_places_view_row_get_eject_button (GtkPlacesViewRow *row)
|
|
{
|
|
g_return_val_if_fail (GTK_IS_PLACES_VIEW_ROW (row), NULL);
|
|
|
|
return GTK_WIDGET (row->eject_button);
|
|
}
|
|
|
|
GtkWidget*
|
|
gtk_places_view_row_get_event_box (GtkPlacesViewRow *row)
|
|
{
|
|
g_return_val_if_fail (GTK_IS_PLACES_VIEW_ROW (row), NULL);
|
|
|
|
return GTK_WIDGET (row->event_box);
|
|
}
|
|
|
|
void
|
|
gtk_places_view_row_set_busy (GtkPlacesViewRow *row,
|
|
gboolean is_busy)
|
|
{
|
|
g_return_if_fail (GTK_IS_PLACES_VIEW_ROW (row));
|
|
|
|
gtk_widget_set_visible (GTK_WIDGET (row->busy_spinner), is_busy);
|
|
}
|
|
|
|
gboolean
|
|
gtk_places_view_row_get_is_network (GtkPlacesViewRow *row)
|
|
{
|
|
g_return_val_if_fail (GTK_IS_PLACES_VIEW_ROW (row), FALSE);
|
|
|
|
return row->is_network;
|
|
}
|
|
|
|
void
|
|
gtk_places_view_row_set_is_network (GtkPlacesViewRow *row,
|
|
gboolean is_network)
|
|
{
|
|
if (row->is_network != is_network)
|
|
{
|
|
row->is_network = is_network;
|
|
|
|
gtk_image_set_from_icon_name (row->eject_icon, "media-eject-symbolic", GTK_ICON_SIZE_BUTTON);
|
|
gtk_widget_set_tooltip_text (GTK_WIDGET (row->eject_button), is_network ? _("Disconnect") : _("Unmount"));
|
|
}
|
|
}
|
|
|
|
void
|
|
gtk_places_view_row_set_path_size_group (GtkPlacesViewRow *row,
|
|
GtkSizeGroup *group)
|
|
{
|
|
if (group)
|
|
gtk_size_group_add_widget (group, GTK_WIDGET (row->path_label));
|
|
}
|
|
|
|
void
|
|
gtk_places_view_row_set_space_size_group (GtkPlacesViewRow *row,
|
|
GtkSizeGroup *group)
|
|
{
|
|
if (group)
|
|
gtk_size_group_add_widget (group, GTK_WIDGET (row->available_space_label));
|
|
}
|