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