[gtk/wip/otte/inspector: 1/2] inspector: Add a clipboard page




commit 600ab5ba5fb2ebcc53b5fba6937e66fd7c8bed2f
Author: Benjamin Otte <otte redhat com>
Date:   Thu Aug 19 18:38:27 2021 +0200

    inspector: Add a clipboard page
    
    Shows all the formats supported by the clipboard (and primary clipboard)
    and allows displaying them (by potentially downloading them)

 gtk/inspector/clipboard.c     | 316 ++++++++++++++++++++++++++++++++++++
 gtk/inspector/clipboard.h     |  38 +++++
 gtk/inspector/clipboard.ui    |  98 +++++++++++
 gtk/inspector/gtkdataviewer.c | 368 ++++++++++++++++++++++++++++++++++++++++++
 gtk/inspector/gtkdataviewer.h |  47 ++++++
 gtk/inspector/init.c          |   2 +
 gtk/inspector/meson.build     |   2 +
 gtk/inspector/window.c        |   3 +
 gtk/inspector/window.h        |   1 +
 gtk/inspector/window.ui       |  18 +++
 10 files changed, 893 insertions(+)
---
diff --git a/gtk/inspector/clipboard.c b/gtk/inspector/clipboard.c
new file mode 100644
index 0000000000..5c7ae7b163
--- /dev/null
+++ b/gtk/inspector/clipboard.c
@@ -0,0 +1,316 @@
+/*
+ * Copyright (c) 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 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include <glib/gi18n-lib.h>
+
+#include "clipboard.h"
+#include "gtkdataviewer.h"
+#include "window.h"
+
+#include "gtkbinlayout.h"
+#include "gtkbox.h"
+#include "gtkdebug.h"
+#include "gtklabel.h"
+#include "gtklistbox.h"
+#include "gtktogglebutton.h"
+
+struct _GtkInspectorClipboard
+{
+  GtkWidget parent;
+
+  GdkDisplay *display;
+
+  GtkWidget *swin;
+
+  GtkWidget *clipboard_formats;
+  GtkWidget *clipboard_info;
+
+  GtkWidget *primary_formats;
+  GtkWidget *primary_info;
+};
+
+typedef struct _GtkInspectorClipboardClass
+{
+  GtkWidgetClass parent_class;
+} GtkInspectorClipboardClass;
+
+G_DEFINE_TYPE (GtkInspectorClipboard, gtk_inspector_clipboard, GTK_TYPE_WIDGET)
+
+static void
+load_gtype_value (GObject      *source,
+                  GAsyncResult *res,
+                  gpointer      data)
+{
+  GdkClipboard *clipboard = GDK_CLIPBOARD (source);
+  GtkDataViewer *viewer = data;
+  const GValue *value;
+  GError *error = NULL;
+
+  value = gdk_clipboard_read_value_finish (clipboard, res, &error);
+  if (value == NULL)
+    gtk_data_viewer_load_error (viewer, error);
+  else
+    gtk_data_viewer_load_value (viewer, value);
+
+  g_object_unref (viewer);
+}
+
+static gboolean
+load_gtype (GtkDataViewer *viewer,
+            GCancellable  *cancellable,
+            gpointer       gtype)
+{
+  GdkClipboard *clipboard = g_object_get_data (G_OBJECT (viewer), "clipboard");
+
+  gdk_clipboard_read_value_async (clipboard,
+                                  GPOINTER_TO_SIZE (gtype),
+                                  G_PRIORITY_DEFAULT,
+                                  cancellable,
+                                  load_gtype_value,
+                                  g_object_ref (viewer));
+
+  return TRUE;
+}
+
+static void
+load_mime_type_stream (GObject      *source,
+                       GAsyncResult *res,
+                       gpointer      data)
+{
+  GdkClipboard *clipboard = GDK_CLIPBOARD (source);
+  GtkDataViewer *viewer = data;
+  GInputStream *stream;
+  GError *error = NULL;
+  const char *mime_type;
+
+  stream = gdk_clipboard_read_finish (clipboard, res, &mime_type, &error);
+  if (stream == NULL)
+    gtk_data_viewer_load_error (viewer, error);
+  else
+    gtk_data_viewer_load_stream (viewer, stream, mime_type);
+
+  g_object_unref (viewer);
+}
+
+static gboolean
+load_mime_type (GtkDataViewer *viewer,
+                GCancellable  *cancellable,
+                gpointer       mime_type)
+{
+  GdkClipboard *clipboard = g_object_get_data (G_OBJECT (viewer), "clipboard");
+
+  gdk_clipboard_read_async (clipboard,
+                            (const char *[2]) { mime_type, NULL },
+                            G_PRIORITY_DEFAULT,
+                            cancellable,
+                            load_mime_type_stream,
+                            g_object_ref (viewer));
+
+  return TRUE;
+}
+
+static void
+add_content_type_row (GtkInspectorClipboard *self,
+                      GtkListBox            *list,
+                      const char            *type_name,
+                      GdkClipboard          *clipboard,
+                      GCallback              load_func,
+                      gpointer               load_func_data)
+{
+  GtkWidget *row, *vbox, *hbox, *label, *viewer, *button;
+
+  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10);
+
+  hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 40);
+  gtk_box_append (GTK_BOX (vbox), hbox);
+
+  label = gtk_label_new (type_name);
+  gtk_widget_set_halign (label, GTK_ALIGN_START);
+  gtk_widget_set_valign (label, GTK_ALIGN_BASELINE);
+  gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+  gtk_widget_set_hexpand (label, TRUE);
+  gtk_box_append (GTK_BOX (hbox), label);
+
+  button = gtk_toggle_button_new_with_label (_("Show"));
+  gtk_widget_set_halign (button, GTK_ALIGN_END);
+  gtk_widget_set_valign (button, GTK_ALIGN_BASELINE);
+  gtk_box_append (GTK_BOX (hbox), button);
+
+  viewer = gtk_data_viewer_new ();
+  g_signal_connect (viewer, "load", load_func, load_func_data);
+  g_object_set_data (G_OBJECT (viewer), "clipboard", clipboard);
+  g_object_bind_property (G_OBJECT (button), "active",
+                          G_OBJECT (viewer), "visible",
+                          G_BINDING_SYNC_CREATE);
+  gtk_box_append (GTK_BOX (vbox), viewer);
+
+  row = gtk_list_box_row_new ();
+  gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), vbox);
+  gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), FALSE);
+
+  gtk_list_box_insert (list, row, -1);
+}
+
+static void
+init_formats (GtkInspectorClipboard *self,
+              GtkListBox            *list,
+              GdkClipboard          *clipboard)
+{
+  GtkListBoxRow *row;
+  GdkContentFormats *formats;
+  const char * const *mime_types;
+  const GType *gtypes;
+  gsize i, n;
+
+  while ((row = gtk_list_box_get_row_at_index (list, 1)))
+    gtk_list_box_remove (list, GTK_WIDGET (row));
+
+  formats = gdk_clipboard_get_formats (clipboard);
+  
+  gtypes = gdk_content_formats_get_gtypes (formats, &n);
+  for (i = 0; i < n; i++)
+    add_content_type_row (self, list, g_type_name (gtypes[i]), clipboard, G_CALLBACK (load_gtype), 
GSIZE_TO_POINTER (gtypes[i]));
+
+  mime_types = gdk_content_formats_get_mime_types (formats, &n);
+  for (i = 0; i < n; i++)
+    add_content_type_row (self, list, mime_types[i], clipboard, G_CALLBACK (load_mime_type), (gpointer) 
mime_types[i]);
+}
+
+static void
+init_info (GtkInspectorClipboard *self,
+           GtkLabel              *label,
+           GdkClipboard          *clipboard)
+{
+  GdkContentFormats *formats;
+
+  formats = gdk_clipboard_get_formats (clipboard);
+  if (gdk_content_formats_get_gtypes (formats, NULL) == NULL &&
+      gdk_content_formats_get_mime_types (formats, NULL) == NULL)
+    {
+      gtk_label_set_text (label, C_("clipboard", "empty"));
+      return;
+    }
+
+  if (gdk_clipboard_is_local (clipboard))
+    gtk_label_set_text (label, C_("clipboard", "local"));
+  else
+    gtk_label_set_text (label, C_("clipboard", "remote"));
+}
+
+static void
+clipboard_notify (GdkClipboard          *clipboard,
+                  GParamSpec            *pspec,
+                  GtkInspectorClipboard *self)
+{
+  if (g_str_equal (pspec->name, "formats"))
+    {
+      init_formats (self, GTK_LIST_BOX (self->clipboard_formats), clipboard);
+    }
+
+  init_info (self, GTK_LABEL (self->clipboard_info), clipboard);
+}
+
+static void
+primary_notify (GdkClipboard          *clipboard,
+                GParamSpec            *pspec,
+                GtkInspectorClipboard *self)
+{
+  if (g_str_equal (pspec->name, "formats"))
+    {
+      init_formats (self, GTK_LIST_BOX (self->primary_formats), clipboard);
+    }
+
+  g_print ("%s: %s\n", pspec->name, gdk_content_formats_to_string (gdk_clipboard_get_formats (clipboard)));
+  init_info (self, GTK_LABEL (self->primary_info), clipboard);
+}
+
+static void
+gtk_inspector_clipboard_unset_display (GtkInspectorClipboard *self)
+{
+  GdkClipboard *clipboard;
+
+  if (self->display == NULL)
+    return;
+
+  clipboard = gdk_display_get_clipboard (self->display);
+  g_signal_handlers_disconnect_by_func (clipboard, clipboard_notify, self);
+
+  clipboard = gdk_display_get_primary_clipboard (self->display);
+  g_signal_handlers_disconnect_by_func (clipboard, primary_notify, self);
+}
+
+static void
+gtk_inspector_clipboard_init (GtkInspectorClipboard *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+static void
+gtk_inspector_clipboard_dispose (GObject *object)
+{
+  GtkInspectorClipboard *self = GTK_INSPECTOR_CLIPBOARD (object);
+
+  gtk_inspector_clipboard_unset_display (self);
+
+  g_clear_pointer (&self->swin, gtk_widget_unparent);
+
+  G_OBJECT_CLASS (gtk_inspector_clipboard_parent_class)->dispose (object);
+}
+
+static void
+gtk_inspector_clipboard_class_init (GtkInspectorClipboardClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->dispose = gtk_inspector_clipboard_dispose;
+
+  gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/inspector/clipboard.ui");
+  gtk_widget_class_bind_template_child (widget_class, GtkInspectorClipboard, swin);
+  gtk_widget_class_bind_template_child (widget_class, GtkInspectorClipboard, clipboard_formats);
+  gtk_widget_class_bind_template_child (widget_class, GtkInspectorClipboard, clipboard_info);
+  gtk_widget_class_bind_template_child (widget_class, GtkInspectorClipboard, primary_formats);
+  gtk_widget_class_bind_template_child (widget_class, GtkInspectorClipboard, primary_info);
+
+  gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
+}
+
+void
+gtk_inspector_clipboard_set_display (GtkInspectorClipboard *self,
+                                     GdkDisplay            *display)
+{
+  GdkClipboard *clipboard;
+
+  gtk_inspector_clipboard_unset_display (self);
+
+  self->display = display;
+
+  if (display == NULL)
+    return;
+
+  clipboard = gdk_display_get_clipboard (display);
+  g_signal_connect (clipboard, "notify", G_CALLBACK (clipboard_notify), self);
+  init_formats (self, GTK_LIST_BOX (self->clipboard_formats), clipboard);
+  init_info (self, GTK_LABEL (self->clipboard_info), clipboard);
+
+  clipboard = gdk_display_get_primary_clipboard (display);
+  g_signal_connect (clipboard, "notify", G_CALLBACK (primary_notify), self);
+  init_formats (self, GTK_LIST_BOX (self->primary_formats), clipboard);
+  init_info (self, GTK_LABEL (self->primary_info), clipboard);
+}
+
diff --git a/gtk/inspector/clipboard.h b/gtk/inspector/clipboard.h
new file mode 100644
index 0000000000..6bc7ee9619
--- /dev/null
+++ b/gtk/inspector/clipboard.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 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 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _GTK_INSPECTOR_CLIPBOARD_H_
+#define _GTK_INSPECTOR_CLIPBOARD_H_
+
+#include <gtk/gtkwidget.h>
+
+#define GTK_TYPE_INSPECTOR_CLIPBOARD            (gtk_inspector_clipboard_get_type())
+#define GTK_INSPECTOR_CLIPBOARD(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), 
GTK_TYPE_INSPECTOR_CLIPBOARD, GtkInspectorClipboard))
+#define GTK_INSPECTOR_IS_CLIPBOARD(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), 
GTK_TYPE_INSPECTOR_CLIPBOARD))
+
+typedef struct _GtkInspectorClipboard GtkInspectorClipboard;
+
+G_BEGIN_DECLS
+
+GType           gtk_inspector_clipboard_get_type                (void);
+
+void            gtk_inspector_clipboard_set_display             (GtkInspectorClipboard  *self,
+                                                                 GdkDisplay             *display);
+
+G_END_DECLS
+
+#endif // _GTK_INSPECTOR_CLIPBOARD_H_
diff --git a/gtk/inspector/clipboard.ui b/gtk/inspector/clipboard.ui
new file mode 100644
index 0000000000..ef83f037e9
--- /dev/null
+++ b/gtk/inspector/clipboard.ui
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface domain="gtk40">
+  <template class="GtkInspectorClipboard" parent="GtkWidget">
+    <child>
+      <object class="GtkScrolledWindow" id="swin">
+        <property name="hscrollbar-policy">never</property>
+        <child>
+          <object class="GtkBox" id="box">
+            <property name="orientation">vertical</property>
+            <property name="margin-start">60</property>
+            <property name="margin-end">60</property>
+            <property name="margin-top">60</property>
+            <property name="margin-bottom">60</property>
+            <property name="spacing">10</property>
+            <child>
+              <object class="GtkFrame">
+                <child>
+                  <object class="GtkListBox" id="clipboard_formats">
+                    <property name="selection-mode">none</property>
+                    <style>
+                      <class name="rich-list"/>
+                    </style>
+                    <child>
+                      <object class="GtkListBoxRow">
+                        <property name="activatable">0</property>
+                        <child>
+                          <object class="GtkBox">
+                            <property name="spacing">40</property>
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="label" translatable="yes">Clipboard</property>
+                                <property name="halign">start</property>
+                                <property name="valign">baseline</property>
+                                <property name="xalign">0.0</property>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkLabel" id="clipboard_info">
+                                <property name="selectable">1</property>
+                                <property name="halign">end</property>
+                                <property name="valign">baseline</property>
+                                <property name="ellipsize">end</property>
+                                <property name="hexpand">1</property>
+                              </object>
+                            </child>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+            <child>
+              <object class="GtkFrame">
+                <child>
+                  <object class="GtkListBox" id="primary_formats">
+                    <property name="selection-mode">none</property>
+                    <style>
+                      <class name="rich-list"/>
+                    </style>
+                    <child>
+                      <object class="GtkListBoxRow">
+                        <property name="activatable">0</property>
+                        <child>
+                          <object class="GtkBox">
+                            <property name="spacing">40</property>
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="label" translatable="yes">Primary</property>
+                                <property name="halign">start</property>
+                                <property name="valign">baseline</property>
+                                <property name="xalign">0.0</property>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkLabel" id="primary_info">
+                                <property name="selectable">1</property>
+                                <property name="halign">end</property>
+                                <property name="valign">baseline</property>
+                                <property name="ellipsize">end</property>
+                                <property name="hexpand">1</property>
+                              </object>
+                            </child>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/gtk/inspector/gtkdataviewer.c b/gtk/inspector/gtkdataviewer.c
new file mode 100644
index 0000000000..acfafea88b
--- /dev/null
+++ b/gtk/inspector/gtkdataviewer.c
@@ -0,0 +1,368 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#include "config.h"
+
+#include "gtkdataviewer.h"
+
+#include "gtkbinlayout.h"
+#include "gtklabel.h"
+#include "gtkpicture.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",
+                          "Loading",
+                          "If the widget is currently loading the data to display",
+                          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
+    {
+      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]);
+}
+
diff --git a/gtk/inspector/gtkdataviewer.h b/gtk/inspector/gtkdataviewer.h
new file mode 100644
index 0000000000..37ce4bc750
--- /dev/null
+++ b/gtk/inspector/gtkdataviewer.h
@@ -0,0 +1,47 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#ifndef __GTK_DATA_VIEWER_H__
+#define __GTK_DATA_VIEWER_H__
+
+#include <gtk/gtkwidget.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_DATA_VIEWER         (gtk_data_viewer_get_type ())
+
+G_DECLARE_FINAL_TYPE (GtkDataViewer, gtk_data_viewer, GTK, DATA_VIEWER, GtkWidget)
+
+GtkWidget *     gtk_data_viewer_new                             (void);
+
+gboolean        gtk_data_viewer_is_loading                      (GtkDataViewer          *self);
+
+void            gtk_data_viewer_reset                           (GtkDataViewer          *self);
+
+void            gtk_data_viewer_load_value                      (GtkDataViewer          *self,
+                                                                 const GValue           *value);
+void            gtk_data_viewer_load_stream                     (GtkDataViewer          *self,
+                                                                 GInputStream           *stream,
+                                                                 const char             *mime_type);
+void            gtk_data_viewer_load_error                      (GtkDataViewer          *self,
+                                                                 GError                 *error);
+
+G_END_DECLS
+
+#endif  /* __GTK_DATA_VIEWER_H__ */
diff --git a/gtk/inspector/init.c b/gtk/inspector/init.c
index ed489baee3..0e0691a16d 100644
--- a/gtk/inspector/init.c
+++ b/gtk/inspector/init.c
@@ -27,6 +27,7 @@
 #include "a11y.h"
 #include "actions.h"
 #include "cellrenderergraph.h"
