19ce16d8158a9b2b698995fa040ef5689d82b92f
[ardour.git] / libs / gtkmm2ext / motionfeedback.cc
1 /*
2     Copyright (C) 1998-99 Paul Barton-Davis
3     This program is free software; you can redistribute it and/or modify
4     it under the terms of the GNU General Public License as published by
5     the Free Software Foundation; either version 2 of the License, or
6     (at your option) any later version.
7
8     This program is distributed in the hope that it will be useful,
9     but WITHOUT ANY WARRANTY; without even the implied warranty of
10     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11     GNU General Public License for more details.
12
13     You should have received a copy of the GNU General Public License
14     along with this program; if not, write to the Free Software
15     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
16
17     $Id: motionfeedback.cc,v 1.5 2004/03/01 03:44:19 pauld Exp $
18 */
19
20 #include <iostream>
21 #include <cmath>
22 #include <unistd.h>
23 #include <stdio.h> /* for snprintf, grrr */
24
25 #include <gdk/gdkkeysyms.h>
26 #include <gtkmm.h>
27
28 #include "gtkmm2ext/motionfeedback.h"
29 #include "gtkmm2ext/keyboard.h"
30
31 using namespace Gtk;
32 using namespace Gtkmm2ext;
33 using namespace sigc;
34
35 MotionFeedback::MotionFeedback (Glib::RefPtr<Gdk::Pixbuf> pix,
36                                 Type t,
37                                 const char *widget_name, 
38                                 Adjustment *adj,
39                                 bool with_numeric_display, int subw, int subh) 
40         : type (t)
41         , value_packer (0)
42         , value (0)
43         , pixbuf (pix)
44         , subwidth (subw)
45         , subheight (subh)
46 {
47         char value_name[1024];
48
49         if (adj == NULL) {
50             i_own_my_adjustment = true;
51             set_adjustment (new Adjustment (0, 0, 10000, 1, 10, 0));
52         } else {
53             i_own_my_adjustment = false;
54             set_adjustment (adj);
55         }
56
57         default_value = adjustment->get_value();
58
59         HBox* hpacker = manage (new HBox);
60         hpacker->pack_start (pixwin, true, false);
61         hpacker->show ();
62         pack_start (*hpacker, false, false);
63         pixwin.show ();
64
65         if (with_numeric_display) {
66
67                 value_packer = new HBox;
68                 value = new SpinButton (*adjustment);
69                 value_packer->pack_start (*value, false, false);
70
71                 if (step_inc < 1) {
72                         value->set_digits (abs ((int) ceil (log10 (step_inc))));
73                 }
74                 
75                 pack_start (*value_packer, false, false);
76
77                 if (widget_name) {
78                         snprintf (value_name, sizeof(value_name), "%sValue", widget_name);
79                         value->set_name (value_name);
80                 }
81
82                 value->show ();
83         }
84
85         adjustment->signal_value_changed().connect (mem_fun (*this, &MotionFeedback::adjustment_changed));
86
87         pixwin.set_events (Gdk::BUTTON_PRESS_MASK|
88                            Gdk::BUTTON_RELEASE_MASK|
89                            Gdk::POINTER_MOTION_MASK|
90                            Gdk::ENTER_NOTIFY_MASK|
91                            Gdk::LEAVE_NOTIFY_MASK|
92                            Gdk::SCROLL_MASK|
93                            Gdk::KEY_PRESS_MASK|
94                            Gdk::KEY_RELEASE_MASK);
95
96         pixwin.set_flags (CAN_FOCUS);
97
98         /* Proxy all important events on the pixwin to ourselves */
99
100         pixwin.signal_button_press_event().connect(mem_fun (*this,&MotionFeedback::pixwin_button_press_event));
101         pixwin.signal_button_release_event().connect(mem_fun (*this,&MotionFeedback::pixwin_button_release_event));
102         pixwin.signal_motion_notify_event().connect(mem_fun (*this,&MotionFeedback::pixwin_motion_notify_event));
103         pixwin.signal_enter_notify_event().connect(mem_fun (*this,&MotionFeedback::pixwin_enter_notify_event));
104         pixwin.signal_leave_notify_event().connect(mem_fun (*this,&MotionFeedback::pixwin_leave_notify_event));
105         pixwin.signal_key_press_event().connect(mem_fun (*this,&MotionFeedback::pixwin_key_press_event));
106         pixwin.signal_scroll_event().connect(mem_fun (*this,&MotionFeedback::pixwin_scroll_event));
107         pixwin.signal_expose_event().connect(mem_fun (*this,&MotionFeedback::pixwin_expose_event), true);
108         pixwin.signal_size_request().connect(mem_fun (*this,&MotionFeedback::pixwin_size_request));
109 }
110
111 MotionFeedback::~MotionFeedback()
112
113 {
114         if (i_own_my_adjustment) {
115                 delete adjustment;
116         }
117
118         delete value;
119         delete value_packer;
120 }
121
122 void
123 MotionFeedback::set_adjustment (Adjustment *adj)
124 {
125         adjustment = adj;
126
127         if (value) {
128                 value->set_adjustment (*adj);
129         }
130
131         _lower = adj->get_lower();
132         _upper = adj->get_upper();
133         _range = _upper - _lower;
134         step_inc = adj->get_step_increment();
135         page_inc = adj->get_page_increment();
136 }
137
138 bool
139 MotionFeedback::pixwin_button_press_event (GdkEventButton *ev) 
140
141         switch (ev->button) {
142         case 2:
143                 return FALSE;  /* XXX why ? */
144
145         case 1:
146                 grab_is_fine = false;
147                 break;
148         case 3:
149                 grab_is_fine = true;
150                 break;
151         }
152
153         gtk_grab_add(GTK_WIDGET(pixwin.gobj()));
154         grabbed_y = ev->y_root;
155         grabbed_x = ev->x_root;
156
157         /* XXX should we return TRUE ? */
158
159         return FALSE;
160 }
161
162 bool
163 MotionFeedback::pixwin_button_release_event (GdkEventButton *ev) 
164
165         switch (ev->button) {
166         case 1:
167                 if (pixwin.has_grab()) {
168                         if (!grab_is_fine) {
169                                 gtk_grab_remove
170                                         (GTK_WIDGET(pixwin.gobj()));
171                         }
172                 }
173                 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
174                         /* shift click back to the default */
175                         adjustment->set_value (default_value);
176                         return true;
177                 }
178                 break;
179                 
180         case 3:
181                 if (pixwin.has_grab()) {
182                         if (grab_is_fine) {
183                                 gtk_grab_remove
184                                         (GTK_WIDGET(pixwin.gobj()));
185                         }
186                 }
187                 break;
188         }
189
190         return VBox::on_button_release_event (ev); 
191 }
192
193 bool
194 MotionFeedback::pixwin_motion_notify_event (GdkEventMotion *ev) 
195
196         gfloat multiplier;
197         gfloat x_delta;
198         gfloat y_delta;
199
200         if(!pixwin.has_grab()) {
201                 return VBox::on_motion_notify_event (ev); 
202         }
203
204         multiplier = ((ev->state & Keyboard::TertiaryModifier) ? 100 : 1) *
205                 ((ev->state & Keyboard::SecondaryModifier) ? 10 : 1) * 
206                 ((ev->state & Keyboard::PrimaryModifier) ? 2 : 1);
207
208         y_delta = grabbed_y - ev->y_root;
209         grabbed_y = ev->y_root;
210
211         x_delta = ev->x_root - grabbed_x;
212
213         if (y_delta == 0) return TRUE;
214
215         y_delta *= 1 + (x_delta/100);
216         y_delta *= multiplier;
217         y_delta /= 10;
218
219         adjustment->set_value (adjustment->get_value() + 
220                                ((grab_is_fine ? step_inc : page_inc) * y_delta));
221
222         return true;
223 }
224
225 bool
226 MotionFeedback::pixwin_enter_notify_event (GdkEventCrossing *ev) 
227 {
228         pixwin.grab_focus();
229         return false;
230 }
231
232 bool
233 MotionFeedback::pixwin_leave_notify_event (GdkEventCrossing *ev) 
234 {
235         pixwin.unset_flags (HAS_FOCUS);
236         return false;
237 }
238
239 bool
240 MotionFeedback::pixwin_key_press_event (GdkEventKey *ev) 
241 {
242         bool retval = false;
243         gfloat curval;
244         gfloat multiplier;
245
246         multiplier = ((ev->state & Keyboard::TertiaryModifier) ? 100 : 1) *
247                 ((ev->state & Keyboard::SecondaryModifier) ? 10 : 1) * 
248                 ((ev->state & Keyboard::PrimaryModifier) ? 2 : 1);
249
250         switch (ev->keyval) {
251         case GDK_Page_Up:
252                 retval = true;
253                 curval = adjustment->get_value();
254                 adjustment->set_value (curval + (multiplier * page_inc));
255                 break;
256
257         case GDK_Page_Down:
258                 retval = true;
259                 curval = adjustment->get_value();
260                 adjustment->set_value (curval - (multiplier * page_inc));
261                 break;
262
263         case GDK_Up:
264                 retval = true;
265                 curval = adjustment->get_value();
266                 adjustment->set_value (curval + (multiplier * step_inc));
267                 break;
268
269         case GDK_Down:
270                 retval = true;
271                 curval = adjustment->get_value();
272                 adjustment->set_value (curval - (multiplier * step_inc));
273                 break;
274
275         case GDK_Home:
276                 retval = true;
277                 adjustment->set_value (_lower);
278                 break;
279
280         case GDK_End:
281                 retval = true;
282                 adjustment->set_value (_upper);
283                 break;
284         }
285         
286         return retval;
287 }
288
289 void
290 MotionFeedback::adjustment_changed ()
291 {
292         pixwin.queue_draw ();
293 }
294
295 bool
296 MotionFeedback::pixwin_expose_event (GdkEventExpose* ev)
297 {
298         GtkWidget* widget = GTK_WIDGET(pixwin.gobj());
299         GdkWindow *window = pixwin.get_window()->gobj();
300         GtkAdjustment* adj = adjustment->gobj();
301
302         int phase = (int)((adj->value - adj->lower) * 64 / 
303                           (adj->upper - adj->lower));
304
305         // skip middle phase except for true middle value
306
307         if (type == Rotary && phase == 32) {
308                 double pt = (adj->value - adj->lower) * 2.0 / 
309                         (adj->upper - adj->lower) - 1.0;
310                 if (pt < 0)
311                         phase = 31;
312                 if (pt > 0)
313                         phase = 33;
314         }
315
316         // endless knob: skip 90deg highlights unless the value is really a multiple of 90deg
317
318         if (type == Endless && !(phase % 16)) {
319                 if (phase == 64) {
320                         phase = 0;
321                 }
322
323                 double nom = adj->lower + phase * (adj->upper - adj->lower) / 64.0;
324                 double diff = (adj->value - nom) / (adj->upper - adj->lower);
325
326                 if (diff > 0.0001)
327                         phase = (phase + 1) % 64;
328                 if (diff < -0.0001)
329                         phase = (phase + 63) % 64;
330         }
331
332         gdk_draw_pixbuf (GDK_DRAWABLE(window), widget->style->fg_gc[0], 
333                          pixbuf->gobj(), 
334                          phase * subwidth, type * subheight, 
335                          0, 0, subwidth, subheight, GDK_RGB_DITHER_NORMAL, 0, 0);
336         
337         return true;
338 }
339
340 bool
341 MotionFeedback::pixwin_scroll_event (GdkEventScroll* ev)
342 {
343         double scale;
344
345         if ((ev->state & (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) == (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) {
346                 scale = 0.01;
347         } else if (ev->state & Keyboard::PrimaryModifier) {
348                 scale = 0.1;
349         } else {
350                 scale = 1.0;
351         }
352
353         switch (ev->direction) {
354         case GDK_SCROLL_UP:
355         case GDK_SCROLL_RIGHT:
356                 adjustment->set_value (adjustment->get_value() + (scale * adjustment->get_step_increment()));
357                 break;
358
359         case GDK_SCROLL_DOWN:
360         case GDK_SCROLL_LEFT:
361                 adjustment->set_value (adjustment->get_value() - (scale * adjustment->get_step_increment()));
362                 break;
363         }
364
365         return true;
366 }
367
368 void
369 MotionFeedback::pixwin_size_request (GtkRequisition* req)
370 {
371         req->width = subwidth;
372         req->height = subheight;
373 }