2
* Copyright (c) 2009 Tias Guns
3
* Copyright (c) 2009 Soren Hauberg
5
* Permission is hereby granted, free of charge, to any person obtaining a copy
6
* of this software and associated documentation files (the "Software"), to deal
7
* in the Software without restriction, including without limitation the rights
8
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
* copies of the Software, and to permit persons to whom the Software is
10
* furnished to do so, subject to the following conditions:
12
* The above copyright notice and this permission notice shall be included in
13
* all copies or substantial portions of the Software.
15
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29
#include <glib/gi18n.h>
33
#include "calibrator.h"
38
struct Calib calibrator;
40
int display_width, display_height;
49
FinishCallback callback;
54
/* Window parameters */
55
#define WINDOW_OPACITY 0.9
57
/* Timeout parameters */
58
#define TIME_STEP 100 /* in milliseconds */
59
#define MAX_TIME 15000 /* 5000 = 5 sec */
61
/* Clock appereance */
62
#define CROSS_LINES 47
63
#define CROSS_CIRCLE 7
64
#define CROSS_CIRCLE2 27
65
#define CLOCK_RADIUS 50
66
#define CLOCK_LINE_WIDTH 10
67
#define CLOCK_LINE_PADDING 10
69
/* Text printed on screen */
70
#define HELP_TEXT_TITLE N_("Screen Calibration")
71
#define HELP_TEXT_MAIN N_("Please tap the target markers as they appear on screen to calibrate the tablet.")
74
set_display_size(CalibArea *calib_area,
81
calib_area->display_width = width;
82
calib_area->display_height = height;
84
/* Compute absolute circle centers */
85
delta_x = calib_area->display_width/NUM_BLOCKS;
86
delta_y = calib_area->display_height/NUM_BLOCKS;
88
calib_area->X[UL] = delta_x;
89
calib_area->Y[UL] = delta_y;
91
calib_area->X[UR] = calib_area->display_width - delta_x - 1;
92
calib_area->Y[UR] = delta_y;
94
calib_area->X[LL] = delta_x;
95
calib_area->Y[LL] = calib_area->display_height - delta_y - 1;
97
calib_area->X[LR] = calib_area->display_width - delta_x - 1;
98
calib_area->Y[LR] = calib_area->display_height - delta_y - 1;
100
/* reset calibration if already started */
101
reset(&calib_area->calibrator);
105
resize_display(CalibArea *calib_area)
107
/* check that screensize did not change (if no manually specified geometry) */
108
GtkAllocation allocation;
109
gtk_widget_get_allocation(calib_area->window, &allocation);
110
if (calib_area->display_width != allocation.width ||
111
calib_area->display_height != allocation.height)
113
set_display_size(calib_area, allocation.width, allocation.height);
118
draw(GtkWidget *widget, cairo_t *cr, gpointer data)
120
CalibArea *calib_area = (CalibArea*)data;
125
PangoRectangle logical_rect;
126
GtkStyleContext *context;
129
resize_display(calib_area);
131
context = gtk_widget_get_style_context (widget);
133
/* Black background and reset the operator */
134
cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
136
cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
138
/* Print the help lines */
139
layout = pango_layout_new (gtk_widget_get_pango_context (widget));
140
pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER);
141
markup = g_strdup_printf ("<span foreground=\"white\"><big><b>%s</b></big>\n<big>%s</big></span>",
144
pango_layout_set_markup (layout, markup, -1);
147
pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
149
x = (calib_area->display_width - logical_rect.width) / 2 + logical_rect.x;
150
y = (calib_area->display_height - logical_rect.height) / 2 - logical_rect.height - 40 + logical_rect.y;
152
gtk_render_layout (context, cr,
156
g_object_unref (layout);
158
/* Draw the points */
159
cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
161
i = calib_area->calibrator.num_clicks;
163
cairo_set_line_width(cr, 1);
164
cairo_move_to(cr, calib_area->X[i] - CROSS_LINES, calib_area->Y[i] - 0.5);
165
cairo_rel_line_to(cr, CROSS_LINES*2, 0);
166
cairo_move_to(cr, calib_area->X[i] - 0.5, calib_area->Y[i] - CROSS_LINES);
167
cairo_rel_line_to(cr, 0, CROSS_LINES*2);
170
cairo_set_line_width(cr, 2);
171
cairo_arc(cr, calib_area->X[i] - 0.5, calib_area->Y[i] - 0.5, CROSS_CIRCLE, 0.0, 2.0 * M_PI);
174
cairo_set_line_width(cr, 5);
175
cairo_arc(cr, calib_area->X[i] - 0.5, calib_area->Y[i] - 0.5, CROSS_CIRCLE2, 0.0, 2.0 * M_PI);
178
/* Draw the clock background */
179
cairo_arc(cr, calib_area->display_width/2, calib_area->display_height/2, CLOCK_RADIUS/2, 0.0, 2.0 * M_PI);
180
cairo_set_source_rgb(cr, 0.5, 0.5, 0.5);
181
cairo_fill_preserve(cr);
184
cairo_set_line_width(cr, CLOCK_LINE_WIDTH);
185
cairo_arc(cr, calib_area->display_width/2, calib_area->display_height/2, (CLOCK_RADIUS - CLOCK_LINE_WIDTH - CLOCK_LINE_PADDING)/2,
186
3/2.0*M_PI, (3/2.0*M_PI) + ((double)calib_area->time_elapsed/(double)MAX_TIME) * 2*M_PI);
187
cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
190
/* Draw the message (if any) */
191
if (calib_area->message != NULL)
193
cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
195
/* Frame the message */
196
layout = pango_layout_new (gtk_widget_get_pango_context (widget));
197
pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER);
198
markup = g_strdup_printf ("<span foreground=\"white\"><big>%s</big></span>",
199
_(calib_area->message));
200
pango_layout_set_markup (layout, markup, -1);
202
pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
204
x = (calib_area->display_width - logical_rect.width) / 2 + logical_rect.x;
205
y = (calib_area->display_height - logical_rect.height + CLOCK_RADIUS) / 2 + 60 + logical_rect.y;
206
cairo_set_line_width(cr, 2);
207
cairo_rectangle(cr, x - 10 - 0.5 , y - 10 - 0.5,
208
logical_rect.width + 20 + 1, logical_rect.height + 20 + 1);
211
/* Print the message */
212
gtk_render_layout (context, cr,
216
g_object_unref (layout);
221
draw_message(CalibArea *calib_area,
224
calib_area->message = msg;
228
redraw(CalibArea *calib_area)
230
GdkWindow *win = gtk_widget_get_window(calib_area->window);
236
rect.width = calib_area->display_width;
237
rect.height = calib_area->display_height;
238
gdk_window_invalidate_rect(win, &rect, FALSE);
243
on_delete_event (GtkWidget *widget,
247
if (area->anim_id > 0) {
248
g_source_remove (area->anim_id);
252
gtk_widget_hide (area->window);
254
(*area->callback) (area, area->user_data);
260
on_button_press_event(GtkWidget *widget,
261
GdkEventButton *event,
267
area->time_elapsed = 0;
268
success = add_click(&area->calibrator, (int)event->x_root, (int)event->y_root);
270
if (!success && area->calibrator.num_clicks == 0)
271
draw_message(area, N_("Mis-click detected, restarting..."));
273
draw_message(area, NULL);
275
/* Are we done yet? */
276
if (area->calibrator.num_clicks >= 4)
278
on_delete_event (widget, NULL, area);
289
on_key_release_event(GtkWidget *widget,
293
if (event->type != GDK_KEY_RELEASE)
295
if (event->keyval != GDK_KEY_Escape)
298
on_delete_event (widget, NULL, area);
304
on_timer_signal(CalibArea *area)
308
area->time_elapsed += TIME_STEP;
309
if (area->time_elapsed > MAX_TIME)
311
on_delete_event (NULL, NULL, area);
316
win = gtk_widget_get_window (area->window);
320
rect.x = area->display_width/2 - CLOCK_RADIUS - CLOCK_LINE_WIDTH;
321
rect.y = area->display_height/2 - CLOCK_RADIUS - CLOCK_LINE_WIDTH;
322
rect.width = 2 * CLOCK_RADIUS + 1 + 2 * CLOCK_LINE_WIDTH;
323
rect.height = 2 * CLOCK_RADIUS + 1 + 2 * CLOCK_LINE_WIDTH;
324
gdk_window_invalidate_rect(win, &rect, FALSE);
331
* Creates the windows and other objects required to do calibration
332
* under GTK. When the window is closed (timed out, calibration finished
333
* or user cancellation), callback will be called, where you should call
334
* calib_area_finish().
337
calib_area_new (GdkScreen *screen,
339
FinishCallback callback,
342
int threshold_doubleclick,
343
int threshold_misclick)
345
CalibArea *calib_area;
351
g_return_val_if_fail (old_axis, NULL);
352
g_return_val_if_fail (callback, NULL);
354
g_debug ("Current calibration: %d, %d, %d, %d\n",
360
calib_area = g_new0 (CalibArea, 1);
361
calib_area->callback = callback;
362
calib_area->user_data = user_data;
363
calib_area->calibrator.old_axis.x_min = old_axis->x_min;
364
calib_area->calibrator.old_axis.x_max = old_axis->x_max;
365
calib_area->calibrator.old_axis.y_min = old_axis->y_min;
366
calib_area->calibrator.old_axis.y_max = old_axis->y_max;
367
calib_area->calibrator.threshold_doubleclick = threshold_doubleclick;
368
calib_area->calibrator.threshold_misclick = threshold_misclick;
370
/* Set up the window */
371
calib_area->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
372
gtk_widget_set_app_paintable (GTK_WIDGET (calib_area->window), TRUE);
374
/* Black background */
375
gdk_rgba_parse (&black, "rgb(0,0,0)");
376
gtk_window_set_opacity (GTK_WINDOW (calib_area->window), WINDOW_OPACITY);
378
gtk_widget_realize (calib_area->window);
379
window = gtk_widget_get_window (calib_area->window);
380
gdk_window_set_background_rgba (window, &black);
383
cursor = gdk_cursor_new (GDK_BLANK_CURSOR);
384
gdk_window_set_cursor (window, cursor);
385
g_object_unref (cursor);
387
/* Listen for mouse events */
388
gtk_widget_add_events (calib_area->window, GDK_KEY_RELEASE_MASK | GDK_BUTTON_PRESS_MASK);
389
gtk_widget_set_can_focus (calib_area->window, TRUE);
390
gtk_window_fullscreen (GTK_WINDOW (calib_area->window));
392
/* Connect callbacks */
393
g_signal_connect (calib_area->window, "draw",
394
G_CALLBACK(draw), calib_area);
395
g_signal_connect (calib_area->window, "button-press-event",
396
G_CALLBACK(on_button_press_event), calib_area);
397
g_signal_connect (calib_area->window, "key-release-event",
398
G_CALLBACK(on_key_release_event), calib_area);
399
g_signal_connect (calib_area->window, "delete-event",
400
G_CALLBACK(on_delete_event), calib_area);
402
/* Setup timer for animation */
403
calib_area->anim_id = g_timeout_add(TIME_STEP, (GSourceFunc)on_timer_signal, calib_area);
405
/* Move to correct screen */
407
screen = gdk_screen_get_default ();
408
gdk_screen_get_monitor_geometry (screen, monitor, &rect);
409
gtk_window_move (GTK_WINDOW (calib_area->window), rect.x, rect.y);
410
gtk_window_set_default_size (GTK_WINDOW (calib_area->window), rect.width, rect.height);
412
calib_area->calibrator.geometry.x = rect.x;
413
calib_area->calibrator.geometry.y = rect.y;
414
calib_area->calibrator.geometry.width = rect.width;
415
calib_area->calibrator.geometry.height = rect.height;
417
gtk_widget_show_all (calib_area->window);
422
/* Finishes the calibration. Note that CalibArea
423
* needs to be destroyed with calib_area_free() afterwards */
425
calib_area_finish (CalibArea *area,
431
g_return_val_if_fail (area != NULL, FALSE);
433
success = finish (&area->calibrator, new_axis, swap_xy);
436
g_debug ("Final calibration: %d, %d, %d, %d\n",
442
g_debug ("Calibration was aborted or timed out");
448
calib_area_free (CalibArea *area)
450
g_return_if_fail (area != NULL);
452
if (area->anim_id > 0) {
453
g_source_remove (area->anim_id);
456
gtk_widget_destroy (area->window);