1 /*
2  * Copyright (C) 2010 Carlos Garcia Campos  <carlosgc@gnome.org>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2, or (at your option)
7  * any later version.
8  *
9  * This program 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
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
17  */
18 
19 #include "config.h"
20 
21 #include <gtk/gtk.h>
22 #include <cairo.h>
23 
24 #include "selections.h"
25 
26 typedef struct
27 {
28     PopplerDocument *doc;
29 
30     /* Properties */
31     gint page_index;
32     gdouble scale;
33 
34     GtkWidget *swindow;
35     GtkWidget *darea;
36     GtkWidget *fg_color_button;
37     GtkWidget *bg_color_button;
38     GtkWidget *copy_button;
39 
40     PopplerPage *page;
41     cairo_surface_t *surface;
42 
43     GdkPoint start;
44     GdkPoint stop;
45     PopplerRectangle doc_area;
46     cairo_surface_t *selection_surface;
47     PopplerSelectionStyle style;
48     PopplerColor glyph_color;
49     PopplerColor background_color;
50     guint selections_idle;
51     cairo_region_t *selection_region;
52     cairo_region_t *selected_region;
53     GdkCursorType cursor;
54     gchar *selected_text;
55 } PgdSelectionsDemo;
56 
pgd_selections_clear_selections(PgdSelectionsDemo * demo)57 static void pgd_selections_clear_selections(PgdSelectionsDemo *demo)
58 {
59     demo->start.x = -1;
60 
61     if (demo->selection_surface) {
62         cairo_surface_destroy(demo->selection_surface);
63         demo->selection_surface = NULL;
64     }
65 
66     if (demo->selection_region) {
67         cairo_region_destroy(demo->selection_region);
68         demo->selection_region = NULL;
69     }
70 
71     if (demo->selected_text) {
72         g_free(demo->selected_text);
73         demo->selected_text = NULL;
74     }
75 
76     if (demo->selected_region) {
77         cairo_region_destroy(demo->selected_region);
78         demo->selected_region = NULL;
79     }
80 }
81 
pgd_selections_free(PgdSelectionsDemo * demo)82 static void pgd_selections_free(PgdSelectionsDemo *demo)
83 {
84     if (!demo)
85         return;
86 
87     if (demo->selections_idle > 0) {
88         g_source_remove(demo->selections_idle);
89         demo->selections_idle = 0;
90     }
91 
92     if (demo->doc) {
93         g_object_unref(demo->doc);
94         demo->doc = NULL;
95     }
96 
97     if (demo->page) {
98         g_object_unref(demo->page);
99         demo->page = NULL;
100     }
101 
102     if (demo->surface) {
103         cairo_surface_destroy(demo->surface);
104         demo->surface = NULL;
105     }
106 
107     pgd_selections_clear_selections(demo);
108 
109     g_free(demo);
110 }
111 
pgd_selections_update_selection_region(PgdSelectionsDemo * demo)112 static void pgd_selections_update_selection_region(PgdSelectionsDemo *demo)
113 {
114     PopplerRectangle area = { 0, 0, 0, 0 };
115 
116     if (demo->selection_region)
117         cairo_region_destroy(demo->selection_region);
118 
119     poppler_page_get_size(demo->page, &area.x2, &area.y2);
120     demo->selection_region = poppler_page_get_selected_region(demo->page, 1.0, POPPLER_SELECTION_GLYPH, &area);
121 }
122 
pgd_selections_update_selected_text(PgdSelectionsDemo * demo)123 static void pgd_selections_update_selected_text(PgdSelectionsDemo *demo)
124 {
125     gchar *text;
126 
127     if (demo->selected_region)
128         cairo_region_destroy(demo->selected_region);
129     demo->selected_region = poppler_page_get_selected_region(demo->page, 1.0, demo->style, &demo->doc_area);
130     if (demo->selected_text)
131         g_free(demo->selected_text);
132     demo->selected_text = NULL;
133 
134     text = poppler_page_get_selected_text(demo->page, demo->style, &demo->doc_area);
135     if (text) {
136         /* For copying text from the document to the clipboard, we want a normalization
137          * that preserves 'canonical equivalence' i.e. that text after normalization
138          * is not visually different than the original text. Issue #724 */
139         demo->selected_text = g_utf8_normalize(text, -1, G_NORMALIZE_NFC);
140         g_free(text);
141         gtk_widget_set_sensitive(demo->copy_button, TRUE);
142     }
143 }
144 
pgd_selections_update_cursor(PgdSelectionsDemo * demo,GdkCursorType cursor_type)145 static void pgd_selections_update_cursor(PgdSelectionsDemo *demo, GdkCursorType cursor_type)
146 {
147     GdkWindow *window = gtk_widget_get_window(demo->darea);
148     GdkCursor *cursor = NULL;
149 
150     if (cursor_type == demo->cursor)
151         return;
152 
153     if (cursor_type != GDK_LAST_CURSOR) {
154         cursor = gdk_cursor_new_for_display(gtk_widget_get_display(demo->darea), cursor_type);
155     }
156 
157     demo->cursor = cursor_type;
158 
159     gdk_window_set_cursor(window, cursor);
160     gdk_display_flush(gtk_widget_get_display(demo->darea));
161     if (cursor)
162         g_object_unref(cursor);
163 }
164 
pgd_selections_render_selections(PgdSelectionsDemo * demo)165 static gboolean pgd_selections_render_selections(PgdSelectionsDemo *demo)
166 {
167     PopplerRectangle doc_area;
168     gdouble page_width, page_height;
169     cairo_t *cr;
170 
171     if (!demo->page || demo->start.x == -1) {
172         demo->selections_idle = 0;
173 
174         return FALSE;
175     }
176 
177     poppler_page_get_size(demo->page, &page_width, &page_height);
178     page_width *= demo->scale;
179     page_height *= demo->scale;
180 
181     doc_area.x1 = demo->start.x / demo->scale;
182     doc_area.y1 = demo->start.y / demo->scale;
183     doc_area.x2 = demo->stop.x / demo->scale;
184     doc_area.y2 = demo->stop.y / demo->scale;
185 
186     if (demo->selection_surface)
187         cairo_surface_destroy(demo->selection_surface);
188     demo->selection_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, page_width, page_height);
189     cr = cairo_create(demo->selection_surface);
190     if (demo->scale != 1.0)
191         cairo_scale(cr, demo->scale, demo->scale);
192     poppler_page_render_selection(demo->page, cr, &doc_area, &demo->doc_area, demo->style, &demo->glyph_color, &demo->background_color);
193     cairo_destroy(cr);
194 
195     demo->doc_area = doc_area;
196     gtk_widget_queue_draw(demo->darea);
197 
198     demo->selections_idle = 0;
199 
200     return FALSE;
201 }
202 
pgd_selections_drawing_area_draw(GtkWidget * area,cairo_t * cr,PgdSelectionsDemo * demo)203 static gboolean pgd_selections_drawing_area_draw(GtkWidget *area, cairo_t *cr, PgdSelectionsDemo *demo)
204 {
205     if (!demo->surface)
206         return FALSE;
207 
208     cairo_save(cr);
209     cairo_set_source_surface(cr, demo->surface, 0, 0);
210     cairo_paint(cr);
211     cairo_restore(cr);
212 
213     if (demo->selection_surface) {
214         cairo_set_source_surface(cr, demo->selection_surface, 0, 0);
215         cairo_paint(cr);
216     }
217 
218     return TRUE;
219 }
220 
pgd_selections_drawing_area_button_press(GtkWidget * area,GdkEventButton * event,PgdSelectionsDemo * demo)221 static gboolean pgd_selections_drawing_area_button_press(GtkWidget *area, GdkEventButton *event, PgdSelectionsDemo *demo)
222 {
223     if (!demo->page)
224         return FALSE;
225 
226     if (event->button != 1)
227         return FALSE;
228 
229     demo->start.x = event->x;
230     demo->start.y = event->y;
231     demo->stop = demo->start;
232 
233     switch (event->type) {
234     case GDK_2BUTTON_PRESS:
235         demo->style = POPPLER_SELECTION_WORD;
236         break;
237     case GDK_3BUTTON_PRESS:
238         demo->style = POPPLER_SELECTION_LINE;
239         break;
240     default:
241         demo->style = POPPLER_SELECTION_GLYPH;
242     }
243 
244     pgd_selections_render_selections(demo);
245 
246     return TRUE;
247 }
248 
pgd_selections_drawing_area_motion_notify(GtkWidget * area,GdkEventMotion * event,PgdSelectionsDemo * demo)249 static gboolean pgd_selections_drawing_area_motion_notify(GtkWidget *area, GdkEventMotion *event, PgdSelectionsDemo *demo)
250 {
251     if (!demo->page)
252         return FALSE;
253 
254     if (demo->start.x != -1) {
255         demo->stop.x = event->x;
256         demo->stop.y = event->y;
257         if (demo->selections_idle == 0) {
258             demo->selections_idle = g_idle_add((GSourceFunc)pgd_selections_render_selections, demo);
259         }
260     } else {
261         gboolean over_text;
262 
263         over_text = cairo_region_contains_point(demo->selection_region, event->x / demo->scale, event->y / demo->scale);
264         pgd_selections_update_cursor(demo, over_text ? GDK_XTERM : GDK_LAST_CURSOR);
265     }
266 
267     return TRUE;
268 }
269 
pgd_selections_drawing_area_button_release(GtkWidget * area,GdkEventButton * event,PgdSelectionsDemo * demo)270 static gboolean pgd_selections_drawing_area_button_release(GtkWidget *area, GdkEventButton *event, PgdSelectionsDemo *demo)
271 {
272     if (!demo->page)
273         return FALSE;
274 
275     if (event->button != 1)
276         return FALSE;
277 
278     if (demo->start.x != -1)
279         pgd_selections_update_selected_text(demo);
280 
281     demo->start.x = -1;
282 
283     if (demo->selections_idle > 0) {
284         g_source_remove(demo->selections_idle);
285         demo->selections_idle = 0;
286     }
287 
288     return TRUE;
289 }
290 
pgd_selections_drawing_area_realize(GtkWidget * area,PgdSelectionsDemo * demo)291 static void pgd_selections_drawing_area_realize(GtkWidget *area, PgdSelectionsDemo *demo)
292 {
293     GtkStyleContext *style_context = gtk_widget_get_style_context(area);
294     GdkRGBA rgba;
295 
296     gtk_widget_add_events(area, GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON1_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
297     g_object_set(area, "has-tooltip", TRUE, NULL);
298 
299     gtk_style_context_get_color(style_context, GTK_STATE_FLAG_SELECTED, &rgba);
300     gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(demo->fg_color_button), &rgba);
301     gtk_style_context_get(style_context, GTK_STATE_FLAG_SELECTED, "background-color", &rgba, NULL);
302     gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(demo->bg_color_button), &rgba);
303 }
304 
pgd_selections_drawing_area_query_tooltip(GtkWidget * area,gint x,gint y,gboolean keyboard_mode,GtkTooltip * tooltip,PgdSelectionsDemo * demo)305 static gboolean pgd_selections_drawing_area_query_tooltip(GtkWidget *area, gint x, gint y, gboolean keyboard_mode, GtkTooltip *tooltip, PgdSelectionsDemo *demo)
306 {
307     gboolean over_selection;
308 
309     if (!demo->selected_text)
310         return FALSE;
311 
312     over_selection = cairo_region_contains_point(demo->selected_region, x / demo->scale, y / demo->scale);
313 
314     if (over_selection) {
315         GdkRectangle selection_area;
316 
317         cairo_region_get_extents(demo->selected_region, (cairo_rectangle_int_t *)&selection_area);
318         selection_area.x *= demo->scale;
319         selection_area.y *= demo->scale;
320         selection_area.width *= demo->scale;
321         selection_area.height *= demo->scale;
322 
323         gtk_tooltip_set_text(tooltip, demo->selected_text);
324         gtk_tooltip_set_tip_area(tooltip, &selection_area);
325 
326         return TRUE;
327     }
328 
329     return FALSE;
330 }
331 
pgd_selections_render(GtkButton * button,PgdSelectionsDemo * demo)332 static void pgd_selections_render(GtkButton *button, PgdSelectionsDemo *demo)
333 {
334     gdouble page_width, page_height;
335     cairo_t *cr;
336 
337     if (!demo->page)
338         demo->page = poppler_document_get_page(demo->doc, demo->page_index);
339 
340     if (!demo->page)
341         return;
342 
343     pgd_selections_clear_selections(demo);
344     pgd_selections_update_selection_region(demo);
345     gtk_widget_set_sensitive(demo->copy_button, FALSE);
346 
347     if (demo->surface)
348         cairo_surface_destroy(demo->surface);
349     demo->surface = NULL;
350 
351     poppler_page_get_size(demo->page, &page_width, &page_height);
352     page_width *= demo->scale;
353     page_height *= demo->scale;
354 
355     demo->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, page_width, page_height);
356     cr = cairo_create(demo->surface);
357 
358     cairo_save(cr);
359 
360     if (demo->scale != 1.0)
361         cairo_scale(cr, demo->scale, demo->scale);
362 
363     poppler_page_render(demo->page, cr);
364     cairo_restore(cr);
365 
366     cairo_set_operator(cr, CAIRO_OPERATOR_DEST_OVER);
367     cairo_set_source_rgb(cr, 1., 1., 1.);
368     cairo_paint(cr);
369 
370     cairo_destroy(cr);
371 
372     gtk_widget_set_size_request(demo->darea, page_width, page_height);
373     gtk_widget_queue_draw(demo->darea);
374 }
375 
pgd_selections_copy(GtkButton * button,PgdSelectionsDemo * demo)376 static void pgd_selections_copy(GtkButton *button, PgdSelectionsDemo *demo)
377 {
378     GtkClipboard *clipboard = gtk_clipboard_get_for_display(gdk_display_get_default(), GDK_SELECTION_CLIPBOARD);
379     gtk_clipboard_set_text(clipboard, demo->selected_text, -1);
380 }
381 
pgd_selections_page_selector_value_changed(GtkSpinButton * spinbutton,PgdSelectionsDemo * demo)382 static void pgd_selections_page_selector_value_changed(GtkSpinButton *spinbutton, PgdSelectionsDemo *demo)
383 {
384     demo->page_index = (gint)gtk_spin_button_get_value(spinbutton) - 1;
385     if (demo->page)
386         g_object_unref(demo->page);
387     demo->page = NULL;
388 }
389 
pgd_selections_scale_selector_value_changed(GtkSpinButton * spinbutton,PgdSelectionsDemo * demo)390 static void pgd_selections_scale_selector_value_changed(GtkSpinButton *spinbutton, PgdSelectionsDemo *demo)
391 {
392     demo->scale = gtk_spin_button_get_value(spinbutton);
393 }
394 
pgd_selections_fg_color_changed(GtkColorButton * button,GParamSpec * pspec,PgdSelectionsDemo * demo)395 static void pgd_selections_fg_color_changed(GtkColorButton *button, GParamSpec *pspec, PgdSelectionsDemo *demo)
396 {
397     GdkRGBA color;
398 
399     gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(button), &color);
400     demo->glyph_color.red = CLAMP((guint)(color.red * 65535), 0, 65535);
401     demo->glyph_color.green = CLAMP((guint)(color.green * 65535), 0, 65535);
402     demo->glyph_color.blue = CLAMP((guint)(color.blue * 65535), 0, 65535);
403 }
404 
pgd_selections_bg_color_changed(GtkColorButton * button,GParamSpec * pspec,PgdSelectionsDemo * demo)405 static void pgd_selections_bg_color_changed(GtkColorButton *button, GParamSpec *pspec, PgdSelectionsDemo *demo)
406 {
407     GdkRGBA color;
408 
409     gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(button), &color);
410     demo->background_color.red = CLAMP((guint)(color.red * 65535), 0, 65535);
411     demo->background_color.green = CLAMP((guint)(color.green * 65535), 0, 65535);
412     demo->background_color.blue = CLAMP((guint)(color.blue * 65535), 0, 65535);
413 }
414 
pgd_selections_properties_selector_create(PgdSelectionsDemo * demo)415 GtkWidget *pgd_selections_properties_selector_create(PgdSelectionsDemo *demo)
416 {
417     GtkWidget *hbox, *vbox;
418     GtkWidget *label;
419     GtkWidget *page_hbox, *page_selector;
420     GtkWidget *scale_hbox, *scale_selector;
421     GtkWidget *rotate_hbox, *rotate_selector;
422     GtkWidget *color_hbox;
423     GtkWidget *button;
424     gint n_pages;
425     gchar *str;
426 
427     n_pages = poppler_document_get_n_pages(demo->doc);
428 
429     vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
430 
431     hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 12);
432     gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
433     gtk_widget_show(hbox);
434 
435     page_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
436 
437     label = gtk_label_new("Page:");
438     gtk_box_pack_start(GTK_BOX(page_hbox), label, TRUE, TRUE, 0);
439     gtk_widget_show(label);
440 
441     page_selector = gtk_spin_button_new_with_range(1, n_pages, 1);
442     g_signal_connect(G_OBJECT(page_selector), "value-changed", G_CALLBACK(pgd_selections_page_selector_value_changed), (gpointer)demo);
443     gtk_box_pack_start(GTK_BOX(page_hbox), page_selector, TRUE, TRUE, 0);
444     gtk_widget_show(page_selector);
445 
446     str = g_strdup_printf("of %d", n_pages);
447     label = gtk_label_new(str);
448     gtk_box_pack_start(GTK_BOX(page_hbox), label, TRUE, TRUE, 0);
449     gtk_widget_show(label);
450     g_free(str);
451 
452     gtk_box_pack_start(GTK_BOX(hbox), page_hbox, FALSE, TRUE, 0);
453     gtk_widget_show(page_hbox);
454 
455     scale_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
456 
457     label = gtk_label_new("Scale:");
458     gtk_box_pack_start(GTK_BOX(scale_hbox), label, TRUE, TRUE, 0);
459     gtk_widget_show(label);
460 
461     scale_selector = gtk_spin_button_new_with_range(0, 10.0, 0.1);
462     gtk_spin_button_set_value(GTK_SPIN_BUTTON(scale_selector), 1.0);
463     g_signal_connect(G_OBJECT(scale_selector), "value-changed", G_CALLBACK(pgd_selections_scale_selector_value_changed), (gpointer)demo);
464     gtk_box_pack_start(GTK_BOX(scale_hbox), scale_selector, TRUE, TRUE, 0);
465     gtk_widget_show(scale_selector);
466 
467     gtk_box_pack_start(GTK_BOX(hbox), scale_hbox, FALSE, TRUE, 0);
468     gtk_widget_show(scale_hbox);
469 
470     rotate_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
471 
472     label = gtk_label_new("Rotate:");
473     gtk_box_pack_start(GTK_BOX(rotate_hbox), label, TRUE, TRUE, 0);
474     gtk_widget_show(label);
475 
476     rotate_selector = gtk_combo_box_text_new();
477     gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(rotate_selector), "0");
478     gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(rotate_selector), "90");
479     gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(rotate_selector), "180");
480     gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(rotate_selector), "270");
481     gtk_combo_box_set_active(GTK_COMBO_BOX(rotate_selector), 0);
482 #if 0
483 	g_signal_connect (G_OBJECT (rotate_selector), "changed",
484 			  G_CALLBACK (pgd_selections_rotate_selector_changed),
485 			  (gpointer)demo);
486 #endif
487     gtk_box_pack_start(GTK_BOX(rotate_hbox), rotate_selector, TRUE, TRUE, 0);
488     gtk_widget_show(rotate_selector);
489 
490     gtk_box_pack_start(GTK_BOX(hbox), rotate_hbox, FALSE, TRUE, 0);
491     gtk_widget_show(rotate_hbox);
492 
493     hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 12);
494     gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
495     gtk_widget_show(hbox);
496 
497     color_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
498 
499     label = gtk_label_new("Foreground Color:");
500     gtk_box_pack_start(GTK_BOX(color_hbox), label, TRUE, TRUE, 0);
501     gtk_widget_show(label);
502 
503     demo->fg_color_button = gtk_color_button_new();
504     g_signal_connect(demo->fg_color_button, "notify::color", G_CALLBACK(pgd_selections_fg_color_changed), (gpointer)demo);
505     gtk_box_pack_start(GTK_BOX(color_hbox), demo->fg_color_button, TRUE, TRUE, 0);
506     gtk_widget_show(demo->fg_color_button);
507 
508     gtk_box_pack_start(GTK_BOX(hbox), color_hbox, FALSE, TRUE, 0);
509     gtk_widget_show(color_hbox);
510 
511     color_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
512 
513     label = gtk_label_new("Background Color:");
514     gtk_box_pack_start(GTK_BOX(color_hbox), label, TRUE, TRUE, 0);
515     gtk_widget_show(label);
516 
517     demo->bg_color_button = gtk_color_button_new();
518     g_signal_connect(demo->bg_color_button, "notify::color", G_CALLBACK(pgd_selections_bg_color_changed), (gpointer)demo);
519     gtk_box_pack_start(GTK_BOX(color_hbox), demo->bg_color_button, TRUE, TRUE, 0);
520     gtk_widget_show(demo->bg_color_button);
521 
522     gtk_box_pack_start(GTK_BOX(hbox), color_hbox, FALSE, TRUE, 0);
523     gtk_widget_show(color_hbox);
524 
525     demo->copy_button = gtk_button_new_with_label("Copy");
526     g_signal_connect(G_OBJECT(demo->copy_button), "clicked", G_CALLBACK(pgd_selections_copy), (gpointer)demo);
527     gtk_box_pack_end(GTK_BOX(hbox), demo->copy_button, FALSE, TRUE, 0);
528     gtk_widget_set_sensitive(demo->copy_button, FALSE);
529     gtk_widget_show(demo->copy_button);
530 
531     button = gtk_button_new_with_label("Render");
532     g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(pgd_selections_render), (gpointer)demo);
533     gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, TRUE, 0);
534     gtk_widget_show(button);
535 
536     return vbox;
537 }
538 
pgd_selections_create_widget(PopplerDocument * document)539 GtkWidget *pgd_selections_create_widget(PopplerDocument *document)
540 {
541     PgdSelectionsDemo *demo;
542     GtkWidget *vbox, *hbox;
543 
544     demo = g_new0(PgdSelectionsDemo, 1);
545 
546     demo->doc = g_object_ref(document);
547     demo->scale = 1.0;
548     demo->cursor = GDK_LAST_CURSOR;
549 
550     pgd_selections_clear_selections(demo);
551 
552     vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
553 
554     hbox = pgd_selections_properties_selector_create(demo);
555     gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 6);
556     gtk_widget_show(hbox);
557 
558     demo->darea = gtk_drawing_area_new();
559     g_signal_connect(demo->darea, "realize", G_CALLBACK(pgd_selections_drawing_area_realize), (gpointer)demo);
560     g_signal_connect(demo->darea, "draw", G_CALLBACK(pgd_selections_drawing_area_draw), (gpointer)demo);
561     g_signal_connect(demo->darea, "button_press_event", G_CALLBACK(pgd_selections_drawing_area_button_press), (gpointer)demo);
562     g_signal_connect(demo->darea, "motion_notify_event", G_CALLBACK(pgd_selections_drawing_area_motion_notify), (gpointer)demo);
563     g_signal_connect(demo->darea, "button_release_event", G_CALLBACK(pgd_selections_drawing_area_button_release), (gpointer)demo);
564     g_signal_connect(demo->darea, "query_tooltip", G_CALLBACK(pgd_selections_drawing_area_query_tooltip), (gpointer)demo);
565     demo->swindow = gtk_scrolled_window_new(NULL, NULL);
566     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(demo->swindow), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
567 #if GTK_CHECK_VERSION(3, 7, 8)
568     gtk_container_add(GTK_CONTAINER(demo->swindow), demo->darea);
569 #else
570     gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(demo->swindow), demo->darea);
571 #endif
572     gtk_widget_show(demo->darea);
573 
574     gtk_box_pack_start(GTK_BOX(vbox), demo->swindow, TRUE, TRUE, 0);
575     gtk_widget_show(demo->swindow);
576 
577     g_object_weak_ref(G_OBJECT(demo->swindow), (GWeakNotify)pgd_selections_free, (gpointer)demo);
578 
579     return vbox;
580 }
581