Somewhat distasteful hack to fix #3469 (broken keyboard-entry of pan values)
[ardour.git] / libs / gtkmm2ext / barcontroller.cc
1 /*
2     Copyright (C) 2004 Paul 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$
18 */
19
20 #include <string>
21 #include <sstream>
22 #include <climits>
23 #include <cstdio>
24 #include <cmath>
25 #include <algorithm>
26
27 #include <pbd/controllable.h>
28 #include <pbd/locale_guard.h>
29
30 #include "gtkmm2ext/gtk_ui.h"
31 #include "gtkmm2ext/utils.h"
32 #include "gtkmm2ext/keyboard.h"
33 #include "gtkmm2ext/barcontroller.h"
34
35 #include "i18n.h"
36
37 using namespace std;
38 using namespace Gtk;
39 using namespace Gtkmm2ext;
40
41 BarController::BarController (Gtk::Adjustment& adj,
42                               boost::shared_ptr<PBD::Controllable> mc)
43
44         : adjustment (adj),
45           binding_proxy (mc),
46           spinner (adjustment)
47
48 {                         
49         _style = LeftToRight;
50         grabbed = false;
51         switching = false;
52         switch_on_release = false;
53         use_parent = false;
54         logarithmic = false;
55
56         layout = darea.create_pango_layout("");
57
58         set_shadow_type (SHADOW_NONE);
59
60         initial_value = adjustment.get_value ();
61
62         adjustment.signal_value_changed().connect (mem_fun (*this, &Gtk::Widget::queue_draw));
63         adjustment.signal_changed().connect (mem_fun (*this, &Gtk::Widget::queue_draw));
64
65         darea.add_events (Gdk::BUTTON_RELEASE_MASK|
66                           Gdk::BUTTON_PRESS_MASK|
67                           Gdk::POINTER_MOTION_MASK|
68                           Gdk::ENTER_NOTIFY_MASK|
69                           Gdk::LEAVE_NOTIFY_MASK|
70                           Gdk::SCROLL_MASK);
71
72         darea.signal_expose_event().connect (mem_fun (*this, &BarController::expose));
73         darea.signal_motion_notify_event().connect (mem_fun (*this, &BarController::motion));
74         darea.signal_button_press_event().connect (mem_fun (*this, &BarController::button_press), false);
75         darea.signal_button_release_event().connect (mem_fun (*this, &BarController::button_release), false);
76         darea.signal_scroll_event().connect (mem_fun (*this, &BarController::scroll));
77
78         spinner.signal_activate().connect (mem_fun (*this, &BarController::entry_activated));
79         spinner.signal_focus_out_event().connect (mem_fun (*this, &BarController::entry_focus_out));
80         spinner.signal_input().connect (mem_fun (*this, &BarController::entry_input));
81         spinner.signal_output().connect (mem_fun (*this, &BarController::entry_output));
82         spinner.set_digits (3);
83         spinner.set_numeric (true);
84
85         add (darea);
86         show_all ();
87 }
88
89 void
90 BarController::drop_grab ()
91 {
92         if (grabbed) {
93                 grabbed = false;
94                 darea.remove_modal_grab();
95                 StopGesture ();
96         }
97 }
98
99 bool
100 BarController::button_press (GdkEventButton* ev)
101 {
102         double fract;
103
104         if (binding_proxy.button_press_handler (ev)) {
105                 return true;
106         }
107
108         switch (ev->button) {
109         case 1:
110                 if (ev->type == GDK_2BUTTON_PRESS) {
111                         switch_on_release = true;
112                         drop_grab ();
113                 } else {
114                         switch_on_release = false;
115                         darea.add_modal_grab();
116                         grabbed = true;
117                         grab_x = ev->x;
118                         grab_window = ev->window;
119                         StartGesture ();
120                 }
121                 return true;
122                 break;
123
124         case 2:
125                 fract = ev->x / (darea.get_width() - 2.0);
126                 adjustment.set_value (adjustment.get_lower() + fract * (adjustment.get_upper() - adjustment.get_lower()));
127
128         case 3:
129                 break;
130
131         case 4:
132         case 5:
133                 break;
134         }
135
136         return false;
137 }
138
139 bool
140 BarController::button_release (GdkEventButton* ev)
141 {
142         drop_grab ();
143         
144         switch (ev->button) {
145         case 1:
146                 if (switch_on_release) {
147                         Glib::signal_idle().connect (mem_fun (*this, &BarController::switch_to_spinner));
148                         return true;
149                 }
150
151                 if ((ev->state & (Keyboard::TertiaryModifier|Keyboard::PrimaryModifier)) == Keyboard::TertiaryModifier) {
152                         adjustment.set_value (initial_value);
153                 } else {
154                         double scale;
155
156                         if ((ev->state & (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) == (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) {
157                                 scale = 0.01;
158                         } else if (ev->state & Keyboard::PrimaryModifier) {
159                                 scale = 0.1;
160                         } else {
161                                 scale = 1.0;
162                         }
163
164                         mouse_control (ev->x, ev->window, scale);
165                 }
166                 break;
167
168         case 2:
169                 break;
170                 
171         case 3:
172                 return false;
173                 
174         default:
175                 break;
176         }
177
178         return true;
179 }
180
181 bool
182 BarController::scroll (GdkEventScroll* ev)
183 {
184         double scale;
185
186         if ((ev->state & (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) == (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) {
187                 scale = 0.01;
188         } else if (ev->state & Keyboard::PrimaryModifier) {
189                 scale = 0.1;
190         } else {
191                 scale = 1.0;
192         }
193
194         switch (ev->direction) {
195         case GDK_SCROLL_UP:
196         case GDK_SCROLL_RIGHT:
197                 adjustment.set_value (adjustment.get_value() + (scale * adjustment.get_step_increment()));
198                 break;
199
200         case GDK_SCROLL_DOWN:
201         case GDK_SCROLL_LEFT:
202                 adjustment.set_value (adjustment.get_value() - (scale * adjustment.get_step_increment()));
203                 break;
204         }
205
206         return true;
207 }
208
209 bool
210 BarController::motion (GdkEventMotion* ev)
211 {
212         double scale;
213         
214         if (!grabbed) {
215                 return true;
216         }
217
218         if ((ev->state & (Keyboard::TertiaryModifier|Keyboard::PrimaryModifier)) == Keyboard::TertiaryModifier) {
219                 return TRUE;
220         }
221
222         if ((ev->state & (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) == (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) {
223                 scale = 0.01;
224         } else if (ev->state & Keyboard::PrimaryModifier) {
225                 scale = 0.1;
226         } else {
227                 scale = 1.0;
228         }
229
230         return mouse_control (ev->x, ev->window, scale);
231 }
232
233 gint
234 BarController::mouse_control (double x, GdkWindow* window, double scaling)
235 {
236         double fract = 0.0;
237         double delta;
238
239         if (window != grab_window) {
240                 grab_x = x;
241                 grab_window = window;
242                 return TRUE;
243         }
244
245         delta = x - grab_x;
246         grab_x = x;
247
248         switch (_style) {
249         case Line:
250         case LeftToRight:
251                 fract = scaling * (delta / (darea.get_width() - 2));
252                 fract = min (1.0, fract);
253                 fract = max (-1.0, fract);
254                 adjustment.set_value (adjustment.get_value() + fract * (adjustment.get_upper() - adjustment.get_lower()));
255                 break;
256
257         default:
258                 fract = 0.0;
259         }
260         
261         
262         return TRUE;
263 }
264
265 bool
266 BarController::expose (GdkEventExpose* /*event*/)
267 {
268         Glib::RefPtr<Gdk::Window> win (darea.get_window());
269         Widget* parent;
270         gint x1=0, x2=0, y1=0, y2=0;
271         gint w, h;
272         double fract;
273
274         fract = ((adjustment.get_value() - adjustment.get_lower()) /
275                  (adjustment.get_upper() - adjustment.get_lower()));
276         
277         switch (_style) {
278         case Line:
279                 w = darea.get_width() - 1;
280                 h = darea.get_height();
281                 x1 = (gint) floor (w * fract);
282                 x2 = x1;
283                 y1 = 0;
284                 y2 = h - 1;
285
286                 if (use_parent) {
287                         parent = get_parent();
288                         
289                         if (parent) {
290                                 win->draw_rectangle (parent->get_style()->get_fg_gc (parent->get_state()),
291                                                      true,
292                                                      0, 0, darea.get_width(), darea.get_height());
293                         }
294
295                 } else {
296
297                         win->draw_rectangle (get_style()->get_bg_gc (get_state()),
298                                              true,
299                                              0, 0, darea.get_width() - ((darea.get_width()+1) % 2), darea.get_height());
300                 }
301                 
302                 win->draw_line (get_style()->get_fg_gc (get_state()), x1, 0, x1, h);
303                 break;
304
305         case CenterOut:
306                 break;
307
308         case LeftToRight:
309
310                 w = darea.get_width() - 2;
311                 h = darea.get_height() - 2;
312
313                 x1 = 0;
314                 x2 = (gint) floor (w * fract);
315                 y1 = 0;
316                 y2 = h - 1;
317
318                 win->draw_rectangle (get_style()->get_bg_gc (get_state()),
319                                     false,
320                                     0, 0, darea.get_width() - 1, darea.get_height() - 1);
321
322                 /* draw active box */
323
324                 win->draw_rectangle (get_style()->get_fg_gc (get_state()),
325                                     true,
326                                     1 + x1,
327                                     1 + y1,
328                                     x2,
329                                     1 + y2);
330                 
331                 /* draw inactive box */
332
333                 win->draw_rectangle (get_style()->get_fg_gc (STATE_INSENSITIVE),
334                                     true,
335                                     1 + x2,
336                                     1 + y1,
337                                     w - x2,
338                                     1 + y2);
339
340                 break;
341
342         case RightToLeft:
343                 break;
344         case TopToBottom:
345                 break;
346         case BottomToTop:
347                 break;
348         }
349
350         /* draw label */
351
352         int xpos = -1;
353         std::string const label = get_label (xpos);
354
355         if (!label.empty()) {
356                 
357                 layout->set_text (label);
358                 
359                 int width, height;
360                 layout->get_pixel_size (width, height);
361
362                 if (xpos == -1) {
363                         xpos = max (3, 1 + (x2 - (width/2)));
364                         xpos = min (darea.get_width() - width - 3, xpos);
365                 }
366                 
367                 win->draw_layout (get_style()->get_text_gc (get_state()),
368                                   xpos,
369                                   (darea.get_height()/2) - (height/2),
370                                   layout);
371         }
372         
373         return true;
374 }
375
376 void
377 BarController::set_style (barStyle s)
378 {
379         _style = s;
380         darea.queue_draw ();
381 }
382
383 gint
384 BarController::switch_to_bar ()
385 {
386         if (switching) {
387                 return FALSE;
388         }
389
390         switching = true;
391
392         if (get_child() == &darea) {
393                 return FALSE;
394         }
395
396         remove ();
397         add (darea);
398         darea.show ();
399
400         switching = false;
401
402         SpinnerActive (false); /* EMIT SIGNAL */
403         
404         return FALSE;
405 }
406
407 gint
408 BarController::switch_to_spinner ()
409 {
410         if (switching) {
411                 return FALSE;
412         }
413
414         switching = true;
415
416         if (get_child() == &spinner) {
417                 return FALSE;
418         }
419
420         remove ();
421         add (spinner);
422         spinner.show ();
423         spinner.select_region (0, spinner.get_text_length());
424         spinner.grab_focus ();
425
426         switching = false;
427
428         SpinnerActive (true); /* EMIT SIGNAL */
429
430         return FALSE;
431 }
432
433 void
434 BarController::entry_activated ()
435 {
436         switch_to_bar ();
437 }
438
439 bool
440 BarController::entry_focus_out (GdkEventFocus* /*ev*/)
441 {
442         entry_activated ();
443         return true;
444 }
445
446 void
447 BarController::set_use_parent (bool yn)
448 {
449         use_parent = yn;
450         queue_draw ();
451 }
452
453 void
454 BarController::set_sensitive (bool yn)
455 {
456         Frame::set_sensitive (yn);
457         darea.set_sensitive (yn);
458 }
459
460 /* 
461     This is called when we need to update the adjustment with the value
462     from the spinner's text entry.
463     
464     We need to use Gtk::Entry::get_text to avoid recursive nastiness :)
465     
466     If we're not in logarithmic mode we can return false to use the 
467     default conversion.
468     
469     In theory we should check for conversion errors but set numeric
470     mode to true on the spinner prevents invalid input.
471 */
472 int
473 BarController::entry_input (double* new_value)
474 {
475         if (!logarithmic) {
476                 return false;
477         }
478
479         // extract a double from the string and take its log
480         Entry *entry = dynamic_cast<Entry *>(&spinner);
481         double value;
482
483         {
484                 // Switch to user's preferred locale so that
485                 // if they use different LC_NUMERIC conventions,
486                 // we will honor them.
487
488                 PBD::LocaleGuard lg ("");
489                 sscanf (entry->get_text().c_str(), "%lf", &value);
490         }
491
492         *new_value = log(value);
493
494         return true;
495 }
496
497 /* 
498     This is called when we need to update the spinner's text entry 
499     with the value of the adjustment.
500     
501     We need to use Gtk::Entry::set_text to avoid recursive nastiness :)
502     
503     If we're not in logarithmic mode we can return false to use the 
504     default conversion.
505 */
506 bool
507 BarController::entry_output ()
508 {
509         if (!logarithmic) {
510                 return false;
511         }
512
513         // generate the exponential and turn it into a string
514         // convert to correct locale. 
515         
516         stringstream stream;
517         string str;
518
519         char buf[128];
520
521         {
522                 // Switch to user's preferred locale so that
523                 // if they use different LC_NUMERIC conventions,
524                 // we will honor them.
525                 
526                 PBD::LocaleGuard lg ("");
527                 snprintf (buf, sizeof (buf), "%g", exp (spinner.get_adjustment()->get_value()));
528         }
529
530         Entry *entry = dynamic_cast<Entry *>(&spinner);
531         entry->set_text(buf);
532         
533         return true;
534 }
535
536
537