gnome-control-center/panels/wacom/calibrator/calibrator-gui.c
Bastien Nocera 55744f6544 wacom: Fix device filtering in calibration UI
We receive ClutterEvents, not GdkEvents for button presses on
ClutterActors, so use the correct functions to filter devices.

This also fixes the offset used to access the coordinates of the
events. We were actually using the pointer to the source and x
struct members instead of x and y.

https://bugzilla.gnome.org/show_bug.cgi?id=707784
2013-10-04 11:16:45 +02:00

787 lines
26 KiB
C

/*
* Copyright © 2013 Red Hat, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Author: Joaquim Rocha <jrocha@redhat.com>
* (based on previous work by Tias Guns and Soren Hauberg)
*/
#include "config.h"
#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include <glib/gi18n.h>
#include <gdk/gdkx.h>
#include <gtk/gtk.h>
#include <cairo.h>
#include <clutter-gtk/clutter-gtk.h>
#include <clutter/clutter.h>
#include "calibrator.h"
#include "calibrator-gui.h"
#include "cc-clock-actor.h"
#include "cc-target-actor.h"
struct CalibArea
{
struct Calib calibrator;
XYinfo axis;
gboolean swap;
gboolean success;
int device_id;
double X[4], Y[4];
int display_width, display_height;
GtkWidget *window;
ClutterActor *stage;
ClutterActor *action_layer;
ClutterActor *clock;
ClutterActor *target;
ClutterActor *success_image;
ClutterActor *text_title_holder;
ClutterActor *helper_text_title;
ClutterActor *text_body_holder;
ClutterActor *helper_text_body;
ClutterActor *error_text;
ClutterTransition *clock_timeline;
GdkPixbuf *icon_success;
FinishCallback callback;
gpointer user_data;
};
#define TARGET_SHOW_ANIMATION_DURATION 500
#define TARGET_HIDE_ANIMATION_DURATION 200
#define COLOR_GRAY 127
/* Window parameters */
#define WINDOW_OPACITY 0.9
/* Timeout parameters */
#define MAX_TIME 15000 /* 5000 = 5 sec */
#define END_TIME 750 /* 750 = 0.75 sec */
/* Text printed on screen */
#define HELP_TEXT_TITLE N_("Screen Calibration")
#define HELP_TEXT_MAIN N_("Please tap the target markers as they " \
"appear on screen to calibrate the tablet.")
#define HELP_TEXT_ANIMATION_DURATION 300
#define ERROR_MESSAGE N_("Mis-click detected, restarting...")
#define ERROR_MESSAGE_ANIMATION_DURATION 500
#define ICON_SUCCESS "emblem-ok-symbolic"
#define ICON_SIZE 300
static void
set_display_size(CalibArea *calib_area,
int width,
int height)
{
int delta_x;
int delta_y;
calib_area->display_width = width;
calib_area->display_height = height;
/* Compute absolute circle centers */
delta_x = calib_area->display_width/NUM_BLOCKS;
delta_y = calib_area->display_height/NUM_BLOCKS;
calib_area->X[UL] = delta_x;
calib_area->Y[UL] = delta_y;
calib_area->X[UR] = calib_area->display_width - delta_x - 1;
calib_area->Y[UR] = delta_y;
calib_area->X[LL] = delta_x;
calib_area->Y[LL] = calib_area->display_height - delta_y - 1;
calib_area->X[LR] = calib_area->display_width - delta_x - 1;
calib_area->Y[LR] = calib_area->display_height - delta_y - 1;
/* reset calibration if already started */
reset(&calib_area->calibrator);
}
static void
resize_display(CalibArea *calib_area)
{
gfloat width, height;
clutter_actor_get_size (calib_area->stage, &width, &height);
if (calib_area->display_width != width ||
calib_area->display_height != height)
{
gint i = calib_area->calibrator.num_clicks;
set_display_size(calib_area, width, height);
cc_target_actor_move (CC_TARGET_ACTOR (calib_area->target),
calib_area->X[i],
calib_area->Y[i]);
}
}
static void
on_allocation_changed (ClutterActor *actor,
ClutterActorBox *box,
ClutterAllocationFlags flags,
CalibArea *area)
{
resize_display (area);
}
static gboolean
on_delete_event (GtkWidget *widget,
GdkEvent *event,
CalibArea *area)
{
clutter_timeline_stop (CLUTTER_TIMELINE (area->clock_timeline));
gtk_widget_hide (area->window);
(*area->callback) (area, area->user_data);
return TRUE;
}
static gboolean
draw_success_end_wait_callback (CalibArea *area)
{
on_delete_event (NULL, NULL, area);
return FALSE;
}
static void
set_success (CalibArea *area)
{
ClutterImage *image;
GdkPixbuf *icon = area->icon_success;
if (icon == NULL)
return;
image = CLUTTER_IMAGE (clutter_actor_get_content (area->success_image));
clutter_image_set_data (image,
gdk_pixbuf_get_pixels (icon),
gdk_pixbuf_get_has_alpha (icon)
? COGL_PIXEL_FORMAT_RGBA_8888
: COGL_PIXEL_FORMAT_RGB_888,
gdk_pixbuf_get_width (icon),
gdk_pixbuf_get_height (icon),
gdk_pixbuf_get_rowstride (icon),
NULL);
clutter_actor_set_size (area->success_image,
gdk_pixbuf_get_width (icon),
gdk_pixbuf_get_height (icon));
clutter_actor_show (area->success_image);
clutter_actor_hide (area->action_layer);
}
static void
set_calibration_status (CalibArea *area)
{
GtkIconTheme *icon_theme;
GtkIconInfo *icon_info;
GdkRGBA white;
icon_theme = gtk_icon_theme_get_default ();
icon_info = gtk_icon_theme_lookup_icon (icon_theme,
ICON_SUCCESS,
ICON_SIZE,
GTK_ICON_LOOKUP_USE_BUILTIN);
if (icon_info == NULL)
{
g_warning ("Failed to find icon \"%s\"", ICON_SUCCESS);
goto out;
}
gdk_rgba_parse (&white, "White");
area->icon_success = gtk_icon_info_load_symbolic (icon_info,
&white,
NULL,
NULL,
NULL,
NULL,
NULL);
g_object_unref (icon_info);
if (!area->icon_success)
g_warning ("Failed to load icon \"%s\"", ICON_SUCCESS);
out:
area->success = finish (&area->calibrator, &area->axis, &area->swap);
if (area->success && area->icon_success)
{
set_success (area);
g_timeout_add (END_TIME,
(GSourceFunc) draw_success_end_wait_callback,
area);
}
else
{
on_delete_event (NULL, NULL, area);
}
}
static ClutterTransition *
get_error_message_transition (CalibArea *area)
{
ClutterTransition *transition;
clutter_actor_show (area->error_text);
transition = clutter_property_transition_new ("opacity");
clutter_timeline_set_progress_mode (CLUTTER_TIMELINE (transition),
CLUTTER_EASE_OUT);
clutter_timeline_set_duration (CLUTTER_TIMELINE (transition),
ERROR_MESSAGE_ANIMATION_DURATION);
clutter_transition_set_animatable (transition,
CLUTTER_ANIMATABLE (area->error_text));
clutter_transition_set_from (transition, G_TYPE_UINT, 0);
clutter_transition_set_to (transition, G_TYPE_UINT, 255);
return transition;
}
static void
show_error_message (CalibArea *area)
{
ClutterTransition *transition;
clutter_actor_show (area->error_text);
transition = get_error_message_transition (area);
clutter_timeline_start (CLUTTER_TIMELINE (transition));
}
static void
on_error_message_transparent (ClutterTimeline *timeline,
CalibArea *area)
{
clutter_actor_hide (area->error_text);
}
static void
hide_error_message (CalibArea *area)
{
ClutterTransition *transition;
transition = get_error_message_transition (area);
clutter_transition_set_from (transition, G_TYPE_UINT, 255);
clutter_transition_set_to (transition, G_TYPE_UINT, 0);
g_signal_connect (CLUTTER_TIMELINE (transition),
"completed",
G_CALLBACK (on_error_message_transparent),
area);
clutter_timeline_start (CLUTTER_TIMELINE (transition));
}
static gboolean
on_button_press_event(ClutterActor *actor,
ClutterButtonEvent *event,
CalibArea *area)
{
gint num_clicks;
gboolean success;
if (area->success)
return FALSE;
/* Check matching device ID if a device ID was provided */
if (area->device_id > -1)
{
ClutterInputDevice *device;
device = ((ClutterButtonEvent *) event)->device;
if (device != NULL && clutter_input_device_get_device_id (device) != area->device_id)
return FALSE;
}
/* Handle click */
clutter_timeline_stop (CLUTTER_TIMELINE (area->clock_timeline));
clutter_timeline_start (CLUTTER_TIMELINE (area->clock_timeline));
success = add_click(&area->calibrator,
(int) event->x,
(int) event->y);
num_clicks = area->calibrator.num_clicks;
if (!success && num_clicks == 0)
show_error_message (area);
else
{
gboolean visible;
g_object_get (area->error_text, "visible", &visible, NULL);
if (visible)
hide_error_message (area);
}
/* Are we done yet? */
if (num_clicks >= 4)
{
set_calibration_status (area);
return FALSE;
}
cc_target_actor_move (CC_TARGET_ACTOR (area->target),
area->X[num_clicks],
area->Y[num_clicks]);
return FALSE;
}
static gboolean
on_key_release_event(ClutterActor *actor,
ClutterKeyEvent *event,
CalibArea *area)
{
if (area->success ||
event->type != CLUTTER_KEY_RELEASE ||
event->keyval != CLUTTER_KEY_Escape)
{
return FALSE;
}
on_delete_event (area->window, NULL, area);
return FALSE;
}
static gboolean
on_focus_out_event (GtkWidget *widget,
GdkEvent *event,
CalibArea *area)
{
if (area->success)
return FALSE;
/* If the calibrator window loses focus, simply bail out... */
on_delete_event (widget, NULL, area);
return FALSE;
}
static void
on_timeout (ClutterTimeline *timeline,
CalibArea *area)
{
set_calibration_status (area);
}
static void
show_helper_text_body (CalibArea *area)
{
ClutterTransition *transition;
gfloat height;
height = clutter_actor_get_height (area->helper_text_body);
clutter_actor_show (area->helper_text_body);
transition = clutter_property_transition_new ("y");
clutter_timeline_set_progress_mode (CLUTTER_TIMELINE (transition),
CLUTTER_EASE_OUT);
clutter_timeline_set_duration (CLUTTER_TIMELINE (transition),
HELP_TEXT_ANIMATION_DURATION);
clutter_transition_set_animatable (transition,
CLUTTER_ANIMATABLE (area->helper_text_body));
clutter_transition_set_from (transition, G_TYPE_FLOAT, -height);
clutter_transition_set_to (transition, G_TYPE_FLOAT, 0.0);
clutter_timeline_start (CLUTTER_TIMELINE (transition));
}
static void
on_helper_text_title_shown (ClutterTimeline *timelines,
CalibArea *area)
{
show_helper_text_body (area);
}
static void
show_helper_text_title (CalibArea *area)
{
ClutterTransition *transition;
gfloat height = clutter_actor_get_height (area->helper_text_title);
clutter_actor_set_y (area->helper_text_title,
- clutter_actor_get_height (area->helper_text_title));
clutter_actor_show (area->helper_text_title);
transition = clutter_property_transition_new ("y");
clutter_timeline_set_progress_mode (CLUTTER_TIMELINE (transition),
CLUTTER_EASE_OUT);
clutter_timeline_set_duration (CLUTTER_TIMELINE (transition),
HELP_TEXT_ANIMATION_DURATION);
clutter_transition_set_animatable (transition,
CLUTTER_ANIMATABLE (area->helper_text_title));
clutter_transition_set_from (transition, G_TYPE_FLOAT, -height);
clutter_transition_set_to (transition, G_TYPE_FLOAT, 0.0);
g_signal_connect (CLUTTER_TIMELINE (transition),
"completed",
G_CALLBACK (on_helper_text_title_shown),
area);
clutter_timeline_start (CLUTTER_TIMELINE (transition));
}
static void
on_fullscreen (GtkWindow *window,
GdkEventWindowState *event,
CalibArea *area)
{
ClutterRect rect;
if ((event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) == 0)
return;
clutter_actor_show (area->action_layer);
clutter_actor_show (area->clock);
rect.origin.x = 0;
rect.origin.y = 0;
clutter_actor_get_size (area->helper_text_title,
&rect.size.width,
&rect.size.height);
g_object_set (area->text_title_holder, "clip-rect", &rect, NULL);
clutter_actor_get_size (area->helper_text_body,
&rect.size.width,
&rect.size.height);
g_object_set (area->text_body_holder, "clip-rect", &rect, NULL);
clutter_actor_set_y (area->helper_text_body,
- clutter_actor_get_height (area->helper_text_body));
show_helper_text_title (area);
}
static void
set_up_stage (CalibArea *calib_area, ClutterActor *stage)
{
ClutterPoint anchor;
ClutterColor color;
ClutterContent *success_content;
gfloat height;
gchar *markup;
calib_area->stage = stage;
calib_area->action_layer = clutter_actor_new ();
calib_area->clock = cc_clock_actor_new ();
calib_area->target = cc_target_actor_new ();
calib_area->text_title_holder = clutter_actor_new ();
calib_area->helper_text_title = clutter_text_new ();
calib_area->text_body_holder = clutter_actor_new ();
calib_area->helper_text_body = clutter_text_new ();
calib_area->error_text = clutter_text_new ();
calib_area->success_image = clutter_actor_new ();
clutter_stage_set_use_alpha (CLUTTER_STAGE (stage), TRUE);
/* bind the action layer's geometry to the stage's */
clutter_actor_add_constraint (calib_area->action_layer,
clutter_bind_constraint_new (stage,
CLUTTER_BIND_SIZE,
0));
clutter_actor_add_child (stage, calib_area->action_layer);
g_signal_connect (stage,
"allocation-changed",
G_CALLBACK (on_allocation_changed),
calib_area);
calib_area->calibrator.geometry.x = clutter_actor_get_x (stage);
calib_area->calibrator.geometry.y = clutter_actor_get_y (stage);
calib_area->calibrator.geometry.width = clutter_actor_get_width (stage);
calib_area->calibrator.geometry.height = clutter_actor_get_height (stage);
clutter_color_from_string (&color, "#000");
color.alpha = WINDOW_OPACITY * 255;
clutter_actor_set_background_color (stage, &color);
clutter_actor_add_child (calib_area->action_layer, calib_area->clock);
clutter_actor_add_constraint (calib_area->clock,
clutter_align_constraint_new (stage,
CLUTTER_ALIGN_BOTH,
0.5));
clutter_actor_add_child (calib_area->action_layer, calib_area->target);
/* set the helper text */
anchor.x = 0;
g_object_set (calib_area->text_title_holder, "pivot-point", &anchor, NULL);
clutter_actor_add_child (calib_area->action_layer,
calib_area->text_title_holder);
clutter_actor_add_child (calib_area->text_title_holder,
calib_area->helper_text_title);
height = clutter_actor_get_height (calib_area->clock);
clutter_actor_add_constraint (calib_area->text_title_holder,
clutter_bind_constraint_new (calib_area->clock,
CLUTTER_BIND_Y,
height * 1.5));
clutter_actor_add_constraint (calib_area->text_title_holder,
clutter_align_constraint_new (stage,
CLUTTER_ALIGN_X_AXIS,
.5));
clutter_text_set_line_alignment (CLUTTER_TEXT (calib_area->helper_text_title),
PANGO_ALIGN_CENTER);
color.red = COLOR_GRAY;
color.green = COLOR_GRAY;
color.blue = COLOR_GRAY;
color.alpha = 255;
markup = g_strdup_printf ("<big><b>%s</b></big>",
_(HELP_TEXT_TITLE));
clutter_text_set_markup (CLUTTER_TEXT (calib_area->helper_text_title), markup);
clutter_text_set_color (CLUTTER_TEXT (calib_area->helper_text_title), &color);
g_free (markup);
g_object_set (calib_area->text_body_holder, "pivot-point", &anchor, NULL);
clutter_actor_add_child (calib_area->action_layer,
calib_area->text_body_holder);
clutter_actor_add_child (calib_area->text_body_holder,
calib_area->helper_text_body);
height = clutter_actor_get_height (calib_area->helper_text_title);
clutter_actor_add_constraint (calib_area->text_body_holder,
clutter_bind_constraint_new (calib_area->text_title_holder,
CLUTTER_BIND_Y,
height * 1.2));
clutter_actor_add_constraint (calib_area->text_body_holder,
clutter_align_constraint_new (stage,
CLUTTER_ALIGN_X_AXIS,
.5));
clutter_text_set_line_alignment (CLUTTER_TEXT (calib_area->helper_text_body),
PANGO_ALIGN_CENTER);
markup = g_strdup_printf ("<span foreground=\"white\"><big>%s</big></span>",
_(HELP_TEXT_MAIN));
clutter_text_set_markup (CLUTTER_TEXT (calib_area->helper_text_body), markup);
g_free (markup);
/* set the error text */
g_object_set (calib_area->error_text, "pivot-point", &anchor, NULL);
clutter_actor_add_child (calib_area->action_layer, calib_area->error_text);
height = clutter_actor_get_height (calib_area->helper_text_body);
clutter_actor_add_constraint (calib_area->error_text,
clutter_bind_constraint_new (calib_area->text_title_holder,
CLUTTER_BIND_Y,
height * 3));
clutter_actor_add_constraint (calib_area->error_text,
clutter_align_constraint_new (stage,
CLUTTER_ALIGN_X_AXIS,
.5));
clutter_text_set_line_alignment (CLUTTER_TEXT (calib_area->error_text),
PANGO_ALIGN_CENTER);
markup = g_strdup_printf ("<span foreground=\"white\"><big>"
"<b>%s</b></big></span>",
ERROR_MESSAGE);
clutter_text_set_markup (CLUTTER_TEXT (calib_area->error_text), markup);
g_free (markup);
clutter_actor_hide (calib_area->error_text);
/* configure success image */
success_content = clutter_image_new ();
clutter_actor_set_content (calib_area->success_image,
success_content);
g_object_unref (success_content);
clutter_actor_add_child (stage, calib_area->success_image);
clutter_actor_add_constraint (calib_area->success_image,
clutter_align_constraint_new (stage,
CLUTTER_ALIGN_BOTH,
.5));
/* animate clock */
calib_area->clock_timeline = clutter_property_transition_new ("angle");
clutter_timeline_set_progress_mode (CLUTTER_TIMELINE (calib_area->clock_timeline),
CLUTTER_LINEAR);
clutter_timeline_set_duration (CLUTTER_TIMELINE (calib_area->clock_timeline),
MAX_TIME);
clutter_transition_set_animatable (calib_area->clock_timeline,
CLUTTER_ANIMATABLE (calib_area->clock));
clutter_transition_set_from (calib_area->clock_timeline, G_TYPE_FLOAT, .0);
clutter_transition_set_to (calib_area->clock_timeline, G_TYPE_FLOAT, 360.0);
clutter_timeline_set_repeat_count (CLUTTER_TIMELINE (calib_area->clock_timeline),
-1);
clutter_timeline_start (CLUTTER_TIMELINE (calib_area->clock_timeline));
g_signal_connect (CLUTTER_TIMELINE (calib_area->clock_timeline),
"completed",
G_CALLBACK (on_timeout),
calib_area);
g_signal_connect (stage,
"button-press-event",
G_CALLBACK (on_button_press_event),
calib_area);
g_signal_connect (stage,
"key-release-event",
G_CALLBACK (on_key_release_event),
calib_area);
}
/**
* Creates the windows and other objects required to do calibration
* under GTK. When the window is closed (timed out, calibration finished
* or user cancellation), callback will be called, where you should call
* calib_area_finish().
*/
CalibArea *
calib_area_new (GdkScreen *screen,
int monitor,
int device_id,
FinishCallback callback,
gpointer user_data,
XYinfo *old_axis,
int threshold_doubleclick,
int threshold_misclick)
{
CalibArea *calib_area;
GdkRectangle rect;
GdkVisual *visual;
#ifndef FAKE_AREA
GdkWindow *window;
GdkCursor *cursor;
#endif /* FAKE_AREA */
GtkWidget *clutter_embed;
ClutterActor *stage;
g_return_val_if_fail (old_axis, NULL);
g_return_val_if_fail (callback, NULL);
g_debug ("Current calibration: %d, %d, %d, %d\n",
old_axis->x_min,
old_axis->y_min,
old_axis->x_max,
old_axis->y_max);
calib_area = g_new0 (CalibArea, 1);
calib_area->callback = callback;
calib_area->user_data = user_data;
calib_area->device_id = device_id;
calib_area->calibrator.old_axis.x_min = old_axis->x_min;
calib_area->calibrator.old_axis.x_max = old_axis->x_max;
calib_area->calibrator.old_axis.y_min = old_axis->y_min;
calib_area->calibrator.old_axis.y_max = old_axis->y_max;
calib_area->calibrator.threshold_doubleclick = threshold_doubleclick;
calib_area->calibrator.threshold_misclick = threshold_misclick;
calib_area->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
#ifndef FAKE_AREA
/* No cursor */
gtk_widget_realize (calib_area->window);
window = gtk_widget_get_window (calib_area->window);
cursor = gdk_cursor_new (GDK_BLANK_CURSOR);
gdk_window_set_cursor (window, cursor);
g_object_unref (cursor);
gtk_widget_set_can_focus (calib_area->window, TRUE);
gtk_window_set_keep_above (GTK_WINDOW (calib_area->window), TRUE);
#endif /* FAKE_AREA */
/* Set up the embedded stage */
clutter_embed = gtk_clutter_embed_new ();
gtk_container_add (GTK_CONTAINER (calib_area->window),
clutter_embed);
stage = gtk_clutter_embed_get_stage (GTK_CLUTTER_EMBED (clutter_embed));
/* Move to correct screen */
if (screen == NULL)
screen = gdk_screen_get_default ();
gdk_screen_get_monitor_geometry (screen, monitor, &rect);
gtk_window_move (GTK_WINDOW (calib_area->window), rect.x, rect.y);
gtk_window_set_default_size (GTK_WINDOW (calib_area->window),
rect.width,
rect.height);
set_up_stage (calib_area, stage);
g_signal_connect (calib_area->window,
"delete-event",
G_CALLBACK (on_delete_event),
calib_area);
g_signal_connect (calib_area->window,
"focus-out-event",
G_CALLBACK(on_focus_out_event),
calib_area);
g_signal_connect (calib_area->window,
"window-state-event",
G_CALLBACK (on_fullscreen),
calib_area);
gtk_window_fullscreen (GTK_WINDOW (calib_area->window));
visual = gdk_screen_get_rgba_visual (screen);
if (visual != NULL)
gtk_widget_set_visual (GTK_WIDGET (calib_area->window), visual);
gtk_widget_show_all (calib_area->window);
clutter_actor_hide (calib_area->action_layer);
return calib_area;
}
/* Finishes the calibration. Note that CalibArea
* needs to be destroyed with calib_area_free() afterwards */
gboolean
calib_area_finish (CalibArea *area,
XYinfo *new_axis,
gboolean *swap_xy)
{
g_return_val_if_fail (area != NULL, FALSE);
*new_axis = area->axis;
*swap_xy = area->swap;
if (area->success)
g_debug ("Final calibration: %d, %d, %d, %d\n",
new_axis->x_min,
new_axis->y_min,
new_axis->x_max,
new_axis->y_max);
else
g_debug ("Calibration was aborted or timed out");
return area->success;
}
void
calib_area_free (CalibArea *area)
{
g_return_if_fail (area != NULL);
g_clear_object (&area->icon_success);
gtk_widget_destroy (area->window);
g_free (area);
}
void
calib_area_get_display_size (CalibArea *area, gint *width, gint *height)
{
g_return_if_fail (area != NULL);
*width = area->display_width;
*height = area->display_height;
}