forked from AuroraMiddleware/gtk
Added Gtk+ testing utilities.
* gtk/gtktestutils.h, gtk/gtktestutils.c: added unit test utility functions. for the most part, the functions herein involve navigating and interacting with dialog elements programatically, to automate user interaction tests of dialogs and widgets. * gtk/gtk.h: include gtk/gtktestutils.h as public API. * gtk/gtk.symbols: added gtk_test_* symbols. * gtk/Makefile.am: include gtktestutils.h and gtktestutils.c into the build. generate gtktypefuncs.c which contains a list of all _get_type functions in Gtk+ and Gdk. svn path=/trunk/; revision=19010
This commit is contained in:
parent
9e0b0194ed
commit
936d27a475
@ -279,6 +279,7 @@ gtk_public_h_sources = \
|
||||
gtkstyle.h \
|
||||
gtktable.h \
|
||||
gtktearoffmenuitem.h \
|
||||
gtktestutils.h \
|
||||
gtktext.h \
|
||||
gtktextbuffer.h \
|
||||
gtktextbufferrichtext.h \
|
||||
@ -559,6 +560,7 @@ gtk_base_c_sources = \
|
||||
gtkstyle.c \
|
||||
gtktable.c \
|
||||
gtktearoffmenuitem.c \
|
||||
gtktestutils.c \
|
||||
gtktext.c \
|
||||
gtktextbtree.c \
|
||||
gtktextbuffer.c \
|
||||
@ -714,6 +716,7 @@ gtk_built_private_headers = \
|
||||
gtk_built_sources = \
|
||||
gtkaliasdef.c \
|
||||
gtktypebuiltins.c \
|
||||
gtktypefuncs.c \
|
||||
gtkmarshalers.c \
|
||||
gtkmarshal.c \
|
||||
${gtk_built_private_headers} \
|
||||
@ -751,7 +754,7 @@ EXTRA_DIST += $(STOCK_ICONS)
|
||||
# rules to generate built sources
|
||||
#
|
||||
# setup autogeneration dependencies
|
||||
gen_sources = xgen-gdef xgen-gtbh xgen-gtic xgen-gmh xgen-gmc xgen-gmlh xgen-gmlc
|
||||
gen_sources = xgen-gdef xgen-gtbh xgen-gtic xgen-gmh xgen-gmc xgen-gmlh xgen-gmlc xgen-gtfsrc.c xgen-gtf
|
||||
CLEANFILES = $(gen_sources)
|
||||
|
||||
BUILT_SOURCES = $(gtk_built_sources)
|
||||
@ -817,6 +820,14 @@ gtktypebuiltins.c: @REBUILD@ $(gtk_public_h_sources) Makefile
|
||||
&& cp xgen-gtbc gtktypebuiltins.c \
|
||||
&& rm -f xgen-gtbc
|
||||
|
||||
gtktypefuncs.c: @REBUILD@ $(top_srcdir)/gtk/*.h $(top_srcdir)/gdk/*.h Makefile
|
||||
echo '#include <gtk/gtk.h>' > xgen-gtfsrc.c && \
|
||||
${CPP} $(DEFS) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) xgen-gtfsrc.c | \
|
||||
egrep -o '\<g[td]k_[a-zA-Z0-9_]+_get_type\>' | sort | uniq | \
|
||||
sed '{ s/^/vgt=/; s/$$/();/; }' > xgen-gtf \
|
||||
&& cp xgen-gtf $@ && rm -f xgen-gtf
|
||||
gtktestutils.c: gtktypefuncs.c
|
||||
|
||||
# target platform:
|
||||
lib_LTLIBRARIES = $(gtktargetlib)
|
||||
|
||||
|
@ -183,6 +183,7 @@
|
||||
#include <gtk/gtktoolitem.h>
|
||||
#include <gtk/gtktooltip.h>
|
||||
#include <gtk/gtktooltips.h>
|
||||
#include <gtk/gtktestutils.h>
|
||||
#include <gtk/gtktree.h>
|
||||
#include <gtk/gtktreednd.h>
|
||||
#include <gtk/gtktreeitem.h>
|
||||
|
@ -3253,6 +3253,29 @@ gtk_recent_info_match
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if IN_HEADER(__GTK_TEST_UTILS_H__)
|
||||
#if IN_FILE(__GTK_TEST_UTILS_C__)
|
||||
gtk_test_create_simple_window
|
||||
gtk_test_create_widget
|
||||
gtk_test_display_button_window
|
||||
gtk_test_find_label
|
||||
gtk_test_find_sibling
|
||||
gtk_test_find_widget
|
||||
gtk_test_init
|
||||
gtk_test_register_all_types
|
||||
gtk_test_simulate_button
|
||||
gtk_test_simulate_key
|
||||
gtk_test_slider_get_value
|
||||
gtk_test_slider_set_perc
|
||||
gtk_test_spin_button_click
|
||||
gtk_test_text_get
|
||||
gtk_test_text_set
|
||||
gtk_test_widget_click
|
||||
gtk_test_widget_send_key
|
||||
gtk_test_xserver_render_sync
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if IN_HEADER(__GTK_TEXT_BUFFER_RICH_TEXT_H__)
|
||||
#if IN_FILE(__GTK_TEXT_BUFFER_RICH_TEXT_C__)
|
||||
gtk_text_buffer_deserialize
|
||||
|
769
gtk/gtktestutils.c
Normal file
769
gtk/gtktestutils.c
Normal file
@ -0,0 +1,769 @@
|
||||
/* Gtk+ testing utilities
|
||||
* Copyright (C) 2007 Tim Janik
|
||||
*
|
||||
* 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
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
#include "gtktestutils.h"
|
||||
#include <gtk/gtk.h>
|
||||
#include <gdk/gdkkeysyms.h>
|
||||
#include <x11/gdkx.h>
|
||||
#include "gtkalias.h"
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <locale.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
|
||||
/* --- test utilities --- */
|
||||
/**
|
||||
* gtk_test_init:
|
||||
* @argc: Address of the <parameter>argc</parameter> parameter of the
|
||||
* main() function. Changed if any arguments were handled.
|
||||
* @argv: Address of the <parameter>argv</parameter> parameter of main().
|
||||
* Any parameters understood by g_test_init() or gtk_init() are
|
||||
* stripped before return.
|
||||
*
|
||||
* This function is used to initialize a Gtk+ test program.
|
||||
* It will in turn call g_test_init() and gtk_init() to properly
|
||||
* initialize the testing framework and graphical toolkit.
|
||||
* It'll also set the program's locale to "C" and prevent loading of
|
||||
* rc files and Gtk+ modules. This is done to make tets program
|
||||
* environments as deterministic as possible.
|
||||
* Like gtk_init() and g_test_init(), any known arguments will be
|
||||
* processed and stripped from @argc and @argv.
|
||||
**/
|
||||
void
|
||||
gtk_test_init (int *argcp,
|
||||
char ***argvp,
|
||||
...)
|
||||
{
|
||||
/* - 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
|
||||
*/
|
||||
// FIXME: g_test_init (argcp, argvp, NULL);
|
||||
g_setenv ("GTK_MODULES", "", TRUE);
|
||||
g_setenv ("GTK2_RC_FILES", "/dev/null", TRUE);
|
||||
gtk_disable_setlocale();
|
||||
setlocale (LC_ALL, "C");
|
||||
gtk_init (argcp, argvp);
|
||||
}
|
||||
|
||||
static GSList*
|
||||
test_find_widget_input_windows (GtkWidget *widget,
|
||||
gboolean input_only)
|
||||
{
|
||||
GList *node, *children;
|
||||
GSList *matches = NULL;
|
||||
gpointer udata;
|
||||
gdk_window_get_user_data (widget->window, &udata);
|
||||
if (udata == widget && (!input_only || (GDK_IS_WINDOW (widget->window) && GDK_WINDOW_OBJECT (widget->window)->input_only)))
|
||||
matches = g_slist_prepend (matches, widget->window);
|
||||
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);
|
||||
if (udata == widget && (!input_only || (GDK_IS_WINDOW (node->data) && GDK_WINDOW_OBJECT (node->data)->input_only)))
|
||||
matches = g_slist_prepend (matches, node->data);
|
||||
}
|
||||
return g_slist_reverse (matches);
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_test_simulate_key
|
||||
* @window: Gdk window to simulate a key event for.
|
||||
* @x: x coordinate within @window for the key event.
|
||||
* @y: y coordinate within @window for the key event.
|
||||
* @keyval: A Gdk keyboard value.
|
||||
* @modifiers: Keyboard modifiers the event is setup with.
|
||||
* @press_or_release: %TRUE to generate key press events, %FALSE to generate key release events.
|
||||
*
|
||||
* This function is intended to be used in Gtk+ test programs.
|
||||
* If (@x,@y) are > (-1,-1), it will warp the mouse pointer to
|
||||
* the given (@x,@y) corrdinates within @window and simulate a
|
||||
* key press or release event.
|
||||
* When the mouse pointer is warped to the target location, use
|
||||
* of this function outside of test programs that run in their
|
||||
* own virtual windowing system (e.g. Xvfb) is not recommended.
|
||||
* If (@x,@y) are passed as (-1,-1), the mouse pointer will not
|
||||
* be warped and @window origin will be used as mouse pointer
|
||||
* location for the event.
|
||||
* Also, gtk_test_simulate_key() is a fairly low level function,
|
||||
* for most testing purposes, gtk_test_widget_send_key() is the
|
||||
* right function to call which will generate a key press event
|
||||
* followed by its accompanying key release event.
|
||||
*
|
||||
* Returns: wether all actions neccessary for a key event simulation were carried out successfully.
|
||||
**/
|
||||
gboolean
|
||||
gtk_test_simulate_key (GdkWindow *window,
|
||||
gint x,
|
||||
gint y,
|
||||
guint keyval,
|
||||
GdkModifierType modifiers,
|
||||
gboolean press_or_release)
|
||||
{
|
||||
GdkScreen *screen = gdk_colormap_get_screen (gdk_drawable_get_colormap (window));
|
||||
GdkKeymapKey *keys = NULL;
|
||||
gboolean success;
|
||||
gint n_keys = 0;
|
||||
XKeyEvent xev = {
|
||||
0, /* type */
|
||||
0, /* serial */
|
||||
1, /* send_event */
|
||||
};
|
||||
if (x < 0 && y < 0)
|
||||
{
|
||||
gdk_drawable_get_size (window, &x, &y);
|
||||
x /= 2;
|
||||
y /= 2;
|
||||
}
|
||||
xev.type = press_or_release ? KeyPress : KeyRelease;
|
||||
xev.display = GDK_DRAWABLE_XDISPLAY (window);
|
||||
xev.window = GDK_WINDOW_XID (window);
|
||||
xev.root = RootWindow (xev.display, GDK_SCREEN_XNUMBER (screen));
|
||||
xev.subwindow = 0;
|
||||
xev.time = 0;
|
||||
xev.x = MAX (x, 0);
|
||||
xev.y = MAX (y, 0);
|
||||
xev.x_root = 0;
|
||||
xev.y_root = 0;
|
||||
xev.state = modifiers;
|
||||
xev.keycode = 0;
|
||||
success = gdk_keymap_get_entries_for_keyval (gdk_keymap_get_for_display (gdk_drawable_get_display (window)), keyval, &keys, &n_keys);
|
||||
success &= n_keys > 0;
|
||||
if (success)
|
||||
{
|
||||
gint i;
|
||||
for (i = 0; i < n_keys; i++)
|
||||
if (keys[i].group == 0 && keys[i].level == 0)
|
||||
{
|
||||
xev.keycode = keys[i].keycode;
|
||||
break;
|
||||
}
|
||||
if (i >= n_keys) /* no match for group==0 and level==0 */
|
||||
xev.keycode = keys[0].keycode;
|
||||
}
|
||||
g_free (keys);
|
||||
if (!success)
|
||||
return FALSE;
|
||||
gdk_error_trap_push ();
|
||||
xev.same_screen = XTranslateCoordinates (xev.display, xev.window, xev.root,
|
||||
xev.x, xev.y, &xev.x_root, &xev.y_root,
|
||||
&xev.subwindow);
|
||||
if (!xev.subwindow)
|
||||
xev.subwindow = xev.window;
|
||||
success &= xev.same_screen;
|
||||
if (x >= 0 && y >= 0)
|
||||
success &= 0 != XWarpPointer (xev.display, None, xev.window, 0, 0, 0, 0, xev.x, xev.y);
|
||||
success &= 0 != XSendEvent (xev.display, xev.window, True, press_or_release ? KeyPressMask : KeyReleaseMask, (XEvent*) &xev);
|
||||
XSync (xev.display, False);
|
||||
success &= 0 == gdk_error_trap_pop();
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_test_widget_send_key
|
||||
* @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.
|
||||
* For %GTK_NO_WINDOW widgets like GtkButton, this will often be an
|
||||
* 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
|
||||
* location, see gtk_test_simulate_key() for details.
|
||||
*
|
||||
* Returns: wether all actions neccessary for the key event simulation were carried out successfully.
|
||||
**/
|
||||
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;
|
||||
k1res = gtk_test_simulate_key (iwindows->data, -1, -1, keyval, modifiers, TRUE);
|
||||
k2res = gtk_test_simulate_key (iwindows->data, -1, -1, keyval, modifiers, FALSE);
|
||||
g_slist_free (iwindows);
|
||||
return k1res && k2res;
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_test_simulate_button
|
||||
* @window: Gdk window to simulate a button event for.
|
||||
* @x: x coordinate within @window for the button event.
|
||||
* @y: y coordinate within @window for the button event.
|
||||
* @button: Number of the pointer button for the event, usually 1, 2 or 3.
|
||||
* @modifiers: Keyboard modifiers the event is setup with.
|
||||
* @press_or_release: %TRUE to generate button press events, %FALSE to generate button release events.
|
||||
*
|
||||
* This function is intended to be used in Gtk+ test programs.
|
||||
* It will warp the mouse pointer to the given (@x,@y) corrdinates
|
||||
* within @window and simulate a button press or release event.
|
||||
* Because the mouse pointer needs to be warped to the target
|
||||
* location, use of this function outside of test programs that
|
||||
* run in their own virtual windowing system (e.g. Xvfb) is not
|
||||
* recommended.
|
||||
* Also, gtk_test_simulate_button() is a fairly low level function,
|
||||
* for most testing purposes, gtk_test_widget_click() is the right
|
||||
* function to call which will generate a button press event followed
|
||||
* by its accompanying button release event.
|
||||
*
|
||||
* Returns: wether all actions neccessary for a button event simulation were carried out successfully.
|
||||
**/
|
||||
gboolean
|
||||
gtk_test_simulate_button (GdkWindow *window,
|
||||
gint x,
|
||||
gint y,
|
||||
guint button, /*1..3*/
|
||||
GdkModifierType modifiers,
|
||||
gboolean press_or_release)
|
||||
{
|
||||
GdkScreen *screen = gdk_colormap_get_screen (gdk_drawable_get_colormap (window));
|
||||
XButtonEvent xev = {
|
||||
0, /* type */
|
||||
0, /* serial */
|
||||
1, /* send_event */
|
||||
};
|
||||
if (x < 0 && y < 0)
|
||||
{
|
||||
gdk_drawable_get_size (window, &x, &y);
|
||||
x /= 2;
|
||||
y /= 2;
|
||||
}
|
||||
xev.type = press_or_release ? ButtonPress : ButtonRelease;
|
||||
xev.display = GDK_DRAWABLE_XDISPLAY (window);
|
||||
xev.window = GDK_WINDOW_XID (window);
|
||||
xev.root = RootWindow (xev.display, GDK_SCREEN_XNUMBER (screen));
|
||||
xev.subwindow = 0;
|
||||
xev.time = 0;
|
||||
xev.x = x;
|
||||
xev.y = y;
|
||||
xev.x_root = 0;
|
||||
xev.y_root = 0;
|
||||
xev.state = modifiers;
|
||||
xev.button = button;
|
||||
gdk_error_trap_push ();
|
||||
xev.same_screen = XTranslateCoordinates (xev.display, xev.window, xev.root,
|
||||
xev.x, xev.y, &xev.x_root, &xev.y_root,
|
||||
&xev.subwindow);
|
||||
if (!xev.subwindow)
|
||||
xev.subwindow = xev.window;
|
||||
gboolean success = xev.same_screen;
|
||||
success &= 0 != XWarpPointer (xev.display, None, xev.window, 0, 0, 0, 0, xev.x, xev.y);
|
||||
success &= 0 != XSendEvent (xev.display, xev.window, True, press_or_release ? ButtonPressMask : ButtonReleaseMask, (XEvent*) &xev);
|
||||
XSync (xev.display, False);
|
||||
success &= 0 == gdk_error_trap_pop();
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_test_widget_click
|
||||
* @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.
|
||||
* For %GTK_NO_WINDOW widgets like GtkButton, this will often be an
|
||||
* 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
|
||||
* location, see gtk_test_simulate_button() for details.
|
||||
*
|
||||
* Returns: wether all actions neccessary for the button click simulation were carried out successfully.
|
||||
**/
|
||||
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;
|
||||
b1res = gtk_test_simulate_button (iwindows->data, -1, -1, button, modifiers, TRUE);
|
||||
b2res = gtk_test_simulate_button (iwindows->data, -1, -1, button, modifiers, FALSE);
|
||||
g_slist_free (iwindows);
|
||||
return b1res && b2res;
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_test_spin_button_click
|
||||
* @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
|
||||
* spin button's value.
|
||||
*
|
||||
* Returns: wether all actions neccessary for the button click simulation were carried out successfully.
|
||||
**/
|
||||
gboolean
|
||||
gtk_test_spin_button_click (GtkSpinButton *spinner,
|
||||
guint button,
|
||||
gboolean upwards)
|
||||
{
|
||||
gboolean b1res = FALSE, b2res = FALSE;
|
||||
if (spinner->panel)
|
||||
{
|
||||
gint width, height, pos;
|
||||
gdk_drawable_get_size (spinner->panel, &width, &height);
|
||||
pos = upwards ? 0 : height - 1;
|
||||
b1res = gtk_test_simulate_button (spinner->panel, width - 1, pos, button, 0, TRUE);
|
||||
b2res = gtk_test_simulate_button (spinner->panel, width - 1, pos, button, 0, FALSE);
|
||||
}
|
||||
return b1res && b2res;
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_test_find_label
|
||||
* @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.
|
||||
* The @label_pattern may contain asterisks '*' and question marks '?' as
|
||||
* placeholders, g_pattern_match() is used for the matching.
|
||||
* Note that locales other than "C" tend to alter (translate" label strings,
|
||||
* so this function is genrally only useful in test programs with
|
||||
* predetermined locales, see gtk_test_init() for more details.
|
||||
*
|
||||
* Returns: a GtkLabel widget if any is found.
|
||||
**/
|
||||
GtkWidget*
|
||||
gtk_test_find_label (GtkWidget *widget,
|
||||
const gchar *label_pattern)
|
||||
{
|
||||
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;
|
||||
}
|
||||
if (GTK_IS_CONTAINER (widget))
|
||||
{
|
||||
GList *node, *list = gtk_container_get_children (GTK_CONTAINER (widget));
|
||||
for (node = list; node; node = node->next)
|
||||
{
|
||||
GtkWidget *label = gtk_test_find_label (node->data, label_pattern);
|
||||
if (label)
|
||||
return label;
|
||||
}
|
||||
g_list_free (list);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
int ax0, ay0, ax1, ay1, bx0, by0, bx1, by1, xdist = 0, ydist = 0;
|
||||
if (!gtk_widget_translate_coordinates (a, base, 0, 0, &ax0, &ay0) ||
|
||||
!gtk_widget_translate_coordinates (a, base, a->allocation.width, a->allocation.height, &ax1, &ay1))
|
||||
return -G_MAXINT;
|
||||
if (!gtk_widget_translate_coordinates (b, base, 0, 0, &bx0, &by0) ||
|
||||
!gtk_widget_translate_coordinates (b, base, b->allocation.width, b->allocation.height, &bx1, &by1))
|
||||
return +G_MAXINT;
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_test_find_sibling
|
||||
* @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.
|
||||
* The general purpose of this function is to find the most likely "action"
|
||||
* widget, relative to another labeling widget. Such as finding a
|
||||
* button or text entry widget, given it's corresponding label widget.
|
||||
*
|
||||
* Returns: a widget of type @widget_type if any is found.
|
||||
**/
|
||||
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)
|
||||
{
|
||||
tmpwidget = tmpwidget->parent;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_test_find_widget
|
||||
* @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
|
||||
* the "OK" button in a dialog and synthesize clicks on it.
|
||||
* 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.
|
||||
*
|
||||
* Returns: a valid widget if any is found or %NULL.
|
||||
**/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_test_slider_set_perc
|
||||
* @widget: valid widget pointer.
|
||||
* @percentage: value between 0 and 100.
|
||||
*
|
||||
* This function will adjust theslider position of all GtkRange
|
||||
* based widgets, such as scrollbars or scales, it'll also adjust
|
||||
* spin buttons. The adjustment value of tehse widgets is set to
|
||||
* a value between the lower and upper limits, according to the
|
||||
* @percentage argument.
|
||||
**/
|
||||
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)
|
||||
gtk_adjustment_set_value (adjustment, adjustment->lower + (adjustment->upper - adjustment->lower - adjustment->page_size) * percentage * 0.01);
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_test_slider_get_value
|
||||
* @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().
|
||||
*
|
||||
* Returns: adjustment->value for an adjustment belonging to @widget.
|
||||
**/
|
||||
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));
|
||||
return adjustment ? adjustment->value : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_test_text_set
|
||||
* @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.
|
||||
**/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_test_text_get
|
||||
* @widget: valid widget pointer.
|
||||
*
|
||||
* Retrive the text string of @widget if it is a GtkLabel,
|
||||
* GtkEditable (entry and text widgets) or GtkTextView.
|
||||
*
|
||||
* Returns: new 0-terminated C string, needs to be releaed with g_free().
|
||||
**/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_test_create_widget
|
||||
* @widget_type: valid widget type.
|
||||
* @first_property_name: Name of first property to set or %NULL
|
||||
*
|
||||
* This function wraps g_object_new() for widget types.
|
||||
* It'll automatically show all created non window widgets, also
|
||||
* g_object_ref_sink() them (to keep them alive across a running test)
|
||||
* and set them up for destruction during test teardown phase.
|
||||
*
|
||||
* Returns: a newly created widget.
|
||||
*/
|
||||
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);
|
||||
#ifdef __G_TESTFRAMEWORK_H__ /* FIXME */
|
||||
g_test_queue_unref (widget);
|
||||
g_test_queue_destroy ((GDestroyNotify) gtk_widget_destroy, widget);
|
||||
#endif
|
||||
}
|
||||
return widget;
|
||||
}
|
||||
|
||||
static void
|
||||
try_main_quit (void)
|
||||
{
|
||||
if (gtk_main_level())
|
||||
gtk_main_quit();
|
||||
}
|
||||
|
||||
static int
|
||||
test_increment_intp (int *intp)
|
||||
{
|
||||
*intp += 1;
|
||||
return 1; /* TRUE in case we're connected to event signals */
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_test_display_button_window
|
||||
* @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.
|
||||
*
|
||||
* Returns: a widget pointer to the newly created GtkWindow.
|
||||
**/
|
||||
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;
|
||||
GtkWidget *window = gtk_test_create_widget (GTK_TYPE_WINDOW, "title", window_title, NULL);
|
||||
GtkWidget *vbox = gtk_test_create_widget (GTK_TYPE_VBOX, "parent", window, NULL);
|
||||
const char *arg1;
|
||||
gtk_test_create_widget (GTK_TYPE_LABEL, "label", dialog_text, "parent", vbox, NULL);
|
||||
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*);
|
||||
GtkWidget *button = gtk_test_create_widget (GTK_TYPE_BUTTON, "label", arg1, "parent", vbox, NULL);
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_test_create_simple_window
|
||||
* @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.
|
||||
*
|
||||
* Returns: a widget pointer to the newly created GtkWindow.
|
||||
**/
|
||||
GtkWidget*
|
||||
gtk_test_create_simple_window (const gchar *window_title,
|
||||
const gchar *dialog_text)
|
||||
{
|
||||
GtkWidget *window = gtk_test_create_widget (GTK_TYPE_WINDOW, "title", window_title, NULL);
|
||||
GtkWidget *vbox = gtk_test_create_widget (GTK_TYPE_VBOX, "parent", window, NULL);
|
||||
gtk_test_create_widget (GTK_TYPE_LABEL, "label", dialog_text, "parent", vbox, NULL);
|
||||
g_signal_connect (window, "destroy", G_CALLBACK (try_main_quit), NULL);
|
||||
gtk_widget_show_all (vbox);
|
||||
return window;
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_test_xserver_render_sync
|
||||
* @window: a mapped GdkWindow
|
||||
*
|
||||
* This function retrives a pixel from @window to force the windowing
|
||||
* system to carry out any pending rendering commands.
|
||||
* This function is intended to be used to syncronize with rendering
|
||||
* pipelines, to benchmark windowing system rendering operations.
|
||||
**/
|
||||
void
|
||||
gtk_test_xserver_render_sync (GdkWindow *window)
|
||||
{
|
||||
static GdkImage *p1image = NULL;
|
||||
/* syncronize to X drawing queue, see:
|
||||
* http://mail.gnome.org/archives/gtk-devel-list/2006-October/msg00103.html
|
||||
*/
|
||||
p1image = gdk_drawable_copy_to_image (window, p1image, 0, 0, 0, 0, 1, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_test_register_all_types
|
||||
*
|
||||
* 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.
|
||||
**/
|
||||
void
|
||||
gtk_test_register_all_types (void)
|
||||
{
|
||||
volatile GType vgt;
|
||||
#include "gtktypefuncs.c"
|
||||
}
|
||||
|
||||
#define __GTK_TEST_UTILS_C__
|
||||
#include "gtkaliasdef.c"
|
80
gtk/gtktestutils.h
Normal file
80
gtk/gtktestutils.h
Normal file
@ -0,0 +1,80 @@
|
||||
/* Gtk+ testing utilities
|
||||
* Copyright (C) 2007 Tim Janik
|
||||
*
|
||||
* 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
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
#ifndef __GTK_TEST_UTILS_H__
|
||||
#define __GTK_TEST_UTILS_H__
|
||||
|
||||
#include <gtk/gtkwidget.h>
|
||||
#include <gtk/gtkspinbutton.h>
|
||||
|
||||
G_BEGIN_DECLS;
|
||||
|
||||
/* --- Gtk+ Test Utility API --- */
|
||||
void gtk_test_init (int *argcp,
|
||||
char ***argvp,
|
||||
...);
|
||||
void gtk_test_register_all_types (void);
|
||||
GtkWidget* gtk_test_find_widget (GtkWidget *widget,
|
||||
const gchar *label_pattern,
|
||||
GType widget_type);
|
||||
GtkWidget* gtk_test_create_widget (GType widget_type,
|
||||
const gchar *first_property_name,
|
||||
...);
|
||||
GtkWidget* gtk_test_create_simple_window (const gchar *window_title,
|
||||
const gchar *dialog_text);
|
||||
GtkWidget* gtk_test_display_button_window (const gchar *window_title,
|
||||
const gchar *dialog_text,
|
||||
...); /* NULL terminated list of (label, &int) pairs */
|
||||
void gtk_test_xserver_render_sync (GdkWindow *window);
|
||||
void gtk_test_slider_set_perc (GtkWidget *widget, /* GtkRange-alike */
|
||||
double percentage);
|
||||
double gtk_test_slider_get_value (GtkWidget *widget);
|
||||
gboolean gtk_test_spin_button_click (GtkSpinButton *widget,
|
||||
guint button,
|
||||
gboolean upwards);
|
||||
gboolean gtk_test_widget_click (GtkWidget *widget,
|
||||
guint button,
|
||||
GdkModifierType modifiers);
|
||||
gboolean gtk_test_widget_send_key (GtkWidget *widget,
|
||||
guint keyval,
|
||||
GdkModifierType modifiers);
|
||||
/* operate on GtkEntry, GtkText, GtkTextView or GtkLabel */
|
||||
void gtk_test_text_set (GtkWidget *widget,
|
||||
const gchar *string);
|
||||
gchar* gtk_test_text_get (GtkWidget *widget);
|
||||
|
||||
/* --- Gtk+ Test low-level API --- */
|
||||
GtkWidget* gtk_test_find_sibling (GtkWidget *base_widget,
|
||||
GType widget_type);
|
||||
GtkWidget* gtk_test_find_label (GtkWidget *widget,
|
||||
const gchar *label_pattern);
|
||||
gboolean gtk_test_simulate_key (GdkWindow *window,
|
||||
gint x,
|
||||
gint y,
|
||||
guint keyval,
|
||||
GdkModifierType modifiers,
|
||||
gboolean press_or_release);
|
||||
gboolean gtk_test_simulate_button (GdkWindow *window,
|
||||
gint x,
|
||||
gint y,
|
||||
guint button, /*1..3*/
|
||||
GdkModifierType modifiers,
|
||||
gboolean press_or_release);
|
||||
G_END_DECLS;
|
||||
|
||||
#endif /* __GTK_TEST_UTILS_H__ */
|
Loading…
Reference in New Issue
Block a user