gtktestutils.c 25.4 KB
Newer Older
Tim Janik's avatar
Tim Janik committed
1
/* Gtk+ testing utilities
2
3
 * Copyright (C) 2007 Imendio AB
 * Authors: Tim Janik
Tim Janik's avatar
Tim Janik committed
4
5
6
7
8
9
10
11
12
13
14
15
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
Javier Jardón's avatar
Javier Jardón committed
16
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
Tim Janik's avatar
Tim Janik committed
17
 */
Matthias Clasen's avatar
Matthias Clasen committed
18

19

20
#include "config.h"
Matthias Clasen's avatar
Matthias Clasen committed
21

22
#include "gtkspinbutton.h"
23
24
25
26
27
28
#include "gtkmain.h"
#include "gtkbox.h"
#include "gtklabel.h"
#include "gtkbutton.h"
#include "gtktextview.h"
#include "gtkrange.h"
Tim Janik's avatar
Tim Janik committed
29
30
31
32
33

#include <locale.h>
#include <string.h>
#include <math.h>

34
35
36
37
38
39
40
41
/* This is a hack.
 * We want to include the same headers as gtktypefuncs.c but we are not
 * allowed to include gtkx.h directly during GTK compilation.
 * So....
 */
#undef GTK_COMPILATION
#include <gtk/gtkx.h>
#define GTK_COMPILATION
Tim Janik's avatar
Tim Janik committed
42

43
44
45
46
47
48
/**
 * SECTION:gtktesting
 * @Short_description: Utilities for testing GTK+ applications
 * @Title: Testing
 */

Tim Janik's avatar
Tim Janik committed
49
50
/**
 * gtk_test_init:
51
 * @argcp: Address of the `argc` parameter of the
Tim Janik's avatar
Tim Janik committed
52
 *        main() function. Changed if any arguments were handled.
53
 * @argvp: (inout) (array length=argcp): Address of the 
54
 *        `argv` parameter of main().
Tim Janik's avatar
Tim Janik committed
55
56
 *        Any parameters understood by g_test_init() or gtk_init() are
 *        stripped before return.
Matthias Clasen's avatar
Matthias Clasen committed
57
 * @...: currently unused
Matthias Clasen's avatar
Matthias Clasen committed
58
59
 *
 * This function is used to initialize a GTK+ test program.
Tim Janik's avatar
Tim Janik committed
60
61
 *
 * It will in turn call g_test_init() and gtk_init() to properly
62
 * initialize the testing framework and graphical toolkit. It’ll 
63
 * also set the program’s locale to “C” and prevent loading of rc 
Matthias Clasen's avatar
Matthias Clasen committed
64
 * files and Gtk+ modules. This is done to make tets program
Tim Janik's avatar
Tim Janik committed
65
 * environments as deterministic as possible.
Matthias Clasen's avatar
Matthias Clasen committed
66
 *
Tim Janik's avatar
Tim Janik committed
67
68
 * Like gtk_init() and g_test_init(), any known arguments will be
 * processed and stripped from @argc and @argv.
Christian Dywan's avatar
Christian Dywan committed
69
70
 *
 * Since: 2.14
Tim Janik's avatar
Tim Janik committed
71
72
73
74
75
76
 **/
void
gtk_test_init (int    *argcp,
               char ***argvp,
               ...)
{
77
  g_test_init (argcp, argvp, NULL);
Tim Janik's avatar
Tim Janik committed
78
79
80
81
82
83
84
85
86
  /* - enter C locale
   * - call g_test_init();
   * - call gtk_init();
   * - prevent RC files from loading;
   * - prevent Gtk modules from loading;
   * - supply mock object for GtkSettings
   * FUTURE TODO:
   * - this function could install a mock object around GtkSettings
   */
87
  g_setenv ("GTK_MODULES", "", TRUE);
Tim Janik's avatar
Tim Janik committed
88
89
  gtk_disable_setlocale();
  setlocale (LC_ALL, "C");
90
  g_test_bug_base ("http://bugzilla.gnome.org/show_bug.cgi?id=%s");
91
92
93
94
95
96
97
98

  /* XSendEvent() doesn't work yet on XI2 events.
   * So at the moment gdk_test_simulate_* can only
   * send events that GTK+ understands if XI2 is
   * disabled, bummer.
   */
  gdk_disable_multidevice ();

Tim Janik's avatar
Tim Janik committed
99
100
101
102
103
104
105
  gtk_init (argcp, argvp);
}

