summaryrefslogtreecommitdiff
path: root/gtkknob.c
diff options
context:
space:
mode:
authorAndrej Shadura <andrew.shadura@collabora.co.uk>2021-10-07 16:12:09 +0200
committerAndrej Shadura <andrew.shadura@collabora.co.uk>2021-10-07 16:12:09 +0200
commitd4700c96fd551eea539921572c03842ecdc6c8c3 (patch)
treeac37ec6ef7d954feab5ad47c427e849f051da604 /gtkknob.c
Import Upstream version 0.2.0+hg20110905r195
Diffstat (limited to 'gtkknob.c')
-rw-r--r--gtkknob.c919
1 files changed, 919 insertions, 0 deletions
diff --git a/gtkknob.c b/gtkknob.c
new file mode 100644
index 0000000..d64a467
--- /dev/null
+++ b/gtkknob.c
@@ -0,0 +1,919 @@
+/*****************************************************************************
+ *
+ * Most of this code comes from gAlan 0.2.0, copyright (C) 1999
+ * Tony Garnock-Jones, with modifications from Sean Bolton,
+ * copyright (C) 2004, William Weston copyright (C) 2007,
+ * Pete Shorthose copyright (C) 2007, and Tomasz Moń,
+ * copyright (C) 2009
+ *
+ * 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 <math.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include "gtkknob.h"
+
+
+#ifndef M_PI
+# define M_PI 3.14159265358979323846 /* pi */
+#endif
+#ifndef M_1_PI
+# define M_1_PI 0.31830988618379067154 /* 1/pi */
+#endif
+
+
+#define SCROLL_DELAY_LENGTH 250
+#define KNOB_SIZE 32 /* this should really be read from the knob image */
+
+#define STATE_IDLE 0
+#define STATE_PRESSED 1
+#define STATE_DRAGGING 2
+
+static void gtk_knob_class_init(GtkKnobClass *klass);
+static void gtk_knob_init(GtkKnob *knob);
+static void gtk_knob_destroy(GtkObject *object);
+static void gtk_knob_realize(GtkWidget *widget);
+static void gtk_knob_size_request(GtkWidget *widget, GtkRequisition *requisition);
+static void gtk_knob_size_allocate(GtkWidget *widget, GtkAllocation *allocation);
+static gint gtk_knob_expose(GtkWidget *widget, GdkEventExpose *event);
+static gint gtk_knob_scroll(GtkWidget *widget, GdkEventScroll *event);
+static gint gtk_knob_button_press(GtkWidget *widget, GdkEventButton *event);
+static gint gtk_knob_button_release(GtkWidget *widget, GdkEventButton *event);
+static gint gtk_knob_key_press(GtkWidget *widget, GdkEventKey *event);
+static gint gtk_knob_motion_notify(GtkWidget *widget, GdkEventMotion *event);
+static gint gtk_knob_timer(GtkKnob *knob);
+
+static void gtk_knob_update_mouse_update(GtkKnob *knob);
+static void gtk_knob_update_mouse(GtkKnob *knob, gint x, gint y, gboolean step);
+static void gtk_knob_update(GtkKnob *knob);
+static void gtk_knob_adjustment_changed(GtkAdjustment *adjustment, gpointer data);
+static void gtk_knob_adjustment_value_changed(GtkAdjustment *adjustment, gpointer data);
+
+/* Local data */
+
+static GtkWidgetClass *parent_class = NULL;
+
+
+/*****************************************************************************
+ *
+ * gtk_knob_get_type()
+ *
+ *****************************************************************************/
+GType
+gtk_knob_get_type(void) {
+ static GType knob_type = 0;
+
+ if (!knob_type) {
+ static const GTypeInfo knob_info = {
+ sizeof (GtkKnobClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gtk_knob_class_init,
+ NULL,
+ NULL,
+ sizeof (GtkKnob),
+ 0,
+ (GInstanceInitFunc) gtk_knob_init,
+ };
+ knob_type = g_type_register_static (GTK_TYPE_WIDGET, "GtkKnob", &knob_info, 0);
+ }
+
+ return knob_type;
+}
+
+
+/*****************************************************************************
+ *
+ * gtk_knob_class_init()
+ *
+ *****************************************************************************/
+static void
+gtk_knob_class_init (GtkKnobClass *klass) {
+ GtkObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+
+ object_class = (GtkObjectClass*) klass;
+ widget_class = (GtkWidgetClass*) klass;
+
+ parent_class = g_type_class_peek_parent(klass);
+
+ object_class->destroy = gtk_knob_destroy;
+
+ widget_class->realize = gtk_knob_realize;
+ widget_class->expose_event = gtk_knob_expose;
+ widget_class->size_request = gtk_knob_size_request;
+ widget_class->size_allocate = gtk_knob_size_allocate;
+ widget_class->scroll_event = gtk_knob_scroll;
+ widget_class->button_press_event = gtk_knob_button_press;
+ widget_class->button_release_event = gtk_knob_button_release;
+ widget_class->key_press_event = gtk_knob_key_press;
+ widget_class->motion_notify_event = gtk_knob_motion_notify;
+}
+
+
+/*****************************************************************************
+ *
+ * gtk_knob_init()
+ *
+ *****************************************************************************/
+static void
+gtk_knob_init (GtkKnob *knob) {
+ knob->policy = GTK_UPDATE_CONTINUOUS;
+ knob->state = STATE_IDLE;
+ knob->saved_x = 0;
+ knob->saved_y = 0;
+ knob->timer = 0;
+ knob->anim = NULL;
+ knob->mask = NULL;
+ knob->mask_gc = NULL;
+ knob->red_gc = NULL;
+ knob->old_value = 0.0;
+ knob->old_lower = 0.0;
+ knob->old_upper = 0.0;
+ knob->adjustment = NULL;
+}
+
+
+/*****************************************************************************
+ *
+ * gtk_knob_new()
+ *
+ *****************************************************************************/
+GtkWidget *
+gtk_knob_new(GtkAdjustment *adjustment, GtkKnobAnim *anim) {
+ GtkKnob *knob;
+
+ g_return_val_if_fail (anim != NULL, NULL);
+ g_return_val_if_fail (GDK_IS_PIXBUF (anim->pixbuf), NULL);
+
+ knob = g_object_new (gtk_knob_get_type (), NULL);
+
+ gtk_knob_set_animation (knob, anim);
+
+ if (!adjustment) {
+ adjustment = (GtkAdjustment*) gtk_adjustment_new (0.0, 0.0, 0.0,
+ 0.0, 0.0, 0.0);
+ }
+
+ gtk_knob_set_adjustment (knob, adjustment);
+
+ return GTK_WIDGET (knob);
+}
+
+
+/*****************************************************************************
+ *
+ * gtk_knob_destroy()
+ *
+ *****************************************************************************/
+static void
+gtk_knob_destroy(GtkObject *object) {
+ GtkKnob *knob;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GTK_IS_KNOB (object));
+
+ knob = GTK_KNOB (object);
+
+ gtk_knob_set_adjustment (knob, NULL);
+ /* FIXME: needs ref counting for automatic GtkKnobAnim cleanup
+ if (knob->anim) {
+ gtk_knob_anim_unref (knob->anim);
+ knob->anim = NULL;
+ }
+ */
+
+ if (knob->mask) {
+ g_object_unref (knob->mask);
+ knob->mask = NULL;
+ }
+
+ if (knob->mask_gc) {
+ g_object_unref (knob->mask_gc);
+ knob->mask_gc = NULL;
+ }
+ if (knob->red_gc) {
+ g_object_unref (knob->red_gc);
+ knob->red_gc = NULL;
+ }
+
+ if (GTK_OBJECT_CLASS (parent_class)->destroy) {
+ (*GTK_OBJECT_CLASS (parent_class)->destroy) (object);
+ }
+}
+
+
+/*****************************************************************************
+ *
+ * gtk_knob_get_adjustment()
+ *
+ *****************************************************************************/
+GtkAdjustment*
+gtk_knob_get_adjustment(GtkKnob *knob) {
+
+ g_return_val_if_fail (knob != NULL, NULL);
+ g_return_val_if_fail (GTK_IS_KNOB (knob), NULL);
+
+ return knob->adjustment;
+}
+
+
+/*****************************************************************************
+ *
+ * gtk_knob_set_update_policy()
+ *
+ *****************************************************************************/
+void
+gtk_knob_set_update_policy(GtkKnob *knob, GtkUpdateType policy) {
+
+ g_return_if_fail (knob != NULL);
+ g_return_if_fail (GTK_IS_KNOB (knob));
+
+ knob->policy = policy;
+}
+
+
+/*****************************************************************************
+ *
+ * gtk_knob_set_adjustment()
+ *
+ *****************************************************************************/
+void
+gtk_knob_set_adjustment(GtkKnob *knob, GtkAdjustment *adjustment) {
+
+ g_return_if_fail (knob != NULL);
+ g_return_if_fail (GTK_IS_KNOB (knob));
+
+ if (knob->adjustment) {
+ g_signal_handlers_disconnect_matched(knob->adjustment,
+ G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL,
+ knob);
+ g_object_unref (knob->adjustment);
+ }
+
+ knob->adjustment = adjustment;
+ if (adjustment) {
+ g_object_ref_sink (adjustment);
+
+ g_signal_connect (adjustment, "changed",
+ G_CALLBACK(gtk_knob_adjustment_changed),
+ knob);
+ g_signal_connect (adjustment, "value_changed",
+ G_CALLBACK(gtk_knob_adjustment_value_changed),
+ knob);
+
+ knob->old_value = adjustment->value;
+ knob->old_lower = adjustment->lower;
+ knob->old_upper = adjustment->upper;
+
+ gtk_knob_update (knob);
+ }
+}
+
+
+/*****************************************************************************
+ *
+ * gtk_knob_realize()
+ *
+ *****************************************************************************/
+static void
+gtk_knob_realize(GtkWidget *widget) {
+ GtkKnob *knob;
+ GdkWindowAttr attributes;
+ gint attributes_mask;
+ GdkColor color = { 0, 0xffff, 0, 0 };
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_KNOB (widget));
+
+ GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
+ GTK_WIDGET_SET_FLAGS (widget, GTK_CAN_FOCUS);
+ knob = GTK_KNOB (widget);
+
+ attributes.x = widget->allocation.x;
+ attributes.y = widget->allocation.y;
+ attributes.width = widget->allocation.width;
+ attributes.height = widget->allocation.height;
+ attributes.wclass = GDK_INPUT_OUTPUT;
+ attributes.window_type = GDK_WINDOW_CHILD;
+ attributes.event_mask =
+ gtk_widget_get_events (widget) |
+ GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK |
+ GDK_POINTER_MOTION_HINT_MASK;
+ attributes.visual = gtk_widget_get_visual (widget);
+ attributes.colormap = gtk_widget_get_colormap (widget);
+
+ attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
+
+ widget->window = gdk_window_new (widget->parent->window,
+ &attributes, attributes_mask);
+
+ widget->style = gtk_style_attach (widget->style, widget->window);
+
+ gdk_window_set_user_data (widget->window, widget);
+
+ gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
+
+ knob->mask_gc = gdk_gc_new (widget->window);
+ gdk_gc_copy (knob->mask_gc, widget->style->bg_gc[GTK_STATE_NORMAL]);
+ gdk_gc_set_clip_mask (knob->mask_gc, knob->mask);
+
+ knob->red_gc = gdk_gc_new (widget->window);
+ gdk_gc_copy (knob->red_gc, widget->style->bg_gc[GTK_STATE_NORMAL]);
+ gdk_colormap_alloc_color (attributes.colormap, &color, FALSE, TRUE);
+ gdk_gc_set_foreground (knob->red_gc, &color);
+}
+
+
+/*****************************************************************************
+ *
+ * gtk_knob_size_request()
+ *
+ *****************************************************************************/
+static void
+gtk_knob_size_request (GtkWidget *widget, GtkRequisition *requisition) {
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_KNOB (widget));
+
+ requisition->width = GTK_KNOB (widget)->width;
+ requisition->height = GTK_KNOB (widget)->height;
+}
+
+
+/*****************************************************************************
+ *
+ * gtk_knob_size_allocate()
+ *
+ *****************************************************************************/
+static void
+gtk_knob_size_allocate (GtkWidget *widget, GtkAllocation *allocation) {
+ GtkKnob *knob;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_KNOB (widget));
+ g_return_if_fail (allocation != NULL);
+
+ widget->allocation = *allocation;
+ knob = GTK_KNOB (widget);
+
+ if (GTK_WIDGET_REALIZED (widget)) {
+ gdk_window_move_resize (widget->window,
+ allocation->x, allocation->y,
+ allocation->width, allocation->height);
+ }
+}
+
+
+/*****************************************************************************
+ *
+ * gtk_knob_expose()
+ *
+ *****************************************************************************/
+static gint
+gtk_knob_expose(GtkWidget *widget, GdkEventExpose *event) {
+ GtkKnob *knob;
+ gfloat dx, dy;
+ gint frames;
+
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_KNOB (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_ADJUSTMENT (GTK_KNOB (widget)->adjustment), FALSE);
+
+ if (event->count > 0)
+ return FALSE;
+
+ knob = GTK_KNOB (widget);
+
+ frames = ((knob->anim->width / knob->anim->frame_width) - 1);
+ dx = knob->adjustment->value - knob->adjustment->lower; /* value, from 0 */
+ dy = knob->adjustment->upper - knob->adjustment->lower; /* range */
+
+ dx = (int)(frames * dx / dy) * knob->width; /* check this for height != width */
+
+ gdk_draw_pixbuf (widget->window, knob->mask_gc, knob->anim->pixbuf,
+ dx, 0, 0, 0, knob->width, knob->height,
+ GDK_RGB_DITHER_NONE, 0, 0);
+
+ if (GTK_WIDGET_HAS_FOCUS(widget)) {
+ gtk_paint_focus (widget->style, widget->window, widget->state,
+ NULL, widget, NULL, 0, 0,
+ widget->allocation.width, widget->allocation.height);
+ }
+
+ return FALSE;
+}
+
+
+/*****************************************************************************
+ *
+ * gtk_knob_scroll()
+ *
+ *****************************************************************************/
+static gint
+gtk_knob_scroll(GtkWidget *widget, GdkEventScroll *event) {
+ GtkKnob *knob;
+
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_KNOB (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_ADJUSTMENT (GTK_KNOB (widget)->adjustment), FALSE);
+
+ knob = GTK_KNOB (widget);
+
+ switch (event->direction) {
+ case GDK_SCROLL_UP:
+ knob->adjustment->value += knob->adjustment->step_increment;
+ g_signal_emit_by_name (knob->adjustment, "value_changed");
+ break;
+ case GDK_SCROLL_DOWN:
+ knob->adjustment->value -= knob->adjustment->step_increment;
+ g_signal_emit_by_name (knob->adjustment, "value_changed");
+ break;
+ default:
+ break;
+ }
+
+ return FALSE;
+}
+
+
+/*****************************************************************************
+ *
+ * gtk_knob_button_press()
+ *
+ *****************************************************************************/
+static gint
+gtk_knob_button_press(GtkWidget *widget, GdkEventButton *event) {
+ GtkKnob *knob;
+
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_KNOB (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_ADJUSTMENT (GTK_KNOB (widget)->adjustment), FALSE);
+
+ knob = GTK_KNOB (widget);
+
+ switch (knob->state) {
+ case STATE_IDLE:
+ switch (event->button) {
+ case 1:
+ case 3:
+ if (!GTK_WIDGET_HAS_FOCUS(widget))
+ gtk_widget_grab_focus(widget);
+ knob->state = STATE_PRESSED;
+ knob->saved_x = event->x;
+ knob->saved_y = event->y;
+ break;
+ case 2:
+ knob->adjustment->value = floor ((knob->adjustment->lower +
+ knob->adjustment->upper + 1.0)
+ * 0.5);
+ g_signal_emit_by_name (knob->adjustment, "value_changed");
+ break;
+ }
+ break;
+ }
+
+ return FALSE;
+}
+
+
+/*****************************************************************************
+ *
+ * gtk_knob_button_release()
+ *
+ *****************************************************************************/
+static gint
+gtk_knob_button_release(GtkWidget *widget, GdkEventButton *event) {
+ GtkKnob *knob;
+
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_KNOB (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_ADJUSTMENT (GTK_KNOB (widget)->adjustment), FALSE);
+
+ knob = GTK_KNOB (widget);
+
+ switch (knob->state) {
+
+ case STATE_PRESSED:
+ knob->state = STATE_IDLE;
+ break;
+
+ case STATE_DRAGGING:
+ knob->state = STATE_IDLE;
+
+ switch (event->button) {
+ case 1:
+ case 3:
+ if (knob->policy != GTK_UPDATE_CONTINUOUS
+ && knob->old_value != knob->adjustment->value)
+ {
+ g_signal_emit_by_name (knob->adjustment, "value_changed");
+ }
+ break;
+ }
+
+ break;
+ }
+
+ return FALSE;
+}
+
+static gint gtk_knob_key_press(GtkWidget *widget, GdkEventKey *event)
+{
+ GtkKnob *knob;
+
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_KNOB (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_ADJUSTMENT (GTK_KNOB (widget)->adjustment), FALSE);
+
+ knob = GTK_KNOB (widget);
+
+ switch (event->keyval) {
+
+ case GDK_Up:
+ if (GTK_WIDGET_HAS_FOCUS (widget))
+ {
+ gtk_adjustment_set_value (knob->adjustment,
+ knob->old_value + knob->adjustment->step_increment);
+ return TRUE;
+ }
+ return FALSE;
+
+ case GDK_Down:
+ if (GTK_WIDGET_HAS_FOCUS (widget))
+ {
+ gtk_adjustment_set_value (knob->adjustment,
+ knob->old_value - knob->adjustment->step_increment);
+ return TRUE;
+ }
+ return FALSE;
+ default:
+ break;
+ }
+
+ return GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, event);
+}
+
+/*****************************************************************************
+ *
+ * gtk_knob_motion_notify()
+ *
+ *****************************************************************************/
+static gint
+gtk_knob_motion_notify(GtkWidget *widget, GdkEventMotion *event) {
+ GtkKnob *knob;
+ GdkModifierType mods;
+ gint x, y;
+
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_KNOB (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+
+ knob = GTK_KNOB (widget);
+
+ x = event->x;
+ y = event->y;
+
+ if (event->is_hint || (event->window != widget->window)) {
+ gdk_window_get_pointer (widget->window, &x, &y, &mods);
+ }
+
+ switch (knob->state) {
+
+ case STATE_PRESSED:
+ knob->state = STATE_DRAGGING;
+ /* fall through */
+
+ case STATE_DRAGGING:
+ if (mods & GDK_BUTTON1_MASK) {
+ gtk_knob_update_mouse (knob, x, y, TRUE);
+ return TRUE;
+ }
+ else if (mods & GDK_BUTTON3_MASK) {
+ gtk_knob_update_mouse (knob, x, y, FALSE);
+ return TRUE;
+ }
+ break;
+ }
+
+ return FALSE;
+}
+
+
+/*****************************************************************************
+ *
+ * gtk_knob_timer()
+ *
+ *****************************************************************************/
+static gint
+gtk_knob_timer(GtkKnob *knob) {
+
+ g_return_val_if_fail (knob != NULL, FALSE);
+ g_return_val_if_fail (GTK_IS_KNOB (knob), FALSE);
+ g_return_val_if_fail (GTK_IS_ADJUSTMENT (knob->adjustment), FALSE);
+
+ if (knob->policy == GTK_UPDATE_DELAYED) {
+ g_signal_emit_by_name (knob->adjustment, "value_changed");
+ }
+
+ /* don't keep running this timer */
+ return FALSE;
+}
+
+
+/*****************************************************************************
+ *
+ * gtk_knob_update_mouse_update()
+ *
+ *****************************************************************************/
+static void
+gtk_knob_update_mouse_update(GtkKnob *knob) {
+ g_return_if_fail(GTK_IS_ADJUSTMENT (knob->adjustment));
+
+ if (knob->policy == GTK_UPDATE_CONTINUOUS) {
+ g_signal_emit_by_name (knob->adjustment, "value_changed");
+ }
+ else {
+ gtk_widget_queue_draw (GTK_WIDGET (knob));
+
+ if (knob->policy == GTK_UPDATE_DELAYED) {
+ if (knob->timer) {
+ g_source_remove (knob->timer);
+ }
+ knob->timer = g_timeout_add (SCROLL_DELAY_LENGTH,
+ (GSourceFunc) gtk_knob_timer,
+ (gpointer) knob);
+ }
+ }
+}
+
+
+/*****************************************************************************
+ *
+ * gtk_knob_update_mouse()
+ *
+ *****************************************************************************/
+static void
+gtk_knob_update_mouse(GtkKnob *knob, gint x, gint y, gboolean step) {
+ gfloat old_value, new_value, dv, dh;
+ gfloat angle;
+
+ g_return_if_fail (knob != NULL);
+ g_return_if_fail (GTK_IS_KNOB (knob));
+ g_return_if_fail (GTK_IS_ADJUSTMENT (knob->adjustment));
+
+ old_value = knob->adjustment->value;
+
+ angle = atan2f (-y + (knob->height >> 1), x - (knob->width >> 1));
+
+ /* inverted cartesian graphics coordinate system */
+ dv = knob->saved_y - y;
+ dh = x - knob->saved_x;
+ knob->saved_x = x;
+ knob->saved_y = y;
+
+ if ((x >= 0) && (x <= knob->width)) {
+ dh = 0; /* dead zone */
+ } else {
+ angle = cosf (angle);
+ dh *= angle * angle;
+ }
+
+ new_value = knob->adjustment->value +
+ dv * (step ? knob->adjustment->step_increment : knob->adjustment->page_increment) +
+ dh * (knob->adjustment->upper -
+ knob->adjustment->lower) * 0.005; /* 0.005 == (1 / 200) */
+
+ new_value = MAX (MIN (new_value, knob->adjustment->upper),
+ knob->adjustment->lower);
+
+ knob->adjustment->value = new_value;
+
+ if (knob->adjustment->value != old_value) {
+ gtk_knob_update_mouse_update (knob);
+ }
+}
+
+
+/*****************************************************************************
+ *
+ * gtk_knob_update()
+ *
+ *****************************************************************************/
+static void
+gtk_knob_update(GtkKnob *knob) {
+ gfloat new_value;
+
+ g_return_if_fail (knob != NULL);
+ g_return_if_fail (GTK_IS_KNOB (knob));
+ g_return_if_fail (GTK_IS_ADJUSTMENT (knob->adjustment));
+
+ if (knob->adjustment->step_increment == 1) {
+ new_value = floor (knob->adjustment->value + 0.5);
+ }
+ else {
+ new_value = knob->adjustment->value;
+ }
+
+ if (new_value < knob->adjustment->lower) {
+ new_value = knob->adjustment->lower;
+ }
+
+ if (new_value > knob->adjustment->upper) {
+ new_value = knob->adjustment->upper;
+ }
+
+ if (new_value != knob->adjustment->value) {
+ knob->adjustment->value = new_value;
+ g_signal_emit_by_name (knob->adjustment, "value_changed");
+ }
+
+ gtk_widget_queue_draw (GTK_WIDGET (knob));
+}
+
+
+/*****************************************************************************
+ *
+ * gtk_knob_adjustment_changed()
+ *
+ *****************************************************************************/
+static void
+gtk_knob_adjustment_changed(GtkAdjustment *adjustment, gpointer data) {
+ GtkKnob *knob;
+
+ g_return_if_fail (adjustment != NULL);
+ g_return_if_fail (data != NULL);
+
+ knob = GTK_KNOB (data);
+
+ if ((knob->old_value != adjustment->value) ||
+ (knob->old_lower != adjustment->lower) ||
+ (knob->old_upper != adjustment->upper))
+ {
+ gtk_knob_update (knob);
+
+ knob->old_value = adjustment->value;
+ knob->old_lower = adjustment->lower;
+ knob->old_upper = adjustment->upper;
+ }
+}
+
+
+/*****************************************************************************
+ *
+ * gtk_knob_adjustment_value_changed()
+ *
+ *****************************************************************************/
+static void
+gtk_knob_adjustment_value_changed (GtkAdjustment *adjustment, gpointer data) {
+ GtkKnob *knob;
+
+ g_return_if_fail (adjustment != NULL);
+ g_return_if_fail (data != NULL);
+
+ knob = GTK_KNOB (data);
+
+ if (knob->old_value != adjustment->value) {
+ gtk_knob_update (knob);
+ knob->old_value = adjustment->value;
+ }
+}
+
+
+/*****************************************************************************
+ *
+ * gtk_knob_set_animation()
+ *
+ *****************************************************************************/
+void
+gtk_knob_set_animation (GtkKnob *knob, GtkKnobAnim *anim) {
+ g_return_if_fail (knob != NULL);
+ g_return_if_fail (anim != NULL);
+ g_return_if_fail (GTK_IS_KNOB (knob));
+ g_return_if_fail (GDK_IS_PIXBUF (anim->pixbuf));
+
+ knob->anim = (GtkKnobAnim *)anim;
+ knob->width = anim->frame_width;
+ knob->height = anim->height;
+
+ if (GTK_WIDGET_REALIZED (knob)) {
+ gtk_widget_queue_resize (GTK_WIDGET (knob));
+ }
+}
+
+
+/*****************************************************************************
+ *
+ * gtk_knob_animation_new_from_file()
+ *
+ *****************************************************************************/
+GtkKnobAnim *
+gtk_knob_animation_new_from_file(gchar *filename) {
+ GtkKnobAnim *anim;
+
+ anim = gtk_knob_animation_new_from_file_full (filename, -1, -1, KNOB_SIZE);
+ return anim;
+}
+
+/*****************************************************************************
+ *
+ * gtk_knob_animation_new_from_inline()
+ *
+ *****************************************************************************/
+GtkKnobAnim *
+gtk_knob_animation_new_from_inline(const guint8 *pixbuf) {
+ GtkKnobAnim *anim = g_new0 (GtkKnobAnim, 1);
+
+ g_return_val_if_fail((pixbuf != NULL), NULL);
+
+ anim->pixbuf = gdk_pixbuf_new_from_inline(-1, pixbuf, FALSE, NULL);
+ if (anim->pixbuf == NULL) {
+ g_free(anim);
+ return NULL;
+ }
+
+ anim->height = gdk_pixbuf_get_height (anim->pixbuf);
+ anim->width = gdk_pixbuf_get_width (anim->pixbuf);
+ anim->frame_width = anim->height;
+
+ return anim;
+}
+
+/*****************************************************************************
+ *
+ * gtk_knob_new_from_file_full()
+ *
+ * frame_width: overrides the frame width (to make rectangular frames)
+ * but doesn't affect the image size width and height cause optional
+ * scaling if not set to -1 when they are derived from the native
+ * image size.
+ *
+ * FIXME: account for any problems where (width % frame_width != 0)
+ *
+ *****************************************************************************/
+GtkKnobAnim *
+gtk_knob_animation_new_from_file_full(gchar *filename, gint frame_width,
+ gint width, gint height) {
+ GtkKnobAnim *anim = g_new0 (GtkKnobAnim, 1);
+
+ g_return_val_if_fail ((filename != NULL), NULL);
+
+ GError *gerror = NULL;
+
+#if GTK_MINOR_VERSION < 10
+ if (!(anim->pixbuf = gdk_pixbuf_new_from_file (filename, &gerror))) {
+ g_error_free(gerror);
+ gerror = NULL;
+ return NULL;
+ }
+#else /* GTK_MINOR_VERSION >= 10 */
+ if (!(anim->pixbuf = gdk_pixbuf_new_from_file_at_size (filename, width,
+ height, &gerror))) {
+ g_error_free(gerror);
+ gerror = NULL;
+ return NULL;
+ }
+#endif /* GTK_MINOR_VERSION < 10 */
+ else {
+ anim->height = gdk_pixbuf_get_height (anim->pixbuf);
+ anim->width = gdk_pixbuf_get_width (anim->pixbuf);
+ anim->frame_width = (frame_width != -1) ? frame_width : anim->height;
+ }
+
+ return anim;
+}
+
+/*****************************************************************************
+ *
+ * gtk_knob_animation_free()
+ *
+ *****************************************************************************/
+void
+gtk_knob_animation_free(GtkKnobAnim *anim) {
+ g_return_if_fail (anim != NULL);
+
+ if (anim->pixbuf)
+ g_object_unref (anim->pixbuf);
+
+ g_free (anim);
+}