/* * Copyright © 2021 Benjamin Otte * * This library 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 library 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 library. If not, see . * * Authors: Benjamin Otte */ #include "config.h" #include "gtkdataviewer.h" #include "gtkbinlayout.h" #include "gtklabel.h" #include "gtkpicture.h" #include "gtkcolorswatchprivate.h" #include "gtkbox.h" struct _GtkDataViewer { GtkWidget parent_instance; GtkWidget *contents; GCancellable *cancellable; GError *error; enum { NOT_LOADED = 0, LOADING_DONE, LOADING_EXTERNALLY, LOADING_INTERNALLY, LOADING_FAILED } loading; }; enum { PROP_0, PROP_LOADING, N_PROPS }; enum { LOAD, LAST_SIGNAL }; G_DEFINE_TYPE (GtkDataViewer, gtk_data_viewer, GTK_TYPE_WIDGET) static GParamSpec *properties[N_PROPS] = { NULL, }; static guint signals[LAST_SIGNAL]; static void gtk_data_viewer_ensure_loaded (GtkDataViewer *self) { gboolean started_loading; if (self->loading != NOT_LOADED) return; self->loading = LOADING_EXTERNALLY; self->cancellable = g_cancellable_new (); g_signal_emit (self, signals[LOAD], 0, self->cancellable, &started_loading); if (!started_loading) { self->loading = LOADING_FAILED; /* avoid notify::is_loading */ gtk_data_viewer_load_error (self, g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED, "Nothing to load")); } g_assert (self->loading != NOT_LOADED); if (gtk_data_viewer_is_loading (self)) g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOADING]); } static void gtk_data_viewer_realize (GtkWidget *widget) { GtkDataViewer *self = GTK_DATA_VIEWER (widget); GTK_WIDGET_CLASS (gtk_data_viewer_parent_class)->realize (widget); gtk_data_viewer_ensure_loaded (self); } static void gtk_data_viewer_unrealize (GtkWidget *widget) { GtkDataViewer *self = GTK_DATA_VIEWER (widget); GTK_WIDGET_CLASS (gtk_data_viewer_parent_class)->unrealize (widget); gtk_data_viewer_reset (self); } static void gtk_data_viewer_dispose (GObject *object) { //GtkDataViewer *self = GTK_DATA_VIEWER (object); G_OBJECT_CLASS (gtk_data_viewer_parent_class)->dispose (object); } static void gtk_data_viewer_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { GtkDataViewer *self = GTK_DATA_VIEWER (object); switch (property_id) { case PROP_LOADING: g_value_set_boolean (value, gtk_data_viewer_is_loading (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gtk_data_viewer_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { //GtkDataViewer *self = GTK_DATA_VIEWER (object); switch (property_id) { default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gtk_data_viewer_class_init (GtkDataViewerClass *klass) { GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GObjectClass *gobject_class = G_OBJECT_CLASS (klass); widget_class->realize = gtk_data_viewer_realize; widget_class->unrealize = gtk_data_viewer_unrealize; gobject_class->dispose = gtk_data_viewer_dispose; gobject_class->get_property = gtk_data_viewer_get_property; gobject_class->set_property = gtk_data_viewer_set_property; properties[PROP_LOADING] = g_param_spec_boolean ("loading", NULL, NULL, FALSE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, N_PROPS, properties); signals[LOAD] = g_signal_new ("load", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, g_signal_accumulator_first_wins, NULL, NULL, G_TYPE_BOOLEAN, 1, G_TYPE_CANCELLABLE); gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); gtk_widget_class_set_css_name (widget_class, "frame"); } static void gtk_data_viewer_init (GtkDataViewer *self) { } GtkWidget * gtk_data_viewer_new (void) { return g_object_new (GTK_TYPE_DATA_VIEWER, NULL); } gboolean gtk_data_viewer_is_loading (GtkDataViewer *self) { g_return_val_if_fail (GTK_IS_DATA_VIEWER (self), FALSE); return self->loading == LOADING_EXTERNALLY || self->loading == LOADING_INTERNALLY; } void gtk_data_viewer_reset (GtkDataViewer *self) { gboolean was_loading; g_return_if_fail (GTK_IS_DATA_VIEWER (self)); g_object_freeze_notify (G_OBJECT (self)); was_loading = gtk_data_viewer_is_loading (self); g_clear_pointer (&self->contents, gtk_widget_unparent); g_clear_error (&self->error); g_cancellable_cancel (self->cancellable); g_clear_object (&self->cancellable); self->loading = NOT_LOADED; if (gtk_widget_get_realized (GTK_WIDGET (self))) gtk_data_viewer_ensure_loaded (self); if (was_loading != gtk_data_viewer_is_loading (self)) g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOADING]); g_object_thaw_notify (G_OBJECT (self)); } void gtk_data_viewer_load_value (GtkDataViewer *self, const GValue *value) { gboolean was_loading; g_return_if_fail (GTK_IS_DATA_VIEWER (self)); was_loading = gtk_data_viewer_is_loading (self); self->loading = LOADING_DONE; g_clear_pointer (&self->contents, gtk_widget_unparent); g_cancellable_cancel (self->cancellable); g_clear_object (&self->cancellable); if (g_type_is_a (G_VALUE_TYPE (value), G_TYPE_STRING)) { self->contents = gtk_label_new (g_value_get_string (value)); gtk_label_set_wrap (GTK_LABEL (self->contents), TRUE); gtk_widget_set_parent (self->contents, GTK_WIDGET (self)); } else if (g_type_is_a (G_VALUE_TYPE (value), GDK_TYPE_PAINTABLE)) { self->contents = gtk_picture_new_for_paintable (g_value_get_object (value)); gtk_widget_set_size_request (self->contents, 256, 256); gtk_widget_set_parent (self->contents, GTK_WIDGET (self)); } else if (g_type_is_a (G_VALUE_TYPE (value), GDK_TYPE_PIXBUF)) { self->contents = gtk_picture_new_for_pixbuf (g_value_get_object (value)); gtk_widget_set_size_request (self->contents, 256, 256); gtk_widget_set_parent (self->contents, GTK_WIDGET (self)); } else if (g_type_is_a (G_VALUE_TYPE (value), GDK_TYPE_RGBA)) { const GdkRGBA *color = g_value_get_boxed (value); self->contents = gtk_color_swatch_new (); gtk_color_swatch_set_rgba (GTK_COLOR_SWATCH (self->contents), color); gtk_widget_set_size_request (self->contents, 48, 32); gtk_widget_set_halign (self->contents, GTK_ALIGN_CENTER); gtk_widget_set_parent (self->contents, GTK_WIDGET (self)); } else if (g_type_is_a (G_VALUE_TYPE (value), G_TYPE_FILE)) { GFile *file = g_value_get_object (value); self->contents = gtk_label_new (g_file_peek_path (file)); gtk_label_set_ellipsize (GTK_LABEL (self->contents), PANGO_ELLIPSIZE_START); gtk_widget_set_halign (self->contents, GTK_ALIGN_CENTER); gtk_widget_set_parent (self->contents, GTK_WIDGET (self)); } else if (g_type_is_a (G_VALUE_TYPE (value), GDK_TYPE_FILE_LIST)) { GList *l; self->contents = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10); gtk_widget_set_parent (self->contents, GTK_WIDGET (self)); for (l = g_value_get_boxed (value); l; l = l->next) { GFile *file = l->data; GtkWidget *label = gtk_label_new (g_file_peek_path (file)); gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_START); gtk_widget_set_halign (label, GTK_ALIGN_CENTER); gtk_box_append (GTK_BOX (self->contents), label); } } else { gtk_data_viewer_load_error (self, g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED, "Cannot display objects of type \"%s\"", G_VALUE_TYPE_NAME (value))); } if (was_loading) g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOADING]); } static void gtk_data_viewer_load_stream_done (GObject *source, GAsyncResult *res, gpointer data) { GtkDataViewer *self = data; GError *error = NULL; GValue value = G_VALUE_INIT; if (!gdk_content_deserialize_finish (res, &value, &error)) { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) gtk_data_viewer_load_error (self, error); else g_clear_error (&error); g_object_unref (self); return; } gtk_data_viewer_load_value (self, &value); g_object_unref (self); g_value_unset (&value); } void gtk_data_viewer_load_stream (GtkDataViewer *self, GInputStream *stream, const char *mime_type) { GdkContentFormats *formats; const GType *gtypes; gboolean was_loading; g_return_if_fail (GTK_IS_DATA_VIEWER (self)); g_return_if_fail (G_IS_INPUT_STREAM (stream)); g_return_if_fail (mime_type != NULL); was_loading = gtk_data_viewer_is_loading (self); self->loading = LOADING_INTERNALLY; if (self->cancellable == NULL) self->cancellable = g_cancellable_new (); formats = gdk_content_formats_new (&mime_type, 1); formats = gdk_content_formats_union_deserialize_gtypes (formats); gtypes = gdk_content_formats_get_gtypes (formats, NULL); if (gtypes) { gdk_content_deserialize_async (stream, mime_type, gtypes[0], G_PRIORITY_DEFAULT, self->cancellable, gtk_data_viewer_load_stream_done, g_object_ref (self)); if (!was_loading) g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOADING]); } else { gtk_data_viewer_load_error (self, g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED, "Cannot display data of type \"%s\"", mime_type)); } gdk_content_formats_unref (formats); } void gtk_data_viewer_load_error (GtkDataViewer *self, GError *error) { gboolean was_loading; g_return_if_fail (GTK_IS_DATA_VIEWER (self)); was_loading = gtk_data_viewer_is_loading (self); self->loading = LOADING_FAILED; g_clear_pointer (&self->contents, gtk_widget_unparent); g_clear_error (&self->error); g_cancellable_cancel (self->cancellable); g_clear_object (&self->cancellable); self->error = error; self->contents = gtk_label_new (error->message); gtk_widget_add_css_class (self->contents, "error"); gtk_widget_set_halign (self->contents, GTK_ALIGN_CENTER); gtk_widget_set_valign (self->contents, GTK_ALIGN_CENTER); gtk_widget_set_parent (self->contents, GTK_WIDGET (self)); if (was_loading) g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOADING]); }