static GSList*
test_find_widget_input_windows (GtkWidget *widget,
                                gboolean   input_only)
{
106
  GdkWindow *window;
Tim Janik's avatar
Tim Janik committed
107
108
109
  GList *node, *children;
  GSList *matches = NULL;
  gpointer udata;
110
111
112
113
114
115

  window = gtk_widget_get_window (widget);

  gdk_window_get_user_data (window, &udata);
  if (udata == widget && (!input_only || (GDK_IS_WINDOW (window) && gdk_window_is_input_only (GDK_WINDOW (window)))))
    matches = g_slist_prepend (matches, window);
Tim Janik's avatar
Tim Janik committed
116
117
118
119
  children = gdk_window_get_children (gtk_widget_get_parent_window (widget));
  for (node = children; node; node = node->next)
    {
      gdk_window_get_user_data (node->data, &udata);
120
      if (udata == widget && (!input_only || (GDK_IS_WINDOW (node->data) && gdk_window_is_input_only (GDK_WINDOW (node->data)))))
Tim Janik's avatar
Tim Janik committed
121
122
123
124
125
        matches = g_slist_prepend (matches, node->data);
    }
  return g_slist_reverse (matches);
}

126
127
128
129
130
131
132
133
134
135
136
137
138
139
static gboolean
quit_main_loop_callback (GtkWidget     *widget,
                         GdkFrameClock *frame_clock,
                         gpointer       user_data)
{
  gtk_main_quit ();

  return G_SOURCE_REMOVE;
}

/**
 * gtk_test_widget_wait_for_draw:
 * @widget: the widget to wait for
 *
William Jon McCann's avatar
William Jon McCann committed
140
 * Enters the main loop and waits for @widget to be “drawn”. In this
141
142
143
144
145
146
 * context that means it waits for the frame clock of @widget to have
 * run a full styling, layout and drawing cycle.
 *
 * This function is intended to be used for syncing with actions that
 * depend on @widget relayouting or on interaction with the display
 * server.
147
148
 *
 * Since: 3.10
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
 **/
void
gtk_test_widget_wait_for_draw (GtkWidget *widget)
{
  g_return_if_fail (GTK_IS_WIDGET (widget));

  /* We can do this here because the whole tick procedure does not
   * reenter the main loop. Otherwise we'd need to manually get the
   * frame clock and connect to the after-paint signal.
   */
  gtk_widget_add_tick_callback (widget,
                                quit_main_loop_callback,
                                NULL,
                                NULL);

  gtk_main ();
}

Tim Janik's avatar
Tim Janik committed
167
/**
168
 * gtk_test_widget_send_key:
Tim Janik's avatar
Tim Janik committed
169
170
171
172
173
174
 * @widget: Widget to generate a key press and release on.
 * @keyval: A Gdk keyboard value.
 * @modifiers: Keyboard modifiers the event is setup with.
 *
 * This function will generate keyboard press and release events in
 * the middle of the first GdkWindow found that belongs to @widget.
175
176
 * For windowless widgets like #GtkButton (which returns %FALSE from
 * gtk_widget_get_has_window()), this will often be an
Tim Janik's avatar
Tim Janik committed
177
178
179
 * input-only event window. For other widgets, this is usually widget->window.
 * Certain caveats should be considered when using this function, in
 * particular because the mouse pointer is warped to the key press
180
 * location, see gdk_test_simulate_key() for details.
Tim Janik's avatar
Tim Janik committed
181
 *
182
 * Returns: whether all actions neccessary for the key event simulation were carried out successfully.
Christian Dywan's avatar
Christian Dywan committed
183
184
 *
 * Since: 2.14
Tim Janik's avatar
Tim Janik committed
185
186
187
188
189
190
191
192
193
194
195
196
 **/
gboolean
gtk_test_widget_send_key (GtkWidget      *widget,
                          guint           keyval,
                          GdkModifierType modifiers)
{
  gboolean k1res, k2res;
  GSList *iwindows = test_find_widget_input_windows (widget, FALSE);
  if (!iwindows)
    iwindows = test_find_widget_input_windows (widget, TRUE);
  if (!iwindows)
    return FALSE;
197
198
  k1res = gdk_test_simulate_key (iwindows->data, -1, -1, keyval, modifiers, GDK_KEY_PRESS);
  k2res = gdk_test_simulate_key (iwindows->data, -1, -1, keyval, modifiers, GDK_KEY_RELEASE);
Tim Janik's avatar
Tim Janik committed
199
200
201
202
203
  g_slist_free (iwindows);
  return k1res && k2res;
}

