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