Do not try to restore Route solo state after clearing all solo state
[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                         requested_speed = shuttle_speed_on_grab;
315                         mouse_shuttle (ev->x, true);
316                         gdk_pointer_grab(ev->window,false,
317                                          GdkEventMask( Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK |Gdk::BUTTON_RELEASE_MASK),
318                                          NULL,NULL,ev->time);
319                 }
320                 break;
321
322         case 2:
323         case 3:
324                 return true;
325                 break;
326         }
327
328         return true;
329 }
330
331 bool
332 ShuttleControl::on_button_release_event (GdkEventButton* ev)
333 {
334         if (!_session) {
335                 return true;
336         }
337
338         switch (ev->button) {
339         case 1:
340                 if (shuttle_grabbed) {
341                         shuttle_grabbed = false;
342                         remove_modal_grab ();
343                         gdk_pointer_ungrab (GDK_CURRENT_TIME);
344
345                         if (Config->get_shuttle_behaviour() == Sprung) {
346                                 if (shuttle_speed_on_grab == 0 ) {
347                                         _session->request_stop ();
348                                 } else {
349                                         _session->request_transport_speed (shuttle_speed_on_grab);
350                                 }
351                         } else {
352                                 mouse_shuttle (ev->x, true);
353                         }
354                 }
355                 return true;
356
357         case 2:
358                 if (_session->transport_rolling()) {
359                         _session->request_transport_speed (1.0, Config->get_shuttle_behaviour() == Wheel);
360                 }
361                 return true;
362
363         case 3:
364         default:
365                 return true;
366
367         }
368
369         return true;
370 }
371
372 bool
373 ShuttleControl::on_query_tooltip (int, int, bool, const Glib::RefPtr<Gtk::Tooltip>&)
374 {
375         return false;
376 }
377
378 bool
379 ShuttleControl::on_scroll_event (GdkEventScroll* ev)
380 {
381         if (!_session || Config->get_shuttle_behaviour() != Wheel) {
382                 return true;
383         }
384
385         bool semis = (Config->get_shuttle_units() == Semitones);
386
387         switch (ev->direction) {
388         case GDK_SCROLL_UP:
389         case GDK_SCROLL_RIGHT:
390                 if (semis) {
391                         if (shuttle_fract == 0) {
392                                 shuttle_fract = semitones_as_fract (1, false);
393                         } else {
394                                 bool rev;
395                                 int st = fract_as_semitones (shuttle_fract, rev);
396                                 shuttle_fract = semitones_as_fract (st + 1, rev);
397                         }
398                 } else {
399                         shuttle_fract += 0.00125;
400                 }
401                 break;
402         case GDK_SCROLL_DOWN:
403         case GDK_SCROLL_LEFT:
404                 if (semis) {
405                         if (shuttle_fract == 0) {
406                                 shuttle_fract = semitones_as_fract (1, true);
407                         } else {
408                                 bool rev;
409                                 int st = fract_as_semitones (shuttle_fract, rev);
410                                 shuttle_fract = semitones_as_fract (st - 1, rev);
411                         }
412                 } else {
413                         shuttle_fract -= 0.00125;
414                 }
415                 break;
416         default:
417                 return false;
418         }
419
420         if (semis) {
421
422                 float lower_side_of_dead_zone = semitones_as_fract (-24, true);
423                 float upper_side_of_dead_zone = semitones_as_fract (-24, false);
424
425                 /* if we entered the "dead zone" (-24 semitones in forward or reverse), jump
426                    to the far side of it.
427                 */
428
429                 if (shuttle_fract > lower_side_of_dead_zone && shuttle_fract < upper_side_of_dead_zone) {
430                         switch (ev->direction) {
431                         case GDK_SCROLL_UP:
432                         case GDK_SCROLL_RIGHT:
433                                 shuttle_fract = upper_side_of_dead_zone;
434                                 break;
435                         case GDK_SCROLL_DOWN:
436                         case GDK_SCROLL_LEFT:
437                                 shuttle_fract = lower_side_of_dead_zone;
438                                 break;
439                         default:
440                                 /* impossible, checked above */
441                                 return false;
442                         }
443                 }
444         }
445
446         use_shuttle_fract (true);
447
448         return true;
449 }
450
451 bool
452 ShuttleControl::on_motion_notify_event (GdkEventMotion* ev)
453 {
454         if (!_session || !shuttle_grabbed) {
455                 return true;
456         }
457
458         return mouse_shuttle (ev->x, false);
459 }
460
461 gint
462 ShuttleControl::mouse_shuttle (double x, bool force)
463 {
464         double const center = get_width() / 2.0;
465         double distance_from_center = x - center;
466
467         if (distance_from_center > 0) {
468                 distance_from_center = min (distance_from_center, center);
469         } else {
470                 distance_from_center = max (distance_from_center, -center);
471         }
472
473         /* compute shuttle fract as expressing how far between the center
474            and the edge we are. positive values indicate we are right of
475            center, negative values indicate left of center
476         */
477
478         shuttle_fract = distance_from_center / center; // center == half the width
479         use_shuttle_fract (force);
480         return true;
481 }
482
483 void
484 ShuttleControl::set_shuttle_fract (double f, bool zero_ok)
485 {
486         shuttle_fract = f;
487         use_shuttle_fract (false, zero_ok);
488 }
489
490 int
491 ShuttleControl::speed_as_semitones (float speed, bool& reverse)
492 {
493         assert (speed != 0.0);
494
495         if (speed < 0.0) {
496                 reverse = true;
497                 return (int) round (12.0 * fast_log2 (-speed));
498         } else {
499                 reverse = false;
500                 return (int) round (12.0 * fast_log2 (speed));
501         }
502 }
503
504 float
505 ShuttleControl::semitones_as_speed (int semi, bool reverse)
506 {
507         if (reverse) {
508                 return -pow (2.0, (semi / 12.0));
509         } else {
510                 return pow (2.0, (semi / 12.0));
511         }
512 }
513
514 float
515 ShuttleControl::semitones_as_fract (int semi, bool reverse)
516 {
517         float speed = semitones_as_speed (semi, reverse);
518         return speed/4.0; /* 4.0 is the maximum speed for a 24 semitone shift */
519 }
520
521 int
522 ShuttleControl::fract_as_semitones (float fract, bool& reverse)
523 {
524         assert (fract != 0.0);
525         return speed_as_semitones (fract * 4.0, reverse);
526 }
527
528 void
529 ShuttleControl::use_shuttle_fract (bool force, bool zero_ok)
530 {
531         microseconds_t now = get_microseconds();
532
533         shuttle_fract = max (-1.0f, shuttle_fract);
534         shuttle_fract = min (1.0f, shuttle_fract);
535
536         /* do not attempt to submit a motion-driven transport speed request
537            more than once per process cycle.
538         */
539
540         if (!force && (last_shuttle_request - now) < (microseconds_t) AudioEngine::instance()->usecs_per_cycle()) {
541                 return;
542         }
543
544         last_shuttle_request = now;
545
546         double speed = 0;
547
548         if (Config->get_shuttle_units() == Semitones) {
549                 if (shuttle_fract != 0.0) {
550                         bool reverse;
551                         int semi = fract_as_semitones (shuttle_fract, reverse);
552                         speed = semitones_as_speed (semi, reverse);
553                 } else {
554                         speed = 0.0;
555                 }
556         } else {
557                 speed = shuttle_max_speed * shuttle_fract;
558         }
559
560         requested_speed = speed;
561         if (zero_ok) {
562                 _session->request_transport_speed (speed, Config->get_shuttle_behaviour() == Wheel);
563         } else {
564                 _session->request_transport_speed_nonzero (speed, Config->get_shuttle_behaviour() == Wheel);
565         }
566 }
567
568 void
569 ShuttleControl::set_colors ()
570 {
571         int r, g, b, a;
572
573         uint32_t bg_color = UIConfiguration::instance().color (X_("shuttle bg"));
574         uint32_t text = UIConfiguration::instance().color (X_("shuttle text"));
575
576         UINT_TO_RGBA (bg_color, &r, &g, &b, &a);
577         bg_r = r/255.0;
578         bg_g = g/255.0;
579         bg_b = b/255.0;
580
581         UINT_TO_RGBA (text, &r, &g, &b, &a);
582
583         /* rescale for Pango colors ... sigh */
584
585         r = lrint (r * 65535.0);
586         g = lrint (g * 65535.0);
587         b = lrint (b * 65535.0);
588
589         delete text_color;
590         text_color = new Pango::AttrColor (Pango::Attribute::create_attr_foreground (r, g, b));
591         text_attributes.change (*text_color);
592 }
593
594 void
595 ShuttleControl::render (cairo_t* cr, cairo_rectangle_t*)
596 {
597         //black border
598         cairo_set_source_rgb (cr, bg_r, bg_g, bg_b);
599         rounded_rectangle (cr, 0, 0, get_width(), get_height(), 4);
600         cairo_fill (cr);
601
602         float speed = 0.0;
603         float acutal_speed = 0.0;
604
605         if (_session) {
606                 speed = _session->transport_speed ();
607                 acutal_speed = speed;
608                 if (shuttle_grabbed) {
609                         speed = requested_speed;
610                 }
611         }
612
613         /* Marker */
614         float visual_fraction = std::min (1.0f, speed / shuttle_max_speed);
615         float marker_size = get_height() - 5.0;
616         float avail_width = get_width() - marker_size - 4;
617         float x = get_width() * 0.5 + visual_fraction * avail_width * 0.5;
618 //      cairo_set_source_rgb (cr, 0, 1, 0.0);
619         cairo_set_source (cr, pattern);
620         if (speed == 1.0) {
621                 cairo_move_to( cr, x, 2.5);
622                 cairo_line_to( cr, x + marker_size * .577, 2.5 + marker_size * 0.5);
623                 cairo_line_to( cr, x, 2.5 + marker_size);
624                 cairo_close_path(cr);
625         } else if ( speed ==0.0 )
626                 rounded_rectangle (cr, x, 2.5, marker_size, marker_size, 1);
627         else
628                 cairo_arc (cr, x, 2.5 + marker_size * .5, marker_size * 0.47, 0, 2.0 * M_PI);
629         cairo_set_line_width (cr, 1.75);
630         cairo_stroke (cr);
631
632         /* speed text */
633
634         char buf[32];
635
636         if (acutal_speed != 0) {
637
638                 if (Config->get_shuttle_units() == Percentage) {
639
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
650                 } else {
651
652                         bool reversed;
653                         int semi = speed_as_semitones (acutal_speed, reversed);
654
655                         if (reversed) {
656                                 snprintf (buf, sizeof (buf), _("<<< %+d semitones"), semi);
657                         } else {
658                                 snprintf (buf, sizeof (buf), _(">>> %+d semitones"), semi);
659                         }
660                 }
661
662         } else {
663                 snprintf (buf, sizeof (buf), "%s", _("Stopped"));
664         }
665
666         last_speed_displayed = acutal_speed;
667
668         const float top_text_margin = 3.0f;
669         const float side_text_margin = 5.0f;
670
671         left_text->set_text (buf);
672         cairo_move_to (cr, side_text_margin, top_text_margin);
673         pango_cairo_show_layout (cr, left_text->gobj());
674
675         /* style text */
676
677         switch (Config->get_shuttle_behaviour()) {
678         case Sprung:
679                 snprintf (buf, sizeof (buf), "%s", _("Sprung"));
680                 break;
681         case Wheel:
682                 snprintf (buf, sizeof (buf), "%s", _("Wheel"));
683                 break;
684         }
685
686         right_text->set_text (buf);
687         Pango::Rectangle r = right_text->get_ink_extents ();
688         cairo_move_to (cr, get_width() - ((r.get_width()/PANGO_SCALE) + side_text_margin), top_text_margin);
689         pango_cairo_show_layout (cr, right_text->gobj());
690
691         if (UIConfiguration::instance().get_widget_prelight()) {
692                 if (_hovering) {
693                         rounded_rectangle (cr, 1, 1, get_width()-2, get_height()-2, 4.0);
694                         cairo_set_source_rgba (cr, 1, 1, 1, 0.15);
695                         cairo_fill (cr);
696                 }
697         }
698 }
699
700 void
701 ShuttleControl::shuttle_unit_clicked ()
702 {
703         if (shuttle_unit_menu == 0) {
704                 shuttle_unit_menu = dynamic_cast<Menu*> (ActionManager::get_widget ("/ShuttleUnitPopup"));
705         }
706         shuttle_unit_menu->popup (1, gtk_get_current_event_time());
707 }
708
709 void
710 ShuttleControl::set_shuttle_style (ShuttleBehaviour s)
711 {
712         Config->set_shuttle_behaviour (s);
713 }
714
715 void
716 ShuttleControl::set_shuttle_units (ShuttleUnits s)
717 {
718         Config->set_shuttle_units (s);
719 }
720
721 ShuttleControl::ShuttleControllable::ShuttleControllable (ShuttleControl& s)
722         : PBD::Controllable (X_("Shuttle"))
723         , sc (s)
724 {
725 }
726
727 void
728 ShuttleControl::ShuttleControllable::set_value (double val, PBD::Controllable::GroupControlDisposition /*group_override*/)
729 {
730         sc.set_shuttle_fract ((val - lower()) / (upper() - lower()), true);
731 }
732
733 double
734 ShuttleControl::ShuttleControllable::get_value () const
735 {
736         return lower() + (sc.get_shuttle_fract () * (upper() - lower()));
737 }
738
739 void
740 ShuttleControl::parameter_changed (std::string p)
741 {
742         if (p == "shuttle-behaviour") {
743                 switch (Config->get_shuttle_behaviour ()) {
744                 case Sprung:
745                         /* back to Sprung - reset to speed = 1.0 if playing
746                          */
747                         if (_session) {
748                                 if (_session->transport_rolling()) {
749                                         if (_session->transport_speed() == 1.0) {
750                                                 queue_draw ();
751                                         } else {
752                                                 /* reset current speed and
753                                                    revert to 1.0 as the default
754                                                 */
755                                                 _session->request_transport_speed (1.0);
756                                                 /* redraw when speed changes */
757                                         }
758                                 } else {
759                                         queue_draw ();
760                                 }
761                         }
762                         break;
763
764                 case Wheel:
765                         queue_draw ();
766                         break;
767                 }
768
769         } else if (p == "shuttle-units") {
770                 queue_draw ();
771         }
772 }
773
774
775 bool
776 ShuttleControl::on_enter_notify_event (GdkEventCrossing* ev)
777 {
778         _hovering = true;
779
780         if (UIConfiguration::instance().get_widget_prelight()) {
781                 queue_draw ();
782         }
783
784         return CairoWidget::on_enter_notify_event (ev);
785 }
786
787 bool
788 ShuttleControl::on_leave_notify_event (GdkEventCrossing* ev)
789 {
790         _hovering = false;
791
792         if (UIConfiguration::instance().get_widget_prelight()) {
793                 queue_draw ();
794         }
795
796         return CairoWidget::on_leave_notify_event (ev);
797 }