consolidate context-menu popup methods
[ardour.git] / gtk2_ardour / shuttle_control.cc
1 /*
2     Copyright (C) 2011 Paul Davis
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 */
18
19 #define BASELINESTRETCH (1.25)
20
21 #include <algorithm>
22
23 #include <cairo.h>
24
25 #include "ardour/ardour.h"
26 #include "ardour/audioengine.h"
27 #include "ardour/rc_configuration.h"
28 #include "ardour/session.h"
29
30 #include "gtkmm2ext/keyboard.h"
31 #include "gtkmm2ext/gui_thread.h"
32 #include "gtkmm2ext/cairocell.h"
33 #include "gtkmm2ext/utils.h"
34 #include "gtkmm2ext/rgb_macros.h"
35
36 #include "canvas/utils.h"
37 #include "canvas/colors.h"
38
39 #include "actions.h"
40 #include "rgb_macros.h"
41 #include "shuttle_control.h"
42 #include "tooltips.h"
43
44 #include "pbd/i18n.h"
45
46 using namespace Gtk;
47 using namespace Gtkmm2ext;
48 using namespace ARDOUR;
49 using namespace ARDOUR_UI_UTILS;
50 using std::min;
51 using std::max;
52
53 gboolean qt (gboolean, gint, gint, gboolean, Gtk::Tooltip*, gpointer)
54 {
55         return FALSE;
56 }
57
58 ShuttleControl::ShuttleControl ()
59         : _controllable (new ShuttleControllable (*this))
60         , binding_proxy (_controllable)
61 {
62         _info_button.set_layout_font (UIConfiguration::instance().get_NormalFont());
63         _info_button.set_sizing_text (S_("LogestShuttle|< +00 st"));
64         _info_button.set_name ("shuttle text");
65         _info_button.set_sensitive (false);
66         _info_button.set_visual_state (Gtkmm2ext::NoVisualState);
67         _info_button.set_elements (ArdourButton::Text);
68
69         set_tooltip (*this, _("Shuttle speed control (Context-click for options)"));
70
71         pattern = 0;
72         shine_pattern = 0;
73         last_shuttle_request = 0;
74         last_speed_displayed = -99999999;
75         shuttle_grabbed = false;
76         shuttle_speed_on_grab = 0;
77         shuttle_fract = 0.0;
78         shuttle_max_speed = 8.0f;
79         shuttle_style_menu = 0;
80         shuttle_unit_menu = 0;
81         shuttle_context_menu = 0;
82         _hovering = false;
83
84         set_flags (CAN_FOCUS);
85         add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK|Gdk::BUTTON_RELEASE_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::POINTER_MOTION_MASK|Gdk::SCROLL_MASK);
86         set_name (X_("ShuttleControl"));
87
88         ensure_style ();
89
90         shuttle_max_speed = Config->get_shuttle_max_speed();
91
92         if      (shuttle_max_speed >= 8.f) { shuttle_max_speed = 8.0f; }
93         else if (shuttle_max_speed >= 6.f) { shuttle_max_speed = 6.0f; }
94         else if (shuttle_max_speed >= 4.f) { shuttle_max_speed = 4.0f; }
95         else if (shuttle_max_speed >= 3.f) { shuttle_max_speed = 3.0f; }
96         else if (shuttle_max_speed >= 2.f) { shuttle_max_speed = 2.0f; }
97         else                               { shuttle_max_speed = 1.5f; }
98
99         Config->ParameterChanged.connect (parameter_connection, MISSING_INVALIDATOR, boost::bind (&ShuttleControl::parameter_changed, this, _1), gui_context());
100         UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &ShuttleControl::set_colors));
101
102         set_colors ();
103
104         /* gtkmm 2.4: the C++ wrapper doesn't work */
105         g_signal_connect ((GObject*) gobj(), "query-tooltip", G_CALLBACK (qt), NULL);
106         // signal_query_tooltip().connect (sigc::mem_fun (*this, &ShuttleControl::on_query_tooltip));
107 }
108
109 ShuttleControl::~ShuttleControl ()
110 {
111         cairo_pattern_destroy (pattern);
112         cairo_pattern_destroy (shine_pattern);
113 }
114
115 void
116 ShuttleControl::set_session (Session *s)
117 {
118         SessionHandlePtr::set_session (s);
119
120         if (_session) {
121                 set_sensitive (true);
122                 _session->add_controllable (_controllable);
123         } else {
124                 set_sensitive (false);
125         }
126 }
127
128 void
129 ShuttleControl::on_size_allocate (Gtk::Allocation& alloc)
130 {
131         if (pattern) {
132                 cairo_pattern_destroy (pattern);
133                 pattern = 0;
134                 cairo_pattern_destroy (shine_pattern);
135                 shine_pattern = 0;
136         }
137
138         CairoWidget::on_size_allocate ( alloc);
139
140         //background
141         pattern = cairo_pattern_create_linear (0, 0, 0, alloc.get_height());
142         uint32_t col = UIConfiguration::instance().color ("shuttle");
143         int r,b,g,a;
144         UINT_TO_RGBA(col, &r, &g, &b, &a);
145         cairo_pattern_add_color_stop_rgb (pattern, 0.0, r/400.0, g/400.0, b/400.0);
146         cairo_pattern_add_color_stop_rgb (pattern, 0.4, r/255.0, g/255.0, b/255.0);
147         cairo_pattern_add_color_stop_rgb (pattern, 1.0, r/512.0, g/512.0, b/512.0);
148
149         //reflection
150         shine_pattern = cairo_pattern_create_linear (0.0, 0.0, 0.0, 10);
151         cairo_pattern_add_color_stop_rgba (shine_pattern, 0, 1,1,1,0.0);
152         cairo_pattern_add_color_stop_rgba (shine_pattern, 0.2, 1,1,1,0.4);
153         cairo_pattern_add_color_stop_rgba (shine_pattern, 1, 1,1,1,0.1);
154 }
155
156 void
157 ShuttleControl::map_transport_state ()
158 {
159         float speed = _session->transport_speed ();
160
161         if ( (fabsf( speed - last_speed_displayed) < 0.005f) // dead-zone
162              && !( speed == 1.f && last_speed_displayed != 1.f)
163              && !( speed == 0.f && last_speed_displayed != 0.f)
164                 )
165         {
166                 return; // nothing to see here, move along.
167         }
168
169         // Q: is there a good reason why we  re-calculate this every time?
170         if (fabs(speed) <= (2*DBL_EPSILON)) {
171                 shuttle_fract = 0;
172         } else {
173                 if (Config->get_shuttle_units() == Semitones) {
174                         bool reverse;
175                         int semi = speed_as_semitones (speed, reverse);
176                         shuttle_fract = semitones_as_fract (semi, reverse);
177                 } else {
178                         shuttle_fract = speed/shuttle_max_speed;
179                 }
180         }
181
182         queue_draw ();
183 }
184
185 void
186 ShuttleControl::build_shuttle_context_menu ()
187 {
188         using namespace Menu_Helpers;
189
190         shuttle_context_menu = new Menu();
191         MenuList& items = shuttle_context_menu->items();
192
193         Menu* speed_menu = manage (new Menu());
194         MenuList& speed_items = speed_menu->items();
195
196         Menu* units_menu = manage (new Menu);
197         MenuList& units_items = units_menu->items();
198         RadioMenuItem::Group units_group;
199
200         units_items.push_back (RadioMenuElem (units_group, _("Percent"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_units), Percentage)));
201         if (Config->get_shuttle_units() == Percentage) {
202                 static_cast<RadioMenuItem*>(&units_items.back())->set_active();
203         }
204         units_items.push_back (RadioMenuElem (units_group, _("Semitones"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_units), Semitones)));
205         if (Config->get_shuttle_units() == Semitones) {
206                 static_cast<RadioMenuItem*>(&units_items.back())->set_active();
207         }
208         items.push_back (MenuElem (_("Units"), *units_menu));
209
210         Menu* style_menu = manage (new Menu);
211         MenuList& style_items = style_menu->items();
212         RadioMenuItem::Group style_group;
213
214         style_items.push_back (RadioMenuElem (style_group, _("Sprung"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_style), Sprung)));
215         if (Config->get_shuttle_behaviour() == Sprung) {
216                 static_cast<RadioMenuItem*>(&style_items.back())->set_active();
217         }
218         style_items.push_back (RadioMenuElem (style_group, _("Wheel"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_style), Wheel)));
219         if (Config->get_shuttle_behaviour() == Wheel) {
220                 static_cast<RadioMenuItem*>(&style_items.back())->set_active();
221         }
222
223         items.push_back (MenuElem (_("Mode"), *style_menu));
224
225         RadioMenuItem::Group speed_group;
226
227         speed_items.push_back (RadioMenuElem (speed_group, "8", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 8.0f)));
228         if (shuttle_max_speed == 8.0) {
229                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
230         }
231         speed_items.push_back (RadioMenuElem (speed_group, "6", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 6.0f)));
232         if (shuttle_max_speed == 6.0) {
233                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
234         }
235         speed_items.push_back (RadioMenuElem (speed_group, "4", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 4.0f)));
236         if (shuttle_max_speed == 4.0) {
237                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
238         }
239         speed_items.push_back (RadioMenuElem (speed_group, "3", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 3.0f)));
240         if (shuttle_max_speed == 3.0) {
241                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
242         }
243         speed_items.push_back (RadioMenuElem (speed_group, "2", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 2.0f)));
244         if (shuttle_max_speed == 2.0) {
245                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
246         }
247         speed_items.push_back (RadioMenuElem (speed_group, "1.5", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 1.5f)));
248         if (shuttle_max_speed == 1.5) {
249                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
250         }
251
252         items.push_back (MenuElem (_("Maximum speed"), *speed_menu));
253
254         items.push_back (SeparatorElem ());
255         items.push_back (MenuElem (_("Reset to 100%"), sigc::mem_fun (*this, &ShuttleControl::reset_speed)));
256 }
257
258 void
259 ShuttleControl::reset_speed ()
260 {
261         if (_session->transport_rolling()) {
262                 _session->request_transport_speed (1.0, true);
263         } else {
264                 _session->request_transport_speed (0.0, true);
265         }
266 }
267
268 void
269 ShuttleControl::set_shuttle_max_speed (float speed)
270 {
271         Config->set_shuttle_max_speed (speed);
272         shuttle_max_speed = speed;
273         last_speed_displayed = -99999999;
274 }
275
276 bool
277 ShuttleControl::on_button_press_event (GdkEventButton* ev)
278 {
279         if (!_session) {
280                 return true;
281         }
282
283         if (binding_proxy.button_press_handler (ev)) {
284                 return true;
285         }
286
287         if (Keyboard::is_context_menu_event (ev)) {
288                 if (shuttle_context_menu == 0) {
289                         build_shuttle_context_menu ();
290                 }
291                 shuttle_context_menu->popup (ev->button, ev->time);
292                 return true;
293         }
294
295         switch (ev->button) {
296         case 1:
297                 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
298                         if (_session->transport_rolling()) {
299                                 _session->request_transport_speed (1.0);
300                         }
301                 } else {
302                         add_modal_grab ();
303                         shuttle_grabbed = true;
304                         shuttle_speed_on_grab = _session->transport_speed ();
305                         requested_speed = shuttle_speed_on_grab;
306                         mouse_shuttle (ev->x, true);
307                         gdk_pointer_grab(ev->window,false,
308                                          GdkEventMask( Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK |Gdk::BUTTON_RELEASE_MASK),
309                                          NULL,NULL,ev->time);
310                 }
311                 break;
312
313         case 2:
314         case 3:
315                 return true;
316                 break;
317         }
318
319         return true;
320 }
321
322 bool
323 ShuttleControl::on_button_release_event (GdkEventButton* ev)
324 {
325         if (!_session) {
326                 return true;
327         }
328
329         switch (ev->button) {
330         case 1:
331                 if (shuttle_grabbed) {
332                         shuttle_grabbed = false;
333                         remove_modal_grab ();
334                         gdk_pointer_ungrab (GDK_CURRENT_TIME);
335
336                         if (Config->get_shuttle_behaviour() == Sprung) {
337                                 if (shuttle_speed_on_grab == 0 ) {
338                                         _session->request_stop ();
339                                 } else {
340                                         _session->request_transport_speed (shuttle_speed_on_grab);
341                                 }
342                         } else {
343                                 mouse_shuttle (ev->x, true);
344                         }
345                 }
346                 return true;
347
348         case 2:
349                 if (_session->transport_rolling()) {
350                         _session->request_transport_speed (1.0, Config->get_shuttle_behaviour() == Wheel);
351                 }
352                 return true;
353
354         case 3:
355         default:
356                 return true;
357
358         }
359
360         return true;
361 }
362
363 bool
364 ShuttleControl::on_query_tooltip (int, int, bool, const Glib::RefPtr<Gtk::Tooltip>&)
365 {
366         return false;
367 }
368
369 bool
370 ShuttleControl::on_scroll_event (GdkEventScroll* ev)
371 {
372         if (!_session || Config->get_shuttle_behaviour() != Wheel) {
373                 return true;
374         }
375
376         bool semis = (Config->get_shuttle_units() == Semitones);
377
378         switch (ev->direction) {
379         case GDK_SCROLL_UP:
380         case GDK_SCROLL_RIGHT:
381                 if (semis) {
382                         if (shuttle_fract == 0) {
383                                 shuttle_fract = semitones_as_fract (1, false);
384                         } else {
385                                 bool rev;
386                                 int st = fract_as_semitones (shuttle_fract, rev);
387                                 shuttle_fract = semitones_as_fract (st + 1, rev);
388                         }
389                 } else {
390                         shuttle_fract += 0.00125;
391                 }
392                 break;
393         case GDK_SCROLL_DOWN:
394         case GDK_SCROLL_LEFT:
395                 if (semis) {
396                         if (shuttle_fract == 0) {
397                                 shuttle_fract = semitones_as_fract (1, true);
398                         } else {
399                                 bool rev;
400                                 int st = fract_as_semitones (shuttle_fract, rev);
401                                 shuttle_fract = semitones_as_fract (st - 1, rev);
402                         }
403                 } else {
404                         shuttle_fract -= 0.00125;
405                 }
406                 break;
407         default:
408                 return false;
409         }
410
411         if (semis) {
412
413                 float lower_side_of_dead_zone = semitones_as_fract (-24, true);
414                 float upper_side_of_dead_zone = semitones_as_fract (-24, false);
415
416                 /* if we entered the "dead zone" (-24 semitones in forward or reverse), jump
417                    to the far side of it.
418                 */
419
420                 if (shuttle_fract > lower_side_of_dead_zone && shuttle_fract < upper_side_of_dead_zone) {
421                         switch (ev->direction) {
422                         case GDK_SCROLL_UP:
423                         case GDK_SCROLL_RIGHT:
424                                 shuttle_fract = upper_side_of_dead_zone;
425                                 break;
426                         case GDK_SCROLL_DOWN:
427                         case GDK_SCROLL_LEFT:
428                                 shuttle_fract = lower_side_of_dead_zone;
429                                 break;
430                         default:
431                                 /* impossible, checked above */
432                                 return false;
433                         }
434                 }
435         }
436
437         use_shuttle_fract (true);
438
439         return true;
440 }
441
442 bool
443 ShuttleControl::on_motion_notify_event (GdkEventMotion* ev)
444 {
445         if (!_session || !shuttle_grabbed) {
446                 return true;
447         }
448
449         return mouse_shuttle (ev->x, false);
450 }
451
452 gint
453 ShuttleControl::mouse_shuttle (double x, bool force)
454 {
455         double const center = get_width() / 2.0;
456         double distance_from_center = x - center;
457
458         if (distance_from_center > 0) {
459                 distance_from_center = min (distance_from_center, center);
460         } else {
461                 distance_from_center = max (distance_from_center, -center);
462         }
463
464         /* compute shuttle fract as expressing how far between the center
465            and the edge we are. positive values indicate we are right of
466            center, negative values indicate left of center
467         */
468
469         shuttle_fract = distance_from_center / center; // center == half the width
470         use_shuttle_fract (force);
471         return true;
472 }
473
474 void
475 ShuttleControl::set_shuttle_fract (double f, bool zero_ok)
476 {
477         shuttle_fract = f;
478         use_shuttle_fract (false, zero_ok);
479 }
480
481 int
482 ShuttleControl::speed_as_semitones (float speed, bool& reverse)
483 {
484         assert (speed != 0.0);
485
486         if (speed < 0.0) {
487                 reverse = true;
488                 return (int) round (12.0 * fast_log2 (-speed));
489         } else {
490                 reverse = false;
491                 return (int) round (12.0 * fast_log2 (speed));
492         }
493 }
494
495 float
496 ShuttleControl::semitones_as_speed (int semi, bool reverse)
497 {
498         if (reverse) {
499                 return -pow (2.0, (semi / 12.0));
500         } else {
501                 return pow (2.0, (semi / 12.0));
502         }
503 }
504
505 float
506 ShuttleControl::semitones_as_fract (int semi, bool reverse)
507 {
508         float speed = semitones_as_speed (semi, reverse);
509         return speed/4.0; /* 4.0 is the maximum speed for a 24 semitone shift */
510 }
511
512 int
513 ShuttleControl::fract_as_semitones (float fract, bool& reverse)
514 {
515         assert (fract != 0.0);
516         return speed_as_semitones (fract * 4.0, reverse);
517 }
518
519 void
520 ShuttleControl::use_shuttle_fract (bool force, bool zero_ok)
521 {
522         microseconds_t now = get_microseconds();
523
524         shuttle_fract = max (-1.0f, shuttle_fract);
525         shuttle_fract = min (1.0f, shuttle_fract);
526
527         /* do not attempt to submit a motion-driven transport speed request
528            more than once per process cycle.
529         */
530
531         if (!force && (last_shuttle_request - now) < (microseconds_t) AudioEngine::instance()->usecs_per_cycle()) {
532                 return;
533         }
534
535         last_shuttle_request = now;
536
537         double speed = 0;
538
539         if (Config->get_shuttle_units() == Semitones) {
540                 if (shuttle_fract != 0.0) {
541                         bool reverse;
542                         int semi = fract_as_semitones (shuttle_fract, reverse);
543                         speed = semitones_as_speed (semi, reverse);
544                 } else {
545                         speed = 0.0;
546                 }
547         } else {
548                 speed = shuttle_max_speed * shuttle_fract;
549         }
550
551         requested_speed = speed;
552         if (zero_ok) {
553                 _session->request_transport_speed (speed, Config->get_shuttle_behaviour() == Wheel);
554         } else {
555                 _session->request_transport_speed_nonzero (speed, Config->get_shuttle_behaviour() == Wheel);
556         }
557 }
558
559 void
560 ShuttleControl::set_colors ()
561 {
562         int r, g, b, a;
563
564         uint32_t bg_color = UIConfiguration::instance().color (X_("shuttle bg"));
565
566         UINT_TO_RGBA (bg_color, &r, &g, &b, &a);
567         bg_r = r/255.0;
568         bg_g = g/255.0;
569         bg_b = b/255.0;
570 }
571
572 void
573 ShuttleControl::render (cairo_t* cr, cairo_rectangle_t*)
574 {
575         // center slider line
576         float yc = get_height() / 2;
577         float lw = 3;
578         cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
579         cairo_set_line_width (cr, 3);
580         cairo_move_to (cr, lw, yc);
581         cairo_line_to (cr, get_width () - lw, yc);
582         cairo_set_source_rgb (cr, bg_r, bg_g, bg_b);
583         if (UIConfiguration::instance().get_widget_prelight() && _hovering) {
584                 cairo_stroke_preserve (cr);
585                 cairo_set_source_rgba (cr, 1, 1, 1, 0.15);
586         }
587         cairo_stroke (cr);
588
589         float speed = 0.0;
590         float acutal_speed = 0.0;
591         char buf[32];
592
593         if (_session) {
594                 speed = _session->transport_speed ();
595                 acutal_speed = speed;
596                 if (shuttle_grabbed) {
597                         speed = requested_speed;
598                 }
599         }
600
601         /* marker */
602         float visual_fraction = std::max (-1.0f, std::min (1.0f, speed / shuttle_max_speed));
603         float marker_size = round (get_height() * 0.66);
604         float avail_width = get_width() - marker_size;
605         float x = 0.5 * (get_width() + visual_fraction * avail_width - marker_size);
606
607         rounded_rectangle (cr, x, 0, marker_size, get_height(), 5);
608         cairo_set_source_rgba (cr, 0, 0, 0, 1);
609         cairo_fill(cr);
610         rounded_rectangle (cr, x + 1, 1, marker_size - 2, get_height() - 2, 3.5);
611         if (_flat_buttons) {
612                 uint32_t col = UIConfiguration::instance().color ("shuttle");
613                 ArdourCanvas::set_source_rgba (cr, col);
614         } else {
615                 cairo_set_source (cr, pattern);
616         }
617         if (UIConfiguration::instance().get_widget_prelight() && _hovering) {
618                 cairo_fill_preserve (cr);
619                 cairo_set_source_rgba (cr, 1, 1, 1, 0.15);
620         }
621         cairo_fill(cr);
622
623         /* text */
624         if (acutal_speed != 0) {
625                 if (Config->get_shuttle_units() == Percentage) {
626                         if (acutal_speed == 1.0) {
627                                 snprintf (buf, sizeof (buf), "%s", _("Play"));
628                         } else {
629                                 if (acutal_speed < 0.0) {
630                                         snprintf (buf, sizeof (buf), "< %.1f%%", -acutal_speed * 100.f);
631                                 } else {
632                                         snprintf (buf, sizeof (buf), "> %.1f%%", acutal_speed * 100.f);
633                                 }
634                         }
635                 } else {
636                         bool reversed;
637                         int semi = speed_as_semitones (acutal_speed, reversed);
638                         if (reversed) {
639                                 snprintf (buf, sizeof (buf), _("< %+2d st"), semi);
640                         } else {
641                                 snprintf (buf, sizeof (buf), _("> %+2d st"), semi);
642                         }
643                 }
644         } else {
645                 snprintf (buf, sizeof (buf), "%s", _("Stop"));
646         }
647
648         last_speed_displayed = acutal_speed;
649
650         _info_button.set_text (buf);
651
652 #if 0
653         if (UIConfiguration::instance().get_widget_prelight()) {
654                 if (_hovering) {
655                         rounded_rectangle (cr, 0, 0, get_width(), get_height(), 3.5);
656                         cairo_set_source_rgba (cr, 1, 1, 1, 0.15);
657                         cairo_fill (cr);
658                 }
659         }
660 #endif
661 }
662
663 void
664 ShuttleControl::set_shuttle_style (ShuttleBehaviour s)
665 {
666         Config->set_shuttle_behaviour (s);
667 }
668
669 void
670 ShuttleControl::set_shuttle_units (ShuttleUnits s)
671 {
672         Config->set_shuttle_units (s);
673 }
674
675 ShuttleControl::ShuttleControllable::ShuttleControllable (ShuttleControl& s)
676         : PBD::Controllable (X_("Shuttle"))
677         , sc (s)
678 {
679 }
680
681 void
682 ShuttleControl::ShuttleControllable::set_value (double val, PBD::Controllable::GroupControlDisposition /*group_override*/)
683 {
684         sc.set_shuttle_fract ((val - lower()) / (upper() - lower()), true);
685 }
686
687 double
688 ShuttleControl::ShuttleControllable::get_value () const
689 {
690         return lower() + (sc.get_shuttle_fract () * (upper() - lower()));
691 }
692
693 void
694 ShuttleControl::parameter_changed (std::string p)
695 {
696         if (p == "shuttle-behaviour") {
697                 switch (Config->get_shuttle_behaviour ()) {
698                 case Sprung:
699                         /* back to Sprung - reset to speed = 1.0 if playing
700                          */
701                         if (_session) {
702                                 if (_session->transport_rolling()) {
703                                         if (_session->transport_speed() == 1.0) {
704                                                 queue_draw ();
705                                         } else {
706                                                 /* reset current speed and
707                                                    revert to 1.0 as the default
708                                                 */
709                                                 _session->request_transport_speed (1.0);
710                                                 /* redraw when speed changes */
711                                         }
712                                 } else {
713                                         queue_draw ();
714                                 }
715                         }
716                         break;
717
718                 case Wheel:
719                         queue_draw ();
720                         break;
721                 }
722
723         } else if (p == "shuttle-max-speed") {
724                 queue_draw ();
725         } else if (p == "shuttle-units") {
726                 queue_draw ();
727         }
728 }
729
730
731 bool
732 ShuttleControl::on_enter_notify_event (GdkEventCrossing* ev)
733 {
734         _hovering = true;
735
736         if (UIConfiguration::instance().get_widget_prelight()) {
737                 queue_draw ();
738         }
739
740         return CairoWidget::on_enter_notify_event (ev);
741 }
742
743 bool
744 ShuttleControl::on_leave_notify_event (GdkEventCrossing* ev)
745 {
746         _hovering = false;
747
748         if (UIConfiguration::instance().get_widget_prelight()) {
749                 queue_draw ();
750         }
751
752         return CairoWidget::on_leave_notify_event (ev);
753 }