/* * Copyright (C) 2004-2006 Jimmy Do * * 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 */ #include "gloo-pulse-button.h" #include static GObjectClass *parent_class = NULL; struct _GlooPulseButtonPrivate { GdkPixbuf *screenshot; gdouble glow_start_time; guint button_glow; gboolean dispose_has_run; }; static void invalidate_button (GlooPulseButton *self) { /* If this method is invoked early, such as before the parent * widget is shown, then widget->window might be NULL. */ if (GTK_WIDGET (self)->window) { GdkRectangle rect; rect.x = 0; rect.y = 0; gdk_drawable_get_size (GTK_WIDGET (self)->window, &rect.width, &rect.height); gdk_window_invalidate_rect (GTK_WIDGET (self)->window, &rect, TRUE /* invalidate child windows */); } } /** * Unmodified code from libwnck:tasklist.c */ static GdkPixbuf * glow_pixbuf (const GdkPixbuf *source, gdouble factor) { gint i, j; gint width, height, has_alpha, rowstride; guchar *destination_data; guchar *source_data; guchar *source_pixel; guchar *destination_pixel; int channel_intensity; guchar r, g, b; GdkPixbuf *destination; has_alpha = gdk_pixbuf_get_has_alpha (source); width = gdk_pixbuf_get_width (source); height = gdk_pixbuf_get_height (source); rowstride = gdk_pixbuf_get_rowstride (source); source_data = gdk_pixbuf_get_pixels (source); destination = gdk_pixbuf_copy (source); destination_data = gdk_pixbuf_get_pixels (destination); for (i = 0; i < height; i++) { source_pixel = source_data + i * rowstride; destination_pixel = destination_data + i * rowstride; for (j = 0; j < width; j++) { enum { CHANNEL_RED = 0, CHANNEL_GREEN, CHANNEL_BLUE, CHANNELS_PER_COLOR_STIMULUS }; r = source_pixel[CHANNEL_RED]; g = source_pixel[CHANNEL_GREEN]; b = source_pixel[CHANNEL_BLUE]; source_pixel += CHANNELS_PER_COLOR_STIMULUS; channel_intensity = r * factor; destination_pixel[CHANNEL_RED] = CLAMP (channel_intensity, 0, 255); channel_intensity = g * factor; destination_pixel[CHANNEL_GREEN] = CLAMP (channel_intensity, 0, 255); channel_intensity = b * factor; destination_pixel[CHANNEL_BLUE] = CLAMP (channel_intensity, 0, 255); destination_pixel += CHANNELS_PER_COLOR_STIMULUS; if (has_alpha) { destination_pixel[0] = source_pixel[0]; source_pixel++; destination_pixel++; } } } return destination; } /** * Modified from libwnck:tasklist.c */ static gboolean on_button_glow (GlooPulseButton *self) { GdkPixbuf *glowing_screenshot; GTimeVal tv; gdouble glow_factor, now; if (self->priv->screenshot == NULL) return TRUE; g_get_current_time (&tv); now = (tv.tv_sec * (1.0 * G_USEC_PER_SEC) + tv.tv_usec) / G_USEC_PER_SEC; if (self->priv->glow_start_time <= G_MINDOUBLE) self->priv->glow_start_time = now; /* These numbers can probably be tweaked some. * The 1.0 is the first value it will get and .4 is * the "radius" from 1.0 that it will go; i.e., * it will go up from 1.0 to 1.4 then down from * 1.4 to .6, then back up again. The 2.0 in the * denomator is how many seconds it takes to travel * through the entire range of numbers. */ glow_factor = .4 * sin ((now - self->priv->glow_start_time) * (2.0 * M_PI) / 2.0) + 1.0; glowing_screenshot = glow_pixbuf (self->priv->screenshot, glow_factor); gdk_draw_pixbuf (GTK_WIDGET (self)->window, GTK_WIDGET (self)->style->fg_gc[GTK_WIDGET_STATE (self)], glowing_screenshot, 0, 0, GTK_WIDGET (self)->allocation.x, GTK_WIDGET (self)->allocation.y, gdk_pixbuf_get_width (glowing_screenshot), gdk_pixbuf_get_height (glowing_screenshot), GDK_RGB_DITHER_NORMAL, 0, 0); g_object_unref (glowing_screenshot); glowing_screenshot = NULL; return TRUE; } /** * Modified from libwnck:tasklist.c */ static void clear_glow_start_timeout_id (GlooPulseButton *self) { self->priv->button_glow = 0; } /** * Modified from libwnck:tasklist.c */ static void start_glow (GlooPulseButton *self) { if (self->priv->button_glow == 0) { self->priv->glow_start_time = 0.0; /* The animation doesn't speed up or slow down based on the * timeout value, but instead will just appear smoother or * choppier. To adjust the speed change the parameter to * sine in the timeout callback. */ self->priv->button_glow = g_timeout_add_full (G_PRIORITY_DEFAULT_IDLE, 50, (GSourceFunc) on_button_glow, self, (GDestroyNotify) clear_glow_start_timeout_id); /* Now that button_glow != 0, we need to trigger an * expose event to get the initial screenshot. */ invalidate_button (self); } } /** * Modified from libwnck:tasklist.c */ static void stop_glow (GlooPulseButton *self) { if (self->priv->button_glow != 0) { g_source_remove (self->priv->button_glow); self->priv->button_glow = 0; self->priv->glow_start_time = 0.0; /* Invalidate button to clear away the current pulse image. */ invalidate_button (self); } } static gboolean on_button_expose (GlooPulseButton *self, GdkEventExpose *event, gpointer data) { GtkWidget *self_widget; self_widget = GTK_WIDGET (self); /* Only get a new screenshot if the entire button * has been updated, so that we avoid grabbing * parts of the button that have been painted with * the pulse animation. */ if (event->area.x <= self_widget->allocation.x && event->area.y <= self_widget->allocation.y && event->area.width >= self_widget->allocation.width && event->area.height >= self_widget->allocation.height) { if (self->priv->screenshot) { g_object_unref (self->priv->screenshot); self->priv->screenshot = NULL; } if (self->priv->button_glow != 0) { self->priv->screenshot = gdk_pixbuf_get_from_drawable (NULL, GTK_WIDGET (self)->window, NULL, GTK_WIDGET (self)->allocation.x, GTK_WIDGET (self)->allocation.y, 0, 0, GTK_WIDGET (self)->allocation.width, GTK_WIDGET (self)->allocation.height); } } return FALSE /* don't stop event propogation */; } static void on_button_unrealize (GlooPulseButton *self, gpointer user_data) { stop_glow (self); } static void gloo_pulse_button_instance_init (GTypeInstance *instance, gpointer g_class) { GlooPulseButton *self; self = (GlooPulseButton *)instance; self->priv = g_new (GlooPulseButtonPrivate, 1); self->priv->glow_start_time = 0.0; self->priv->button_glow = 0; self->priv->screenshot = NULL; g_signal_connect_after (self, "expose-event", G_CALLBACK (on_button_expose), NULL); g_signal_connect (G_OBJECT (self), "unrealize", G_CALLBACK (on_button_unrealize), NULL); } static void gloo_pulse_button_destroy (GtkObject *obj) { GlooPulseButton *self; self = (GlooPulseButton *)obj; if (self->priv->screenshot) { g_object_unref (self->priv->screenshot); self->priv->screenshot = NULL; } stop_glow (self); GTK_OBJECT_CLASS (parent_class)->destroy (obj); } static void gloo_pulse_button_finalize (GObject *obj) { GlooPulseButton *self; self = (GlooPulseButton *)obj; g_free (self->priv); self->priv = NULL; G_OBJECT_CLASS (parent_class)->finalize (obj); } static void gloo_pulse_button_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { GlooPulseButton *self; self = (GlooPulseButton *)object; switch (property_id) { default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gloo_pulse_button_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { GlooPulseButton *self; self = (GlooPulseButton *)object; switch (property_id) { default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } void gloo_pulse_button_start_pulsing (GlooPulseButton *self) { start_glow (self); } void gloo_pulse_button_stop_pulsing (GlooPulseButton *self) { stop_glow (self); } gboolean gloo_pulse_button_is_pulsing (GlooPulseButton *self) { return self->priv->button_glow != 0; } /** Class-related functions **/ static void gloo_pulse_button_class_init (gpointer g_class, gpointer g_class_data) { GObjectClass *gobject_class; GtkObjectClass *object_class; GlooPulseButtonClass *klass; gobject_class = G_OBJECT_CLASS (g_class); object_class = GTK_OBJECT_CLASS (g_class); klass = GLOO_PULSE_BUTTON_CLASS (g_class); gobject_class->set_property = gloo_pulse_button_set_property; gobject_class->get_property = gloo_pulse_button_get_property; /* gobject_class->dispose = gloo_pulse_button_dispose; */ gobject_class->finalize = gloo_pulse_button_finalize; object_class->destroy = gloo_pulse_button_destroy; parent_class = g_type_class_peek_parent (klass); } GType gloo_pulse_button_get_type (void) { static GType type = 0; if (type == 0) { static const GTypeInfo info = { sizeof (GlooPulseButtonClass), NULL, NULL, gloo_pulse_button_class_init, NULL, NULL, sizeof (GlooPulseButton), 0, gloo_pulse_button_instance_init }; type = g_type_register_static (GTK_TYPE_BUTTON, "GlooPulseButtonType", &info, 0); } return type; }