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