/**
204
 * gtk_test_widget_click:
Tim Janik's avatar
Tim Janik committed
205
206
207
208
209
210
211
 * @widget: Widget to generate a button click on.
 * @button: Number of the pointer button for the event, usually 1, 2 or 3.
 * @modifiers: Keyboard modifiers the event is setup with.
 *
 * This function will generate a @button click (button press and button
 * release event) in the middle of the first GdkWindow found that belongs
 * to @widget.
212
213
 * For windowless widgets like #GtkButton (which returns %FALSE from
 * gtk_widget_get_has_window()), this will often be an
Tim Janik's avatar
Tim Janik committed
214
215
216
 * input-only event window. For other widgets, this is usually widget->window.
 * Certain caveats should be considered when using this function, in
 * particular because the mouse pointer is warped to the button click
217
 * location, see gdk_test_simulate_button() for details.
Tim Janik's avatar
Tim Janik committed
218
 *
219
 * Returns: whether all actions neccessary for the button click simulation were carried out successfully.
Christian Dywan's avatar
Christian Dywan committed
220
221
 *
 * Since: 2.14
222
223
 *
 * Deprecated: 3.20: This testing infrastructure is phased out in favor of reftests.
Tim Janik's avatar
Tim Janik committed
224
225
226
227
228
229
230
231
232
233
234
235
 **/
gboolean
gtk_test_widget_click (GtkWidget      *widget,
                       guint           button,
                       GdkModifierType modifiers)
{
  gboolean b1res, b2res;
  GSList *iwindows = test_find_widget_input_windows (widget, FALSE);
  if (!iwindows)
    iwindows = test_find_widget_input_windows (widget, TRUE);
  if (!iwindows)
    return FALSE;
236
237
  b1res = gdk_test_simulate_button (iwindows->data, -1, -1, button, modifiers, GDK_BUTTON_PRESS);
  b2res = gdk_test_simulate_button (iwindows->data, -1, -1, button, modifiers, GDK_BUTTON_RELEASE);
Tim Janik's avatar
Tim Janik committed
238
239
240
241
242
  g_slist_free (iwindows);
  return b1res && b2res;
}

/**
243
 * gtk_test_spin_button_click:
Tim Janik's avatar
Tim Janik committed
244
245
246
247
248
249
 * @spinner: valid GtkSpinButton widget.
 * @button:  Number of the pointer button for the event, usually 1, 2 or 3.
 * @upwards: %TRUE for upwards arrow click, %FALSE for downwards arrow click.
 *
 * This function will generate a @button click in the upwards or downwards
 * spin button arrow areas, usually leading to an increase or decrease of
250
 * spin button’s value.
Tim Janik's avatar
Tim Janik committed
251
 *
252
 * Returns: whether all actions neccessary for the button click simulation were carried out successfully.
Christian Dywan's avatar
Christian Dywan committed
253
254
 *
 * Since: 2.14
255
256
 *
 * Deprecated: 3.20: This testing infrastructure is phased out in favor of reftests.
Tim Janik's avatar
Tim Janik committed
257
258
259
260
261
262
 **/
gboolean
gtk_test_spin_button_click (GtkSpinButton  *spinner,
                            guint           button,
                            gboolean        upwards)
{
263
  GdkWindow *down_panel = NULL, *up_panel = NULL, *panel;
Tim Janik's avatar
Tim Janik committed
264
  gboolean b1res = FALSE, b2res = FALSE;
265

266
267
268
  _gtk_spin_button_get_panels (spinner, &down_panel, &up_panel);

  panel = (upwards) ? up_panel : down_panel;
269
270

  if (panel)
Tim Janik's avatar
Tim Janik committed
271
    {
272
273
274
      gint width = gdk_window_get_width (panel);
      b1res = gdk_test_simulate_button (panel, width - 1, 1, button, 0, GDK_BUTTON_PRESS);
      b2res = gdk_test_simulate_button (panel, width - 1, 1, button, 0, GDK_BUTTON_RELEASE);
Tim Janik's avatar
Tim Janik committed
275
276
277
278
279
    }
  return b1res && b2res;
}

/**
280
 * gtk_test_find_label:
Tim Janik's avatar
Tim Janik committed
281
282
283
284
285
 * @widget:        Valid label or container widget.
 * @label_pattern: Shell-glob pattern to match a label string.
 *
 * This function will search @widget and all its descendants for a GtkLabel
 * widget with a text string matching @label_pattern.
286
 * The @label_pattern may contain asterisks “*” and question marks “?” as
Tim Janik's avatar
Tim Janik committed
287
 * placeholders, g_pattern_match() is used for the matching.
William Jon McCann's avatar
William Jon McCann committed
288
 * Note that locales other than "C“ tend to alter (translate” label strings,
Tim Janik's avatar
Tim Janik committed
289
290
291
 * so this function is genrally only useful in test programs with
 * predetermined locales, see gtk_test_init() for more details.
 *
292
 * Returns: (transfer none): a GtkLabel widget if any is found.
Christian Dywan's avatar
Christian Dywan committed
293
294
 *
 * Since: 2.14
Tim Janik's avatar
Tim Janik committed
295
296
297
298
299
 **/
