fix plugin bypass button action
[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 #include "gtkmm2ext/cairo_widget.h"
35
36 #include "i18n.h"
37
38 using namespace std;
39 using namespace Gtk;
40 using namespace Gtkmm2ext;
41
42 BarController::BarController (Gtk::Adjustment& adj,
43                               boost::shared_ptr<PBD::Controllable> mc)
44
45         : adjustment (adj)
46           , binding_proxy (mc)
47           , _hovering (false)
48           , spinner (adjustment)
49 {                         
50         _style = LeftToRight;
51         grabbed = false;
52         switching = false;
53         switch_on_release = false;
54         use_parent = false;
55         logarithmic = false;
56
57         layout = darea.create_pango_layout("");
58
59         set_shadow_type (SHADOW_NONE);
60
61         initial_value = adjustment.get_value ();
62
63         adjustment.signal_value_changed().connect (mem_fun (*this, &Gtk::Widget::queue_draw));
64         adjustment.signal_changed().connect (mem_fun (*this, &Gtk::Widget::queue_draw));
65
66         darea.add_events (Gdk::BUTTON_RELEASE_MASK|
67                           Gdk::BUTTON_PRESS_MASK|
68                           Gdk::POINTER_MOTION_MASK|
69                           Gdk::ENTER_NOTIFY_MASK|
70                           Gdk::LEAVE_NOTIFY_MASK|
71                           Gdk::SCROLL_MASK);
72
73         darea.signal_expose_event().connect (mem_fun (*this, &BarController::expose));
74         darea.signal_motion_notify_event().connect (mem_fun (*this, &BarController::motion));
75         darea.signal_button_press_event().connect (mem_fun (*this, &BarController::button_press), false);
76         darea.signal_button_release_event().connect (mem_fun (*this, &BarController::button_release), false);
77         darea.signal_scroll_event().connect (mem_fun (*this, &BarController::scroll));
78         darea.signal_enter_notify_event().connect (mem_fun (*this, &BarController::on_enter_notify_event));
79         darea.signal_leave_notify_event().connect (mem_fun (*this, &BarController::on_leave_notify_event));
80
81         spinner.signal_activate().connect (mem_fun (*this, &BarController::entry_activated));
82         spinner.signal_focus_out_event().connect (mem_fun (*this, &BarController::entry_focus_out));
83         spinner.signal_input().connect (mem_fun (*this, &BarController::entry_input));
84         spinner.signal_output().connect (mem_fun (*this, &BarController::entry_output));
85         spinner.set_digits (9);
86         spinner.set_numeric (true);
87         
88         add (darea);
89
90         show_all ();
91 }
92
93 BarController::~BarController ()
94 {
95 }
96
97 bool
98 BarController::on_enter_notify_event (GdkEventCrossing*)
99 {
100         _hovering = true;
101         Keyboard::magic_widget_grab_focus ();
102         queue_draw ();
103         return false;
104 }
105
106 bool
107 BarController::on_leave_notify_event (GdkEventCrossing*)
108 {
109         _hovering = false;
110         Keyboard::magic_widget_drop_focus();
111         queue_draw ();
112         return false;
113 }
114
115 void
116 BarController::drop_grab ()
117 {
118         if (grabbed) {
119                 grabbed = false;
120                 darea.remove_modal_grab();
121                 StopGesture ();
122         }
123 }
124
125 bool
126 BarController::button_press (GdkEventButton* ev)
127 {
128         double fract;
129
130         if (binding_proxy.button_press_handler (ev)) {
131                 return true;
132         }
133
134         switch (ev->button) {
135         case 1:
136                 if (ev->type == GDK_2BUTTON_PRESS) {
137                         switch_on_release = true;
138                         drop_grab ();
139                 } else {
140                         switch_on_release = false;
141                         darea.add_modal_grab();
142                         grabbed = true;
143                         grab_x = ev->x;
144                         grab_window = ev->window;
145                         StartGesture ();
146                 }
147                 return true;
148                 break;
149
150         case 2:
151                 fract = ev->x / (darea.get_width() - 2.0);
152                 adjustment.set_value (adjustment.get_lower() + fract * (adjustment.get_upper() - adjustment.get_lower()));
153
154         case 3:
155                 break;
156
157         case 4:
158         case 5:
159                 break;
160         }
161
162         return false;
163 }
164
165 bool
166 BarController::button_release (GdkEventButton* ev)
167 {
168         drop_grab ();
169         
170         switch (ev->button) {
171         case 1:
172                 if (switch_on_release) {
173                         Glib::signal_idle().connect (mem_fun (*this, &BarController::switch_to_spinner));
174                         return true;
175                 }
176
177                 if ((ev->state & (Keyboard::TertiaryModifier|Keyboard::PrimaryModifier)) == Keyboard::TertiaryModifier) {
178                         adjustment.set_value (initial_value);
179                 } else {
180                         double scale;
181
182                         if ((ev->state & (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) == (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) {
183                                 scale = 0.01;
184                         } else if (ev->state & Keyboard::PrimaryModifier) {
185                                 scale = 0.1;
186                         } else {
187                                 scale = 1.0;
188                         }
189
190                         mouse_control (ev->x, ev->window, scale);
191                 }
192                 break;
193
194         case 2:
195                 break;
196                 
197         case 3:
198                 return false;
199                 
200         default:
201                 break;
202         }
203
204         return true;
205 }
206
207 bool
208 BarController::scroll (GdkEventScroll* ev)
209 {
210         double scale;
211
212         if ((ev->state & (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) == (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) {
213                 scale = 0.01;
214         } else if (ev->state & Keyboard::PrimaryModifier) {
215                 scale = 0.1;
216         } else {
217                 scale = 1.0;
218         }
219
220         switch (ev->direction) {
221         case GDK_SCROLL_UP:
222         case GDK_SCROLL_RIGHT:
223                 adjustment.set_value (adjustment.get_value() + (scale * adjustment.get_step_increment()));
224                 break;
225
226         case GDK_SCROLL_DOWN:
227         case GDK_SCROLL_LEFT:
228                 adjustment.set_value (adjustment.get_value() - (scale * adjustment.get_step_increment()));
229                 break;
230         }
231
232         return true;
233 }
234
235 bool
236 BarController::motion (GdkEventMotion* ev)
237 {
238         double scale;
239         
240         if (!grabbed) {
241                 return true;
242         }
243
244         if ((ev->state & (Keyboard::TertiaryModifier|Keyboard::PrimaryModifier)) == Keyboard::TertiaryModifier) {
245                 return TRUE;
246         }
247
248         if ((ev->state & (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) == (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) {
249                 scale = 0.01;
250         } else if (ev->state & Keyboard::PrimaryModifier) {
251                 scale = 0.1;
252         } else {
253                 scale = 1.0;
254         }
255
256         return mouse_control (ev->x, ev->window, scale);
257 }
258
259 gint
260 BarController::mouse_control (double x, GdkWindow* window, double scaling)
261 {
262         double fract = 0.0;
263         double delta;
264
265         if (window != grab_window) {
266                 grab_x = x;
267                 grab_window = window;
268                 return TRUE;
269         }
270
271         delta = x - grab_x;
272         grab_x = x;
273         
274         switch (_style) {
275         case Line:
276         case Blob:
277         case LeftToRight:
278         case CenterOut:
279                 fract = scaling * (delta / (darea.get_width() - 2));
280                 fract = min (1.0, fract);
281                 fract = max (-1.0, fract);
282                 adjustment.set_value (adjustment.get_value() + fract * (adjustment.get_upper() - adjustment.get_lower()));
283                 break;
284         default:
285                 fract = 0.0;
286         }
287         
288         
289         return TRUE;
290 }
291
292 Gdk::Color
293 BarController::get_parent_bg ()
294 {
295         Widget* parent;
296
297         parent = get_parent ();
298
299         while (parent) {
300                 static const char* has_cairo_widget_background_info = "has_cairo_widget_background_info";
301                 void* p = g_object_get_data (G_OBJECT(parent->gobj()), has_cairo_widget_background_info);
302
303                 if (p) {
304                         Glib::RefPtr<Gtk::Style> style = parent->get_style();
305                         return style->get_bg (get_state());
306                 }
307                 
308                 if (!parent->get_has_window()) {
309                         parent = parent->get_parent();
310                 } else {
311                         break;
312                 }
313         }
314
315         if (parent && parent->get_has_window()) {
316                 return parent->get_style ()->get_bg (parent->get_state());
317         } 
318
319         return get_style ()->get_bg (get_state());
320 }
321
322 bool
323 BarController::expose (GdkEventExpose* /*event*/)
324 {
325         Glib::RefPtr<Gdk::Window> win (darea.get_window());
326         Cairo::RefPtr<Cairo::Context> context = win->create_cairo_context();
327         cairo_t* cr = context->cobj();
328
329         Gdk::Color fg_col = get_style()->get_fg (get_state());
330
331         double fract = ((adjustment.get_value() - adjustment.get_lower()) /
332                  (adjustment.get_upper() - adjustment.get_lower()));
333         
334         gint w = darea.get_width() ;
335         gint h = darea.get_height();
336         gint bar_start, bar_width;
337         double radius = 4;
338
339         switch (_style) {
340         case Line:
341                 bar_start = (gint) floor ((w-1) * fract);
342                 bar_width = 1;
343                 break;
344
345         case Blob:
346                 // ????
347                 break;
348
349         case CenterOut:
350         bar_width = (w*fract);
351         bar_start = (w/2) - bar_width/2; // center, back up half the bar width
352                 break;
353
354         case LeftToRight:
355                 bar_start = 1;
356                 bar_width = floor((w-2)*fract);
357
358                 break;
359
360         case RightToLeft:
361                 break;
362         case TopToBottom:
363                 break;
364         case BottomToTop:
365                 break;
366         }
367
368         //fill in the bg rect ... 
369         Gdk::Color c = get_parent_bg(); //get_style()->get_bg (Gtk::STATE_PRELIGHT);  //why prelight?  Shouldn't we be using the parent's color?  maybe   get_parent_bg  ?
370         CairoWidget::set_source_rgb_a (cr, c);
371         cairo_rectangle (cr, 0, 0, w, h);
372         cairo_fill(cr);
373
374         //"slot"
375         cairo_set_source_rgba (cr, 0.17, 0.17, 0.17, 1.0);
376         Gtkmm2ext::rounded_rectangle (cr, 1, 1, w-2, h-2, radius-0.5);
377         cairo_fill(cr);
378
379         //mask off the corners
380         Gtkmm2ext::rounded_rectangle (cr, 1, 1, w-2, h-2, radius-0.5);
381         cairo_clip(cr);
382         
383                 //background gradient
384                 if ( !CairoWidget::flat_buttons() ) {
385                         cairo_pattern_t *bg_gradient = cairo_pattern_create_linear (0.0, 0.0, 0, h);
386                         cairo_pattern_add_color_stop_rgba (bg_gradient, 0, 0, 0, 0, 0.4);
387                         cairo_pattern_add_color_stop_rgba (bg_gradient, 0.2, 0, 0, 0, 0.2);
388                         cairo_pattern_add_color_stop_rgba (bg_gradient, 1, 0, 0, 0, 0.0);
389                         cairo_set_source (cr, bg_gradient);
390                         Gtkmm2ext::rounded_rectangle (cr, 1, 1, w-2, h-2, radius-1.5);
391                         cairo_fill (cr);
392                         cairo_pattern_destroy(bg_gradient);
393                 }
394                 
395                 //fg color
396                 CairoWidget::set_source_rgb_a (cr, fg_col, 1.0);
397                 Gtkmm2ext::rounded_rectangle (cr, bar_start, 1, bar_width, h-2, radius - 1.5);
398                 cairo_fill(cr);
399
400                 //fg gradient
401                 if (!CairoWidget::flat_buttons() ) {
402                         cairo_pattern_t * fg_gradient = cairo_pattern_create_linear (0.0, 0.0, 0, h);
403                         cairo_pattern_add_color_stop_rgba (fg_gradient, 0, 0, 0, 0, 0.0);
404                         cairo_pattern_add_color_stop_rgba (fg_gradient, 0.1, 0, 0, 0, 0.0);
405                         cairo_pattern_add_color_stop_rgba (fg_gradient, 1, 0, 0, 0, 0.3);
406                         cairo_set_source (cr, fg_gradient);
407                         Gtkmm2ext::rounded_rectangle (cr, bar_start, 1, bar_width, h-2, radius - 1.5);
408                         cairo_fill (cr);
409                         cairo_pattern_destroy(fg_gradient);
410                 }
411                 
412         cairo_reset_clip(cr);
413
414         //black border
415         cairo_set_line_width (cr, 1.0);
416         cairo_set_source_rgba (cr, 0, 0, 0, 1.0);
417         Gtkmm2ext::rounded_rectangle (cr, 0.5, 0.5, w-1, h-1, radius);
418         cairo_stroke(cr);
419
420         /* draw the unity-position line if it's not at either end*/
421 /*      if (unity_loc > 0) {
422                 context->set_line_width (1);
423                 cairo_set_source_rgba (cr, 1,1,1, 1.0);
424                 if ( _orien == VERT) {
425                         if (unity_loc < h ) {
426                                 context->move_to (2.5, unity_loc + radius + .5);
427                                 context->line_to (girth-2.5, unity_loc + radius + .5);
428                                 context->stroke ();
429                         }
430                 } else {
431                         if ( unity_loc < w ){
432                                 context->move_to (unity_loc - radius + .5, 3.5);
433                                 context->line_to (unity_loc - radius + .5, girth-3.5);
434                                 context->stroke ();
435                         }
436                 }
437         }*/
438         
439         if (!darea.get_sensitive()) {
440                 rounded_rectangle (context, 0, 0, darea.get_width(), darea.get_height(), 3);
441                 context->set_source_rgba (0.505, 0.517, 0.525, 0.6);
442                 context->fill ();
443         } else if (_hovering) {
444                 Gtkmm2ext::rounded_rectangle (cr, 1, 1, w-2, h-2, radius);
445                 cairo_set_source_rgba (cr, 0.905, 0.917, 0.925, 0.1);
446                 cairo_fill (cr);
447         }
448
449         /* draw label */
450
451         double xpos = -1;
452         std::string const label = get_label (xpos);
453         if (!label.empty()) {
454                 
455                 int twidth, theight;
456                 layout->set_text (label);
457                 layout->get_pixel_size (twidth, theight);
458
459                 c = get_style()->get_text (get_state());
460                 CairoWidget::set_source_rgb_a (cr, c, 1.0);
461                 context->move_to ( (fract > 0.5) ? w - twidth/0.7 : twidth/0.7, (darea.get_height()/2) - (theight/2));
462                 layout->show_in_cairo_context (context);
463         }
464         
465         return true;
466 }
467
468 void
469 BarController::set_style (barStyle s)
470 {
471         _style = s;
472         darea.queue_draw ();
473 }
474
475 gint
476 BarController::switch_to_bar ()
477 {
478         if (switching) {
479                 return FALSE;
480         }
481
482         switching = true;
483
484         if (get_child() == &darea) {
485                 return FALSE;
486         }
487
488         remove ();
489         add (darea);
490         darea.show ();
491
492         switching = false;
493
494         SpinnerActive (false); /* EMIT SIGNAL */
495         
496         return FALSE;
497 }
498
499 gint
500 BarController::switch_to_spinner ()
501 {
502         if (switching) {
503                 return FALSE;
504         }
505
506         switching = true;
507
508         if (get_child() == &spinner) {
509                 return FALSE;
510         }
511
512         remove ();
513         add (spinner);
514         spinner.show ();
515         spinner.select_region (0, spinner.get_text_length());
516         spinner.grab_focus ();
517
518         switching = false;
519
520         SpinnerActive (true); /* EMIT SIGNAL */
521
522         return FALSE;
523 }
524
525 void
526 BarController::entry_activated ()
527 {
528         switch_to_bar ();
529 }
530
531 bool
532 BarController::entry_focus_out (GdkEventFocus* /*ev*/)
533 {
534         entry_activated ();
535         return true;
536 }
537
538 void
539 BarController::set_use_parent (bool yn)
540 {
541         use_parent = yn;
542         queue_draw ();
543 }
544
545 void
546 BarController::set_sensitive (bool yn)
547 {
548         Frame::set_sensitive (yn);
549         darea.set_sensitive (yn);
550 }
551
552 /* 
553     This is called when we need to update the adjustment with the value
554     from the spinner's text entry.
555     
556     We need to use Gtk::Entry::get_text to avoid recursive nastiness :)
557     
558     If we're not in logarithmic mode we can return false to use the 
559     default conversion.
560     
561     In theory we should check for conversion errors but set numeric
562     mode to true on the spinner prevents invalid input.
563 */
564 int
565 BarController::entry_input (double* new_value)
566 {
567         if (!logarithmic) {
568                 return false;
569         }
570
571         // extract a double from the string and take its log
572         Entry *entry = dynamic_cast<Entry *>(&spinner);
573         double value;
574
575         {
576                 // Switch to user's preferred locale so that
577                 // if they use different LC_NUMERIC conventions,
578                 // we will honor them.
579
580                 PBD::LocaleGuard lg ("");
581                 sscanf (entry->get_text().c_str(), "%lf", &value);
582         }
583
584         *new_value = log(value);
585
586         return true;
587 }
588
589 /* 
590     This is called when we need to update the spinner's text entry 
591     with the value of the adjustment.
592     
593     We need to use Gtk::Entry::set_text to avoid recursive nastiness :)
594     
595     If we're not in logarithmic mode we can return false to use the 
596     default conversion.
597 */
598 bool
599 BarController::entry_output ()
600 {
601         if (!logarithmic) {
602                 return false;
603         }
604
605         // generate the exponential and turn it into a string
606         // convert to correct locale. 
607         
608         stringstream stream;
609         string str;
610
611         char buf[128];
612
613         {
614                 // Switch to user's preferred locale so that
615                 // if they use different LC_NUMERIC conventions,
616                 // we will honor them.
617                 
618                 PBD::LocaleGuard lg ("");
619                 snprintf (buf, sizeof (buf), "%g", exp (spinner.get_adjustment()->get_value()));
620         }
621
622         Entry *entry = dynamic_cast<Entry *>(&spinner);
623         entry->set_text(buf);
624         
625         return true;
626 }
627
628
629