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