GtkWidget*
gtk_test_find_label (GtkWidget    *widget,
                     const gchar  *label_pattern)
{
300
301
  GtkWidget *label = NULL;

Tim Janik's avatar
Tim Janik committed
302
303
304
305
306
307
  if (GTK_IS_LABEL (widget))
    {
      const gchar *text = gtk_label_get_text (GTK_LABEL (widget));
      if (g_pattern_match_simple (label_pattern, text))
        return widget;
    }
308

Tim Janik's avatar
Tim Janik committed
309
310
  if (GTK_IS_CONTAINER (widget))
    {
311
312
313
      GList *node, *list;

      list = gtk_container_get_children (GTK_CONTAINER (widget));
Tim Janik's avatar
Tim Janik committed
314
315
      for (node = list; node; node = node->next)
        {
316
          label = gtk_test_find_label (node->data, label_pattern);
Tim Janik's avatar
Tim Janik committed
317
          if (label)
318
            break;
Tim Janik's avatar
Tim Janik committed
319
320
321
        }
      g_list_free (list);
    }
322
  return label;
Tim Janik's avatar
Tim Janik committed
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
}

static GList*
test_list_descendants (GtkWidget *widget,
                       GType      widget_type)
{
  GList *results = NULL;
  if (GTK_IS_CONTAINER (widget))
    {
      GList *node, *list = gtk_container_get_children (GTK_CONTAINER (widget));
      for (node = list; node; node = node->next)
        {
          if (!widget_type || g_type_is_a (G_OBJECT_TYPE (node->data), widget_type))
            results = g_list_prepend (results, node->data);
          else
            results = g_list_concat (results, test_list_descendants (node->data, widget_type));
        }
      g_list_free (list);
    }
  return results;
}

static int
widget_geo_dist (GtkWidget *a,
                 GtkWidget *b,
                 GtkWidget *base)
{
350
  GtkAllocation allocation;
Tim Janik's avatar
Tim Janik committed
351
  int ax0, ay0, ax1, ay1, bx0, by0, bx1, by1, xdist = 0, ydist = 0;
352
353

  gtk_widget_get_allocation (a, &allocation);
Tim Janik's avatar
Tim Janik committed
354
  if (!gtk_widget_translate_coordinates (a, base, 0, 0, &ax0, &ay0) ||
355
      !gtk_widget_translate_coordinates (a, base, allocation.width, allocation.height, &ax1, &ay1))
Tim Janik's avatar
Tim Janik committed
356
    return -G_MAXINT;
357
358

  gtk_widget_get_allocation (b, &allocation);
Tim Janik's avatar
Tim Janik committed
359
  if (!gtk_widget_translate_coordinates (b, base, 0, 0, &bx0, &by0) ||
360
      !gtk_widget_translate_coordinates (b, base, allocation.width, allocation.height, &bx1, &by1))
Tim Janik's avatar
Tim Janik committed
361
    return +G_MAXINT;
362

Tim Janik's avatar
Tim Janik committed
363
364
365
366
367
368
369
370
  if (bx0 >= ax1)
    xdist = bx0 - ax1;
  else if (ax0 >= bx1)
    xdist = ax0 - bx1;
  if (by0 >= ay1)
    ydist = by0 - ay1;
  else if (ay0 >= by1)
    ydist = ay0 - by1;
371

Tim Janik's avatar
Tim Janik committed
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
  return xdist + ydist;
}

static int
widget_geo_cmp (gconstpointer a,
                gconstpointer b,
                gpointer      user_data)
{
  gpointer *data = user_data;
  GtkWidget *wa = (void*) a, *wb = (void*) b, *toplevel = data[0], *base_widget = data[1];
  int adist = widget_geo_dist (wa, base_widget, toplevel);
  int bdist = widget_geo_dist (wb, base_widget, toplevel);
  return adist > bdist ? +1 : adist == bdist ? 0 : -1;
}