+#include "clipboard.h"
 #include "controllers.h"
 #include "css-editor.h"
 #include "css-node-tree.h"
@@ -64,6 +65,7 @@ gtk_inspector_init (void)
   g_type_ensure (GTK_TYPE_GRAPH_DATA);
   g_type_ensure (GTK_TYPE_INSPECTOR_A11Y);
   g_type_ensure (GTK_TYPE_INSPECTOR_ACTIONS);
+  g_type_ensure (GTK_TYPE_INSPECTOR_CLIPBOARD);
   g_type_ensure (GTK_TYPE_INSPECTOR_CONTROLLERS);
   g_type_ensure (GTK_TYPE_INSPECTOR_CSS_EDITOR);
   g_type_ensure (GTK_TYPE_INSPECTOR_CSS_NODE_TREE);
diff --git a/gtk/inspector/meson.build b/gtk/inspector/meson.build
index 0ae9927011..422eab7f24 100644
--- a/gtk/inspector/meson.build
+++ b/gtk/inspector/meson.build
@@ -5,6 +5,7 @@ inspector_sources = files(
   'actions.c',
   'baselineoverlay.c',
   'cellrenderergraph.c',
+  'clipboard.c',
   'controllers.c',
   'css-editor.c',
   'css-node-tree.c',
@@ -13,6 +14,7 @@ inspector_sources = files(
   'general.c',
   'graphdata.c',
   'gtktreemodelcssnode.c',
+  'gtkdataviewer.c',
   'highlightoverlay.c',
   'init.c',
   'inspect-button.c',
diff --git a/gtk/inspector/window.c b/gtk/inspector/window.c
index 1690951140..8b07fb3de3 100644
--- a/gtk/inspector/window.c
+++ b/gtk/inspector/window.c
@@ -30,6 +30,7 @@
 #include "init.h"
 #include "window.h"
 #include "prop-list.h"
+#include "clipboard.h"
 #include "controllers.h"
 #include "css-editor.h"
 #include "css-node-tree.h"
@@ -298,6 +299,7 @@ gtk_inspector_window_constructed (GObject *object)
   gtk_inspector_css_editor_set_display (GTK_INSPECTOR_CSS_EDITOR (iw->css_editor), iw->inspected_display);
   gtk_inspector_visual_set_display (GTK_INSPECTOR_VISUAL (iw->visual), iw->inspected_display);
   gtk_inspector_general_set_display (GTK_INSPECTOR_GENERAL (iw->general), iw->inspected_display);
+  gtk_inspector_clipboard_set_display (GTK_INSPECTOR_CLIPBOARD (iw->clipboard), iw->inspected_display);
   gtk_inspector_logs_set_display (GTK_INSPECTOR_LOGS (iw->logs), iw->inspected_display);
   gtk_inspector_css_node_tree_set_display (GTK_INSPECTOR_CSS_NODE_TREE (iw->widget_css_node_tree), 
iw->inspected_display);
 }
@@ -652,6 +654,7 @@ gtk_inspector_window_class_init (GtkInspectorWindowClass *klass)
   gtk_widget_class_bind_template_child (widget_class, GtkInspectorWindow, css_editor);
   gtk_widget_class_bind_template_child (widget_class, GtkInspectorWindow, visual);
   gtk_widget_class_bind_template_child (widget_class, GtkInspectorWindow, general);
+  gtk_widget_class_bind_template_child (widget_class, GtkInspectorWindow, clipboard);
   gtk_widget_class_bind_template_child (widget_class, GtkInspectorWindow, logs);
 
   gtk_widget_class_bind_template_child (widget_class, GtkInspectorWindow, go_up_button);
diff --git a/gtk/inspector/window.h b/gtk/inspector/window.h
index 90841a8ae4..efaec419a5 100644
--- a/gtk/inspector/window.h
+++ b/gtk/inspector/window.h
@@ -76,6 +76,7 @@ typedef struct
   GtkWidget *sidebar_revealer;
   GtkWidget *css_editor;
   GtkWidget *visual;
+  GtkWidget *clipboard;
   GtkWidget *general;
   GtkWidget *logs;
 
diff --git a/gtk/inspector/window.ui b/gtk/inspector/window.ui
index 69ae8624e5..2345a9393c 100644
--- a/gtk/inspector/window.ui
+++ b/gtk/inspector/window.ui
@@ -130,6 +130,14 @@
                         </property>
                       </object>
                     </child>
+                    <child>
+                      <object class="GtkStackPage">
+                        <property name="name">clipboard</property>
+                        <property name="child">
+                          <object class="GtkBox"/>
+                        </property>
+                      </object>
+                    </child>
                     <child>
                       <object class="GtkStackPage">
                         <property name="name">statistics</property>
@@ -560,6 +568,16 @@
                         </property>
                       </object>
                     </child>
+                    <child>
+                      <object class="GtkStackPage">
+                        <property name="name">clipboard</property>
+                        <property name="title" translatable="yes">Clipboard</property>
+                        <property name="child">
+                          <object class="GtkInspectorClipboard" id="clipboard">
+                          </object>
+                        </property>
+                      </object>
+                    </child>
                     <child>
                       <object class="GtkStackPage">
                         <property name="name">statistics</property>


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]