938ba08c51fe3a35f9a51ba094082b686514ce83
[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 }
108
109 void
110 ShuttleControl::set_session (Session *s)
111 {
112         SessionHandlePtr::set_session (s);
113
114         if (_session) {
115                 set_sensitive (true);
116                 _session->add_controllable (_controllable);
117         } else {
118                 set_sensitive (false);
119         }
120 }
121
122 void
123 ShuttleControl::on_size_allocate (Gtk::Allocation& alloc)
124 {
125         if (pattern) {
126                 cairo_pattern_destroy (pattern);
127                 pattern = 0;
128                 cairo_pattern_destroy (shine_pattern);
129                 shine_pattern = 0;
130         }
131
132         CairoWidget::on_size_allocate ( alloc);
133
134         //background
135         pattern = cairo_pattern_create_linear (0, 0, 0, alloc.get_height());
136         uint32_t col = UIConfiguration::instance().color ("shuttle");
137         int r,b,g,a;
138         UINT_TO_RGBA(col, &r, &g, &b, &a);
139         cairo_pattern_add_color_stop_rgb (pattern, 0.0, r/400.0, g/400.0, b/400.0);
140         cairo_pattern_add_color_stop_rgb (pattern, 0.4, r/255.0, g/255.0, b/255.0);
141         cairo_pattern_add_color_stop_rgb (pattern, 1.0, r/512.0, g/512.0, b/512.0);
142
143         //reflection
144         shine_pattern = cairo_pattern_create_linear (0.0, 0.0, 0.0, 10);
145         cairo_pattern_add_color_stop_rgba (shine_pattern, 0, 1,1,1,0.0);
146         cairo_pattern_add_color_stop_rgba (shine_pattern, 0.2, 1,1,1,0.4);
147         cairo_pattern_add_color_stop_rgba (shine_pattern, 1, 1,1,1,0.1);
148
149         Pango::AttrFontDesc* font_attr;
150
151         font_attr = new Pango::AttrFontDesc (Pango::Attribute::create_attr_font_desc (UIConfiguration::instance().get_NormalBoldFont()));
152         text_attributes.change (*font_attr);
153
154         delete font_attr;
155 }
156
157 void
158 ShuttleControl::map_transport_state ()
159 {
160         float speed = _session->transport_speed ();
161
162         if ( (fabsf( speed - last_speed_displayed) < 0.005f) // dead-zone
163              && !( speed == 1.f && last_speed_displayed != 1.f)
164              && !( speed == 0.f && last_speed_displayed != 0.f)
165                 )
166         {
167                 return; // nothing to see here, move along.
168         }
169
170         // Q: is there a good reason why we  re-calculate this every time?
171         if (fabs(speed) <= (2*DBL_EPSILON)) {
172                 shuttle_fract = 0;
173         } else {
174                 if (Config->get_shuttle_units() == Semitones) {
175                         bool reverse;
176                         int semi = speed_as_semitones (speed, reverse);
177                         shuttle_fract = semitones_as_fract (semi, reverse);
178                 } else {
179                         shuttle_fract = speed/shuttle_max_speed;
180                 }
181         }
182
183         queue_draw ();
184 }
185
186 void
187 ShuttleControl::build_shuttle_context_menu ()
188 {
189         using namespace Menu_Helpers;
190
191         shuttle_context_menu = new Menu();
192         MenuList& items = shuttle_context_menu->items();
193
194         Menu* speed_menu = manage (new Menu());
195         MenuList& speed_items = speed_menu->items();
196
197         Menu* units_menu = manage (new Menu);
198         MenuList& units_items = units_menu->items();
199         RadioMenuItem::Group units_group;
200
201         units_items.push_back (RadioMenuElem (units_group, _("Percent"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_units), Percentage)));
202         if (Config->get_shuttle_units() == Percentage) {
203                 static_cast<RadioMenuItem*>(&units_items.back())->set_active();
204         }
205         units_items.push_back (RadioMenuElem (units_group, _("Semitones"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_units), Semitones)));
206         if (Config->get_shuttle_units() == Semitones) {
207                 static_cast<RadioMenuItem*>(&units_items.back())->set_active();
208         }
209         items.push_back (MenuElem (_("Units"), *units_menu));
210
211         Menu* style_menu = manage (new Menu);
212         MenuList& style_items = style_menu->items();
213         RadioMenuItem::Group style_group;
214
215         style_items.push_back (RadioMenuElem (style_group, _("Sprung"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_style), Sprung)));
216         if (Config->get_shuttle_behaviour() == Sprung) {
217                 static_cast<RadioMenuItem*>(&style_items.back())->set_active();
218         }
219         style_items.push_back (RadioMenuElem (style_group, _("Wheel"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_style), Wheel)));
220         if (Config->get_shuttle_behaviour() == Wheel) {
221                 static_cast<RadioMenuItem*>(&style_items.back())->set_active();
222         }
223
224         items.push_back (MenuElem (_("Mode"), *style_menu));
225
226         RadioMenuItem::Group speed_group;
227
228         speed_items.push_back (RadioMenuElem (speed_group, "8", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 8.0f)));
229         if (shuttle_max_speed == 8.0) {
230                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
231         }
232         speed_items.push_back (RadioMenuElem (speed_group, "6", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 6.0f)));
233         if (shuttle_max_speed == 6.0) {
234                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
235         }
236         speed_items.push_back (RadioMenuElem (speed_group, "4", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 4.0f)));
237         if (shuttle_max_speed == 4.0) {
238                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
239         }
240         speed_items.push_back (RadioMenuElem (speed_group, "3", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 3.0f)));
241         if (shuttle_max_speed == 3.0) {
242                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
243         }
244         speed_items.push_back (RadioMenuElem (speed_group, "2", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 2.0f)));
245         if (shuttle_max_speed == 2.0) {
246                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
247         }
248         speed_items.push_back (RadioMenuElem (speed_group, "1.5", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 1.5f)));
249         if (shuttle_max_speed == 1.5) {
250                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
251         }
252
253         items.push_back (MenuElem (_("Maximum speed"), *speed_menu));
254
255         items.push_back (SeparatorElem ());
256         items.push_back (MenuElem (_("Reset to 100%"), sigc::mem_fun (*this, &ShuttleControl::reset_speed)));
257 }
258
259 void
260 ShuttleControl::show_shuttle_context_menu ()
261 {
262         if (shuttle_context_menu == 0) {
263                 build_shuttle_context_menu ();
264         }
265
266         shuttle_context_menu->popup (1, gtk_get_current_event_time());
267 }
268
269 void
270 ShuttleControl::reset_speed ()
271 {
272         if (_session->transport_rolling()) {
273                 _session->request_transport_speed (1.0, true);
274         } else {
275                 _session->request_transport_speed (0.0, true);
276         }
277 }
278
279 void
280 ShuttleControl::set_shuttle_max_speed (float speed)
281 {
282         Config->set_shuttle_max_speed (speed);
283         shuttle_max_speed = speed;
284         last_speed_displayed = -99999999;
285 }
286
287 bool
288 ShuttleControl::on_button_press_event (GdkEventButton* ev)
289 {
290         if (!_session) {
291                 return true;
292         }
293
294         if (binding_proxy.button_press_handler (ev)) {
295                 return true;
296         }
297
298         if (Keyboard::is_context_menu_event (ev)) {
299                 show_shuttle_context_menu ();
300                 return true;
301         }
302
303         switch (ev->button) {
304         case 1:
305                 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
306                         if (_session->transport_rolling()) {
307                                 _session->request_transport_speed (1.0);
308                         }
309                 } else {
310                         add_modal_grab ();
311                         shuttle_grabbed = true;
312                         shuttle_speed_on_grab = _session->transport_speed ();
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         if (zero_ok) {
559                 _session->request_transport_speed (speed, Config->get_shuttle_behaviour() == Wheel);
560         } else {
561                 _session->request_transport_speed_nonzero (speed, Config->get_shuttle_behaviour() == Wheel);
562         }
563 }
564
565 void
566 ShuttleControl::set_colors ()
567 {
568         int r, g, b, a;
569
570         uint32_t bg_color = UIConfiguration::instance().color (X_("shuttle bg"));
571         uint32_t text = UIConfiguration::instance().color (X_("shuttle text"));
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         UINT_TO_RGBA (text, &r, &g, &b, &a);
579
580         /* rescale for Pango colors ... sigh */
581
582         r = lrint (r * 65535.0);
583         g = lrint (g * 65535.0);
584         b = lrint (b * 65535.0);
585
586         delete text_color;
587         text_color = new Pango::AttrColor (Pango::Attribute::create_attr_foreground (r, g, b));
588         text_attributes.change (*text_color);
589 }
590
591 void
592 ShuttleControl::render (cairo_t* cr, cairo_rectangle_t*)
593 {
594         //black border
595         cairo_set_source_rgb (cr, bg_r, bg_g, bg_b);
596         rounded_rectangle (cr, 0, 0, get_width(), get_height(), 4);
597         cairo_fill (cr);
598
599         float speed = 0.0;
600
601         if (_session) {
602                 speed = _session->transport_speed ();
603         }
604
605         /* Marker */
606         float visual_fraction = std::min (1.0f, speed / shuttle_max_speed);
607         float marker_size = get_height() - 5.0;
608         float avail_width = get_width() - marker_size - 4;
609         float x = get_width() * 0.5 + visual_fraction * avail_width * 0.5;
610 //      cairo_set_source_rgb (cr, 0, 1, 0.0);
611         cairo_set_source (cr, pattern);
612         if (speed == 1.0) {
613                 cairo_move_to( cr, x, 2.5);
614                 cairo_line_to( cr, x + marker_size * .577, 2.5 + marker_size * 0.5);
615                 cairo_line_to( cr, x, 2.5 + marker_size);
616                 cairo_close_path(cr);
617         } else if ( speed ==0.0 )
618                 rounded_rectangle (cr, x, 2.5, marker_size, marker_size, 1);
619         else
620                 cairo_arc (cr, x, 2.5 + marker_size * .5, marker_size * 0.47, 0, 2.0 * M_PI);
621         cairo_set_line_width (cr, 1.75);
622         cairo_stroke (cr);
623
624         /* speed text */
625
626         char buf[32];
627
628         if (speed != 0) {
629
630                 if (Config->get_shuttle_units() == Percentage) {
631
632                         if (speed == 1.0) {
633                                 snprintf (buf, sizeof (buf), "%s", _("Playing"));
634                         } else {
635                                 if (speed < 0.0) {
636                                         snprintf (buf, sizeof (buf), "<<< %.1f%%", -speed * 100.f);
637                                 } else {
638                                         snprintf (buf, sizeof (buf), ">>> %.1f%%", speed * 100.f);
639                                 }
640                         }
641
642                 } else {
643
644                         bool reversed;
645                         int semi = speed_as_semitones (speed, reversed);
646
647                         if (reversed) {
648                                 snprintf (buf, sizeof (buf), _("<<< %+d semitones"), semi);
649                         } else {
650                                 snprintf (buf, sizeof (buf), _(">>> %+d semitones"), semi);
651                         }
652                 }
653
654         } else {
655                 snprintf (buf, sizeof (buf), "%s", _("Stopped"));
656         }
657
658         last_speed_displayed = speed;
659
660         const float top_text_margin = 3.0f;
661         const float side_text_margin = 5.0f;
662
663         left_text->set_text (buf);
664         cairo_move_to (cr, side_text_margin, top_text_margin);
665         pango_cairo_show_layout (cr, left_text->gobj());
666
667         /* style text */
668
669         switch (Config->get_shuttle_behaviour()) {
670         case Sprung:
671                 snprintf (buf, sizeof (buf), "%s", _("Sprung"));
672                 break;
673         case Wheel:
674                 snprintf (buf, sizeof (buf), "%s", _("Wheel"));
675                 break;
676         }
677
678         right_text->set_text (buf);
679         Pango::Rectangle r = right_text->get_ink_extents ();
680         cairo_move_to (cr, get_width() - ((r.get_width()/PANGO_SCALE) + side_text_margin), top_text_margin);
681         pango_cairo_show_layout (cr, right_text->gobj());
682
683         if (UIConfiguration::instance().get_widget_prelight()) {
684                 if (_hovering) {
685                         rounded_rectangle (cr, 1, 1, get_width()-2, get_height()-2, 4.0);
686                         cairo_set_source_rgba (cr, 1, 1, 1, 0.15);
687                         cairo_fill (cr);
688                 }
689         }
690 }
691
692 void
693 ShuttleControl::shuttle_unit_clicked ()
694 {
695         if (shuttle_unit_menu == 0) {
696                 shuttle_unit_menu = dynamic_cast<Menu*> (ActionManager::get_widget ("/ShuttleUnitPopup"));
697         }
698         shuttle_unit_menu->popup (1, gtk_get_current_event_time());
699 }
700
701 void
702 ShuttleControl::set_shuttle_style (ShuttleBehaviour s)
703 {
704         Config->set_shuttle_behaviour (s);
705 }
706
707 void
708 ShuttleControl::set_shuttle_units (ShuttleUnits s)
709 {
710         Config->set_shuttle_units (s);
711 }
712
713 ShuttleControl::ShuttleControllable::ShuttleControllable (ShuttleControl& s)
714         : PBD::Controllable (X_("Shuttle"))
715         , sc (s)
716 {
717 }
718
719 void
720 ShuttleControl::ShuttleControllable::set_value (double val, PBD::Controllable::GroupControlDisposition /*group_override*/)
721 {
722         sc.set_shuttle_fract ((val - lower()) / (upper() - lower()), true);
723 }
724
725 double
726 ShuttleControl::ShuttleControllable::get_value () const
727 {
728         return lower() + (sc.get_shuttle_fract () * (upper() - lower()));
729 }
730
731 void
732 ShuttleControl::parameter_changed (std::string p)
733 {
734         if (p == "shuttle-behaviour") {
735                 switch (Config->get_shuttle_behaviour ()) {
736                 case Sprung:
737                         /* back to Sprung - reset to speed = 1.0 if playing
738                          */
739                         if (_session) {
740                                 if (_session->transport_rolling()) {
741                                         if (_session->transport_speed() == 1.0) {
742                                                 queue_draw ();
743                                         } else {
744                                                 /* reset current speed and
745                                                    revert to 1.0 as the default
746                                                 */
747                                                 _session->request_transport_speed (1.0);
748                                                 /* redraw when speed changes */
749                                         }
750                                 } else {
751                                         queue_draw ();
752                                 }
753                         }
754                         break;
755
756                 case Wheel:
757                         queue_draw ();
758                         break;
759                 }
760
761         } else if (p == "shuttle-units") {
762                 queue_draw ();
763         }
764 }
765
766
767 bool
768 ShuttleControl::on_enter_notify_event (GdkEventCrossing* ev)
769 {
770         _hovering = true;
771
772         if (UIConfiguration::instance().get_widget_prelight()) {
773                 queue_draw ();
774         }
775
776         return CairoWidget::on_enter_notify_event (ev);
777 }
778
779 bool
780 ShuttleControl::on_leave_notify_event (GdkEventCrossing* ev)
781 {
782         _hovering = false;
783
784         if (UIConfiguration::instance().get_widget_prelight()) {
785                 queue_draw ();
786         }
787
788         return CairoWidget::on_leave_notify_event (ev);
789 }