/**
388
 * gtk_test_find_sibling:
Tim Janik's avatar
Tim Janik committed
389
390
391
392
393
394
395
 * @base_widget:        Valid widget, part of a widget hierarchy
 * @widget_type:        Type of a aearched for sibling widget
 *
 * This function will search siblings of @base_widget and siblings of its
 * ancestors for all widgets matching @widget_type.
 * Of the matching widgets, the one that is geometrically closest to
 * @base_widget will be returned.
William Jon McCann's avatar
William Jon McCann committed
396
 * The general purpose of this function is to find the most likely “action”
Tim Janik's avatar
Tim Janik committed
397
 * widget, relative to another labeling widget. Such as finding a
398
 * button or text entry widget, given its corresponding label widget.
Tim Janik's avatar
Tim Janik committed
399
 *
400
 * Returns: (transfer none): a widget of type @widget_type if any is found.
Christian Dywan's avatar
Christian Dywan committed
401
402
 *
 * Since: 2.14
Tim Janik's avatar
Tim Janik committed
403
404
405
406
407
408
409
410
411
412
413
 **/
GtkWidget*
gtk_test_find_sibling (GtkWidget *base_widget,
                       GType      widget_type)
{
  GList *siblings = NULL;
  GtkWidget *tmpwidget = base_widget;
  gpointer data[2];
  /* find all sibling candidates */
  while (tmpwidget)
    {
414
      tmpwidget = gtk_widget_get_parent (tmpwidget);
Tim Janik's avatar
Tim Janik committed
415
416
417
418
419
420
421
422
423
424
425
426
427
428
      siblings = g_list_concat (siblings, test_list_descendants (tmpwidget, widget_type));
    }
  /* sort them by distance to base_widget */
  data[0] = gtk_widget_get_toplevel (base_widget);
  data[1] = base_widget;
  siblings = g_list_sort_with_data (siblings, widget_geo_cmp, data);
  /* pick nearest != base_widget */
  siblings = g_list_remove (siblings, base_widget);
  tmpwidget = siblings ? siblings->data : NULL;
  g_list_free (siblings);
  return tmpwidget;
}

/**
429
 * gtk_test_find_widget:
Tim Janik's avatar
Tim Janik committed
430
431
432
433
434
435
436
 * @widget:        Container widget, usually a GtkWindow.
 * @label_pattern: Shell-glob pattern to match a label string.
 * @widget_type:   Type of a aearched for label sibling widget.
 *
 * This function will search the descendants of @widget for a widget
 * of type @widget_type that has a label matching @label_pattern next
 * to it. This is most useful for automated GUI testing, e.g. to find
William Jon McCann's avatar
William Jon McCann committed
437
 * the “OK” button in a dialog and synthesize clicks on it.
Tim Janik's avatar
Tim Janik committed
438
439
440
441
 * However see gtk_test_find_label(), gtk_test_find_sibling() and
 * gtk_test_widget_click() for possible caveats involving the search of
 * such widgets and synthesizing widget events.
 *
442
 * Returns: (nullable) (transfer none): a valid widget if any is found or %NULL.
Christian Dywan's avatar
Christian Dywan committed
443
444
 *
 * Since: 2.14
Tim Janik's avatar
Tim Janik committed
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
 **/
GtkWidget*
gtk_test_find_widget (GtkWidget    *widget,
                      const gchar  *label_pattern,
                      GType         widget_type)
{
  GtkWidget *label = gtk_test_find_label (widget, label_pattern);
  if (!label)
    label = gtk_test_find_label (gtk_widget_get_toplevel (widget), label_pattern);
  if (label)
    return gtk_test_find_sibling (label, widget_type);
  return NULL;
}

/**
460
 * gtk_test_slider_set_perc:
Tim Janik's avatar
Tim Janik committed
461
462
463
 * @widget:     valid widget pointer.
 * @percentage: value between 0 and 100.
 *
464
 * This function will adjust the slider position of all GtkRange
465
 * based widgets, such as scrollbars or scales, it’ll also adjust
466
 * spin buttons. The adjustment value of these widgets is set to
Tim Janik's avatar
Tim Janik committed
467
468
 * a value between the lower and upper limits, according to the
 * @percentage argument.
Christian Dywan's avatar
Christian Dywan committed
469
470
 *
 * Since: 2.14
471
472
 *
 * Deprecated: 3.20: This testing infrastructure is phased out in favor of reftests.
Tim Janik's avatar
Tim Janik committed
473
474
475
476
477
478
479
480
481
482
483
 **/
void
gtk_test_slider_set_perc (GtkWidget      *widget,
                          double          percentage)
{
  GtkAdjustment *adjustment = NULL;
  if (GTK_IS_RANGE (widget))
    adjustment = gtk_range_get_adjustment (GTK_RANGE (widget));
  else if (GTK_IS_SPIN_BUTTON (widget))
    adjustment = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (widget));
  if (adjustment)
