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