70293cd20ed9feab9d7eaba269bb43778c45dda5
[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 "actions.h"
37 #include "rgb_macros.h"
38 #include "shuttle_control.h"
39 #include "tooltips.h"
40
41 #include "pbd/i18n.h"
42
43 using namespace Gtk;
44 using namespace Gtkmm2ext;
45 using namespace ARDOUR;
46 using namespace ARDOUR_UI_UTILS;
47 using std::min;
48 using std::max;
49
50 gboolean qt (gboolean, gint, gint, gboolean, Gtk::Tooltip*, gpointer)
51 {
52         return FALSE;
53 }
54
55 ShuttleControl::ShuttleControl ()
56         : _controllable (new ShuttleControllable (*this))
57         , binding_proxy (_controllable)
58         , text_color (0)
59 {
60
61         Pango::AttrFontDesc* font_attr;
62         font_attr = new Pango::AttrFontDesc (Pango::Attribute::create_attr_font_desc (UIConfiguration::instance().get_NormalFont()));
63         text_attributes.change (*font_attr);
64         delete font_attr;
65
66         _text = Pango::Layout::create (get_pango_context());
67         _text->set_attributes (text_attributes);
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         int tw, th, h;
89         ensure_style ();
90         _text->set_text ("@Sp");
91         _text->get_pixel_size (tw, th);
92         h = (int) ceil(th * BASELINESTRETCH + 1.0);
93         if ((h - th) & 1) { ++h; }
94         set_size_request (85, h);
95
96         shuttle_max_speed = Config->get_shuttle_max_speed();
97
98         if      (shuttle_max_speed >= 8.f) { shuttle_max_speed = 8.0f; }
99         else if (shuttle_max_speed >= 6.f) { shuttle_max_speed = 6.0f; }
100         else if (shuttle_max_speed >= 4.f) { shuttle_max_speed = 4.0f; }
101         else if (shuttle_max_speed >= 3.f) { shuttle_max_speed = 3.0f; }
102         else if (shuttle_max_speed >= 2.f) { shuttle_max_speed = 2.0f; }
103         else                               { shuttle_max_speed = 1.5f; }
104
105         Config->ParameterChanged.connect (parameter_connection, MISSING_INVALIDATOR, boost::bind (&ShuttleControl::parameter_changed, this, _1), gui_context());
106         UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &ShuttleControl::set_colors));
107
108         set_colors ();
109
110         /* gtkmm 2.4: the C++ wrapper doesn't work */
111         g_signal_connect ((GObject*) gobj(), "query-tooltip", G_CALLBACK (qt), NULL);
112         // signal_query_tooltip().connect (sigc::mem_fun (*this, &ShuttleControl::on_query_tooltip));
113 }
114
115 ShuttleControl::~ShuttleControl ()
116 {
117         cairo_pattern_destroy (pattern);
118         cairo_pattern_destroy (shine_pattern);
119         delete text_color;
120 }
121
122 void
123 ShuttleControl::set_session (Session *s)
124 {
125         SessionHandlePtr::set_session (s);
126
127         if (_session) {
128                 set_sensitive (true);
129                 _session->add_controllable (_controllable);
130         } else {
131                 set_sensitive (false);
132         }
133 }
134
135 void
136 ShuttleControl::on_size_allocate (Gtk::Allocation& alloc)
137 {
138         if (pattern) {
139                 cairo_pattern_destroy (pattern);
140                 pattern = 0;
141                 cairo_pattern_destroy (shine_pattern);
142                 shine_pattern = 0;
143         }
144
145         CairoWidget::on_size_allocate ( alloc);
146
147         //background
148         pattern = cairo_pattern_create_linear (0, 0, 0, alloc.get_height());
149         uint32_t col = UIConfiguration::instance().color ("shuttle");
150         int r,b,g,a;
151         UINT_TO_RGBA(col, &r, &g, &b, &a);
152         cairo_pattern_add_color_stop_rgb (pattern, 0.0, r/400.0, g/400.0, b/400.0);
153         cairo_pattern_add_color_stop_rgb (pattern, 0.4, r/255.0, g/255.0, b/255.0);
154         cairo_pattern_add_color_stop_rgb (pattern, 1.0, r/512.0, g/512.0, b/512.0);
155
156         //reflection
157         shine_pattern = cairo_pattern_create_linear (0.0, 0.0, 0.0, 10);
158         cairo_pattern_add_color_stop_rgba (shine_pattern, 0, 1,1,1,0.0);
159         cairo_pattern_add_color_stop_rgba (shine_pattern, 0.2, 1,1,1,0.4);
160         cairo_pattern_add_color_stop_rgba (shine_pattern, 1, 1,1,1,0.1);
161 }
162
163 void
164 ShuttleControl::map_transport_state ()
165 {
166         float speed = _session->transport_speed ();
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         speed_items.push_back (RadioMenuElem (speed_group, "8", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 8.0f)));
235         if (shuttle_max_speed == 8.0) {
236                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
237         }
238         speed_items.push_back (RadioMenuElem (speed_group, "6", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 6.0f)));
239         if (shuttle_max_speed == 6.0) {
240                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
241         }
242         speed_items.push_back (RadioMenuElem (speed_group, "4", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 4.0f)));
243         if (shuttle_max_speed == 4.0) {
244                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
245         }
246         speed_items.push_back (RadioMenuElem (speed_group, "3", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 3.0f)));
247         if (shuttle_max_speed == 3.0) {
248                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
249         }
250         speed_items.push_back (RadioMenuElem (speed_group, "2", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 2.0f)));
251         if (shuttle_max_speed == 2.0) {
252                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
253         }
254         speed_items.push_back (RadioMenuElem (speed_group, "1.5", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 1.5f)));
255         if (shuttle_max_speed == 1.5) {
256                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
257         }
258
259         items.push_back (MenuElem (_("Maximum speed"), *speed_menu));
260
261         items.push_back (SeparatorElem ());
262         items.push_back (MenuElem (_("Reset to 100%"), sigc::mem_fun (*this, &ShuttleControl::reset_speed)));
263 }
264
265 void
266 ShuttleControl::show_shuttle_context_menu ()
267 {
268         if (shuttle_context_menu == 0) {
269                 build_shuttle_context_menu ();
270         }
271
272         shuttle_context_menu->popup (1, gtk_get_current_event_time());
273 }
274
275 void
276 ShuttleControl::reset_speed ()
277 {
278         if (_session->transport_rolling()) {
279                 _session->request_transport_speed (1.0, true);
280         } else {
281                 _session->request_transport_speed (0.0, true);
282         }
283 }
284
285 void
286 ShuttleControl::set_shuttle_max_speed (float speed)
287 {
288         Config->set_shuttle_max_speed (speed);
289         shuttle_max_speed = speed;
290         last_speed_displayed = -99999999;
291 }
292
293 bool
294 ShuttleControl::on_button_press_event (GdkEventButton* ev)
295 {
296         if (!_session) {
297                 return true;
298         }
299
300         if (binding_proxy.button_press_handler (ev)) {
301                 return true;
302         }
303
304         if (Keyboard::is_context_menu_event (ev)) {
305                 show_shuttle_context_menu ();
306                 return true;
307         }
308
309         switch (ev->button) {
310         case 1:
311                 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
312                         if (_session->transport_rolling()) {
313                                 _session->request_transport_speed (1.0);
314                         }
315                 } else {
316                         add_modal_grab ();
317                         shuttle_grabbed = true;
318                         shuttle_speed_on_grab = _session->transport_speed ();
319                         requested_speed = shuttle_speed_on_grab;
320                         mouse_shuttle (ev->x, true);
321                         gdk_pointer_grab(ev->window,false,
322                                          GdkEventMask( Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK |Gdk::BUTTON_RELEASE_MASK),
323                                          NULL,NULL,ev->time);
324                 }
325                 break;
326
327         case 2:
328         case 3:
329                 return true;
330                 break;
331         }
332
333         return true;
334 }
335
336 bool
337 ShuttleControl::on_button_release_event (GdkEventButton* ev)
338 {
339         if (!_session) {
340                 return true;
341         }
342
343         switch (ev->button) {
344         case 1:
345                 if (shuttle_grabbed) {
346                         shuttle_grabbed = false;
347                         remove_modal_grab ();
348                         gdk_pointer_ungrab (GDK_CURRENT_TIME);
349
350                         if (Config->get_shuttle_behaviour() == Sprung) {
351                                 if (shuttle_speed_on_grab == 0 ) {
352                                         _session->request_stop ();
353                                 } else {
354                                         _session->request_transport_speed (shuttle_speed_on_grab);
355                                 }
356                         } else {
357                                 mouse_shuttle (ev->x, true);
358                         }
359                 }
360                 return true;
361
362         case 2:
363                 if (_session->transport_rolling()) {
364                         _session->request_transport_speed (1.0, Config->get_shuttle_behaviour() == Wheel);
365                 }
366                 return true;
367
368         case 3:
369         default:
370                 return true;
371
372         }
373
374         return true;
375 }
376
377 bool
378 ShuttleControl::on_query_tooltip (int, int, bool, const Glib::RefPtr<Gtk::Tooltip>&)
379 {
380         return false;
381 }
382
383 bool
384 ShuttleControl::on_scroll_event (GdkEventScroll* ev)
385 {
386         if (!_session || Config->get_shuttle_behaviour() != Wheel) {
387                 return true;
388         }
389
390         bool semis = (Config->get_shuttle_units() == Semitones);
391
392         switch (ev->direction) {
393         case GDK_SCROLL_UP:
394         case GDK_SCROLL_RIGHT:
395                 if (semis) {
396                         if (shuttle_fract == 0) {
397                                 shuttle_fract = semitones_as_fract (1, false);
398                         } else {
399                                 bool rev;
400                                 int st = fract_as_semitones (shuttle_fract, rev);
401                                 shuttle_fract = semitones_as_fract (st + 1, rev);
402                         }
403                 } else {
404                         shuttle_fract += 0.00125;
405                 }
406                 break;
407         case GDK_SCROLL_DOWN:
408         case GDK_SCROLL_LEFT:
409                 if (semis) {
410                         if (shuttle_fract == 0) {
411                                 shuttle_fract = semitones_as_fract (1, true);
412                         } else {
413                                 bool rev;
414                                 int st = fract_as_semitones (shuttle_fract, rev);
415                                 shuttle_fract = semitones_as_fract (st - 1, rev);
416                         }
417                 } else {
418                         shuttle_fract -= 0.00125;
419                 }
420                 break;
421         default:
422                 return false;
423         }
424
425         if (semis) {
426
427                 float lower_side_of_dead_zone = semitones_as_fract (-24, true);
428                 float upper_side_of_dead_zone = semitones_as_fract (-24, false);
429
430                 /* if we entered the "dead zone" (-24 semitones in forward or reverse), jump
431                    to the far side of it.
432                 */
433
434                 if (shuttle_fract > lower_side_of_dead_zone && shuttle_fract < upper_side_of_dead_zone) {
435                         switch (ev->direction) {
436                         case GDK_SCROLL_UP:
437                         case GDK_SCROLL_RIGHT:
438                                 shuttle_fract = upper_side_of_dead_zone;
439                                 break;
440                         case GDK_SCROLL_DOWN:
441                         case GDK_SCROLL_LEFT:
442                                 shuttle_fract = lower_side_of_dead_zone;
443                                 break;
444                         default:
445                                 /* impossible, checked above */
446                                 return false;
447                         }
448                 }
449         }
450
451         use_shuttle_fract (true);
452
453         return true;
454 }
455
456 bool
457 ShuttleControl::on_motion_notify_event (GdkEventMotion* ev)
458 {
459         if (!_session || !shuttle_grabbed) {
460                 return true;
461         }
462
463         return mouse_shuttle (ev->x, false);
464 }
465
466 gint
467 ShuttleControl::mouse_shuttle (double x, bool force)
468 {
469         double const center = get_width() / 2.0;
470         double distance_from_center = x - center;
471
472         if (distance_from_center > 0) {
473                 distance_from_center = min (distance_from_center, center);
474         } else {
475                 distance_from_center = max (distance_from_center, -center);
476         }
477
478         /* compute shuttle fract as expressing how far between the center
479            and the edge we are. positive values indicate we are right of
480            center, negative values indicate left of center
481         */
482
483         shuttle_fract = distance_from_center / center; // center == half the width
484         use_shuttle_fract (force);
485         return true;
486 }
487
488 void
489 ShuttleControl::set_shuttle_fract (double f, bool zero_ok)
490 {
491         shuttle_fract = f;
492         use_shuttle_fract (false, zero_ok);
493 }
494
495 int
496 ShuttleControl::speed_as_semitones (float speed, bool& reverse)
497 {
498         assert (speed != 0.0);
499
500         if (speed < 0.0) {
501                 reverse = true;
502                 return (int) round (12.0 * fast_log2 (-speed));
503         } else {
504                 reverse = false;
505                 return (int) round (12.0 * fast_log2 (speed));
506         }
507 }
508
509 float
510 ShuttleControl::semitones_as_speed (int semi, bool reverse)
511 {
512         if (reverse) {
513                 return -pow (2.0, (semi / 12.0));
514         } else {
515                 return pow (2.0, (semi / 12.0));
516         }
517 }
518
519 float
520 ShuttleControl::semitones_as_fract (int semi, bool reverse)
521 {
522         float speed = semitones_as_speed (semi, reverse);
523         return speed/4.0; /* 4.0 is the maximum speed for a 24 semitone shift */
524 }
525
526 int
527 ShuttleControl::fract_as_semitones (float fract, bool& reverse)
528 {
529         assert (fract != 0.0);
530         return speed_as_semitones (fract * 4.0, reverse);
531 }
532
533 void
534 ShuttleControl::use_shuttle_fract (bool force, bool zero_ok)
535 {
536         microseconds_t now = get_microseconds();
537
538         shuttle_fract = max (-1.0f, shuttle_fract);
539         shuttle_fract = min (1.0f, shuttle_fract);
540
541         /* do not attempt to submit a motion-driven transport speed request
542            more than once per process cycle.
543         */
544
545         if (!force && (last_shuttle_request - now) < (microseconds_t) AudioEngine::instance()->usecs_per_cycle()) {
546                 return;
547         }
548
549         last_shuttle_request = now;
550
551         double speed = 0;
552
553         if (Config->get_shuttle_units() == Semitones) {
554                 if (shuttle_fract != 0.0) {
555                         bool reverse;
556                         int semi = fract_as_semitones (shuttle_fract, reverse);
557                         speed = semitones_as_speed (semi, reverse);
558                 } else {
559                         speed = 0.0;
560                 }
561         } else {
562                 speed = shuttle_max_speed * shuttle_fract;
563         }
564
565         requested_speed = speed;
566         if (zero_ok) {
567                 _session->request_transport_speed (speed, Config->get_shuttle_behaviour() == Wheel);
568         } else {
569                 _session->request_transport_speed_nonzero (speed, Config->get_shuttle_behaviour() == Wheel);
570         }
571 }
572
573 void
574 ShuttleControl::set_colors ()
575 {
576         int r, g, b, a;
577
578         uint32_t bg_color = UIConfiguration::instance().color (X_("shuttle bg"));
579         uint32_t text = UIConfiguration::instance().color (X_("shuttle text"));
580
581         UINT_TO_RGBA (bg_color, &r, &g, &b, &a);
582         bg_r = r/255.0;
583         bg_g = g/255.0;
584         bg_b = b/255.0;
585
586         UINT_TO_RGBA (text, &r, &g, &b, &a);
587
588         /* rescale for Pango colors ... sigh */
589
590         r = lrint (r * 65535.0);
591         g = lrint (g * 65535.0);
592         b = lrint (b * 65535.0);
593
594         delete text_color;
595         text_color = new Pango::AttrColor (Pango::Attribute::create_attr_foreground (r, g, b));
596         text_attributes.change (*text_color);
597 }
598
599 void
600 ShuttleControl::render (cairo_t* cr, cairo_rectangle_t*)
601 {
602         // center slider line
603         float yc = get_height() / 2;
604         float lw = 3;
605         cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
606         cairo_set_line_width (cr, 3);
607         cairo_move_to (cr, lw, yc);
608         cairo_line_to (cr, get_width () - lw, yc);
609         cairo_set_source_rgb (cr, bg_r, bg_g, bg_b);
610         cairo_stroke (cr);
611
612         float speed = 0.0;
613         float acutal_speed = 0.0;
614         char buf[32];
615
616         if (_session) {
617                 speed = _session->transport_speed ();
618                 acutal_speed = speed;
619                 if (shuttle_grabbed) {
620                         speed = requested_speed;
621                 }
622         }
623
624         /* marker */
625         float visual_fraction = std::max (-1.0f, std::min (1.0f, speed / shuttle_max_speed));
626         float marker_size = round (get_height() * 0.66);
627         float avail_width = get_width() - marker_size;
628         float x = 0.5 * (get_width() + visual_fraction * avail_width - marker_size);
629
630         rounded_rectangle (cr, x, 0, marker_size, get_height(), 5);
631         cairo_set_source_rgba (cr, 0, 0, 0, 1);
632         cairo_fill(cr);
633         rounded_rectangle (cr, x + 1, 1, marker_size - 2, get_height() - 2, 3.5);
634         cairo_set_source (cr, pattern);
635         cairo_fill(cr);
636
637         /* text */
638         if (acutal_speed != 0) {
639                 if (Config->get_shuttle_units() == Percentage) {
640                         if (acutal_speed == 1.0) {
641                                 snprintf (buf, sizeof (buf), "%s", _("Playing"));
642                         } else {
643                                 if (acutal_speed < 0.0) {
644                                         snprintf (buf, sizeof (buf), "< %.1f%%", -acutal_speed * 100.f);
645                                 } else {
646                                         snprintf (buf, sizeof (buf), "> %.1f%%", acutal_speed * 100.f);
647                                 }
648                         }
649                 } else {
650                         bool reversed;
651                         int semi = speed_as_semitones (acutal_speed, reversed);
652                         if (reversed) {
653                                 snprintf (buf, sizeof (buf), _("< %+2d semi"), semi);
654                         } else {
655                                 snprintf (buf, sizeof (buf), _("> %+2d semi"), semi);
656                         }
657                 }
658         } else {
659                 snprintf (buf, sizeof (buf), "%s", _("Stopped"));
660         }
661
662         last_speed_displayed = acutal_speed;
663
664         int tw, th;
665         _text->set_text (buf);
666         _text->get_pixel_size (tw, th);
667         cairo_move_to (cr, 0.5 * (get_width() - tw), 0.5 * (get_height() - th));
668         pango_cairo_show_layout (cr, _text->gobj());
669
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 }
678
679 void
680 ShuttleControl::shuttle_unit_clicked ()
681 {
682         if (shuttle_unit_menu == 0) {
683                 shuttle_unit_menu = dynamic_cast<Menu*> (ActionManager::get_widget ("/ShuttleUnitPopup"));
684         }
685         shuttle_unit_menu->popup (1, gtk_get_current_event_time());
686 }
687
688 void
689 ShuttleControl::set_shuttle_style (ShuttleBehaviour s)
690 {
691         Config->set_shuttle_behaviour (s);
692 }
693
694 void
695 ShuttleControl::set_shuttle_units (ShuttleUnits s)
696 {
697         Config->set_shuttle_units (s);
698 }
699
700 ShuttleControl::ShuttleControllable::ShuttleControllable (ShuttleControl& s)
701         : PBD::Controllable (X_("Shuttle"))
702         , sc (s)
703 {
704 }
705
706 void
707 ShuttleControl::ShuttleControllable::set_value (double val, PBD::Controllable::GroupControlDisposition /*group_override*/)
708 {
709         sc.set_shuttle_fract ((val - lower()) / (upper() - lower()), true);
710 }
711
712 double
713 ShuttleControl::ShuttleControllable::get_value () const
714 {
715         return lower() + (sc.get_shuttle_fract () * (upper() - lower()));
716 }
717
718 void
719 ShuttleControl::parameter_changed (std::string p)
720 {
721         if (p == "shuttle-behaviour") {
722                 switch (Config->get_shuttle_behaviour ()) {
723                 case Sprung:
724                         /* back to Sprung - reset to speed = 1.0 if playing
725                          */
726                         if (_session) {
727                                 if (_session->transport_rolling()) {
728                                         if (_session->transport_speed() == 1.0) {
729                                                 queue_draw ();
730                                         } else {
731                                                 /* reset current speed and
732                                                    revert to 1.0 as the default
733                                                 */
734                                                 _session->request_transport_speed (1.0);
735                                                 /* redraw when speed changes */
736                                         }
737                                 } else {
738                                         queue_draw ();
739                                 }
740                         }
741                         break;
742
743                 case Wheel:
744                         queue_draw ();
745                         break;
746                 }
747
748         } else if (p == "shuttle-max-speed") {
749                 queue_draw ();
750         } else if (p == "shuttle-units") {
751                 queue_draw ();
752         }
753 }
754
755
756 bool
757 ShuttleControl::on_enter_notify_event (GdkEventCrossing* ev)
758 {
759         _hovering = true;
760
761         if (UIConfiguration::instance().get_widget_prelight()) {
762                 queue_draw ();
763         }
764
765         return CairoWidget::on_enter_notify_event (ev);
766 }
767
768 bool
769 ShuttleControl::on_leave_notify_event (GdkEventCrossing* ev)
770 {
771         _hovering = false;
772
773         if (UIConfiguration::instance().get_widget_prelight()) {
774                 queue_draw ();
775         }
776
777         return CairoWidget::on_leave_notify_event (ev);
778 }