484
485
486
487
488
489
    gtk_adjustment_set_value (adjustment, 
                              gtk_adjustment_get_lower (adjustment) 
                              + (gtk_adjustment_get_upper (adjustment) 
                                 - gtk_adjustment_get_lower (adjustment) 
                                 - gtk_adjustment_get_page_size (adjustment))
                                * percentage * 0.01);
Tim Janik's avatar
Tim Janik committed
490
491
492
}

/**
493
 * gtk_test_slider_get_value:
Tim Janik's avatar
Tim Janik committed
494
495
496
497
498
499
500
501
 * @widget:     valid widget pointer.
 *
 * Retrive the literal adjustment value for GtkRange based
 * widgets and spin buttons. Note that the value returned by
 * this function is anything between the lower and upper bounds
 * of the adjustment belonging to @widget, and is not a percentage
 * as passed in to gtk_test_slider_set_perc().
 *
502
 * Returns: gtk_adjustment_get_value (adjustment) for an adjustment belonging to @widget.
Christian Dywan's avatar
Christian Dywan committed
503
504
 *
 * Since: 2.14
505
506
 *
 * Deprecated: 3.20: This testing infrastructure is phased out in favor of reftests.
Tim Janik's avatar
Tim Janik committed
507
508
509
510
511
512
513
514
515
 **/
double
gtk_test_slider_get_value (GtkWidget *widget)
{
  GtkAdjustment *adjustment = NULL;
  if (GTK_IS_RANGE (widget))
    adjustment = gtk_range_get_adjustment (GTK_RANGE (widget));
  else if (GTK_IS_SPIN_BUTTON (widget))
    adjustment = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (widget));
516
  return adjustment ? gtk_adjustment_get_value (adjustment) : 0;
Tim Janik's avatar
Tim Janik committed
517
518
519
}

/**
520
 * gtk_test_text_set:
Tim Janik's avatar
Tim Janik committed
521
522
523
524
525
 * @widget:     valid widget pointer.
 * @string:     a 0-terminated C string
 *
 * Set the text string of @widget to @string if it is a GtkLabel,
 * GtkEditable (entry and text widgets) or GtkTextView.
Christian Dywan's avatar
Christian Dywan committed
526
527
 *
 * Since: 2.14
528
529
 *
 * Deprecated: 3.20: This testing infrastructure is phased out in favor of reftests.
Tim Janik's avatar
Tim Janik committed
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
 **/
void
gtk_test_text_set (GtkWidget   *widget,
                   const gchar *string)
{
  if (GTK_IS_LABEL (widget))
    gtk_label_set_text (GTK_LABEL (widget), string);
  else if (GTK_IS_EDITABLE (widget))
    {
      int pos = 0;
      gtk_editable_delete_text (GTK_EDITABLE (widget), 0, -1);
      gtk_editable_insert_text (GTK_EDITABLE (widget), string, -1, &pos);
    }
  else if (GTK_IS_TEXT_VIEW (widget))
    {
      GtkTextBuffer *tbuffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
      gtk_text_buffer_set_text (tbuffer, string, -1);
    }
}

/**
551
 * gtk_test_text_get:
Tim Janik's avatar
Tim Janik committed
552
553
554
555
556
 * @widget:     valid widget pointer.
 *
 * Retrive the text string of @widget if it is a GtkLabel,
 * GtkEditable (entry and text widgets) or GtkTextView.
 *
557
 * Returns: new 0-terminated C string, needs to be released with g_free().
Christian Dywan's avatar
Christian Dywan committed
558
559
 *
 * Since: 2.14
560
561
 *
 * Deprecated: 3.20: This testing infrastructure is phased out in favor of reftests.
Tim Janik's avatar
Tim Janik committed
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
 **/
gchar*
gtk_test_text_get (GtkWidget *widget)
{
  if (GTK_IS_LABEL (widget))
    return g_strdup (gtk_label_get_text (GTK_LABEL (widget)));
  else if (GTK_IS_EDITABLE (widget))
    {
      return g_strdup (gtk_editable_get_chars (GTK_EDITABLE (widget), 0, -1));
    }
  else if (GTK_IS_TEXT_VIEW (widget))
    {
      GtkTextBuffer *tbuffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
      GtkTextIter start, end;
      gtk_text_buffer_get_start_iter (tbuffer, &start);
      gtk_text_buffer_get_end_iter (tbuffer, &end);
      return gtk_text_buffer_get_text (tbuffer, &start, &end, FALSE);
    }
  return NULL;
}

