]> Pileus Git - ~andy/gtk/blob - examples/gtkdial/gtkdial.c
- re-write the GtkProgressBar section to the 1.1 API. - add an Appendix
[~andy/gtk] / examples / gtkdial / gtkdial.c
1 /* example-start gtkdial gtkdial.c */
2
3 /* GTK - The GIMP Toolkit
4  * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  */
21 #include <math.h>
22 #include <stdio.h>
23 #include <gtk/gtkmain.h>
24 #include <gtk/gtksignal.h>
25
26 #include "gtkdial.h"
27
28 #define SCROLL_DELAY_LENGTH  300
29 #define DIAL_DEFAULT_SIZE 100
30
31 /* Forward declarations */
32
33 static void gtk_dial_class_init               (GtkDialClass    *klass);
34 static void gtk_dial_init                     (GtkDial         *dial);
35 static void gtk_dial_destroy                  (GtkObject        *object);
36 static void gtk_dial_realize                  (GtkWidget        *widget);
37 static void gtk_dial_size_request             (GtkWidget      *widget,
38                                                GtkRequisition *requisition);
39 static void gtk_dial_size_allocate            (GtkWidget     *widget,
40                                                GtkAllocation *allocation);
41 static gint gtk_dial_expose                   (GtkWidget        *widget,
42                                                 GdkEventExpose   *event);
43 static gint gtk_dial_button_press             (GtkWidget        *widget,
44                                                 GdkEventButton   *event);
45 static gint gtk_dial_button_release           (GtkWidget        *widget,
46                                                 GdkEventButton   *event);
47 static gint gtk_dial_motion_notify            (GtkWidget        *widget,
48                                                 GdkEventMotion   *event);
49 static gint gtk_dial_timer                    (GtkDial         *dial);
50
51 static void gtk_dial_update_mouse             (GtkDial *dial, gint x, gint y);
52 static void gtk_dial_update                   (GtkDial *dial);
53 static void gtk_dial_adjustment_changed       (GtkAdjustment    *adjustment,
54                                                 gpointer          data);
55 static void gtk_dial_adjustment_value_changed (GtkAdjustment    *adjustment,
56                                                 gpointer          data);
57
58 /* Local data */
59
60 static GtkWidgetClass *parent_class = NULL;
61
62 guint
63 gtk_dial_get_type ()
64 {
65   static guint dial_type = 0;
66
67   if (!dial_type)
68     {
69       GtkTypeInfo dial_info =
70       {
71         "GtkDial",
72         sizeof (GtkDial),
73         sizeof (GtkDialClass),
74         (GtkClassInitFunc) gtk_dial_class_init,
75         (GtkObjectInitFunc) gtk_dial_init,
76         (GtkArgSetFunc) NULL,
77         (GtkArgGetFunc) NULL,
78       };
79
80       dial_type = gtk_type_unique (gtk_widget_get_type (), &dial_info);
81     }
82
83   return dial_type;
84 }
85
86 static void
87 gtk_dial_class_init (GtkDialClass *class)
88 {
89   GtkObjectClass *object_class;
90   GtkWidgetClass *widget_class;
91
92   object_class = (GtkObjectClass*) class;
93   widget_class = (GtkWidgetClass*) class;
94
95   parent_class = gtk_type_class (gtk_widget_get_type ());
96
97   object_class->destroy = gtk_dial_destroy;
98
99   widget_class->realize = gtk_dial_realize;
100   widget_class->expose_event = gtk_dial_expose;
101   widget_class->size_request = gtk_dial_size_request;
102   widget_class->size_allocate = gtk_dial_size_allocate;
103   widget_class->button_press_event = gtk_dial_button_press;
104   widget_class->button_release_event = gtk_dial_button_release;
105   widget_class->motion_notify_event = gtk_dial_motion_notify;
106 }
107
108 static void
109 gtk_dial_init (GtkDial *dial)
110 {
111   dial->button = 0;
112   dial->policy = GTK_UPDATE_CONTINUOUS;
113   dial->timer = 0;
114   dial->radius = 0;
115   dial->pointer_width = 0;
116   dial->angle = 0.0;
117   dial->old_value = 0.0;
118   dial->old_lower = 0.0;
119   dial->old_upper = 0.0;
120   dial->adjustment = NULL;
121 }
122
123 GtkWidget*
124 gtk_dial_new (GtkAdjustment *adjustment)
125 {
126   GtkDial *dial;
127
128   dial = gtk_type_new (gtk_dial_get_type ());
129
130   if (!adjustment)
131     adjustment = (GtkAdjustment*) gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
132
133   gtk_dial_set_adjustment (dial, adjustment);
134
135   return GTK_WIDGET (dial);
136 }
137
138 static void
139 gtk_dial_destroy (GtkObject *object)
140 {
141   GtkDial *dial;
142
143   g_return_if_fail (object != NULL);
144   g_return_if_fail (GTK_IS_DIAL (object));
145
146   dial = GTK_DIAL (object);
147
148   if (dial->adjustment)
149     gtk_object_unref (GTK_OBJECT (dial->adjustment));
150
151   if (GTK_OBJECT_CLASS (parent_class)->destroy)
152     (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
153 }
154
155 GtkAdjustment*
156 gtk_dial_get_adjustment (GtkDial *dial)
157 {
158   g_return_val_if_fail (dial != NULL, NULL);
159   g_return_val_if_fail (GTK_IS_DIAL (dial), NULL);
160
161   return dial->adjustment;
162 }
163
164 void
165 gtk_dial_set_update_policy (GtkDial      *dial,
166                              GtkUpdateType  policy)
167 {
168   g_return_if_fail (dial != NULL);
169   g_return_if_fail (GTK_IS_DIAL (dial));
170
171   dial->policy = policy;
172 }
173
174 void
175 gtk_dial_set_adjustment (GtkDial      *dial,
176                           GtkAdjustment *adjustment)
177 {
178   g_return_if_fail (dial != NULL);
179   g_return_if_fail (GTK_IS_DIAL (dial));
180
181   if (dial->adjustment)
182     {
183       gtk_signal_disconnect_by_data (GTK_OBJECT (dial->adjustment), (gpointer) dial);
184       gtk_object_unref (GTK_OBJECT (dial->adjustment));
185     }
186
187   dial->adjustment = adjustment;
188   gtk_object_ref (GTK_OBJECT (dial->adjustment));
189
190   gtk_signal_connect (GTK_OBJECT (adjustment), "changed",
191                       (GtkSignalFunc) gtk_dial_adjustment_changed,
192                       (gpointer) dial);
193   gtk_signal_connect (GTK_OBJECT (adjustment), "value_changed",
194                       (GtkSignalFunc) gtk_dial_adjustment_value_changed,
195                       (gpointer) dial);
196
197   dial->old_value = adjustment->value;
198   dial->old_lower = adjustment->lower;
199   dial->old_upper = adjustment->upper;
200
201   gtk_dial_update (dial);
202 }
203
204 static void
205 gtk_dial_realize (GtkWidget *widget)
206 {
207   GtkDial *dial;
208   GdkWindowAttr attributes;
209   gint attributes_mask;
210
211   g_return_if_fail (widget != NULL);
212   g_return_if_fail (GTK_IS_DIAL (widget));
213
214   GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
215   dial = GTK_DIAL (widget);
216
217   attributes.x = widget->allocation.x;
218   attributes.y = widget->allocation.y;
219   attributes.width = widget->allocation.width;
220   attributes.height = widget->allocation.height;
221   attributes.wclass = GDK_INPUT_OUTPUT;
222   attributes.window_type = GDK_WINDOW_CHILD;
223   attributes.event_mask = gtk_widget_get_events (widget) | 
224     GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | 
225     GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK |
226     GDK_POINTER_MOTION_HINT_MASK;
227   attributes.visual = gtk_widget_get_visual (widget);
228   attributes.colormap = gtk_widget_get_colormap (widget);
229
230   attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
231   widget->window = gdk_window_new (widget->parent->window, &attributes, attributes_mask);
232
233   widget->style = gtk_style_attach (widget->style, widget->window);
234
235   gdk_window_set_user_data (widget->window, widget);
236
237   gtk_style_set_background (widget->style, widget->window, GTK_STATE_ACTIVE);
238 }
239
240 static void 
241 gtk_dial_size_request (GtkWidget      *widget,
242                        GtkRequisition *requisition)
243 {
244   requisition->width = DIAL_DEFAULT_SIZE;
245   requisition->height = DIAL_DEFAULT_SIZE;
246 }
247
248 static void
249 gtk_dial_size_allocate (GtkWidget     *widget,
250                         GtkAllocation *allocation)
251 {
252   GtkDial *dial;
253
254   g_return_if_fail (widget != NULL);
255   g_return_if_fail (GTK_IS_DIAL (widget));
256   g_return_if_fail (allocation != NULL);
257
258   widget->allocation = *allocation;
259   dial = GTK_DIAL (widget);
260
261   if (GTK_WIDGET_REALIZED (widget))
262     {
263
264       gdk_window_move_resize (widget->window,
265                               allocation->x, allocation->y,
266                               allocation->width, allocation->height);
267
268     }
269   dial->radius = MIN(allocation->width,allocation->height) * 0.45;
270   dial->pointer_width = dial->radius / 5;
271 }
272
273 static gint
274 gtk_dial_expose (GtkWidget      *widget,
275                  GdkEventExpose *event)
276 {
277   GtkDial *dial;
278   GdkPoint points[3];
279   gdouble s,c;
280   gdouble theta;
281   gint xc, yc;
282   gint tick_length;
283   gint i;
284
285   g_return_val_if_fail (widget != NULL, FALSE);
286   g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
287   g_return_val_if_fail (event != NULL, FALSE);
288
289   if (event->count > 0)
290     return FALSE;
291   
292   dial = GTK_DIAL (widget);
293
294   gdk_window_clear_area (widget->window,
295                          0, 0,
296                          widget->allocation.width,
297                          widget->allocation.height);
298
299   xc = widget->allocation.width/2;
300   yc = widget->allocation.height/2;
301
302   /* Draw ticks */
303
304   for (i=0; i<25; i++)
305     {
306       theta = (i*M_PI/18. - M_PI/6.);
307       s = sin(theta);
308       c = cos(theta);
309
310       tick_length = (i%6 == 0) ? dial->pointer_width : dial->pointer_width/2;
311       
312       gdk_draw_line (widget->window,
313                      widget->style->fg_gc[widget->state],
314                      xc + c*(dial->radius - tick_length),
315                      yc - s*(dial->radius - tick_length),
316                      xc + c*dial->radius,
317                      yc - s*dial->radius);
318     }
319
320   /* Draw pointer */
321
322   s = sin(dial->angle);
323   c = cos(dial->angle);
324
325
326   points[0].x = xc + s*dial->pointer_width/2;
327   points[0].y = yc + c*dial->pointer_width/2;
328   points[1].x = xc + c*dial->radius;
329   points[1].y = yc - s*dial->radius;
330   points[2].x = xc - s*dial->pointer_width/2;
331   points[2].y = yc - c*dial->pointer_width/2;
332
333   gtk_draw_polygon (widget->style,
334                     widget->window,
335                     GTK_STATE_NORMAL,
336                     GTK_SHADOW_OUT,
337                     points, 3,
338                     TRUE);
339   
340   return FALSE;
341 }
342
343 static gint
344 gtk_dial_button_press (GtkWidget      *widget,
345                        GdkEventButton *event)
346 {
347   GtkDial *dial;
348   gint dx, dy;
349   double s, c;
350   double d_parallel;
351   double d_perpendicular;
352
353   g_return_val_if_fail (widget != NULL, FALSE);
354   g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
355   g_return_val_if_fail (event != NULL, FALSE);
356
357   dial = GTK_DIAL (widget);
358
359   /* Determine if button press was within pointer region - we 
360      do this by computing the parallel and perpendicular distance of
361      the point where the mouse was pressed from the line passing through
362      the pointer */
363   
364   dx = event->x - widget->allocation.width / 2;
365   dy = widget->allocation.height / 2 - event->y;
366   
367   s = sin(dial->angle);
368   c = cos(dial->angle);
369   
370   d_parallel = s*dy + c*dx;
371   d_perpendicular = fabs(s*dx - c*dy);
372   
373   if (!dial->button &&
374       (d_perpendicular < dial->pointer_width/2) &&
375       (d_parallel > - dial->pointer_width))
376     {
377       gtk_grab_add (widget);
378
379       dial->button = event->button;
380
381       gtk_dial_update_mouse (dial, event->x, event->y);
382     }
383
384   return FALSE;
385 }
386
387 static gint
388 gtk_dial_button_release (GtkWidget      *widget,
389                           GdkEventButton *event)
390 {
391   GtkDial *dial;
392
393   g_return_val_if_fail (widget != NULL, FALSE);
394   g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
395   g_return_val_if_fail (event != NULL, FALSE);
396
397   dial = GTK_DIAL (widget);
398
399   if (dial->button == event->button)
400     {
401       gtk_grab_remove (widget);
402
403       dial->button = 0;
404
405       if (dial->policy == GTK_UPDATE_DELAYED)
406         gtk_timeout_remove (dial->timer);
407       
408       if ((dial->policy != GTK_UPDATE_CONTINUOUS) &&
409           (dial->old_value != dial->adjustment->value))
410         gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
411     }
412
413   return FALSE;
414 }
415
416 static gint
417 gtk_dial_motion_notify (GtkWidget      *widget,
418                          GdkEventMotion *event)
419 {
420   GtkDial *dial;
421   GdkModifierType mods;
422   gint x, y, mask;
423
424   g_return_val_if_fail (widget != NULL, FALSE);
425   g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
426   g_return_val_if_fail (event != NULL, FALSE);
427
428   dial = GTK_DIAL (widget);
429
430   if (dial->button != 0)
431     {
432       x = event->x;
433       y = event->y;
434
435       if (event->is_hint || (event->window != widget->window))
436         gdk_window_get_pointer (widget->window, &x, &y, &mods);
437
438       switch (dial->button)
439         {
440         case 1:
441           mask = GDK_BUTTON1_MASK;
442           break;
443         case 2:
444           mask = GDK_BUTTON2_MASK;
445           break;
446         case 3:
447           mask = GDK_BUTTON3_MASK;
448           break;
449         default:
450           mask = 0;
451           break;
452         }
453
454       if (mods & mask)
455         gtk_dial_update_mouse (dial, x,y);
456     }
457
458   return FALSE;
459 }
460
461 static gint
462 gtk_dial_timer (GtkDial *dial)
463 {
464   g_return_val_if_fail (dial != NULL, FALSE);
465   g_return_val_if_fail (GTK_IS_DIAL (dial), FALSE);
466
467   if (dial->policy == GTK_UPDATE_DELAYED)
468     gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
469
470   return FALSE;
471 }
472
473 static void
474 gtk_dial_update_mouse (GtkDial *dial, gint x, gint y)
475 {
476   gint xc, yc;
477   gfloat old_value;
478
479   g_return_if_fail (dial != NULL);
480   g_return_if_fail (GTK_IS_DIAL (dial));
481
482   xc = GTK_WIDGET(dial)->allocation.width / 2;
483   yc = GTK_WIDGET(dial)->allocation.height / 2;
484
485   old_value = dial->adjustment->value;
486   dial->angle = atan2(yc-y, x-xc);
487
488   if (dial->angle < -M_PI/2.)
489     dial->angle += 2*M_PI;
490
491   if (dial->angle < -M_PI/6)
492     dial->angle = -M_PI/6;
493
494   if (dial->angle > 7.*M_PI/6.)
495     dial->angle = 7.*M_PI/6.;
496
497   dial->adjustment->value = dial->adjustment->lower + (7.*M_PI/6 - dial->angle) *
498     (dial->adjustment->upper - dial->adjustment->lower) / (4.*M_PI/3.);
499
500   if (dial->adjustment->value != old_value)
501     {
502       if (dial->policy == GTK_UPDATE_CONTINUOUS)
503         {
504           gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
505         }
506       else
507         {
508           gtk_widget_draw (GTK_WIDGET(dial), NULL);
509
510           if (dial->policy == GTK_UPDATE_DELAYED)
511             {
512               if (dial->timer)
513                 gtk_timeout_remove (dial->timer);
514
515               dial->timer = gtk_timeout_add (SCROLL_DELAY_LENGTH,
516                                              (GtkFunction) gtk_dial_timer,
517                                              (gpointer) dial);
518             }
519         }
520     }
521 }
522
523 static void
524 gtk_dial_update (GtkDial *dial)
525 {
526   gfloat new_value;
527   
528   g_return_if_fail (dial != NULL);
529   g_return_if_fail (GTK_IS_DIAL (dial));
530
531   new_value = dial->adjustment->value;
532   
533   if (new_value < dial->adjustment->lower)
534     new_value = dial->adjustment->lower;
535
536   if (new_value > dial->adjustment->upper)
537     new_value = dial->adjustment->upper;
538
539   if (new_value != dial->adjustment->value)
540     {
541       dial->adjustment->value = new_value;
542       gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
543     }
544
545   dial->angle = 7.*M_PI/6. - (new_value - dial->adjustment->lower) * 4.*M_PI/3. /
546     (dial->adjustment->upper - dial->adjustment->lower);
547
548   gtk_widget_draw (GTK_WIDGET(dial), NULL);
549 }
550
551 static void
552 gtk_dial_adjustment_changed (GtkAdjustment *adjustment,
553                               gpointer       data)
554 {
555   GtkDial *dial;
556
557   g_return_if_fail (adjustment != NULL);
558   g_return_if_fail (data != NULL);
559
560   dial = GTK_DIAL (data);
561
562   if ((dial->old_value != adjustment->value) ||
563       (dial->old_lower != adjustment->lower) ||
564       (dial->old_upper != adjustment->upper))
565     {
566       gtk_dial_update (dial);
567
568       dial->old_value = adjustment->value;
569       dial->old_lower = adjustment->lower;
570       dial->old_upper = adjustment->upper;
571     }
572 }
573
574 static void
575 gtk_dial_adjustment_value_changed (GtkAdjustment *adjustment,
576                                     gpointer       data)
577 {
578   GtkDial *dial;
579
580   g_return_if_fail (adjustment != NULL);
581   g_return_if_fail (data != NULL);
582
583   dial = GTK_DIAL (data);
584
585   if (dial->old_value != adjustment->value)
586     {
587       gtk_dial_update (dial);
588
589       dial->old_value = adjustment->value;
590     }
591 }
592 /* example-end */