/* GTK - The GIMP Toolkit * Copyright (C) 2012 Red Hat, Inc. * * 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 "config.h" #include "gtkhsv.h" #include "gtkcolorplane.h" struct _GtkColorPlanePrivate { cairo_surface_t *surface; gdouble h, s, v; gint x, y; gboolean in_drag; }; enum { CHANGED, LAST_SIGNAL }; guint signals[LAST_SIGNAL]; G_DEFINE_TYPE (GtkColorPlane, gtk_color_plane, GTK_TYPE_DRAWING_AREA) static gboolean sv_draw (GtkWidget *widget, cairo_t *cr) { GtkColorPlane *plane = GTK_COLOR_PLANE (widget); gint x, y; gint width, height; cairo_set_source_surface (cr, plane->priv->surface, 0, 0); cairo_paint (cr); x = plane->priv->x; y = plane->priv->y; width = gtk_widget_get_allocated_width (widget); height = gtk_widget_get_allocated_height (widget); cairo_move_to (cr, 0, y + 0.5); cairo_line_to (cr, width, y + 0.5); cairo_move_to (cr, x + 0.5, 0); cairo_line_to (cr, x + 0.5, height); if (gtk_widget_has_visible_focus (widget)) { cairo_set_line_width (cr, 3.0); cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.6); cairo_stroke_preserve (cr); cairo_set_line_width (cr, 1.0); cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.8); cairo_stroke (cr); } else { cairo_set_line_width (cr, 1.0); cairo_set_source_rgba (cr, 0.8, 0.8, 0.8, 0.8); cairo_stroke (cr); } return FALSE; } static void create_sv_surface (GtkColorPlane *plane) { GtkWidget *widget = GTK_WIDGET (plane); cairo_t *cr; cairo_surface_t *surface; gint width, height, stride; cairo_surface_t *tmp; guint red, green, blue; guint32 *data, *p; gdouble h, s, v; gdouble r, g, b; gdouble sf, vf; gint x, y; if (!gtk_widget_get_realized (widget)) return; width = gtk_widget_get_allocated_width (widget); height = gtk_widget_get_allocated_height (widget); surface = gdk_window_create_similar_surface (gtk_widget_get_window (widget), CAIRO_CONTENT_COLOR, width, height); if (plane->priv->surface) cairo_surface_destroy (plane->priv->surface); plane->priv->surface = surface; if (width == 1 || height == 1) return; stride = cairo_format_stride_for_width (CAIRO_FORMAT_RGB24, width); data = g_malloc (4 * height * stride); h = plane->priv->h; sf = 1.0 / (height - 1); vf = 1.0 / (width - 1); for (y = 0; y < height; y++) { s = CLAMP (1.0 - y * sf, 0.0, 1.0); p = data + y * (stride / 4); for (x = 0; x < width; x++) { v = x * vf; gtk_hsv_to_rgb (h, s, v, &r, &g, &b); red = CLAMP (r * 255, 0, 255); green = CLAMP (g * 255, 0, 255); blue = CLAMP (b * 255, 0, 255); p[x] = (red << 16) | (green << 8) | blue; } } tmp = cairo_image_surface_create_for_data ((guchar *)data, CAIRO_FORMAT_RGB24, width, height, stride); cr = cairo_create (surface); cairo_set_source_surface (cr, tmp, 0, 0); cairo_paint (cr); cairo_destroy (cr); cairo_surface_destroy (tmp); g_free (data); } static void hsv_to_xy (GtkColorPlane *plane) { gint width, height; width = gtk_widget_get_allocated_width (GTK_WIDGET (plane)); height = gtk_widget_get_allocated_height (GTK_WIDGET (plane)); plane->priv->x = CLAMP (width * plane->priv->v, 0, width - 1); plane->priv->y = CLAMP (height * (1 - plane->priv->s), 0, height - 1); } static gboolean sv_configure (GtkWidget *widget, GdkEventConfigure *event) { GtkColorPlane *plane = GTK_COLOR_PLANE (widget); create_sv_surface (plane); hsv_to_xy (plane); return TRUE; } static void set_cross_grab (GtkWidget *widget, GdkDevice *device, guint32 time) { GdkCursor *cursor; cursor = gdk_cursor_new_for_display (gtk_widget_get_display (GTK_WIDGET (widget)), GDK_CROSSHAIR); gdk_device_grab (device, gtk_widget_get_window (widget), GDK_OWNERSHIP_NONE, FALSE, GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_RELEASE_MASK, cursor, time); g_object_unref (cursor); } static gboolean sv_grab_broken (GtkWidget *widget, GdkEventGrabBroken *event) { GtkColorPlane *plane = GTK_COLOR_PLANE (widget); plane->priv->in_drag = FALSE; return TRUE; } static void sv_update_color (GtkColorPlane *plane, gint x, gint y) { GtkWidget *widget = GTK_WIDGET (plane); plane->priv->x = x; plane->priv->y = y; plane->priv->s = CLAMP (1 - y * (1.0 / gtk_widget_get_allocated_height (widget)), 0, 1); plane->priv->v = CLAMP (x * (1.0 / gtk_widget_get_allocated_width (widget)), 0, 1); g_signal_emit (plane, signals[CHANGED], 0); gtk_widget_queue_draw (widget); } static gboolean sv_button_press (GtkWidget *widget, GdkEventButton *event) { GtkColorPlane *plane = GTK_COLOR_PLANE (widget); if (plane->priv->in_drag || event->button != GDK_BUTTON_PRIMARY) return FALSE; plane->priv->in_drag = TRUE; set_cross_grab (widget, gdk_event_get_device ((GdkEvent*)event), event->time); sv_update_color (plane, event->x, event->y); gtk_widget_grab_focus (widget); return TRUE; } static gboolean sv_button_release (GtkWidget *widget, GdkEventButton *event) { GtkColorPlane *plane = GTK_COLOR_PLANE (widget); if (!plane->priv->in_drag || event->button != GDK_BUTTON_PRIMARY) return FALSE; plane->priv->in_drag = FALSE; sv_update_color (plane, event->x, event->y); gdk_device_ungrab (gdk_event_get_device ((GdkEvent *) event), event->time); return TRUE; } static gboolean sv_motion (GtkWidget *widget, GdkEventMotion *event) { GtkColorPlane *plane = GTK_COLOR_PLANE (widget); if (!plane->priv->in_drag) return FALSE; gdk_event_request_motions (event); sv_update_color (plane, event->x, event->y); return TRUE; } static void sv_move (GtkColorPlane *plane, gdouble ds, gdouble dv) { if (plane->priv->s + ds > 1) { if (plane->priv->s < 1) plane->priv->s = 1; else goto error; } else if (plane->priv->s + ds < 0) { if (plane->priv->s > 0) plane->priv->s = 0; else goto error; } else { plane->priv->s += ds; } if (plane->priv->v + dv > 1) { if (plane->priv->v < 1) plane->priv->v = 1; else goto error; } else if (plane->priv->v + dv < 0) { if (plane->priv->v > 0) plane->priv->v = 0; else goto error; } else { plane->priv->v += dv; } hsv_to_xy (plane); g_signal_emit (plane, signals[CHANGED], 0); gtk_widget_queue_draw (GTK_WIDGET (plane)); return; error: gtk_widget_error_bell (GTK_WIDGET (plane)); } static gboolean sv_key_press (GtkWidget *widget, GdkEventKey *event) { GtkColorPlane *plane = GTK_COLOR_PLANE (widget); gdouble step; /* FIXME: turn into bindings */ if ((event->state & GDK_MOD1_MASK) != 0) step = 0.1; else step = 0.01; if (event->keyval == GDK_KEY_Up || event->keyval == GDK_KEY_KP_Up) sv_move (plane, step, 0); else if (event->keyval == GDK_KEY_Down || event->keyval == GDK_KEY_KP_Down) sv_move (plane, -step, 0); else if (event->keyval == GDK_KEY_Left || event->keyval == GDK_KEY_KP_Left) sv_move (plane, 0, -step); else if (event->keyval == GDK_KEY_Right || event->keyval == GDK_KEY_KP_Right) sv_move (plane, 0, step); else return FALSE; return TRUE; } static void gtk_color_plane_init (GtkColorPlane *plane) { plane->priv = G_TYPE_INSTANCE_GET_PRIVATE (plane, GTK_TYPE_COLOR_PLANE, GtkColorPlanePrivate); gtk_widget_set_can_focus (GTK_WIDGET (plane), TRUE); gtk_widget_set_events (GTK_WIDGET (plane), GDK_KEY_PRESS_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK); } static void sv_finalize (GObject *object) { GtkColorPlane *plane = GTK_COLOR_PLANE (object); cairo_surface_destroy (plane->priv->surface); G_OBJECT_CLASS (gtk_color_plane_parent_class)->finalize (object); } static void gtk_color_plane_class_init (GtkColorPlaneClass *class) { GObjectClass *object_class = G_OBJECT_CLASS (class); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); object_class->finalize = sv_finalize; widget_class->draw = sv_draw; widget_class->configure_event = sv_configure; widget_class->button_press_event = sv_button_press; widget_class->button_release_event = sv_button_release; widget_class->motion_notify_event = sv_motion; widget_class->grab_broken_event = sv_grab_broken; widget_class->key_press_event = sv_key_press; signals[CHANGED] = g_signal_new ("changed", GTK_TYPE_COLOR_PLANE, G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GtkColorPlaneClass, changed), NULL, NULL, NULL, G_TYPE_NONE, 0); g_type_class_add_private (class, sizeof (GtkColorPlanePrivate)); } gdouble gtk_color_plane_get_h (GtkColorPlane *plane) { return plane->priv->h; } gdouble gtk_color_plane_get_s (GtkColorPlane *plane) { return plane->priv->s; } gdouble gtk_color_plane_get_v (GtkColorPlane *plane) { return plane->priv->v; } void gtk_color_plane_set_h (GtkColorPlane *plane, gdouble h) { plane->priv->h = h; create_sv_surface (plane); gtk_widget_queue_draw (GTK_WIDGET (plane)); } void gtk_color_plane_set_s (GtkColorPlane *plane, gdouble s) { plane->priv->s = s; hsv_to_xy (plane); gtk_widget_queue_draw (GTK_WIDGET (plane)); } void gtk_color_plane_set_v (GtkColorPlane *plane, gdouble v) { plane->priv->v = v; hsv_to_xy (plane); gtk_widget_queue_draw (GTK_WIDGET (plane)); } GtkWidget * gtk_color_plane_new (void) { return (GtkWidget *) g_object_new (GTK_TYPE_COLOR_PLANE, NULL); }