1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; coding: utf-8 -*-
2  *
3  * This file tests the GtkImageToolSelector class.
4  **/
5 #include <src/gtkimagetoolselector.h>
6 #include <assert.h>
7 #include "testlib/testlib.h"
8 
9 static GtkImageView *view = NULL;
10 // Use two global variables to avoid castings.
11 static GtkIImageTool *tool = NULL;
12 static GtkImageToolSelector *selector = NULL;
13 
14 static void
setup()15 setup ()
16 {
17     view = GTK_IMAGE_VIEW (gtk_image_view_new ());
18     g_object_ref (view);
19     gtk_object_sink (GTK_OBJECT (view));
20     fake_realize (GTK_WIDGET (view));
21 
22     tool = gtk_image_tool_selector_new (view);
23     selector = GTK_IMAGE_TOOL_SELECTOR (tool);
24     gtk_image_view_set_tool (view, tool);
25 }
26 
27 static void
teardown()28 teardown ()
29 {
30     g_object_unref (selector);
31     gtk_widget_destroy (GTK_WIDGET (view));
32     g_object_unref (view);
33 }
34 
35 /**
36  * test_null_image_view:
37  *
38  * Ensure that an error is printed if the view argument to the
39  * constructor is NULL.
40  **/
41 static void
test_null_image_view()42 test_null_image_view ()
43 {
44     printf ("test_null_image_view (one error message expected)\n");
45     GtkIImageTool *tool = gtk_image_tool_selector_new (NULL);
46     assert (!tool);
47 }
48 
49 /**
50  * test_default_selection:
51  *
52  * Ensure that the default selection rectangle is (0, 0), (0, 0).
53  **/
54 static void
test_default_selection()55 test_default_selection ()
56 {
57     printf ("test_default_selection\n");
58     setup ();
59     GdkRectangle sel;
60     gtk_image_tool_selector_get_selection (selector, &sel);
61     assert (gdk_rectangle_eq (sel, (GdkRectangle){0, 0, 0, 0}));
62     teardown ();
63 }
64 
65 /**
66  * test_tool_ref_count:
67  *
68  * Ensure that the tool has the correct number of references.
69  **/
70 static void
test_tool_ref_count()71 test_tool_ref_count ()
72 {
73     printf ("test_tool_ref_count\n");
74     setup ();
75 
76     // The selector has an implied reference and the view
77     assert (G_OBJECT (selector)->ref_count == 2);
78 
79     teardown ();
80 }
81 
82 /**
83  * test_get_set_selection:
84  *
85  * Ensure that getting and setting the selection rectangle works when
86  * the view shows a pixbuf.
87  **/
88 static void
test_get_set_selection()89 test_get_set_selection ()
90 {
91     printf ("test_get_set_selection\n");
92     setup ();
93     GdkPixbuf *pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, 50, 50);
94     gtk_image_view_set_pixbuf (view, pixbuf, FALSE);
95     GdkRectangle sel = {10, 11, 12, 13};
96     gtk_image_tool_selector_set_selection (selector, &sel);
97     GdkRectangle ret;
98     gtk_image_tool_selector_get_selection (selector, &ret);
99     assert (gdk_rectangle_eq (ret, (GdkRectangle){10, 11, 12, 13}));
100     g_object_unref (pixbuf);
101     teardown();
102 }
103 
104 /**
105  * test_set_selection_no_pixbuf:
106  *
107  * Ensure that setting the selection when there is no pixbuf in the
108  * view works. The selection should not be modified.
109  **/
110 static void
test_set_selection_no_pixbuf()111 test_set_selection_no_pixbuf ()
112 {
113     printf ("test_set_selection_no_pixbuf\n");
114     setup ();
115     GdkRectangle sel = {10, 11, 12, 13};
116     gtk_image_view_set_pixbuf (view, NULL, FALSE);
117     gtk_image_tool_selector_set_selection (selector, &sel);
118 
119     gtk_image_tool_selector_get_selection (selector, &sel);
120     assert (sel.x == 0 && sel.y == 0 && sel.width == 0 && sel.height == 0);
121     teardown ();
122 }
123 
124 /**
125  * test_set_selection_to_big:
126  *
127  * Ensure that setting a to big selection rectangle leaves the
128  * selection rectangle untouched.
129  **/
130 static void
test_set_selection_to_big()131 test_set_selection_to_big ()
132 {
133     printf ("test_selection_to_big\n");
134     setup ();
135     GdkPixbuf *pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, 20, 20);
136     gtk_image_view_set_pixbuf (view, pixbuf, TRUE);
137 
138     GdkRectangle good_sel = {0, 0, 10, 10};
139     gtk_image_tool_selector_set_selection (selector, &good_sel);
140 
141     GdkRectangle bad_sel = {0, 0, 50, 50};
142     gtk_image_tool_selector_set_selection (selector, &bad_sel);
143 
144     GdkRectangle curr_sel;
145     gtk_image_tool_selector_get_selection (selector, &curr_sel);
146     assert (gdk_rectangle_eq (curr_sel, (GdkRectangle){0, 0, 10, 10}));
147 
148     g_object_unref (pixbuf);
149     teardown ();
150 }
151 
152 /**
153  * test_set_selection_outside_pixbuf:
154  *
155  * Ensure that a selection rectangle outside the pixbuf is moved back
156  * in.
157  **/
158 static void
test_set_selection_outside_pixbuf()159 test_set_selection_outside_pixbuf ()
160 {
161     printf ("test_set_selection_outside_pixbuf\n");
162     setup ();
163     GdkPixbuf *pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, 20, 20);
164     gtk_image_view_set_pixbuf (view, pixbuf, TRUE);
165 
166     GdkRectangle good_sel = {0, 0, 10, 10};
167     gtk_image_tool_selector_set_selection (selector, &good_sel);
168 
169     GdkRectangle bad_sel = {15, 15, 10, 10};
170     gtk_image_tool_selector_set_selection (selector, &bad_sel);
171 
172     GdkRectangle curr_sel;
173     gtk_image_tool_selector_get_selection (selector, &curr_sel);
174     assert (gdk_rectangle_eq (curr_sel, (GdkRectangle){10, 10, 10, 10}));
175 
176     g_object_unref (pixbuf);
177     teardown ();
178 }
179 
180 /**
181  * test_motion_notify:
182  *
183  * Ensure that the selector handles motion notifies.
184  **/
185 static void
test_motion_notify()186 test_motion_notify ()
187 {
188     printf ("test_lookup_cursors\n");
189     setup ();
190     GdkEventMotion ev = {.x = 10, .y = 10};
191     assert (!gtk_iimage_tool_motion_notify (tool, &ev));
192     teardown();
193 }
194 
195 /**
196  * test_motion_notify_in_selection:
197  *
198  * Ensure that the selector handles a motion notify occuring inside
199  * the selection.
200  **/
201 static void
test_motion_notify_in_selection()202 test_motion_notify_in_selection ()
203 {
204     printf ("test_motion_notify_in_selection\n");
205     setup ();
206 
207     // Fake an allocation
208     GTK_WIDGET (view)->allocation = (GtkAllocation){0, 0, 40, 40};
209 
210     GdkPixbuf *pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, 20, 20);
211     gtk_image_view_set_pixbuf (view, pixbuf, TRUE);
212 
213     GdkRectangle sel_rect = {0, 0, 20, 20};
214     gtk_image_tool_selector_set_selection (selector, &sel_rect);
215 
216     // Put the pointer inside the selection area.
217     GdkEventMotion ev = {.x = 20, .y = 20};
218     assert (!gtk_iimage_tool_motion_notify (tool, &ev));
219     g_object_unref (pixbuf);
220     teardown ();
221 }
222 
223 /**
224  * test_set_selection_on_unrealized_view:
225  *
226  * The objective of this test is to verify that setting the selection
227  * on a selector tool with an unrealized view works as expected.
228  **/
229 static void
test_set_selection_on_unrealized_view()230 test_set_selection_on_unrealized_view ()
231 {
232     printf ("test_set_selection_on_unrealized_view\n");
233     GtkImageView *view2 = GTK_IMAGE_VIEW (gtk_image_view_new ());
234     g_object_ref (view2);
235     gtk_object_sink (GTK_OBJECT (view2));
236     tool = gtk_image_tool_selector_new (view2);
237     selector = GTK_IMAGE_TOOL_SELECTOR (tool);
238 
239     gtk_image_view_set_tool (view2, tool);
240     GdkPixbuf *pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, 20, 20);
241     gtk_image_view_set_pixbuf (view2, pixbuf, TRUE);
242 
243     GdkRectangle sel = {10, 10, 10, 10};
244 
245     gtk_image_tool_selector_set_selection (selector, &sel);
246 
247     g_object_unref (pixbuf);
248     g_object_unref (selector);
249     gtk_widget_destroy (GTK_WIDGET (view2));
250     g_object_unref (view2);
251 }
252 
253 /**
254  * test_motion_notify_keeps_accuracy:
255  *
256  * Motion notify of #GtkImageToolSelector must remember the initial
257  * zoom space position of the mouse pointer. Because the geometry of
258  * the selection rectangle is stored in image space coordinates, it
259  * means that multiple motion notify events that defines drags that
260  * are shorter must be recorded somehow.
261  *
262  * The easiest way to accomplish that is to remember the position
263  * where the drag began.
264  **/
265 static void
test_motion_notify_keeps_accuracy()266 test_motion_notify_keeps_accuracy ()
267 {
268     printf ("test_motion_notify_keeps_accuracy\n");
269     setup ();
270 
271     GTK_WIDGET (view)->allocation = (GtkAllocation){0, 0, 500, 500};
272 
273     GdkPixbuf *pixbuf =
274         gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, 100, 100);
275     gtk_image_view_set_pixbuf (view, pixbuf, TRUE);
276     gtk_image_view_set_zoom (view, 5.0);
277 
278     // Set the initial selection
279     GdkRectangle sel;
280     sel = (GdkRectangle){10, 10, 50, 50};
281     gtk_image_tool_selector_set_selection (selector, &sel);
282 
283     // Fake drag the selection some
284     GtkWidget *widget = GTK_WIDGET (view);
285     GtkWidgetClass *wid_class = GTK_WIDGET_GET_CLASS (widget);
286     GdkEventMotion motion_ev;
287 
288     // First, fake a button click in the selection at widget
289     // coordinates (100, 100) which is inside the selection.
290     GdkEventButton button_ev = {.button = 1,
291                                 .window = GTK_WIDGET (view)->window,
292                                 .x = 100, .y = 100};
293     wid_class->button_press_event (widget, &button_ev);
294 
295     // Then a motion event at (103, 105)
296     motion_ev = (GdkEventMotion){.x = 103, .y = 105};
297     wid_class->motion_notify_event (widget, &motion_ev);
298 
299     // Since 100 // 5 == 103 // 5, the selection should not move in
300     // the x direction. But should move one pixel downwards in the y
301     // direction.
302     gtk_image_tool_selector_get_selection (selector, &sel);
303     assert (sel.x == 10 && sel.y == 11);
304 
305     // The another mtoin event at (106, 110)
306     motion_ev = (GdkEventMotion){.x = 106, .y = 110};
307     wid_class->motion_notify_event (widget, &motion_ev);
308 
309     // 6 // 5 = 1 and 10 // 5 = 2, so the selection should now have
310     // moved one pixel to the right and two pixels downwards from its
311     // original position.
312     gtk_image_tool_selector_get_selection (selector, &sel);
313     assert (sel.x == 11 && sel.y == 12);
314 
315     g_object_unref (pixbuf);
316     teardown ();
317 }
318 
319 /**
320  * test_button_press_before_realize:
321  *
322  * Ensure that the selector can handle button press events even before
323  * its view has been realized.
324  **/
325 static void
test_button_press_before_realize()326 test_button_press_before_realize ()
327 {
328     printf ("test_button_press_before_realize\n");
329     setup ();
330     GTK_WIDGET (view)->allocation = (GtkAllocation){0, 0, 10, 10};
331     GdkPixbuf *pixbuf =
332         gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, 10, 10);
333     gtk_image_view_set_pixbuf (view, pixbuf, TRUE);
334     GdkEventButton ev = {.button = 1,
335                          .window = GTK_WIDGET (view)->window,
336                          .x = 5, .y = 5};
337     gtk_iimage_tool_button_press (tool, &ev);
338     g_object_unref (pixbuf);
339     teardown ();
340 }
341 
342 /**
343  * test_cursor_at_point_inside_selection:
344  *
345  * Ensure that the correct cursor is returned when
346  * gtk_iimage_tool_cursor_at_point() is called with a position inside
347  * the selection.
348  **/
349 static void
test_cursor_at_point_inside_selection()350 test_cursor_at_point_inside_selection ()
351 {
352     printf ("test_cursor_at_point_inside_selection\n");
353     setup ();
354     GTK_WIDGET (view)->allocation = (GtkAllocation){0, 0, 50, 50};
355     GdkPixbuf *pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, 90, 90);
356     gtk_image_view_set_pixbuf (view, pixbuf, FALSE);
357 
358     gtk_image_view_set_zoom (view, 1.0);
359     GdkRectangle sel = {0, 0, 90, 90};
360     gtk_image_tool_selector_set_selection (selector, &sel);
361     GdkCursor *cursor = gtk_iimage_tool_cursor_at_point (tool, 0, 0);
362     assert (cursor);
363 
364     g_object_unref (pixbuf);
365     teardown ();
366 }
367 
368 /**
369  * test_cursor_at_point_on_null_pixbuf:
370  *
371  * Ensure that the cursor returned is %NULL if there is no pixbuf in
372  * the view.
373  **/
374 static void
test_cursor_at_point_on_null_pixbuf()375 test_cursor_at_point_on_null_pixbuf ()
376 {
377     printf ("test_cursor_at_point_on_null_pixbuf\n");
378     setup ();
379     GdkCursor *cursor = gtk_iimage_tool_cursor_at_point (tool, 10, 10);
380     assert (!cursor);
381     teardown ();
382 }
383 
384 /**
385  * test_cursor_outside_widget:
386  *
387  * Ensure that the cursor returned is %NULL when the coordinate is
388  * outside the widget.
389  **/
390 static void
test_cursor_outside_widget()391 test_cursor_outside_widget ()
392 {
393     printf ("test_cursor_outside_widget\n");
394     setup ();
395     GTK_WIDGET (view)->allocation = (GtkAllocation){0, 0, 50, 50};
396     GdkPixbuf *pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, 90, 90);
397     gtk_image_view_set_pixbuf (view, pixbuf, FALSE);
398 
399     int coords[] = {
400         250, 250,
401         250, 25,
402         -10, -10
403     };
404     for (int n = 0; n < G_N_ELEMENTS(coords); n += 2)
405     {
406         int x = coords[n];
407         int y = coords[n + 1];
408         GdkCursor *cursor = gtk_iimage_tool_cursor_at_point (tool, x, y);
409         assert (!cursor);
410     }
411     g_object_unref (pixbuf);
412     teardown ();
413 }
414 
415 /**
416  * test_selection_after_pixbuf_change:
417  *
418  * Ensure that the selection becomes (0,0)-[0,0] when the pixbuf is
419  * changed if reset_fit is %TRUE.
420  **/
421 static void
test_selection_after_pixbuf_change()422 test_selection_after_pixbuf_change ()
423 {
424     printf ("test_selection_after_pixbuf_change\n");
425     setup ();
426     GdkRectangle rect;
427 
428     GdkPixbuf *pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, 90, 90);
429     gtk_image_view_set_pixbuf (view, pixbuf, TRUE);
430     gtk_image_tool_selector_get_selection (selector, &rect);
431     assert (gdk_rectangle_eq (rect, (GdkRectangle){0, 0, 0, 0}));
432 
433     rect = (GdkRectangle){1, 2, 3, 4};
434     gtk_image_tool_selector_set_selection (selector, &rect);
435     gtk_image_view_set_pixbuf (view, NULL, TRUE);
436     gtk_image_tool_selector_get_selection (selector, &rect);
437     assert (gdk_rectangle_eq (rect, (GdkRectangle){0, 0, 0, 0}));
438 
439     g_object_unref (pixbuf);
440     teardown ();
441 }
442 
443 int
main(int argc,char * argv[])444 main (int   argc,
445       char *argv[])
446 {
447     gtk_init (&argc, &argv);
448     test_null_image_view ();
449     test_default_selection ();
450     test_tool_ref_count ();
451     test_get_set_selection ();
452     test_set_selection_no_pixbuf ();
453     test_set_selection_to_big ();
454     test_set_selection_outside_pixbuf ();
455     test_motion_notify ();
456     test_motion_notify_in_selection ();
457     test_set_selection_on_unrealized_view ();
458     test_motion_notify_keeps_accuracy ();
459     test_button_press_before_realize ();
460     test_cursor_at_point_inside_selection ();
461     test_cursor_at_point_on_null_pixbuf ();
462     test_cursor_outside_widget ();
463     test_selection_after_pixbuf_change ();
464     printf ("16 tests passed.\n");
465 }
466