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