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