/* gtk - the gimp toolkit
* copyright (c) 1995-1997 peter mattis, spencer kimball and josh macdonald
*
* this library is free software; you can redistribute it and/or
* modify it under the terms of the gnu library 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
* library general public license for more details.
*
* you should have received a copy of the gnu library 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 <math.h>
#include <stdio.h>
#include <gtk/gtkmain.h>
#include <gtk/gtksignal.h>
#include "gtkdial.h"
#define scroll_delay_length 300
#define dial_default_size 100
/* forward declarations */
static void gtk_dial_class_init (gtkdialclass *klass);
static void gtk_dial_init (gtkdial *dial);
static void gtk_dial_destroy (gtkobject *object);
static void gtk_dial_realize (gtkwidget *widget);
static void gtk_dial_size_request (gtkwidget *widget,
gtkrequisition *requisition);
static void gtk_dial_size_allocate (gtkwidget *widget,
gtkallocation *allocation);
static gint gtk_dial_expose (gtkwidget *widget,
gdkeventexpose *event);
static gint gtk_dial_button_press (gtkwidget *widget,
gdkeventbutton *event);
static gint gtk_dial_button_release (gtkwidget *widget,
gdkeventbutton *event);
static gint gtk_dial_motion_notify (gtkwidget *widget,
gdkeventmotion *event);
static gint gtk_dial_timer (gtkdial *dial);
static void gtk_dial_update_mouse (gtkdial *dial, gint x, gint y);
static void gtk_dial_update (gtkdial *dial);
static void gtk_dial_adjustment_changed (gtkadjustment *adjustment,
gpointer data);
static void gtk_dial_adjustment_value_changed (gtkadjustment *adjustment,
gpointer data);
/* local data */
static gtkwidgetclass *parent_class = null;
gtype
gtk_dial_get_type ()
{
static gtype dial_type = 0;
if (!dial_type)
{
static const gtypeinfo dial_info =
{
sizeof (gtkdialclass),
null,
null,
(gclassinitfunc) gtk_dial_class_init,
null,
null,
sizeof (gtkdial),
0,
(ginstanceinitfunc) gtk_dial_init,
};
dial_type = g_type_register_static (gtk_type_widget, "gtkdial", &dial_info, 0);
}
return dial_type;
}
static void
gtk_dial_class_init (gtkdialclass *class)
{
gtkobjectclass *object_class;
gtkwidgetclass *widget_class;
object_class = (gtkobjectclass*) class;
widget_class = (gtkwidgetclass*) class;
parent_class = gtk_type_class (gtk_widget_get_type ());
object_class->destroy = gtk_dial_destroy;
widget_class->realize = gtk_dial_realize;
widget_class->expose_event = gtk_dial_expose;
widget_class->size_request = gtk_dial_size_request;
widget_class->size_allocate = gtk_dial_size_allocate;
widget_class->button_press_event = gtk_dial_button_press;
widget_class->button_release_event = gtk_dial_button_release;
widget_class->motion_notify_event = gtk_dial_motion_notify;
}
static void
gtk_dial_init (gtkdial *dial)
{
dial->button = 0;
dial->policy = gtk_update_continuous;
dial->timer = 0;
dial->radius = 0;
dial->pointer_width = 0;
dial->angle = 0.0;
dial->old_value = 0.0;
dial->old_lower = 0.0;
dial->old_upper = 0.0;
dial->adjustment = null;
}
gtkwidget*
gtk_dial_new (gtkadjustment *adjustment)
{
gtkdial *dial;
dial = g_object_new (gtk_dial_get_type (), null);
if (!adjustment)
adjustment = (gtkadjustment*) gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
gtk_dial_set_adjustment (dial, adjustment);
return gtk_widget (dial);
}
static void
gtk_dial_destroy (gtkobject *object)
{
gtkdial *dial;
g_return_if_fail (object != null);
g_return_if_fail (gtk_is_dial (object));
dial = gtk_dial (object);
if (dial->adjustment)
{
g_object_unref (gtk_object (dial->adjustment));
dial->adjustment = null;
}
if (gtk_object_class (parent_class)->destroy)
(* gtk_object_class (parent_class)->destroy) (object);
}
gtkadjustment*
gtk_dial_get_adjustment (gtkdial *dial)
{
g_return_val_if_fail (dial != null, null);
g_return_val_if_fail (gtk_is_dial (dial), null);
return dial->adjustment;
}
void
gtk_dial_set_update_policy (gtkdial *dial,
gtkupdatetype policy)
{
g_return_if_fail (dial != null);
g_return_if_fail (gtk_is_dial (dial));
dial->policy = policy;
}
void
gtk_dial_set_adjustment (gtkdial *dial,
gtkadjustment *adjustment)
{
g_return_if_fail (dial != null);
g_return_if_fail (gtk_is_dial (dial));
if (dial->adjustment)
{
g_signal_handlers_disconnect_by_func (gtk_object (dial->adjustment), null, (gpointer) dial);
g_object_unref (gtk_object (dial->adjustment));
}
dial->adjustment = adjustment;
g_object_ref (gtk_object (dial->adjustment));
g_signal_connect (gtk_object (adjustment), "changed",
gtk_signal_func (gtk_dial_adjustment_changed),
(gpointer) dial);
g_signal_connect (gtk_object (adjustment), "value_changed",
gtk_signal_func (gtk_dial_adjustment_value_changed),
(gpointer) dial);
dial->old_value = adjustment->value;
dial->old_lower = adjustment->lower;
dial->old_upper = adjustment->upper;
gtk_dial_update (dial);
}
static void
gtk_dial_realize (gtkwidget *widget)
{
gtkdial *dial;
gdkwindowattr attributes;
gint attributes_mask;
g_return_if_fail (widget != null);
g_return_if_fail (gtk_is_dial (widget));
gtk_widget_set_flags (widget, gtk_realized);
dial = gtk_dial (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;
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_active);
}
gtk_dial_size_request (gtkwidget *widget,
gtkrequisition *requisition)
{
requisition->width = dial_default_size;
requisition->height = dial_default_size;
}
static void
gtk_dial_size_allocate (gtkwidget *widget,
gtkallocation *allocation)
{
gtkdial *dial;
g_return_if_fail (widget != null);
g_return_if_fail (gtk_is_dial (widget));
g_return_if_fail (allocation != null);
widget->allocation = *allocation;
dial = gtk_dial (widget);
if (gtk_widget_realized (widget))
{
gdk_window_move_resize (widget->window,
allocation->x, allocation->y,
allocation->width, allocation->height);
}
dial->radius = min (allocation->width, allocation->height) * 0.45;
dial->pointer_width = dial->radius / 5;
}
static gint
gtk_dial_expose (gtkwidget *widget,
gdkeventexpose *event)
{
gtkdial *dial;
gdkpoint points[6];
gdouble s,c;
gdouble theta, last, increment;
gtkstyle *blankstyle;
gint xc, yc;
gint upper, lower;
gint tick_length;
gint i, inc;
g_return_val_if_fail (widget != null, false);
g_return_val_if_fail (gtk_is_dial (widget), false);
g_return_val_if_fail (event != null, false);
if (event->count > 0)
return false;
dial = gtk_dial (widget);
/* gdk_window_clear_area (widget->window,
0, 0,
widget->allocation.width,
widget->allocation.height);
*/
xc = widget->allocation.width / 2;
yc = widget->allocation.height / 2;
upper = dial->adjustment->upper;
lower = dial->adjustment->lower;
/* erase old pointer */
s = sin (dial->last_angle);
c = cos (dial->last_angle);
dial->last_angle = dial->angle;
points[0].x = xc + s*dial->pointer_width/2;
points[0].y = yc + c*dial->pointer_width/2;
points[1].x = xc + c*dial->radius;
points[1].y = yc - s*dial->radius;
points[2].x = xc - s*dial->pointer_width/2;
points[2].y = yc - c*dial->pointer_width/2;
points[3].x = xc - c*dial->radius/10;
points[3].y = yc + s*dial->radius/10;
points[4].x = points[0].x;
points[4].y = points[0].y;
blankstyle = gtk_style_new ();
blankstyle->bg_gc[gtk_state_normal] =
widget->style->bg_gc[gtk_state_normal];
blankstyle->dark_gc[gtk_state_normal] =
widget->style->bg_gc[gtk_state_normal];
blankstyle->light_gc[gtk_state_normal] =
widget->style->bg_gc[gtk_state_normal];
blankstyle->black_gc =
widget->style->bg_gc[gtk_state_normal];
gtk_paint_polygon (blankstyle,
widget->window,
gtk_state_normal,
gtk_shadow_out,
null,
widget,
null,
points, 5,
false);
g_object_unref (blankstyle);
/* draw ticks */
if ((upper - lower) == 0)
return false;
increment = (100*m_pi) / (dial->radius*dial->radius);
inc = (upper - lower);
while (inc < 100) inc *= 10;
while (inc >= 1000) inc /= 10;
last = -1;
for (i = 0; i <= inc; i++)
{
theta = ((gfloat)i*m_pi / (18*inc/24.) - m_pi/6.);
if ((theta - last) < (increment))
last = theta;
s = sin (theta);
c = cos (theta);
tick_length = (i%(inc/10) == 0) ? dial->pointer_width : dial->pointer_width / 2;
gdk_draw_line (widget->window,
widget->style->fg_gc[widget->state],
xc + c*(dial->radius - tick_length),
yc - s*(dial->radius - tick_length),
xc + c*dial->radius,
yc - s*dial->radius);
}
/* draw pointer */
s = sin (dial->angle);
c = cos (dial->angle);
dial->last_angle = dial->angle;
points[0].x = xc + s*dial->pointer_width/2;
points[0].y = yc + c*dial->pointer_width/2;
points[1].x = xc + c*dial->radius;
points[1].y = yc - s*dial->radius;
points[2].x = xc - s*dial->pointer_width/2;
points[2].y = yc - c*dial->pointer_width/2;
points[3].x = xc - c*dial->radius/10;
points[3].y = yc + s*dial->radius/10;
points[4].x = points[0].x;
points[4].y = points[0].y;
gtk_paint_polygon (widget->style,
widget->window,
gtk_state_normal,
gtk_shadow_out,
null,
widget,
null,
points, 5,
true);
return false;
}
static gint
gtk_dial_button_press (gtkwidget *widget,
gdkeventbutton *event)
{
gtkdial *dial;
gint dx, dy;
double s, c;
double d_parallel;
double d_perpendicular;
g_return_val_if_fail (widget != null, false);
g_return_val_if_fail (gtk_is_dial (widget), false);
g_return_val_if_fail (event != null, false);
dial = gtk_dial (widget);
do this by computing the parallel and perpendicular distance of
the point where the mouse was pressed from the line passing through
the pointer */
dx = event->x - widget->allocation.width / 2;
dy = widget->allocation.height / 2 - event->y;
s = sin (dial->angle);
c = cos (dial->angle);
d_parallel = s*dy + c*dx;
d_perpendicular = fabs (s*dx - c*dy);
if (!dial->button &&
(d_perpendicular < dial->pointer_width/2) &&
(d_parallel > - dial->pointer_width))
{
gtk_grab_add (widget);
dial->button = event->button;
gtk_dial_update_mouse (dial, event->x, event->y);
}
return false;
}
static gint
gtk_dial_button_release (gtkwidget *widget,
gdkeventbutton *event)
{
gtkdial *dial;
g_return_val_if_fail (widget != null, false);
g_return_val_if_fail (gtk_is_dial (widget), false);
g_return_val_if_fail (event != null, false);
dial = gtk_dial (widget);
if (dial->button == event->button)
{
gtk_grab_remove (widget);
dial->button = 0;
if (dial->policy == gtk_update_delayed)
gtk_timeout_remove (dial->timer);
if ((dial->policy != gtk_update_continuous) &&
(dial->old_value != dial->adjustment->value))
g_signal_emit_by_name (gtk_object (dial->adjustment), "value_changed");
}
return false;
}
static gint
gtk_dial_motion_notify (gtkwidget *widget,
gdkeventmotion *event)
{
gtkdial *dial;
gdkmodifiertype mods;
gint x, y, mask;
g_return_val_if_fail (widget != null, false);
g_return_val_if_fail (gtk_is_dial (widget), false);
g_return_val_if_fail (event != null, false);
dial = gtk_dial (widget);
if (dial->button != 0)
{
x = event->x;
y = event->y;
if (event->is_hint || (event->window != widget->window))
gdk_window_get_pointer (widget->window, &x, &y, &mods);
switch (dial->button)
{
case 1:
mask = gdk_button1_mask;
break;
case 2:
mask = gdk_button2_mask;
break;
case 3:
mask = gdk_button3_mask;
break;
default:
mask = 0;
break;
}
if (mods & mask)
gtk_dial_update_mouse (dial, x,y);
}
return false;
}
static gint
gtk_dial_timer (gtkdial *dial)
{
g_return_val_if_fail (dial != null, false);
g_return_val_if_fail (gtk_is_dial (dial), false);
if (dial->policy == gtk_update_delayed)
g_signal_emit_by_name (gtk_object (dial->adjustment), "value_changed");
return false;
}
static void
gtk_dial_update_mouse (gtkdial *dial, gint x, gint y)
{
gint xc, yc;
gfloat old_value;
g_return_if_fail (dial != null);
g_return_if_fail (gtk_is_dial (dial));
xc = gtk_widget(dial)->allocation.width / 2;
yc = gtk_widget(dial)->allocation.height / 2;
old_value = dial->adjustment->value;
dial->angle = atan2(yc-y, x-xc);
if (dial->angle < -m_pi/2.)
dial->angle += 2*m_pi;
if (dial->angle < -m_pi/6)
dial->angle = -m_pi/6;
if (dial->angle > 7.*m_pi/6.)
dial->angle = 7.*m_pi/6.;
dial->adjustment->value = dial->adjustment->lower + (7.*m_pi/6 - dial->angle) *
(dial->adjustment->upper - dial->adjustment->lower) / (4.*m_pi/3.);
if (dial->adjustment->value != old_value)
{
if (dial->policy == gtk_update_continuous)
{
g_signal_emit_by_name (gtk_object (dial->adjustment), "value_changed");
}
else
{
gtk_widget_queue_draw (gtk_widget (dial));
if (dial->policy == gtk_update_delayed)
{
if (dial->timer)
gtk_timeout_remove (dial->timer);
dial->timer = gtk_timeout_add (scroll_delay_length,
(gtkfunction) gtk_dial_timer,
(gpointer) dial);
}
}
}
}
static void
gtk_dial_update (gtkdial *dial)
{
gfloat new_value;
g_return_if_fail (dial != null);
g_return_if_fail (gtk_is_dial (dial));
new_value = dial->adjustment->value;
if (new_value < dial->adjustment->lower)
new_value = dial->adjustment->lower;
if (new_value > dial->adjustment->upper)
new_value = dial->adjustment->upper;
if (new_value != dial->adjustment->value)
{
dial->adjustment->value = new_value;
g_signal_emit_by_name (gtk_object (dial->adjustment), "value_changed");
}
dial->angle = 7.*m_pi/6. - (new_value - dial->adjustment->lower) * 4.*m_pi/3. /
(dial->adjustment->upper - dial->adjustment->lower);
gtk_widget_queue_draw (gtk_widget (dial));
}
static void
gtk_dial_adjustment_changed (gtkadjustment *adjustment,
gpointer data)
{
gtkdial *dial;
g_return_if_fail (adjustment != null);
g_return_if_fail (data != null);
dial = gtk_dial (data);
if ((dial->old_value != adjustment->value) ||
(dial->old_lower != adjustment->lower) ||
(dial->old_upper != adjustment->upper))
{
gtk_dial_update (dial);
dial->old_value = adjustment->value;
dial->old_lower = adjustment->lower;
dial->old_upper = adjustment->upper;
}
}
static void
gtk_dial_adjustment_value_changed (gtkadjustment *adjustment,
gpointer data)
{
gtkdial *dial;
g_return_if_fail (adjustment != null);
g_return_if_fail (data != null);
dial = gtk_dial (data);
if (dial->old_value != adjustment->value)
{
gtk_dial_update (dial);
dial->old_value = adjustment->value;
}
} |