1/*
2 * Copyright © 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.1 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 * Authors: Benjamin Otte <otte@gnome.org>
18 */
19
20#include "config.h"
21
22#include "gtkdataviewer.h"
23
24#include "gtkbinlayout.h"
25#include "gtklabel.h"
26#include "gtkpicture.h"
27#include "gtkcolorswatchprivate.h"
28#include "gtkbox.h"
29
30
31struct _GtkDataViewer
32{
33 GtkWidget parent_instance;
34
35 GtkWidget *contents;
36 GCancellable *cancellable;
37 GError *error;
38
39 enum {
40 NOT_LOADED = 0,
41 LOADING_DONE,
42 LOADING_EXTERNALLY,
43 LOADING_INTERNALLY,
44 LOADING_FAILED
45 } loading;
46};
47
48enum
49{
50 PROP_0,
51 PROP_LOADING,
52
53 N_PROPS
54};
55
56enum {
57 LOAD,
58 LAST_SIGNAL
59};
60
61G_DEFINE_TYPE (GtkDataViewer, gtk_data_viewer, GTK_TYPE_WIDGET)
62
63static GParamSpec *properties[N_PROPS] = { NULL, };
64static guint signals[LAST_SIGNAL];
65
66static void
67gtk_data_viewer_ensure_loaded (GtkDataViewer *self)
68{
69 gboolean started_loading;
70
71 if (self->loading != NOT_LOADED)
72 return;
73
74 self->loading = LOADING_EXTERNALLY;
75 self->cancellable = g_cancellable_new ();
76 g_signal_emit (instance: self, signal_id: signals[LOAD], detail: 0, self->cancellable, &started_loading);
77
78 if (!started_loading)
79 {
80 self->loading = LOADING_FAILED; /* avoid notify::is_loading */
81 gtk_data_viewer_load_error (self, error: g_error_new (G_IO_ERROR, code: G_IO_ERROR_FAILED, format: "Nothing to load"));
82 }
83
84 g_assert (self->loading != NOT_LOADED);
85
86 if (gtk_data_viewer_is_loading (self))
87 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_LOADING]);
88}
89
90static void
91gtk_data_viewer_realize (GtkWidget *widget)
92{
93 GtkDataViewer *self = GTK_DATA_VIEWER (ptr: widget);
94
95 GTK_WIDGET_CLASS (gtk_data_viewer_parent_class)->realize (widget);
96
97 gtk_data_viewer_ensure_loaded (self);
98}
99
100static void
101gtk_data_viewer_unrealize (GtkWidget *widget)
102{
103 GtkDataViewer *self = GTK_DATA_VIEWER (ptr: widget);
104
105 GTK_WIDGET_CLASS (gtk_data_viewer_parent_class)->unrealize (widget);
106
107 gtk_data_viewer_reset (self);
108}
109
110static void
111gtk_data_viewer_dispose (GObject *object)
112{
113 //GtkDataViewer *self = GTK_DATA_VIEWER (object);
114
115 G_OBJECT_CLASS (gtk_data_viewer_parent_class)->dispose (object);
116}
117
118static void
119gtk_data_viewer_get_property (GObject *object,
120 guint property_id,
121 GValue *value,
122 GParamSpec *pspec)
123{
124 GtkDataViewer *self = GTK_DATA_VIEWER (ptr: object);
125
126 switch (property_id)
127 {
128 case PROP_LOADING:
129 g_value_set_boolean (value, v_boolean: gtk_data_viewer_is_loading (self));
130 break;
131
132 default:
133 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
134 break;
135 }
136}
137
138static void
139gtk_data_viewer_set_property (GObject *object,
140 guint property_id,
141 const GValue *value,
142 GParamSpec *pspec)
143{
144 //GtkDataViewer *self = GTK_DATA_VIEWER (object);
145
146 switch (property_id)
147 {
148 default:
149 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
150 break;
151 }
152}
153
154static void
155gtk_data_viewer_class_init (GtkDataViewerClass *klass)
156{
157 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
158 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
159
160 widget_class->realize = gtk_data_viewer_realize;
161 widget_class->unrealize = gtk_data_viewer_unrealize;
162
163 gobject_class->dispose = gtk_data_viewer_dispose;
164 gobject_class->get_property = gtk_data_viewer_get_property;
165 gobject_class->set_property = gtk_data_viewer_set_property;
166
167 properties[PROP_LOADING] =
168 g_param_spec_boolean (name: "loading",
169 nick: "Loading",
170 blurb: "If the widget is currently loading the data to display",
171 FALSE,
172 flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
173
174 g_object_class_install_properties (oclass: gobject_class, n_pspecs: N_PROPS, pspecs: properties);
175
176 signals[LOAD] =
177 g_signal_new (signal_name: "load",
178 G_TYPE_FROM_CLASS (klass),
179 signal_flags: G_SIGNAL_RUN_LAST,
180 class_offset: 0,
181 accumulator: g_signal_accumulator_first_wins, NULL,
182 NULL,
183 G_TYPE_BOOLEAN, n_params: 1,
184 G_TYPE_CANCELLABLE);
185
186 gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
187 gtk_widget_class_set_css_name (widget_class, name: "frame");
188}
189
190static void
191gtk_data_viewer_init (GtkDataViewer *self)
192{
193}
194
195GtkWidget *
196gtk_data_viewer_new (void)
197{
198 return g_object_new (GTK_TYPE_DATA_VIEWER, NULL);
199}
200
201gboolean
202gtk_data_viewer_is_loading (GtkDataViewer *self)
203{
204 g_return_val_if_fail (GTK_IS_DATA_VIEWER (self), FALSE);
205
206 return self->loading == LOADING_EXTERNALLY ||
207 self->loading == LOADING_INTERNALLY;
208}
209
210void
211gtk_data_viewer_reset (GtkDataViewer *self)
212{
213 gboolean was_loading;
214
215 g_return_if_fail (GTK_IS_DATA_VIEWER (self));
216
217 g_object_freeze_notify (G_OBJECT (self));
218
219 was_loading = gtk_data_viewer_is_loading (self);
220
221 g_clear_pointer (&self->contents, gtk_widget_unparent);
222 g_clear_error (err: &self->error);
223 g_cancellable_cancel (cancellable: self->cancellable);
224 g_clear_object (&self->cancellable);
225
226 self->loading = NOT_LOADED;
227
228 if (gtk_widget_get_realized (GTK_WIDGET (self)))
229 gtk_data_viewer_ensure_loaded (self);
230
231 if (was_loading != gtk_data_viewer_is_loading (self))
232 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_LOADING]);
233
234 g_object_thaw_notify (G_OBJECT (self));
235}
236
237void
238gtk_data_viewer_load_value (GtkDataViewer *self,
239 const GValue *value)
240{
241 gboolean was_loading;
242
243 g_return_if_fail (GTK_IS_DATA_VIEWER (self));
244
245 was_loading = gtk_data_viewer_is_loading (self);
246 self->loading = LOADING_DONE;
247
248 g_clear_pointer (&self->contents, gtk_widget_unparent);
249 g_cancellable_cancel (cancellable: self->cancellable);
250 g_clear_object (&self->cancellable);
251
252 if (g_type_is_a (G_VALUE_TYPE (value), G_TYPE_STRING))
253 {
254 self->contents = gtk_label_new (str: g_value_get_string (value));
255 gtk_label_set_wrap (GTK_LABEL (self->contents), TRUE);
256 gtk_widget_set_parent (widget: self->contents, GTK_WIDGET (self));
257 }
258 else if (g_type_is_a (G_VALUE_TYPE (value), GDK_TYPE_PAINTABLE))
259 {
260 self->contents = gtk_picture_new_for_paintable (paintable: g_value_get_object (value));
261 gtk_widget_set_size_request (widget: self->contents, width: 256, height: 256);
262 gtk_widget_set_parent (widget: self->contents, GTK_WIDGET (self));
263 }
264 else if (g_type_is_a (G_VALUE_TYPE (value), GDK_TYPE_PIXBUF))
265 {
266 self->contents = gtk_picture_new_for_pixbuf (pixbuf: g_value_get_object (value));
267 gtk_widget_set_size_request (widget: self->contents, width: 256, height: 256);
268 gtk_widget_set_parent (widget: self->contents, GTK_WIDGET (self));
269 }
270 else if (g_type_is_a (G_VALUE_TYPE (value), GDK_TYPE_RGBA))
271 {
272 const GdkRGBA *color = g_value_get_boxed (value);
273
274 self->contents = gtk_color_swatch_new ();
275 gtk_color_swatch_set_rgba (GTK_COLOR_SWATCH (self->contents), color);
276 gtk_widget_set_size_request (widget: self->contents, width: 48, height: 32);
277 gtk_widget_set_halign (widget: self->contents, align: GTK_ALIGN_CENTER);
278 gtk_widget_set_parent (widget: self->contents, GTK_WIDGET (self));
279 }
280 else if (g_type_is_a (G_VALUE_TYPE (value), G_TYPE_FILE))
281 {
282 GFile *file = g_value_get_object (value);
283
284 self->contents = gtk_label_new (str: g_file_peek_path (file));
285 gtk_label_set_ellipsize (GTK_LABEL (self->contents), mode: PANGO_ELLIPSIZE_START);
286 gtk_widget_set_halign (widget: self->contents, align: GTK_ALIGN_CENTER);
287 gtk_widget_set_parent (widget: self->contents, GTK_WIDGET (self));
288 }
289 else if (g_type_is_a (G_VALUE_TYPE (value), GDK_TYPE_FILE_LIST))
290 {
291 GList *l;
292
293 self->contents = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 10);
294 gtk_widget_set_parent (widget: self->contents, GTK_WIDGET (self));
295
296 for (l = g_value_get_boxed (value); l; l = l->next)
297 {
298 GFile *file = l->data;
299 GtkWidget *label = gtk_label_new (str: g_file_peek_path (file));
300 gtk_label_set_ellipsize (GTK_LABEL (label), mode: PANGO_ELLIPSIZE_START);
301 gtk_widget_set_halign (widget: label, align: GTK_ALIGN_CENTER);
302 gtk_box_append (GTK_BOX (self->contents), child: label);
303 }
304 }
305 else
306 {
307 gtk_data_viewer_load_error (self,
308 error: g_error_new (G_IO_ERROR,
309 code: G_IO_ERROR_FAILED,
310 format: "Cannot display objects of type \"%s\"", G_VALUE_TYPE_NAME (value)));
311 }
312
313 if (was_loading)
314 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_LOADING]);
315}
316
317static void
318gtk_data_viewer_load_stream_done (GObject *source,
319 GAsyncResult *res,
320 gpointer data)
321{
322 GtkDataViewer *self = data;
323 GError *error = NULL;
324 GValue value = G_VALUE_INIT;
325
326 if (!gdk_content_deserialize_finish (result: res, value: &value, error: &error))
327 {
328 if (!g_error_matches (error, G_IO_ERROR, code: G_IO_ERROR_CANCELLED))
329 gtk_data_viewer_load_error (self, error);
330 else
331 g_clear_error (err: &error);
332
333 g_object_unref (object: self);
334 return;
335 }
336
337 gtk_data_viewer_load_value (self, value: &value);
338 g_object_unref (object: self);
339 g_value_unset (value: &value);
340}
341
342void
343gtk_data_viewer_load_stream (GtkDataViewer *self,
344 GInputStream *stream,
345 const char *mime_type)
346{
347 GdkContentFormats *formats;
348 const GType *gtypes;
349 gboolean was_loading;
350
351 g_return_if_fail (GTK_IS_DATA_VIEWER (self));
352 g_return_if_fail (G_IS_INPUT_STREAM (stream));
353 g_return_if_fail (mime_type != NULL);
354
355 was_loading = gtk_data_viewer_is_loading (self);
356 self->loading = LOADING_INTERNALLY;
357 if (self->cancellable == NULL)
358 self->cancellable = g_cancellable_new ();
359
360 formats = gdk_content_formats_new (mime_types: &mime_type, n_mime_types: 1);
361 formats = gdk_content_formats_union_deserialize_gtypes (formats);
362 gtypes = gdk_content_formats_get_gtypes (formats, NULL);
363 if (gtypes)
364 {
365 gdk_content_deserialize_async (stream,
366 mime_type,
367 type: gtypes[0],
368 G_PRIORITY_DEFAULT,
369 cancellable: self->cancellable,
370 callback: gtk_data_viewer_load_stream_done,
371 g_object_ref (self));
372
373 if (!was_loading)
374 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_LOADING]);
375 }
376 else
377 {
378 gtk_data_viewer_load_error (self,
379 error: g_error_new (G_IO_ERROR, code: G_IO_ERROR_FAILED,
380 format: "Cannot display data of type \"%s\"", mime_type));
381 }
382
383 gdk_content_formats_unref (formats);
384}
385
386void
387gtk_data_viewer_load_error (GtkDataViewer *self,
388 GError *error)
389{
390 gboolean was_loading;
391
392 g_return_if_fail (GTK_IS_DATA_VIEWER (self));
393
394 was_loading = gtk_data_viewer_is_loading (self);
395 self->loading = LOADING_FAILED;
396
397 g_clear_pointer (&self->contents, gtk_widget_unparent);
398 g_clear_error (err: &self->error);
399 g_cancellable_cancel (cancellable: self->cancellable);
400 g_clear_object (&self->cancellable);
401
402 self->error = error;
403 self->contents = gtk_label_new (str: error->message);
404 gtk_widget_add_css_class (widget: self->contents, css_class: "error");
405 gtk_widget_set_halign (widget: self->contents, align: GTK_ALIGN_CENTER);
406 gtk_widget_set_valign (widget: self->contents, align: GTK_ALIGN_CENTER);
407 gtk_widget_set_parent (widget: self->contents, GTK_WIDGET (self));
408
409 if (was_loading)
410 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_LOADING]);
411}
412
413

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