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