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