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