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