Move UIConfiguration Singleton into UIConfiguration header
[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 = UIConfiguration::instance().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         items.push_back (SeparatorElem ());
238         items.push_back (MenuElem (_("Reset to 100%"), sigc::mem_fun (*this, &ShuttleControl::reset_speed)));
239 }
240
241 void
242 ShuttleControl::show_shuttle_context_menu ()
243 {
244         if (shuttle_context_menu == 0) {
245                 build_shuttle_context_menu ();
246         }
247
248         shuttle_context_menu->popup (1, gtk_get_current_event_time());
249 }
250
251 void
252 ShuttleControl::reset_speed ()
253 {
254         if (_session->transport_rolling()) {
255                 _session->request_transport_speed (1.0, true);
256         } else {
257                 _session->request_transport_speed (0.0, true);
258         }
259 }
260
261 void
262 ShuttleControl::set_shuttle_max_speed (float speed)
263 {
264         Config->set_shuttle_max_speed (speed);
265         shuttle_max_speed = speed;
266         last_speed_displayed = -99999999;
267 }
268
269 bool
270 ShuttleControl::on_button_press_event (GdkEventButton* ev)
271 {
272         if (!_session) {
273                 return true;
274         }
275
276         if (binding_proxy.button_press_handler (ev)) {
277                 return true;
278         }
279
280         if (Keyboard::is_context_menu_event (ev)) {
281                 show_shuttle_context_menu ();
282                 return true;
283         }
284
285         switch (ev->button) {
286         case 1:
287                 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
288                         if (_session->transport_rolling()) {
289                                 _session->request_transport_speed (1.0);
290                         }
291                 } else {
292                         add_modal_grab ();
293                         shuttle_grabbed = true;
294                         shuttle_speed_on_grab = _session->transport_speed ();
295                         mouse_shuttle (ev->x, true);
296                         gdk_pointer_grab(ev->window,false,
297                                         GdkEventMask( Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK |Gdk::BUTTON_RELEASE_MASK),
298                                         NULL,NULL,ev->time);
299                 }
300                 break;
301
302         case 2:
303         case 3:
304                 return true;
305                 break;
306         }
307
308         return true;
309 }
310
311 bool
312 ShuttleControl::on_button_release_event (GdkEventButton* ev)
313 {
314         if (!_session) {
315                 return true;
316         }
317
318         switch (ev->button) {
319         case 1:
320                 if (shuttle_grabbed) {
321                         shuttle_grabbed = false;
322                         remove_modal_grab ();
323                         gdk_pointer_ungrab (GDK_CURRENT_TIME);
324                         
325                         if (Config->get_shuttle_behaviour() == Sprung) {
326                                 if (shuttle_speed_on_grab == 0 ) {
327                                         _session->request_stop ();
328                                 } else {
329                                         _session->request_transport_speed (shuttle_speed_on_grab);
330                                 }
331                         } else {
332                                 mouse_shuttle (ev->x, true);
333                         }
334                 }
335                 return true;
336
337         case 2:
338                 if (_session->transport_rolling()) {
339                         _session->request_transport_speed (1.0, Config->get_shuttle_behaviour() == Wheel);
340                 }
341                 return true;
342
343         case 3:
344         default:
345                 return true;
346
347         }
348
349         return true;
350 }
351
352 bool
353 ShuttleControl::on_query_tooltip (int, int, bool, const Glib::RefPtr<Gtk::Tooltip>&)
354 {
355         return false;
356 }
357
358 bool
359 ShuttleControl::on_scroll_event (GdkEventScroll* ev)
360 {
361         if (!_session || Config->get_shuttle_behaviour() != Wheel) {
362                 return true;
363         }
364
365         bool semis = (Config->get_shuttle_units() == Semitones);
366
367         switch (ev->direction) {
368         case GDK_SCROLL_UP:
369         case GDK_SCROLL_RIGHT:
370                 if (semis) {
371                         if (shuttle_fract == 0) {
372                                 shuttle_fract = semitones_as_fract (1, false);
373                         } else {
374                                 bool rev;
375                                 int st = fract_as_semitones (shuttle_fract, rev);
376                                 shuttle_fract = semitones_as_fract (st + 1, rev);
377                         }
378                 } else {
379                         shuttle_fract += 0.00125;
380                 }
381                 break;
382         case GDK_SCROLL_DOWN:
383         case GDK_SCROLL_LEFT:
384                 if (semis) {
385                         if (shuttle_fract == 0) {
386                                 shuttle_fract = semitones_as_fract (1, true);
387                         } else {
388                                 bool rev;
389                                 int st = fract_as_semitones (shuttle_fract, rev);
390                                 shuttle_fract = semitones_as_fract (st - 1, rev);
391                         }
392                 } else {
393                         shuttle_fract -= 0.00125;
394                 }
395                 break;
396         default:
397                 return false;
398         }
399         
400         if (semis) {
401
402                 float lower_side_of_dead_zone = semitones_as_fract (-24, true);
403                 float upper_side_of_dead_zone = semitones_as_fract (-24, false);
404
405                 /* if we entered the "dead zone" (-24 semitones in forward or reverse), jump
406                    to the far side of it.
407                 */
408
409                 if (shuttle_fract > lower_side_of_dead_zone && shuttle_fract < upper_side_of_dead_zone) {
410                         switch (ev->direction) {
411                         case GDK_SCROLL_UP:
412                         case GDK_SCROLL_RIGHT:
413                                 shuttle_fract = upper_side_of_dead_zone;
414                                 break;
415                         case GDK_SCROLL_DOWN:
416                         case GDK_SCROLL_LEFT:
417                                 shuttle_fract = lower_side_of_dead_zone;
418                                 break;
419                         default:
420                                 /* impossible, checked above */
421                                 return false;
422                         }
423                 }
424         }
425
426         use_shuttle_fract (true);
427
428         return true;
429 }
430
431 bool
432 ShuttleControl::on_motion_notify_event (GdkEventMotion* ev)
433 {
434         if (!_session || !shuttle_grabbed) {
435                 return true;
436         }
437
438         return mouse_shuttle (ev->x, false);
439 }
440
441 gint
442 ShuttleControl::mouse_shuttle (double x, bool force)
443 {
444         double const center = get_width() / 2.0;
445         double distance_from_center = x - center;
446
447         if (distance_from_center > 0) {
448                 distance_from_center = min (distance_from_center, center);
449         } else {
450                 distance_from_center = max (distance_from_center, -center);
451         }
452
453         /* compute shuttle fract as expressing how far between the center
454            and the edge we are. positive values indicate we are right of
455            center, negative values indicate left of center
456         */
457
458         shuttle_fract = distance_from_center / center; // center == half the width
459         use_shuttle_fract (force);
460         return true;
461 }
462
463 void
464 ShuttleControl::set_shuttle_fract (double f, bool zero_ok)
465 {
466         shuttle_fract = f;
467         use_shuttle_fract (false, zero_ok);
468 }
469
470 int
471 ShuttleControl::speed_as_semitones (float speed, bool& reverse)
472 {
473         assert (speed != 0.0);
474
475         if (speed < 0.0) {
476                 reverse = true;
477                 return (int) round (12.0 * fast_log2 (-speed));
478         } else {
479                 reverse = false;
480                 return (int) round (12.0 * fast_log2 (speed));
481         }
482 }
483
484 float
485 ShuttleControl::semitones_as_speed (int semi, bool reverse)
486 {
487         if (reverse) {
488                 return -pow (2.0, (semi / 12.0));
489         } else {
490                 return pow (2.0, (semi / 12.0));
491         }
492 }
493
494 float
495 ShuttleControl::semitones_as_fract (int semi, bool reverse)
496 {
497         float speed = semitones_as_speed (semi, reverse);
498         return speed/4.0; /* 4.0 is the maximum speed for a 24 semitone shift */
499 }
500
501 int
502 ShuttleControl::fract_as_semitones (float fract, bool& reverse)
503 {
504         assert (fract != 0.0);
505         return speed_as_semitones (fract * 4.0, reverse);
506 }
507
508 void
509 ShuttleControl::use_shuttle_fract (bool force, bool zero_ok)
510 {
511         microseconds_t now = get_microseconds();
512
513         shuttle_fract = max (-1.0f, shuttle_fract);
514         shuttle_fract = min (1.0f, shuttle_fract);
515
516         /* do not attempt to submit a motion-driven transport speed request
517            more than once per process cycle.
518         */
519
520         if (!force && (last_shuttle_request - now) < (microseconds_t) AudioEngine::instance()->usecs_per_cycle()) {
521                 return;
522         }
523
524         last_shuttle_request = now;
525
526         double speed = 0;
527
528         if (Config->get_shuttle_units() == Semitones) {
529                 if (shuttle_fract != 0.0) {
530                         bool reverse;
531                         int semi = fract_as_semitones (shuttle_fract, reverse);
532                         speed = semitones_as_speed (semi, reverse);
533                 } else {
534                         speed = 0.0;
535                 }
536         } else {
537                 speed = shuttle_max_speed * shuttle_fract;
538         }
539
540         if (zero_ok) {
541                 _session->request_transport_speed (speed, Config->get_shuttle_behaviour() == Wheel);
542         } else {
543                 _session->request_transport_speed_nonzero (speed, Config->get_shuttle_behaviour() == Wheel);
544         }
545 }
546
547 void
548 ShuttleControl::render (cairo_t* cr, cairo_rectangle_t*)
549 {
550         cairo_text_extents_t extents;
551
552         //black border
553         cairo_set_source_rgb (cr, 0, 0.0, 0.0);
554         rounded_rectangle (cr, 0, 0, get_width(), get_height(), 4);
555         cairo_fill (cr);
556
557         float speed = 0.0;
558
559         if (_session) {
560                 speed = _session->transport_speed ();
561         }
562
563         /* Marker */
564         float visual_fraction = std::min (1.0f, speed / shuttle_max_speed);
565         float marker_size = get_height() - 5.0;
566         float avail_width = get_width() - marker_size - 4;
567         float x = get_width() * 0.5 + visual_fraction * avail_width * 0.5;
568 //      cairo_set_source_rgb (cr, 0, 1, 0.0);
569         cairo_set_source (cr, pattern);
570         if (speed == 1.0) {
571                 cairo_move_to( cr, x, 2.5);
572                 cairo_line_to( cr, x + marker_size * .577, 2.5 + marker_size * 0.5);
573                 cairo_line_to( cr, x, 2.5 + marker_size);
574                 cairo_close_path(cr);
575         } else if ( speed ==0.0 )
576                 rounded_rectangle (cr, x, 2.5, marker_size, marker_size, 1);
577         else
578                 cairo_arc (cr, x, 2.5 + marker_size * .5, marker_size * 0.47, 0, 2.0 * M_PI);
579         cairo_set_line_width (cr, 1.75);
580         cairo_stroke (cr);
581
582         /* speed text */
583
584         char buf[32];
585
586         if (speed != 0) {
587
588                 if (Config->get_shuttle_units() == Percentage) {
589
590                         if (speed == 1.0) {
591                                 snprintf (buf, sizeof (buf), "%s", _("Playing"));
592                         } else {
593                                 if (speed < 0.0) {
594                                         snprintf (buf, sizeof (buf), "<<< %.1f%%", -speed * 100.f);
595                                 } else {
596                                         snprintf (buf, sizeof (buf), ">>> %.1f%%", speed * 100.f);
597                                 }
598                         }
599
600                 } else {
601
602                         bool reversed;
603                         int semi = speed_as_semitones (speed, reversed);
604
605                         if (reversed) {
606                                 snprintf (buf, sizeof (buf), _("<<< %+d semitones"), semi);
607                         } else {
608                                 snprintf (buf, sizeof (buf), _(">>> %+d semitones"), semi);
609                         }
610                 }
611
612         } else {
613                 snprintf (buf, sizeof (buf), "%s", _("Stopped"));
614         }
615
616         last_speed_displayed = speed;
617
618         // TODO use a proper pango layout, scale font
619         cairo_set_source_rgb (cr, 0.6, 0.6, 0.6);
620         cairo_set_font_size (cr, 13.0);
621         cairo_text_extents (cr, "0|", &extents); // note the descender
622         const float text_ypos = (get_height() + extents.height - 1.) * .5;
623
624         cairo_move_to (cr, 10, text_ypos);
625         cairo_show_text (cr, buf);
626
627         /* style text */
628
629
630         switch (Config->get_shuttle_behaviour()) {
631         case Sprung:
632                 snprintf (buf, sizeof (buf), "%s", _("Sprung"));
633                 break;
634         case Wheel:
635                 snprintf (buf, sizeof (buf), "%s", _("Wheel"));
636                 break;
637         }
638
639         cairo_text_extents (cr, buf, &extents);
640         cairo_move_to (cr, get_width() - (fabs(extents.x_advance) + 5), text_ypos);
641         cairo_show_text (cr, buf);
642
643         if (UIConfiguration::instance().get_widget_prelight()) {
644                 if (_hovering) {
645                         rounded_rectangle (cr, 1, 1, get_width()-2, get_height()-2, 4.0);
646                         cairo_set_source_rgba (cr, 1, 1, 1, 0.2);
647                         cairo_fill (cr);
648                 }
649         }
650 }
651
652 void
653 ShuttleControl::shuttle_unit_clicked ()
654 {
655         if (shuttle_unit_menu == 0) {
656                 shuttle_unit_menu = dynamic_cast<Menu*> (ActionManager::get_widget ("/ShuttleUnitPopup"));
657         }
658         shuttle_unit_menu->popup (1, gtk_get_current_event_time());
659 }
660
661 void
662 ShuttleControl::set_shuttle_style (ShuttleBehaviour s)
663 {
664         Config->set_shuttle_behaviour (s);
665 }
666
667 void
668 ShuttleControl::set_shuttle_units (ShuttleUnits s)
669 {
670         Config->set_shuttle_units (s);
671 }
672
673 ShuttleControl::ShuttleControllable::ShuttleControllable (ShuttleControl& s)
674         : PBD::Controllable (X_("Shuttle"))
675         , sc (s)
676 {
677 }
678
679 void
680 ShuttleControl::ShuttleControllable::set_value (double val)
681 {
682         sc.set_shuttle_fract ((val - lower()) / (upper() - lower()), true);
683 }
684
685 double
686 ShuttleControl::ShuttleControllable::get_value () const
687 {
688         return lower() + (sc.get_shuttle_fract () * (upper() - lower()));
689 }
690
691 void
692 ShuttleControl::parameter_changed (std::string p)
693 {
694         if (p == "shuttle-behaviour") {
695                 switch (Config->get_shuttle_behaviour ()) {
696                 case Sprung:
697                         /* back to Sprung - reset to speed = 1.0 if playing
698                          */
699                         if (_session) {
700                                 if (_session->transport_rolling()) {
701                                         if (_session->transport_speed() == 1.0) {
702                                                 queue_draw ();
703                                         } else {
704                                                 /* reset current speed and
705                                                    revert to 1.0 as the default
706                                                 */
707                                                 _session->request_transport_speed (1.0);
708                                                 /* redraw when speed changes */
709                                         }
710                                 } else {
711                                         queue_draw ();
712                                 }
713                         }
714                         break;
715
716                 case Wheel:
717                         queue_draw ();
718                         break;
719                 }
720
721         } else if (p == "shuttle-units") {
722                 queue_draw ();
723         }
724 }
725
726
727 bool
728 ShuttleControl::on_enter_notify_event (GdkEventCrossing* ev)
729 {
730         _hovering = true;
731
732         if (UIConfiguration::instance().get_widget_prelight()) {
733                 queue_draw ();
734         }
735
736         return CairoWidget::on_enter_notify_event (ev);
737 }
738
739 bool
740 ShuttleControl::on_leave_notify_event (GdkEventCrossing* ev)
741 {
742         _hovering = false;
743
744         if (UIConfiguration::instance().get_widget_prelight()) {
745                 queue_draw ();
746         }
747
748         return CairoWidget::on_leave_notify_event (ev);
749 }