1/*
2 * Copyright (c) 2021 Benjamin Otte
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "config.h"
19#include <glib/gi18n-lib.h>
20
21#include "clipboard.h"
22#include "gtkdataviewer.h"
23#include "window.h"
24
25#include "gtkbinlayout.h"
26#include "gtkbox.h"
27#include "gtkdebug.h"
28#include "gtkdropcontrollermotion.h"
29#include "gtklabel.h"
30#include "gtklistbox.h"
31#include "gtktogglebutton.h"
32
33struct _GtkInspectorClipboard
34{
35 GtkWidget parent;
36
37 GdkDisplay *display;
38
39 GtkWidget *swin;
40
41 GtkWidget *dnd_formats;
42 GtkWidget *dnd_info;
43
44 GtkWidget *clipboard_formats;
45 GtkWidget *clipboard_info;
46
47 GtkWidget *primary_formats;
48 GtkWidget *primary_info;
49};
50
51typedef struct _GtkInspectorClipboardClass
52{
53 GtkWidgetClass parent_class;
54} GtkInspectorClipboardClass;
55
56G_DEFINE_TYPE (GtkInspectorClipboard, gtk_inspector_clipboard, GTK_TYPE_WIDGET)
57
58static void
59load_gtype_value (GObject *source,
60 GAsyncResult *res,
61 gpointer data)
62{
63 GtkDataViewer *viewer = data;
64 const GValue *value;
65 GError *error = NULL;
66
67 if (GDK_IS_CLIPBOARD (source))
68 value = gdk_clipboard_read_value_finish (GDK_CLIPBOARD (source), result: res, error: &error);
69 else if (GDK_IS_DROP (source))
70 value = gdk_drop_read_value_finish (GDK_DROP (source), result: res, error: &error);
71 else
72 g_assert_not_reached ();
73
74 if (value == NULL)
75 gtk_data_viewer_load_error (self: viewer, error);
76 else
77 gtk_data_viewer_load_value (self: viewer, value);
78
79 g_object_unref (object: viewer);
80}
81
82static gboolean
83load_gtype (GtkDataViewer *viewer,
84 GCancellable *cancellable,
85 gpointer gtype)
86{
87 GObject *data_source = g_object_get_data (G_OBJECT (viewer), key: "data-source");
88
89 if (GDK_IS_CLIPBOARD (data_source))
90 {
91 gdk_clipboard_read_value_async (GDK_CLIPBOARD (data_source),
92 GPOINTER_TO_SIZE (gtype),
93 G_PRIORITY_DEFAULT,
94 cancellable,
95 callback: load_gtype_value,
96 g_object_ref (viewer));
97 }
98 else if (GDK_IS_DROP (data_source))
99 {
100 gdk_drop_read_value_async (GDK_DROP (data_source),
101 GPOINTER_TO_SIZE (gtype),
102 G_PRIORITY_DEFAULT,
103 cancellable,
104 callback: load_gtype_value,
105 g_object_ref (viewer));
106 }
107 else
108 {
109 g_assert_not_reached ();
110 }
111
112 return TRUE;
113}
114
115static void
116load_mime_type_stream (GObject *source,
117 GAsyncResult *res,
118 gpointer data)
119{
120 GtkDataViewer *viewer = data;
121 GInputStream *stream;
122 GError *error = NULL;
123 const char *mime_type;
124
125 if (GDK_IS_CLIPBOARD (source))
126 stream = gdk_clipboard_read_finish (GDK_CLIPBOARD (source), result: res, out_mime_type: &mime_type, error: &error);
127 else if (GDK_IS_DROP (source))
128 stream = gdk_drop_read_finish (GDK_DROP (source), result: res, out_mime_type: &mime_type, error: &error);
129 else
130 g_assert_not_reached ();
131
132 if (stream == NULL)
133 gtk_data_viewer_load_error (self: viewer, error);
134 else
135 gtk_data_viewer_load_stream (self: viewer, stream, mime_type);
136
137 g_object_unref (object: viewer);
138}
139
140static gboolean
141load_mime_type (GtkDataViewer *viewer,
142 GCancellable *cancellable,
143 gpointer mime_type)
144{
145 GObject *data_source = g_object_get_data (G_OBJECT (viewer), key: "data-source");
146
147 if (GDK_IS_CLIPBOARD (data_source))
148 {
149 gdk_clipboard_read_async (GDK_CLIPBOARD (data_source),
150 mime_types: (const char *[2]) { mime_type, NULL },
151 G_PRIORITY_DEFAULT,
152 cancellable,
153 callback: load_mime_type_stream,
154 g_object_ref (viewer));
155 }
156 else if (GDK_IS_DROP (data_source))
157 {
158 gdk_drop_read_async (GDK_DROP (data_source),
159 mime_types: (const char *[2]) { mime_type, NULL },
160 G_PRIORITY_DEFAULT,
161 cancellable,
162 callback: load_mime_type_stream,
163 g_object_ref (viewer));
164 }
165 else
166 {
167 g_assert_not_reached ();
168 }
169
170 return TRUE;
171}
172
173static void
174on_drop_row_enter (GtkDropControllerMotion *motion,
175 double x,
176 double y,
177 GtkWidget *viewer)
178{
179 gtk_widget_set_visible (widget: viewer, TRUE);
180}
181
182static void
183add_content_type_row (GtkInspectorClipboard *self,
184 GtkListBox *list,
185 const char *type_name,
186 GObject *data_source,
187 GCallback load_func,
188 gpointer load_func_data)
189{
190 GtkWidget *row, *vbox, *hbox, *label, *viewer, *button;
191
192 vbox = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 10);
193
194 hbox = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 40);
195 gtk_box_append (GTK_BOX (vbox), child: hbox);
196
197 label = gtk_label_new (str: type_name);
198 gtk_widget_set_halign (widget: label, align: GTK_ALIGN_START);
199 gtk_widget_set_valign (widget: label, align: GTK_ALIGN_BASELINE);
200 gtk_label_set_xalign (GTK_LABEL (label), xalign: 0.0);
201 gtk_widget_set_hexpand (widget: label, TRUE);
202 gtk_box_append (GTK_BOX (hbox), child: label);
203
204 viewer = gtk_data_viewer_new ();
205 g_signal_connect (viewer, "load", load_func, load_func_data);
206 g_object_set_data (G_OBJECT (viewer), key: "data-source", data: data_source);
207 gtk_box_append (GTK_BOX (vbox), child: viewer);
208
209 if (GDK_IS_CLIPBOARD (data_source))
210 {
211 button = gtk_toggle_button_new_with_label (_("Show"));
212 gtk_widget_set_halign (widget: button, align: GTK_ALIGN_END);
213 gtk_widget_set_valign (widget: button, align: GTK_ALIGN_BASELINE);
214 gtk_box_append (GTK_BOX (hbox), child: button);
215
216 g_object_bind_property (G_OBJECT (button), source_property: "active",
217 G_OBJECT (viewer), target_property: "visible",
218 flags: G_BINDING_SYNC_CREATE);
219 }
220 else
221 {
222 GtkEventController *controller = gtk_drop_controller_motion_new ();
223 g_signal_connect (controller, "enter", G_CALLBACK (on_drop_row_enter), viewer);
224 gtk_widget_add_controller (widget: vbox, controller);
225
226 gtk_widget_set_visible (widget: viewer, FALSE);
227
228 label = gtk_label_new (_("Hover to load"));
229 g_object_bind_property (G_OBJECT (viewer), source_property: "visible",
230 G_OBJECT (label), target_property: "visible",
231 flags: G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN);
232 gtk_widget_set_halign (widget: label, align: GTK_ALIGN_END);
233 gtk_widget_set_valign (widget: label, align: GTK_ALIGN_BASELINE);
234 gtk_box_append (GTK_BOX (hbox), child: label);
235 }
236
237 row = gtk_list_box_row_new ();
238 gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), child: vbox);
239 gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), FALSE);
240
241 gtk_list_box_insert (box: list, child: row, position: -1);
242}
243
244static void
245init_formats (GtkInspectorClipboard *self,
246 GtkListBox *list,
247 GdkContentFormats *formats,
248 GObject *data_source)
249{
250 GtkListBoxRow *row;
251 const char * const *mime_types;
252 const GType *gtypes;
253 gsize i, n;
254
255 while ((row = gtk_list_box_get_row_at_index (box: list, index_: 1)))
256 gtk_list_box_remove (box: list, GTK_WIDGET (row));
257
258 gtypes = gdk_content_formats_get_gtypes (formats, n_gtypes: &n);
259 for (i = 0; i < n; i++)
260 add_content_type_row (self, list, type_name: g_type_name (type: gtypes[i]), data_source, G_CALLBACK (load_gtype), GSIZE_TO_POINTER (gtypes[i]));
261
262 mime_types = gdk_content_formats_get_mime_types (formats, n_mime_types: &n);
263 for (i = 0; i < n; i++)
264 add_content_type_row (self, list, type_name: mime_types[i], data_source, G_CALLBACK (load_mime_type), load_func_data: (gpointer) mime_types[i]);
265}
266
267static void
268init_info (GtkInspectorClipboard *self,
269 GtkLabel *label,
270 GdkClipboard *clipboard)
271{
272 GdkContentFormats *formats;
273
274 formats = gdk_clipboard_get_formats (clipboard);
275 if (gdk_content_formats_get_gtypes (formats, NULL) == NULL &&
276 gdk_content_formats_get_mime_types (formats, NULL) == NULL)
277 {
278 gtk_label_set_text (self: label, C_("clipboard", "empty"));
279 return;
280 }
281
282 if (gdk_clipboard_is_local (clipboard))
283 gtk_label_set_text (self: label, C_("clipboard", "local"));
284 else
285 gtk_label_set_text (self: label, C_("clipboard", "remote"));
286}
287
288static void
289clipboard_notify (GdkClipboard *clipboard,
290 GParamSpec *pspec,
291 GtkInspectorClipboard *self)
292{
293 if (g_str_equal (v1: pspec->name, v2: "formats"))
294 {
295 init_formats (self, GTK_LIST_BOX (self->clipboard_formats), formats: gdk_clipboard_get_formats (clipboard), G_OBJECT (clipboard));
296 }
297
298 init_info (self, GTK_LABEL (self->clipboard_info), clipboard);
299}
300
301static void
302primary_notify (GdkClipboard *clipboard,
303 GParamSpec *pspec,
304 GtkInspectorClipboard *self)
305{
306 if (g_str_equal (v1: pspec->name, v2: "formats"))
307 {
308 init_formats (self, GTK_LIST_BOX (self->primary_formats), formats: gdk_clipboard_get_formats (clipboard), G_OBJECT (clipboard));
309 }
310
311 init_info (self, GTK_LABEL (self->primary_info), clipboard);
312}
313
314static void
315on_drop_enter (GtkDropControllerMotion *motion,
316 double x,
317 double y,
318 GtkInspectorClipboard *self)
319{
320 GdkDrop *drop = gtk_drop_controller_motion_get_drop (self: motion);
321
322 init_formats (self, GTK_LIST_BOX (self->dnd_formats), formats: gdk_drop_get_formats (self: drop), G_OBJECT (drop));
323
324 if (gdk_drop_get_drag (self: drop))
325 gtk_label_set_text (GTK_LABEL (self->dnd_info), C_("clipboard", "local"));
326 else
327 gtk_label_set_text (GTK_LABEL (self->dnd_info), C_("clipboard", "remote"));
328}
329
330static void
331gtk_inspector_clipboard_unset_display (GtkInspectorClipboard *self)
332{
333 GdkClipboard *clipboard;
334
335 if (self->display == NULL)
336 return;
337
338 clipboard = gdk_display_get_clipboard (display: self->display);
339 g_signal_handlers_disconnect_by_func (clipboard, clipboard_notify, self);
340
341 clipboard = gdk_display_get_primary_clipboard (display: self->display);
342 g_signal_handlers_disconnect_by_func (clipboard, primary_notify, self);
343}
344
345static void
346gtk_inspector_clipboard_init (GtkInspectorClipboard *self)
347{
348 gtk_widget_init_template (GTK_WIDGET (self));
349}
350
351static void
352gtk_inspector_clipboard_dispose (GObject *object)
353{
354 GtkInspectorClipboard *self = GTK_INSPECTOR_CLIPBOARD (object);
355
356 gtk_inspector_clipboard_unset_display (self);
357
358 g_clear_pointer (&self->swin, gtk_widget_unparent);
359
360 G_OBJECT_CLASS (gtk_inspector_clipboard_parent_class)->dispose (object);
361}
362
363static void
364gtk_inspector_clipboard_class_init (GtkInspectorClipboardClass *klass)
365{
366 GObjectClass *object_class = G_OBJECT_CLASS (klass);
367 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
368
369 object_class->dispose = gtk_inspector_clipboard_dispose;
370
371 gtk_widget_class_set_template_from_resource (widget_class, resource_name: "/org/gtk/libgtk/inspector/clipboard.ui");
372 gtk_widget_class_bind_template_child (widget_class, GtkInspectorClipboard, swin);
373 gtk_widget_class_bind_template_child (widget_class, GtkInspectorClipboard, dnd_formats);
374 gtk_widget_class_bind_template_child (widget_class, GtkInspectorClipboard, dnd_info);
375 gtk_widget_class_bind_template_child (widget_class, GtkInspectorClipboard, clipboard_formats);
376 gtk_widget_class_bind_template_child (widget_class, GtkInspectorClipboard, clipboard_info);
377 gtk_widget_class_bind_template_child (widget_class, GtkInspectorClipboard, primary_formats);
378 gtk_widget_class_bind_template_child (widget_class, GtkInspectorClipboard, primary_info);
379
380 gtk_widget_class_bind_template_callback (widget_class, on_drop_enter);
381
382 gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
383}
384
385void
386gtk_inspector_clipboard_set_display (GtkInspectorClipboard *self,
387 GdkDisplay *display)
388{
389 GdkClipboard *clipboard;
390
391 gtk_inspector_clipboard_unset_display (self);
392
393 self->display = display;
394
395 if (display == NULL)
396 return;
397
398 clipboard = gdk_display_get_clipboard (display);
399 g_signal_connect (clipboard, "notify", G_CALLBACK (clipboard_notify), self);
400 init_formats (self, GTK_LIST_BOX (self->clipboard_formats), formats: gdk_clipboard_get_formats (clipboard), G_OBJECT (clipboard));
401 init_info (self, GTK_LABEL (self->clipboard_info), clipboard);
402
403 clipboard = gdk_display_get_primary_clipboard (display);
404 g_signal_connect (clipboard, "notify", G_CALLBACK (primary_notify), self);
405 init_formats (self, GTK_LIST_BOX (self->primary_formats), formats: gdk_clipboard_get_formats (clipboard), G_OBJECT (clipboard));
406 init_info (self, GTK_LABEL (self->primary_info), clipboard);
407}
408
409

source code of gtk/gtk/inspector/clipboard.c