/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8; tab-width: 8 -*- * * Copyright (C) 2005 William Jon McCann * * 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., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301, USA. * */ #include "config.h" #include #include #include #include #include #include #include "gs-theme-engine.h" #include "gste-popsquares.h" static void gste_popsquares_class_init (GSTEPopsquaresClass *klass); static void gste_popsquares_init (GSTEPopsquares *engine); static void gste_popsquares_finalize (GObject *object); static void draw_frame (GSTEPopsquares *pop, cairo_t *cr); typedef struct _square { int x, y, w, h; int color; } square; struct GSTEPopsquaresPrivate { guint timeout_id; int ncolors; int subdivision; GdkColor *colors; square *squares; }; #define GSTE_POPSQUARES_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSTE_TYPE_POPSQUARES, GSTEPopsquaresPrivate)) static GObjectClass *parent_class = NULL; G_DEFINE_TYPE (GSTEPopsquares, gste_popsquares, GS_TYPE_THEME_ENGINE) static void hsv_to_rgb (int h, double s, double v, unsigned short *r, unsigned short *g, unsigned short *b) { double H, S, V, R, G, B; double p1, p2, p3; double f; int i; if (s < 0) { s = 0; } if (v < 0) { v = 0; } if (s > 1) { s = 1; } if (v > 1) { v = 1; } S = s; V = v; H = (h % 360) / 60.0; i = H; f = H - i; p1 = V * (1 - S); p2 = V * (1 - (S * f)); p3 = V * (1 - (S * (1 - f))); if (i == 0) { R = V; G = p3; B = p1; } else if (i == 1) { R = p2; G = V; B = p1; } else if (i == 2) { R = p1; G = V; B = p3; } else if (i == 3) { R = p1; G = p2; B = V; } else if (i == 4) { R = p3; G = p1; B = V; } else { R = V; G = p1; B = p2; } *r = R * 65535; *g = G * 65535; *b = B * 65535; } static void rgb_to_hsv (unsigned short r, unsigned short g, unsigned short b, int *h, double *s, double *v) { double R, G, B, H, S, V; double cmax, cmin; double cmm; int imax; R = ((double) r) / 65535.0; G = ((double) g) / 65535.0; B = ((double) b) / 65535.0; cmax = R; cmin = G; imax = 1; if (cmax < G) { cmax = G; cmin = R; imax = 2; } if (cmax < B) { cmax = B; imax = 3; } if (cmin > B) { cmin = B; } cmm = cmax - cmin; V = cmax; if (cmm == 0) { S = H = 0; } else { S = cmm / cmax; if (imax == 1) { H = (G - B) / cmm; } else if (imax == 2) { H = 2.0 + (B - R) / cmm; } else { /*if (imax == 3)*/ H = 4.0 + (R - G) / cmm; } if (H < 0) { H += 6.0; } } *h = (H * 60.0); *s = S; *v = V; } static void #if GTK_CHECK_VERSION (3, 0, 0) make_color_ramp (int h1, #else make_color_ramp (GdkColormap *colormap, int h1, #endif double s1, double v1, int h2, double s2, double v2, GdkColor *colors, int n_colors, #if GTK_CHECK_VERSION (3, 0, 0) gboolean closed) #else gboolean closed, gboolean allocate, gboolean writable) #endif { double dh, ds, dv; /* deltas */ int i; int ncolors, wanted; int total_ncolors = n_colors; wanted = total_ncolors; if (closed) { wanted = (wanted / 2) + 1; } ncolors = total_ncolors; memset (colors, 0, n_colors * sizeof (*colors)); if (closed) { ncolors = (ncolors / 2) + 1; } /* Note: unlike other routines in this module, this function assumes that if h1 and h2 are more than 180 degrees apart, then the desired direction is always from h1 to h2 (rather than the shorter path.) make_uniform depends on this. */ dh = ((double)h2 - (double)h1) / ncolors; ds = (s2 - s1) / ncolors; dv = (v2 - v1) / ncolors; for (i = 0; i < ncolors; i++) { hsv_to_rgb ((int) (h1 + (i * dh)), (s1 + (i * ds)), (v1 + (i * dv)), &colors [i].red, &colors [i].green, &colors [i].blue); #if !GTK_CHECK_VERSION (3, 0, 0) if (allocate) { gdk_colormap_alloc_color (colormap, &colors [i], writable, TRUE); } #endif } if (closed) { for (i = ncolors; i < n_colors; i++) { colors [i] = colors [n_colors - i]; } } } static void randomize_square_colors (square *squares, int nsquares, int ncolors) { int i; square *s; s = squares; for (i = 0; i < nsquares; i++) { s[i].color = g_random_int_range (0, ncolors); } } static void set_colors (GdkWindow *window, GdkColor *fg, GdkColor *bg) { GtkWidget *widget; GdkColor color; widget = gtk_invisible_new (); color = gtk_widget_get_style (widget)->dark [GTK_STATE_SELECTED]; fg->red = color.red; fg->green = color.green; fg->blue = color.blue; color = gtk_widget_get_style (widget)->bg [GTK_STATE_SELECTED]; bg->red = color.red; bg->green = color.green; bg->blue = color.blue; } static void gste_popsquares_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GSTE_POPSQUARES (object); switch (prop_id) { default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gste_popsquares_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GSTE_POPSQUARES (object); switch (prop_id) { default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void setup_squares (GSTEPopsquares *pop) { int window_width; int window_height; int nsquares; int x, y; int sw, sh, gw, gh; GdkWindow *window; window = gs_theme_engine_get_window (GS_THEME_ENGINE (pop)); if (window == NULL) { return; } gs_theme_engine_get_window_size (GS_THEME_ENGINE (pop), &window_width, &window_height); sw = window_width / pop->priv->subdivision; sh = window_height / pop->priv->subdivision; gw = pop->priv->subdivision; gh = pop->priv->subdivision; nsquares = gw * gh; if (pop->priv->squares) { g_free (pop->priv->squares); } pop->priv->squares = g_new0 (square, nsquares); for (y = 0; y < gh; y++) { for (x = 0; x < gw; x++) { square *s = (square *) &pop->priv->squares [gw * y + x]; s->w = sw; s->h = sh; s->x = x * sw; s->y = y * sh; } } } static void setup_colors (GSTEPopsquares *pop) { double s1, v1, s2, v2 = 0; int h1, h2 = 0; int nsquares; GdkColor fg; GdkColor bg; GdkWindow *window; window = gs_theme_engine_get_window (GS_THEME_ENGINE (pop)); if (window == NULL) { return; } set_colors (window, &fg, &bg); if (pop->priv->colors) { g_free (pop->priv->colors); } pop->priv->colors = g_new0 (GdkColor, pop->priv->ncolors); rgb_to_hsv (fg.red, fg.green, fg.blue, &h1, &s1, &v1); rgb_to_hsv (bg.red, bg.green, bg.blue, &h2, &s2, &v2); #if GTK_CHECK_VERSION (3, 0, 0) make_color_ramp (h1, s1, v1, h2, s2, v2, pop->priv->colors, pop->priv->ncolors, TRUE); #else make_color_ramp (gtk_widget_get_colormap (GTK_WIDGET (pop)), h1, s1, v1, h2, s2, v2, pop->priv->colors, pop->priv->ncolors, TRUE, TRUE, FALSE); #endif nsquares = pop->priv->subdivision * pop->priv->subdivision; randomize_square_colors (pop->priv->squares, nsquares, pop->priv->ncolors); } static void gste_popsquares_real_show (GtkWidget *widget) { GSTEPopsquares *pop = GSTE_POPSQUARES (widget); /* start */ setup_squares (pop); setup_colors (pop); if (GTK_WIDGET_CLASS (parent_class)->show) { GTK_WIDGET_CLASS (parent_class)->show (widget); } } static gboolean #if GTK_CHECK_VERSION (3, 0, 0) gste_popsquares_real_draw (GtkWidget *widget, cairo_t *cr) { if (GTK_WIDGET_CLASS (parent_class)->draw) { GTK_WIDGET_CLASS (parent_class)->draw (widget, cr); } #else gste_popsquares_real_expose (GtkWidget *widget, GdkEventExpose *event) { if (GTK_WIDGET_CLASS (parent_class)->expose_event) { GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event); } cairo_t *cr = gdk_cairo_create (event->window); #endif draw_frame (GSTE_POPSQUARES (widget), cr); #if !GTK_CHECK_VERSION (3, 0, 0) cairo_destroy (cr); #endif return TRUE; } static gboolean gste_popsquares_real_configure (GtkWidget *widget, GdkEventConfigure *event) { GSTEPopsquares *pop = GSTE_POPSQUARES (widget); gboolean handled = FALSE; /* resize */ /* just reset everything */ setup_squares (pop); setup_colors (pop); /* schedule a redraw */ gtk_widget_queue_draw (widget); if (GTK_WIDGET_CLASS (parent_class)->configure_event) { handled = GTK_WIDGET_CLASS (parent_class)->configure_event (widget, event); } return handled; } static void gste_popsquares_class_init (GSTEPopsquaresClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); parent_class = g_type_class_peek_parent (klass); object_class->finalize = gste_popsquares_finalize; object_class->get_property = gste_popsquares_get_property; object_class->set_property = gste_popsquares_set_property; widget_class->show = gste_popsquares_real_show; #if GTK_CHECK_VERSION (3, 0, 0) widget_class->draw = gste_popsquares_real_draw; #else widget_class->expose_event = gste_popsquares_real_expose; #endif widget_class->configure_event = gste_popsquares_real_configure; g_type_class_add_private (klass, sizeof (GSTEPopsquaresPrivate)); } static void draw_frame (GSTEPopsquares *pop, cairo_t *cr) { int border = 1; gboolean twitch = FALSE; int x, y; int gw, gh; int nsquares; int window_width; int window_height; GdkWindow *window; window = gs_theme_engine_get_window (GS_THEME_ENGINE (pop)); if (window == NULL) { return; } gs_theme_engine_get_window_size (GS_THEME_ENGINE (pop), &window_width, &window_height); gw = pop->priv->subdivision; gh = pop->priv->subdivision; nsquares = gw * gh; for (y = 0; y < gh; y++) { for (x = 0; x < gw; x++) { square *s = (square *) &pop->priv->squares [gw * y + x]; gdk_cairo_set_source_color (cr, &(pop->priv->colors [s->color])); cairo_rectangle (cr, s->x, s->y, border ? s->w - border : s->w, border ? s->h - border : s->h); cairo_fill (cr); s->color++; if (s->color == pop->priv->ncolors) { if (twitch && ((g_random_int_range (0, 4)) == 0)) { randomize_square_colors (pop->priv->squares, nsquares, pop->priv->ncolors); } else { s->color = g_random_int_range (0, pop->priv->ncolors); } } } } } static gboolean draw_iter (GSTEPopsquares *pop) { gtk_widget_queue_draw (GTK_WIDGET (pop)); return TRUE; } static void gste_popsquares_init (GSTEPopsquares *pop) { int delay; pop->priv = GSTE_POPSQUARES_GET_PRIVATE (pop); pop->priv->ncolors = 128; pop->priv->subdivision = 5; delay = 25; pop->priv->timeout_id = g_timeout_add (delay, (GSourceFunc)draw_iter, pop); } static void gste_popsquares_finalize (GObject *object) { GSTEPopsquares *pop; g_return_if_fail (object != NULL); g_return_if_fail (GSTE_IS_POPSQUARES (object)); pop = GSTE_POPSQUARES (object); g_return_if_fail (pop->priv != NULL); if (pop->priv->timeout_id > 0) { g_source_remove (pop->priv->timeout_id); pop->priv->timeout_id = 0; } g_free (pop->priv->squares); g_free (pop->priv->colors); G_OBJECT_CLASS (parent_class)->finalize (object); }