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