/*
* 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, see .
*
* Author: Carlos Garnacho
* (based on previous work by Joaquim Rocha, Tias Guns and Soren Hauberg)
*/
#include "config.h"
#include
#include
#include
#include
#include
#include
#include "calibrator.h"
#include "calibrator-gui.h"
#include "cc-clock.h"
struct _CcCalibArea
{
GtkWindow parent_instance;
struct Calib calibrator;
XYinfo axis;
gboolean swap;
gboolean success;
GdkDevice *device;
GtkWidget *error_revealer;
GtkWidget *title_revealer;
GtkWidget *subtitle_revealer;
GtkWidget *clock;
GtkWidget *target1, *target2, *target3, *target4;
GtkWidget *stack;
GtkWidget *success_page;
GtkCssProvider *style_provider;
FinishCallback callback;
gpointer user_data;
};
G_DEFINE_TYPE (CcCalibArea, cc_calib_area, GTK_TYPE_WINDOW)
/* Timeout parameters */
#define MAX_TIME 15000 /* 15000 = 15 sec */
#define END_TIME 750 /* 750 = 0.75 sec */
static void
cc_calib_area_notify_finish (CcCalibArea *area)
{
gtk_widget_hide (GTK_WIDGET (area));
(*area->callback) (area, area->user_data);
}
static gboolean
on_close_request (GtkWidget *widget,
CcCalibArea *area)
{
cc_calib_area_notify_finish (area);
return GDK_EVENT_PROPAGATE;
}
static gboolean
cc_calib_area_finish_idle_cb (CcCalibArea *area)
{
cc_calib_area_notify_finish (area);
return FALSE;
}
static void
set_success (CcCalibArea *area)
{
gtk_stack_set_visible_child (GTK_STACK (area->stack), area->success_page);
}
static void
set_calibration_status (CcCalibArea *area)
{
area->success = finish (&area->calibrator, &area->axis, &area->swap);
if (area->success)
{
set_success (area);
g_timeout_add (END_TIME,
(GSourceFunc) cc_calib_area_finish_idle_cb,
area);
}
else
{
g_idle_add ((GSourceFunc) cc_calib_area_finish_idle_cb, area);
}
}
static void
show_error_message (CcCalibArea *area)
{
gtk_revealer_set_reveal_child (GTK_REVEALER (area->error_revealer), TRUE);
}
static void
hide_error_message (CcCalibArea *area)
{
gtk_revealer_set_reveal_child (GTK_REVEALER (area->error_revealer), FALSE);
}
static void
set_active_target (CcCalibArea *area,
int n_target)
{
GtkWidget *targets[] = {
area->target1,
area->target2,
area->target3,
area->target4,
};
int i;
for (i = 0; i < G_N_ELEMENTS (targets); i++)
gtk_widget_set_sensitive (targets[i], i == n_target);
}
static void
on_gesture_press (GtkGestureClick *gesture,
guint n_press,
gdouble x,
gdouble y,
CcCalibArea *area)
{
gint num_clicks;
gboolean success;
GdkDevice *source;
if (area->success)
return;
source = gtk_gesture_get_device (GTK_GESTURE (gesture));
if (gdk_device_get_source (source) == GDK_SOURCE_TOUCHSCREEN)
return;
/* Check matching device if a device was provided */
if (area->device && area->device != source)
{
g_debug ("Ignoring input from device %s",
gdk_device_get_name (source));
return;
}
/* Handle click */
/* FIXME: reset clock */
success = add_click(&area->calibrator,
(int) x,
(int) y);
num_clicks = area->calibrator.num_clicks;
if (!success && num_clicks == 0)
show_error_message (area);
else
hide_error_message (area);
/* Are we done yet? */
if (num_clicks >= 4)
{
set_calibration_status (area);
return;
}
set_active_target (area, num_clicks);
}
static gboolean
on_key_release (GtkEventControllerKey *controller,
guint keyval,
guint keycode,
GdkModifierType state,
CcCalibArea *area)
{
if (area->success || keyval != GDK_KEY_Escape)
return GDK_EVENT_PROPAGATE;
cc_calib_area_notify_finish (area);
return GDK_EVENT_STOP;
}
static void
on_clock_finished (CcClock *clock,
CcCalibArea *area)
{
set_calibration_status (area);
}
static void
on_title_revealed (CcCalibArea *area)
{
gtk_revealer_set_reveal_child (GTK_REVEALER (area->subtitle_revealer), TRUE);
}
static void
on_fullscreen (GtkWindow *window,
GParamSpec *pspec,
CcCalibArea *area)
{
if (!gtk_window_is_fullscreen (window))
return;
g_signal_connect_swapped (area->title_revealer,
"notify::child-revealed",
G_CALLBACK (on_title_revealed),
area);
gtk_revealer_set_reveal_child (GTK_REVEALER (area->title_revealer), TRUE);
set_active_target (area, 0);
}
static void
cc_calib_area_finalize (GObject *object)
{
CcCalibArea *area = CC_CALIB_AREA (object);
gtk_style_context_remove_provider_for_display (gtk_widget_get_display (GTK_WIDGET (area)),
GTK_STYLE_PROVIDER (area->style_provider));
G_OBJECT_CLASS (cc_calib_area_parent_class)->finalize (object);
}
static void
cc_calib_area_size_allocate (GtkWidget *widget,
int width,
int height,
int baseline)
{
CcCalibArea *calib_area = CC_CALIB_AREA (widget);
if (calib_area->calibrator.geometry.width != width ||
calib_area->calibrator.geometry.height != height)
{
calib_area->calibrator.geometry.width = width;
calib_area->calibrator.geometry.height = height;
/* reset calibration if already started */
reset (&calib_area->calibrator);
set_active_target (calib_area, 0);
}
GTK_WIDGET_CLASS (cc_calib_area_parent_class)->size_allocate (widget,
width,
height,
baseline);
}
static void
cc_calib_area_class_init (CcCalibAreaClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->finalize = cc_calib_area_finalize;
widget_class->size_allocate = cc_calib_area_size_allocate;
g_type_ensure (CC_TYPE_CLOCK);
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/wacom/calibrator/calibrator.ui");
gtk_widget_class_bind_template_child (widget_class, CcCalibArea, error_revealer);
gtk_widget_class_bind_template_child (widget_class, CcCalibArea, title_revealer);
gtk_widget_class_bind_template_child (widget_class, CcCalibArea, subtitle_revealer);
gtk_widget_class_bind_template_child (widget_class, CcCalibArea, clock);
gtk_widget_class_bind_template_child (widget_class, CcCalibArea, target1);
gtk_widget_class_bind_template_child (widget_class, CcCalibArea, target2);
gtk_widget_class_bind_template_child (widget_class, CcCalibArea, target3);
gtk_widget_class_bind_template_child (widget_class, CcCalibArea, target4);
gtk_widget_class_bind_template_child (widget_class, CcCalibArea, stack);
gtk_widget_class_bind_template_child (widget_class, CcCalibArea, success_page);
}
static void
cc_calib_area_init (CcCalibArea *calib_area)
{
GtkGesture *click;
GtkEventController *key;
gtk_widget_init_template (GTK_WIDGET (calib_area));
calib_area->style_provider = gtk_css_provider_new ();
gtk_css_provider_load_from_resource (calib_area->style_provider, "/org/gnome/control-center/wacom/calibrator/calibrator.css");
gtk_style_context_add_provider_for_display (gtk_widget_get_display (GTK_WIDGET (calib_area)),
GTK_STYLE_PROVIDER (calib_area->style_provider),
GTK_STYLE_PROVIDER_PRIORITY_USER);
cc_clock_set_duration (CC_CLOCK (calib_area->clock), MAX_TIME);
g_signal_connect (calib_area->clock, "finished",
G_CALLBACK (on_clock_finished), calib_area);
#ifndef FAKE_AREA
/* No cursor */
gtk_widget_realize (GTK_WIDGET (calib_area));
gtk_widget_set_cursor_from_name (GTK_WIDGET (calib_area), "blank");
gtk_widget_set_can_focus (GTK_WIDGET (calib_area), TRUE);
#endif /* FAKE_AREA */
g_signal_connect (calib_area,
"close-request",
G_CALLBACK (on_close_request),
calib_area);
g_signal_connect (calib_area,
"notify::fullscreened",
G_CALLBACK (on_fullscreen),
calib_area);
click = gtk_gesture_click_new ();
gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (click), GDK_BUTTON_PRIMARY);
g_signal_connect (click, "pressed",
G_CALLBACK (on_gesture_press), calib_area);
gtk_widget_add_controller (GTK_WIDGET (calib_area),
GTK_EVENT_CONTROLLER (click));
key = gtk_event_controller_key_new ();
g_signal_connect (key, "key-released",
G_CALLBACK (on_key_release), calib_area);
gtk_widget_add_controller (GTK_WIDGET (calib_area), key);
}
/**
* 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
* cc_calib_area_finish().
*/
CcCalibArea *
cc_calib_area_new (GdkDisplay *display,
GdkMonitor *monitor,
GdkDevice *device,
FinishCallback callback,
gpointer user_data,
int threshold_doubleclick,
int threshold_misclick)
{
CcCalibArea *calib_area;
g_return_val_if_fail (callback, NULL);
calib_area = g_object_new (CC_TYPE_CALIB_AREA, NULL);
calib_area->callback = callback;
calib_area->user_data = user_data;
calib_area->device = device;
calib_area->calibrator.threshold_doubleclick = threshold_doubleclick;
calib_area->calibrator.threshold_misclick = threshold_misclick;
/* Move to correct screen */
if (monitor)
gtk_window_fullscreen_on_monitor (GTK_WINDOW (calib_area), monitor);
else
gtk_window_fullscreen (GTK_WINDOW (calib_area));
gtk_widget_show (GTK_WIDGET (calib_area));
return calib_area;
}
/* Finishes the calibration. Note that CalibArea
* needs to be destroyed with Cccalib_area_free() afterwards */
gboolean
cc_calib_area_finish (CcCalibArea *area)
{
g_return_val_if_fail (area != NULL, FALSE);
if (area->success)
g_debug ("Final calibration: %f, %f, %f, %f\n",
area->axis.x_min,
area->axis.y_min,
area->axis.x_max,
area->axis.y_max);
else
g_debug ("Calibration was aborted or timed out");
return area->success;
}
void
cc_calib_area_free (CcCalibArea *area)
{
gtk_window_destroy (GTK_WINDOW (area));
}
void
cc_calib_area_get_axis (CcCalibArea *area,
XYinfo *new_axis,
gboolean *swap_xy)
{
g_return_if_fail (area != NULL);
*new_axis = area->axis;
*swap_xy = area->swap;
}
void
cc_calib_area_get_padding (CcCalibArea *area,
XYinfo *padding)
{
g_return_if_fail (area != NULL);
/* min/max values are monitor coordinates scaled to be between
* 0 and 1, padding starts at 0 on "the edge", and positive
* values grow towards the center of the rectangle.
*/
padding->x_min = area->axis.x_min;
padding->y_min = area->axis.y_min;
padding->x_max = 1 - area->axis.x_max;
padding->y_max = 1 - area->axis.y_max;
}