/**
584
 * gtk_test_create_widget:
Matthias Clasen's avatar
Matthias Clasen committed
585
 * @widget_type: a valid widget type.
Johan Dahlin's avatar
Johan Dahlin committed
586
 * @first_property_name: (allow-none): Name of first property to set or %NULL
Matthias Clasen's avatar
Matthias Clasen committed
587
 * @...: value to set the first property to, followed by more
Matthias Clasen's avatar
Matthias Clasen committed
588
 *    name-value pairs, terminated by %NULL
Tim Janik's avatar
Tim Janik committed
589
590
 *
 * This function wraps g_object_new() for widget types.
591
 * It’ll automatically show all created non window widgets, also
Tim Janik's avatar
Tim Janik committed
592
 * g_object_ref_sink() them (to keep them alive across a running test)
593
 * and set them up for destruction during the next test teardown phase.
Tim Janik's avatar
Tim Janik committed
594
 *
595
 * Returns: (transfer none): a newly created widget.
Christian Dywan's avatar
Christian Dywan committed
596
597
 *
 * Since: 2.14
598
599
 *
 * Deprecated: 3.20: This testing infrastructure is phased out in favor of reftests.
Tim Janik's avatar
Tim Janik committed
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
 */
GtkWidget*
gtk_test_create_widget (GType        widget_type,
                        const gchar *first_property_name,
                        ...)
{
  GtkWidget *widget;
  va_list var_args;
  g_return_val_if_fail (g_type_is_a (widget_type, GTK_TYPE_WIDGET), NULL);
  va_start (var_args, first_property_name);
  widget = (GtkWidget*) g_object_new_valist (widget_type, first_property_name, var_args);
  va_end (var_args);
  if (widget)
    {
      if (!GTK_IS_WINDOW (widget))
        gtk_widget_show (widget);
      g_object_ref_sink (widget);
      g_test_queue_unref (widget);
      g_test_queue_destroy ((GDestroyNotify) gtk_widget_destroy, widget);
    }
  return widget;
}

static void
try_main_quit (void)
{
  if (gtk_main_level())
    gtk_main_quit();
}

static int
test_increment_intp (int *intp)
{
633
634
  if (intp != NULL)
    *intp += 1;
Tim Janik's avatar
Tim Janik committed
635
636
637
638
  return 1; /* TRUE in case we're connected to event signals */
}

/**
639
 * gtk_test_display_button_window:
Tim Janik's avatar
Tim Janik committed
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
 * @window_title:       Title of the window to be displayed.
 * @dialog_text:        Text inside the window to be displayed.
 * @...:                %NULL terminated list of (const char *label, int *nump) pairs.
 *
 * Create a window with window title @window_title, text contents @dialog_text,
 * and a number of buttons, according to the paired argument list given
 * as @... parameters.
 * Each button is created with a @label and a ::clicked signal handler that
 * incremrents the integer stored in @nump.
 * The window will be automatically shown with gtk_widget_show_now() after
 * creation, so when this function returns it has already been mapped,
 * resized and positioned on screen.
 * The window will quit any running gtk_main()-loop when destroyed, and it
 * will automatically be destroyed upon test function teardown.
 *
655
 * Returns: (transfer full): a widget pointer to the newly created GtkWindow.
Christian Dywan's avatar
Christian Dywan committed
656
657
 *
 * Since: 2.14
658
659
 *
 * Deprecated: 3.20: This testing infrastructure is phased out in favor of reftests.
Tim Janik's avatar
Tim Janik committed
660
661
662
663
664
665
666
 **/
GtkWidget*
gtk_test_display_button_window (const gchar *window_title,
                                const gchar *dialog_text,
                                ...) /* NULL terminated list of (label, &int) pairs */
{
  va_list var_args;
667
  GtkWidget *window, *vbox;
Tim Janik's avatar
Tim Janik committed
668
  const char *arg1;
669
670
671
G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
  window = gtk_test_create_widget (GTK_TYPE_WINDOW, "title", window_title, NULL);
  vbox = gtk_test_create_widget (GTK_TYPE_BOX, "parent", window, "orientation", GTK_ORIENTATION_VERTICAL, NULL);
Tim Janik's avatar
Tim Janik committed
672
  gtk_test_create_widget (GTK_TYPE_LABEL, "label", dialog_text, "parent", vbox, NULL);
673
G_GNUC_END_IGNORE_DEPRECATIONS;
Tim Janik's avatar
Tim Janik committed
674
675
676
677
678
679
  g_signal_connect (window, "destroy", G_CALLBACK (try_main_quit), NULL);
  va_start (var_args, dialog_text);
  arg1 = va_arg (var_args, const char*);
  while (arg1)
    {
      int *arg2 = va_arg (var_args, int*);
680
      GtkWidget *button;
681
G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
682
      button = gtk_test_create_widget (GTK_TYPE_BUTTON, "label", arg1, "parent", vbox, NULL);
683
G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
Tim Janik's avatar
Tim Janik committed
684
685
686
687
688
689
690
691
692
693
694
695
      g_signal_connect_swapped (button, "clicked", G_CALLBACK (test_increment_intp), arg2);
      arg1 = va_arg (var_args, const char*);
    }
  va_end (var_args);
  gtk_widget_show_all (vbox);
  gtk_widget_show_now (window);
  while (gtk_events_pending ())
    gtk_main_iteration ();
  return window;
}

