Trim calls to parameter_count() a bit during the process
[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 (9);
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 Blob:
251         case LeftToRight:
252         case CenterOut:
253                 fract = scaling * (delta / (darea.get_width() - 2));
254                 fract = min (1.0, fract);
255                 fract = max (-1.0, fract);
256                 adjustment.set_value (adjustment.get_value() + fract * (adjustment.get_upper() - adjustment.get_lower()));
257                 break;
258         default:
259                 fract = 0.0;
260         }
261         
262         
263         return TRUE;
264 }
265
266 bool
267 BarController::expose (GdkEventExpose* /*event*/)
268 {
269         Glib::RefPtr<Gdk::Window> win (darea.get_window());
270         Cairo::RefPtr<Cairo::Context> context = win->create_cairo_context();
271         Gdk::Color c;
272         Widget* parent;
273         gint x1=0, x2=0, y1=0, y2=0;
274         gint w, h;
275         double fract;
276         float r, g, b;
277
278         fract = ((adjustment.get_value() - adjustment.get_lower()) /
279                  (adjustment.get_upper() - adjustment.get_lower()));
280         
281         switch (_style) {
282         case Line:
283                 w = darea.get_width() - 1;
284                 h = darea.get_height();
285                 x1 = (gint) floor (w * fract);
286                 x2 = x1;
287                 y1 = 0;
288                 y2 = h - 1;
289
290                 if (use_parent) {
291                         parent = get_parent();
292                         
293                         if (parent) {
294                                 c = parent->get_style()->get_fg (parent->get_state());
295                                 r = c.get_red_p ();
296                                 g = c.get_green_p ();
297                                 b = c.get_blue_p ();
298                                 context->set_source_rgb (r, g, b);
299                                 context->rectangle (0, 0, darea.get_width(), darea.get_height());
300                                 context->fill ();
301                         }
302
303                 } else {
304
305                         c = get_style()->get_bg (get_state());
306                         r = c.get_red_p ();
307                         g = c.get_green_p ();
308                         b = c.get_blue_p ();
309                         context->set_source_rgb (r, g, b);
310                         context->rectangle (0, 0, darea.get_width() - ((darea.get_width()+1) % 2), darea.get_height());
311                         context->fill ();
312                 }
313                 
314                 c = get_style()->get_fg (get_state());
315                 r = c.get_red_p ();
316                 g = c.get_green_p ();
317                 b = c.get_blue_p ();
318                 context->set_source_rgb (r, g, b);
319                 context->move_to (x1, 0);
320                 context->line_to (x1, h);
321                 context->stroke ();
322                 break;
323
324         case Blob:
325                 w = darea.get_width() - 1;
326                 h = darea.get_height();
327                 x1 = (gint) floor (w * fract);
328                 x2 = min (w-2,h-2);
329
330                 if (use_parent) {
331                         parent = get_parent();
332                         
333                         if (parent) {
334                                 c = parent->get_style()->get_fg (parent->get_state());
335                                 r = c.get_red_p ();
336                                 g = c.get_green_p ();
337                                 b = c.get_blue_p ();
338                                 context->set_source_rgb (r, g, b);
339                                 context->rectangle (0, 0, darea.get_width(), darea.get_height());
340                                 context->fill ();
341                         }
342
343                 } else {
344
345                         c = get_style()->get_bg (get_state());
346                         r = c.get_red_p ();
347                         g = c.get_green_p ();
348                         b = c.get_blue_p ();
349                         context->set_source_rgb (r, g, b);
350                         context->rectangle (0, 0, darea.get_width() - ((darea.get_width()+1) % 2), darea.get_height());
351                         context->fill ();
352                 }
353                 
354                 c = get_style()->get_fg (get_state());
355                 r = c.get_red_p ();
356                 g = c.get_green_p ();
357                 b = c.get_blue_p ();
358                 context->arc (x1, ((h-2)/2)-1, x2, 0, 2*M_PI);
359                 break;
360
361         case CenterOut:
362                 w = darea.get_width();
363                 h = darea.get_height()-2;
364                 if (use_parent) {
365                         parent = get_parent();
366                         if (parent) {
367                                 c = parent->get_style()->get_fg (parent->get_state());
368                                 r = c.get_red_p ();
369                                 g = c.get_green_p ();
370                                 b = c.get_blue_p ();
371                                 context->set_source_rgb (r, g, b);
372                                 context->rectangle (0, 0, darea.get_width(), darea.get_height());
373                                 context->fill ();
374                         }
375                 } else {
376                         c = get_style()->get_bg (get_state());
377                         r = c.get_red_p ();
378                         g = c.get_green_p ();
379                         b = c.get_blue_p ();
380                         context->set_source_rgb (r, g, b);
381                         context->rectangle (0, 0, darea.get_width(), darea.get_height());
382                         context->fill ();
383                 }
384                 c = get_style()->get_fg (get_state());
385                 r = c.get_red_p ();
386                 g = c.get_green_p ();
387                 b = c.get_blue_p ();
388                 x1 = (w/2) - ((w*fract)/2); // center, back up half the bar width
389                 context->set_source_rgb (r, g, b);
390                 context->rectangle (x1, 1, w*fract, h);
391                 context->fill ();
392                 break;
393
394         case LeftToRight:
395
396                 w = darea.get_width() - 2;
397                 h = darea.get_height() - 2;
398
399                 x1 = 0;
400                 x2 = (gint) floor (w * fract);
401                 y1 = 0;
402                 y2 = h;
403
404                 /* bounding box */
405
406                 c = get_style()->get_bg (get_state());
407                 r = c.get_red_p ();
408                 g = c.get_green_p ();
409                 b = c.get_blue_p ();
410                 context->set_source_rgb (r, g, b);
411                 rounded_rectangle (context, 0, 0, darea.get_width(), darea.get_height());
412                 context->stroke ();
413
414                 /* draw active box */
415
416                 c = get_style()->get_fg (get_state());
417                 r = c.get_red_p ();
418                 g = c.get_green_p ();
419                 b = c.get_blue_p ();
420                 context->set_source_rgb (r, g, b);
421                 rounded_rectangle (context, 1 + x1, 1 + y1, x2, y2);
422                 context->fill ();
423
424                 /* draw inactive box */
425
426                 c = get_style()->get_fg (STATE_INSENSITIVE);
427                 r = c.get_red_p ();
428                 g = c.get_green_p ();
429                 b = c.get_blue_p ();
430                 context->set_source_rgb (r, g, b);
431                 rounded_rectangle (context, 1 + x2, 1 + y1, w - x2, y2);
432                 context->fill ();
433
434                 break;
435
436         case RightToLeft:
437                 break;
438         case TopToBottom:
439                 break;
440         case BottomToTop:
441                 break;
442         }
443
444         /* draw label */
445
446         double xpos = -1;
447         std::string const label = get_label (xpos);
448
449         if (!label.empty()) {
450                 
451                 layout->set_text (label);
452                 
453                 int width, height, x;
454                 layout->get_pixel_size (width, height);
455
456                 if (xpos == -1) {
457                         x = max (3, 1 + (x2 - (width/2)));
458                         x = min (darea.get_width() - width - 3, (int) lrint (xpos));
459                 } else {
460                         x = lrint (darea.get_width() * xpos);
461                 }
462
463                 c = get_style()->get_text (get_state());
464                 r = c.get_red_p ();
465                 g = c.get_green_p ();
466                 b = c.get_blue_p ();
467                 context->set_source_rgb (r, g, b);
468                 context->move_to (x, (darea.get_height()/2) - (height/2));
469                 layout->show_in_cairo_context (context);
470         }
471         
472         return true;
473 }
474
475 void
476 BarController::set_style (barStyle s)
477 {
478         _style = s;
479         darea.queue_draw ();
480 }
481
482 gint
483 BarController::switch_to_bar ()
484 {
485         if (switching) {
486                 return FALSE;
487         }
488
489         switching = true;
490
491         if (get_child() == &darea) {
492                 return FALSE;
493         }
494
495         remove ();
496         add (darea);
497         darea.show ();
498
499         switching = false;
500
501         SpinnerActive (false); /* EMIT SIGNAL */
502         
503         return FALSE;
504 }
505
506 gint
507 BarController::switch_to_spinner ()
508 {
509         if (switching) {
510                 return FALSE;
511         }
512
513         switching = true;
514
515         if (get_child() == &spinner) {
516                 return FALSE;
517         }
518
519         remove ();
520         add (spinner);
521         spinner.show ();
522         spinner.select_region (0, spinner.get_text_length());
523         spinner.grab_focus ();
524
525         switching = false;
526
527         SpinnerActive (true); /* EMIT SIGNAL */
528
529         return FALSE;
530 }
531
532 void
533 BarController::entry_activated ()
534 {
535         switch_to_bar ();
536 }
537
538 bool
539 BarController::entry_focus_out (GdkEventFocus* /*ev*/)
540 {
541         entry_activated ();
542         return true;
543 }
544
545 void
546 BarController::set_use_parent (bool yn)
547 {
548         use_parent = yn;
549         queue_draw ();
550 }
551
552 void
553 BarController::set_sensitive (bool yn)
554 {
555         Frame::set_sensitive (yn);
556         darea.set_sensitive (yn);
557 }
558
559 /* 
560     This is called when we need to update the adjustment with the value
561     from the spinner's text entry.
562     
563     We need to use Gtk::Entry::get_text to avoid recursive nastiness :)
564     
565     If we're not in logarithmic mode we can return false to use the 
566     default conversion.
567     
568     In theory we should check for conversion errors but set numeric
569     mode to true on the spinner prevents invalid input.
570 */
571 int
572 BarController::entry_input (double* new_value)
573 {
574         if (!logarithmic) {
575                 return false;
576         }
577
578         // extract a double from the string and take its log
579         Entry *entry = dynamic_cast<Entry *>(&spinner);
580         double value;
581
582         {
583                 // Switch to user's preferred locale so that
584                 // if they use different LC_NUMERIC conventions,
585                 // we will honor them.
586
587                 PBD::LocaleGuard lg ("");
588                 sscanf (entry->get_text().c_str(), "%lf", &value);
589         }
590
591         *new_value = log(value);
592
593         return true;
594 }
595
596 /* 
597     This is called when we need to update the spinner's text entry 
598     with the value of the adjustment.
599     
600     We need to use Gtk::Entry::set_text to avoid recursive nastiness :)
601     
602     If we're not in logarithmic mode we can return false to use the 
603     default conversion.
604 */
605 bool
606 BarController::entry_output ()
607 {
608         if (!logarithmic) {
609                 return false;
610         }
611
612         // generate the exponential and turn it into a string
613         // convert to correct locale. 
614         
615         stringstream stream;
616         string str;
617
618         char buf[128];
619
620         {
621                 // Switch to user's preferred locale so that
622                 // if they use different LC_NUMERIC conventions,
623                 // we will honor them.
624                 
625                 PBD::LocaleGuard lg ("");
626                 snprintf (buf, sizeof (buf), "%g", exp (spinner.get_adjustment()->get_value()));
627         }
628
629         Entry *entry = dynamic_cast<Entry *>(&spinner);
630         entry->set_text(buf);
631         
632         return true;
633 }
634
635
636