1 /*
2 * Copyright (C) 2019 Red Hat Inc.
3 *
4 * Author:
5 * Matthias Clasen <mclasen@redhat.com>
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
16 *
17 * You should have received a copy of the GNU Library General Public
18 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include <string.h>
22 #include <glib/gstdio.h>
23 #include <gtk/gtk.h>
24 #include "testsuite/testutils.h"
25
26 #ifdef G_OS_WIN32
27 # include <io.h>
28 #endif
29
30 struct {
31 GtkDirectionType dir;
32 const char *ext;
33 } extensions[] = {
34 { GTK_DIR_TAB_FORWARD, "tab" },
35 { GTK_DIR_TAB_BACKWARD, "tab-backward" },
36 { GTK_DIR_UP, "up" },
37 { GTK_DIR_DOWN, "down" },
38 { GTK_DIR_LEFT, "left" },
39 { GTK_DIR_RIGHT, "right" }
40 };
41
42 static void
check_focus_states(GtkWidget * focus_widget)43 check_focus_states (GtkWidget *focus_widget)
44 {
45 GtkStateFlags state;
46 GtkWidget *parent;
47
48 if (focus_widget == NULL)
49 return;
50
51 /* Check that we set :focus and :focus-within on the focus_widget,
52 * and :focus-within on its ancestors
53 */
54
55 state = gtk_widget_get_state_flags (focus_widget);
56 g_assert_true ((state & (GTK_STATE_FLAG_FOCUSED|GTK_STATE_FLAG_FOCUS_WITHIN)) ==
57 (GTK_STATE_FLAG_FOCUSED|GTK_STATE_FLAG_FOCUS_WITHIN));
58
59 parent = gtk_widget_get_parent (focus_widget);
60 while (parent)
61 {
62 state = gtk_widget_get_state_flags (parent);
63 g_assert_true ((state & GTK_STATE_FLAG_FOCUS_WITHIN) == GTK_STATE_FLAG_FOCUS_WITHIN);
64 g_assert_true ((state & GTK_STATE_FLAG_FOCUSED) == 0);
65
66 parent = gtk_widget_get_parent (parent);
67 }
68 }
69
70 static char *
generate_focus_chain(GtkWidget * window,GtkDirectionType dir)71 generate_focus_chain (GtkWidget *window,
72 GtkDirectionType dir)
73 {
74 char *first = NULL;
75 char *last = NULL;
76 char *name = NULL;
77 char *key = NULL;
78 GString *output = g_string_new ("");
79 GtkWidget *focus;
80 int count = 0;
81
82 gtk_widget_show (window);
83
84 /* start without focus */
85 gtk_window_set_focus (GTK_WINDOW (window), NULL);
86
87 while (TRUE)
88 {
89 g_signal_emit_by_name (window, "move-focus", dir);
90
91 focus = gtk_window_get_focus (GTK_WINDOW (window));
92
93 check_focus_states (focus);
94
95 if (focus)
96 {
97 /* ui files can't put a name on the embedded text,
98 * so include the parent entry here
99 */
100 if (GTK_IS_TEXT (focus))
101 name = g_strdup_printf ("%s %s",
102 gtk_widget_get_name (gtk_widget_get_parent (focus)),
103 gtk_widget_get_name (focus));
104 else
105 name = g_strdup (gtk_widget_get_name (focus));
106
107 key = g_strdup_printf ("%s %p", name, focus);
108 }
109 else
110 {
111 name = g_strdup ("NONE");
112 key = g_strdup (key);
113 }
114
115 if (first && g_str_equal (key, first))
116 {
117 g_string_append (output, "WRAP\n");
118 break; /* cycle completed */
119 }
120
121 if (last && g_str_equal (key, last))
122 {
123 g_string_append (output, "STOP\n");
124 break; /* dead end */
125 }
126
127 g_string_append_printf (output, "%s\n", name);
128 count++;
129
130 if (!first)
131 first = g_strdup (key);
132
133 g_free (last);
134 last = key;
135
136 if (count == 100)
137 {
138 g_string_append (output, "ABORT\n");
139 break;
140 }
141
142 g_free (name);
143 }
144
145 g_free (name);
146 g_free (key);
147 g_free (first);
148 g_free (last);
149
150 return g_string_free (output, FALSE);
151 }
152
153 static GtkDirectionType
get_dir_for_file(const char * path)154 get_dir_for_file (const char *path)
155 {
156 char *p;
157 int i;
158
159 p = strrchr (path, '.');
160 p++;
161
162 for (i = 0; i < G_N_ELEMENTS (extensions); i++)
163 {
164 if (strcmp (p, extensions[i].ext) == 0)
165 return extensions[i].dir;
166 }
167
168 g_error ("Could not find direction for %s", path);
169
170 return 0;
171 }
172
173 static gboolean
quit_iteration_loop(gpointer user_data)174 quit_iteration_loop (gpointer user_data)
175 {
176 gboolean *keep_running = user_data;
177
178 *keep_running = FALSE;
179
180 return G_SOURCE_REMOVE;
181 }
182
183 static gboolean
load_ui_file(GFile * ui_file,GFile * ref_file,const char * ext)184 load_ui_file (GFile *ui_file,
185 GFile *ref_file,
186 const char *ext)
187 {
188 GtkBuilder *builder;
189 GtkWidget *window;
190 char *output;
191 char *diff;
192 char *ui_path, *ref_path;
193 GError *error = NULL;
194 GtkDirectionType dir;
195 gboolean success = FALSE;
196 gboolean keep_running = TRUE;
197 guint timeout_handle_id;
198
199 ui_path = g_file_get_path (ui_file);
200
201 builder = gtk_builder_new_from_file (ui_path);
202 window = GTK_WIDGET (gtk_builder_get_object (builder, "window"));
203
204 g_assert_nonnull (window);
205
206 gtk_widget_show (window);
207
208 timeout_handle_id = g_timeout_add (2000,
209 quit_iteration_loop,
210 &keep_running);
211 while (keep_running)
212 {
213 if (!g_main_context_iteration (NULL, FALSE))
214 break;
215 }
216 if (keep_running)
217 g_source_remove (timeout_handle_id);
218
219 if (ext)
220 {
221 int i;
222
223 for (i = 0; i < G_N_ELEMENTS (extensions); i++)
224 {
225 if (g_str_equal (ext, extensions[i].ext))
226 {
227 output = generate_focus_chain (window, extensions[i].dir);
228 g_print ("%s", output);
229 success = TRUE;
230 goto out;
231 }
232 }
233
234 g_error ("Not a supported direction: %s", ext);
235 goto out;
236 }
237
238 g_assert_nonnull (ref_file);
239
240 ref_path = g_file_get_path (ref_file);
241
242 dir = get_dir_for_file (ref_path);
243 output = generate_focus_chain (window, dir);
244 diff = diff_with_file (ref_path, output, -1, &error);
245 g_assert_no_error (error);
246
247 if (diff && diff[0])
248 g_print ("Resulting output doesn't match reference:\n%s", diff);
249 else
250 success = TRUE;
251 g_free (ref_path);
252 g_free (diff);
253
254 out:
255 g_free (output);
256 g_free (ui_path);
257
258 return success;
259 }
260
261 static const char *arg_generate;
262
263 static const GOptionEntry options[] = {
264 { "generate", 0, 0, G_OPTION_ARG_STRING, &arg_generate, "DIRECTION" },
265 { NULL }
266 };
267
268 int
main(int argc,char ** argv)269 main (int argc, char **argv)
270 {
271 GOptionContext *context;
272 GFile *ui_file;
273 GFile *ref_file;
274 GError *error = NULL;
275 gboolean success;
276
277 context = g_option_context_new ("- run focus chain tests");
278 g_option_context_add_main_entries (context, options, NULL);
279 g_option_context_set_ignore_unknown_options (context, TRUE);
280
281 if (!g_option_context_parse (context, &argc, &argv, &error))
282 {
283 g_error ("Option parsing failed: %s\n", error->message);
284 return 1;
285 }
286 g_option_context_free (context);
287
288 gtk_init ();
289
290 if (arg_generate)
291 {
292 g_assert_cmpint (argc, ==, 2);
293
294 ui_file = g_file_new_for_commandline_arg (argv[1]);
295
296 success = load_ui_file (ui_file, NULL, arg_generate);
297
298 g_object_unref (ui_file);
299 }
300 else
301 {
302 g_assert_cmpint (argc, ==, 3);
303
304 ui_file = g_file_new_for_commandline_arg (argv[1]);
305 ref_file = g_file_new_for_commandline_arg (argv[2]);
306
307 success = load_ui_file (ui_file, ref_file, NULL);
308
309 g_object_unref (ui_file);
310 g_object_unref (ref_file);
311 }
312
313 return success ? 0 : 1;
314 }
315
316