/**
696
 * gtk_test_create_simple_window:
Tim Janik's avatar
Tim Janik committed
697
698
699
700
701
702
703
704
 * @window_title:       Title of the window to be displayed.
 * @dialog_text:        Text inside the window to be displayed.
 *
 * Create a simple window with window title @window_title and
 * text contents @dialog_text.
 * The window will quit any running gtk_main()-loop when destroyed, and it
 * will automatically be destroyed upon test function teardown.
 *
705
 * Returns: (transfer none): a widget pointer to the newly created GtkWindow.
Christian Dywan's avatar
Christian Dywan committed
706
707
 *
 * Since: 2.14
708
709
 *
 * Deprecated: 3.20: This testing infrastructure is phased out in favor of reftests.
Tim Janik's avatar
Tim Janik committed
710
711
712
713
714
 **/
GtkWidget*
gtk_test_create_simple_window (const gchar *window_title,
                               const gchar *dialog_text)
{
715
  GtkWidget *window, *vbox;
716
G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
717
718
  window = gtk_test_create_widget (GTK_TYPE_WINDOW, "title", window_title, NULL);
  vbox = gtk_test_create_widget (GTK_TYPE_BOX, "parent", window, "orientation", GTK_ORIENTATION_VERTICAL, NULL);
Tim Janik's avatar
Tim Janik committed
719
  gtk_test_create_widget (GTK_TYPE_LABEL, "label", dialog_text, "parent", vbox, NULL);
720
G_GNUC_END_IGNORE_DEPRECATIONS;
Tim Janik's avatar
Tim Janik committed
721
722
723
724
725
  g_signal_connect (window, "destroy", G_CALLBACK (try_main_quit), NULL);
  gtk_widget_show_all (vbox);
  return window;
}

726
727
728
729
static GType *all_registered_types = NULL;
static guint  n_all_registered_types = 0;

/**
730
 * gtk_test_list_all_types:
731
732
733
734
 * @n_types: location to store number of types
 *
 * Return the type ids that have been registered after
 * calling gtk_test_register_all_types().
Christian Dywan's avatar
Christian Dywan committed
735
 *
Matthias Clasen's avatar
Matthias Clasen committed
736
737
738
 * Returns: (array length=n_types zero-terminated=1) (transfer none):
 *    0-terminated array of type ids
 *
Christian Dywan's avatar
Christian Dywan committed
739
 * Since: 2.14
Matthias Clasen's avatar
Matthias Clasen committed
740
 */
741
742
743
744
745
746
747
748
const GType*
gtk_test_list_all_types (guint *n_types)
{
  if (n_types)
    *n_types = n_all_registered_types;
  return all_registered_types;
}

Tim Janik's avatar
Tim Janik committed
749
/**
750
 * gtk_test_register_all_types:
Tim Janik's avatar
Tim Janik committed
751
752
753
754
 *
 * Force registration of all core Gtk+ and Gdk object types.
 * This allowes to refer to any of those object types via
 * g_type_from_name() after calling this function.
Christian Dywan's avatar
Christian Dywan committed
755
756
 *
 * Since: 2.14
Tim Janik's avatar
Tim Janik committed
757
758
759
760
 **/
void
gtk_test_register_all_types (void)
{
761
762
763
764
765
766
  if (!all_registered_types)
    {
      const guint max_gtk_types = 999;
      GType *tp;
      all_registered_types = g_new0 (GType, max_gtk_types);
      tp = all_registered_types;
767
#include "gtktypefuncs.inc"
768
769
770
771
      n_all_registered_types = tp - all_registered_types;
      g_assert (n_all_registered_types + 1 < max_gtk_types);
      *tp = 0;
    }
Tim Janik's avatar
Tim Janik committed
772
}