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 | |
33 | struct _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 | |
51 | typedef struct _GtkInspectorClipboardClass |
52 | { |
53 | GtkWidgetClass parent_class; |
54 | } GtkInspectorClipboardClass; |
55 | |
56 | G_DEFINE_TYPE (GtkInspectorClipboard, gtk_inspector_clipboard, GTK_TYPE_WIDGET) |
57 | |
58 | static void |
59 | load_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 | |
82 | static gboolean |
83 | load_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 | |
115 | static void |
116 | load_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 | |
140 | static gboolean |
141 | load_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 | |
173 | static void |
174 | on_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 | |
182 | static void |
183 | add_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 | |
244 | static void |
245 | init_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 | |
267 | static void |
268 | init_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 | |
288 | static void |
289 | clipboard_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 | |
301 | static void |
302 | primary_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 | |
314 | static void |
315 | on_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 | |
330 | static void |
331 | gtk_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 | |
345 | static void |
346 | gtk_inspector_clipboard_init (GtkInspectorClipboard *self) |
347 | { |
348 | gtk_widget_init_template (GTK_WIDGET (self)); |
349 | } |
350 | |
351 | static void |
352 | gtk_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 | |
363 | static void |
364 | gtk_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 | |
385 | void |
386 | gtk_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 | |