Re-fix compile failure with --no-nls (#3111).
[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/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
31 #include "ardour_ui.h"
32 #include "shuttle_control.h"
33
34 #include "i18n.h"
35
36 using namespace Gtk;
37 using namespace Gtkmm2ext;
38 using namespace ARDOUR;
39 using std::min;
40 using std::max;
41
42 gboolean qt (gboolean, gint, gint, gboolean, Gtk::Tooltip*, gpointer)
43 {
44         return FALSE;
45 }
46
47 ShuttleControl::ShuttleControl ()
48         : _controllable (new ShuttleControllable (*this))
49         , binding_proxy (_controllable)
50 {
51         ARDOUR_UI::instance()->set_tip (*this, _("Shuttle speed control (Context-click for options)"));
52
53         pattern = 0;
54         last_shuttle_request = 0;
55         last_speed_displayed = -99999999;
56         shuttle_grabbed = false;
57         shuttle_fract = 0.0;
58         shuttle_max_speed = 8.0f;
59         shuttle_style_menu = 0;
60         shuttle_unit_menu = 0;
61         shuttle_context_menu = 0;
62
63         set_flags (CAN_FOCUS);
64         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);
65         set_size_request (100, 15);
66         set_name (X_("ShuttleControl"));
67
68         Config->ParameterChanged.connect (parameter_connection, MISSING_INVALIDATOR, ui_bind (&ShuttleControl::parameter_changed, this, _1), gui_context());
69
70         /* gtkmm 2.4: the C++ wrapper doesn't work */
71         g_signal_connect ((GObject*) gobj(), "query-tooltip", G_CALLBACK (qt), NULL);
72         // signal_query_tooltip().connect (sigc::mem_fun (*this, &ShuttleControl::on_query_tooltip));
73 }
74
75 ShuttleControl::~ShuttleControl ()
76 {
77         cairo_pattern_destroy (pattern);
78 }
79
80 void
81 ShuttleControl::set_session (Session *s)
82 {
83         SessionHandlePtr::set_session (s);
84
85         if (_session) {
86                 set_sensitive (true);
87                 _session->add_controllable (_controllable);
88         } else {
89                 set_sensitive (false);
90         }
91 }
92
93 void
94 ShuttleControl::on_size_allocate (Gtk::Allocation& alloc)
95 {
96         if (pattern) {
97                 cairo_pattern_destroy (pattern);
98                 pattern = 0;
99         }
100
101         pattern = cairo_pattern_create_linear (0, 0, alloc.get_width(), alloc.get_height());
102         
103         /* add 3 color stops */
104
105         cairo_pattern_add_color_stop_rgb (pattern, 0.0, 0, 0, 0);
106         cairo_pattern_add_color_stop_rgb (pattern, 0.5, 0.0, 0.0, 1.0);
107         cairo_pattern_add_color_stop_rgb (pattern, 1.0, 0, 0, 0);
108
109         DrawingArea::on_size_allocate (alloc);
110 }
111
112 void
113 ShuttleControl::map_transport_state ()
114 {
115         float speed = _session->transport_speed ();
116
117         if (fabs(speed) <= (2*DBL_EPSILON)) {
118                 shuttle_fract = 0;
119         } else {
120                 if (Config->get_shuttle_units() == Semitones) {
121                         bool reverse;
122                         int semi = speed_as_semitones (speed, reverse);
123                         shuttle_fract = semitones_as_fract (semi, reverse);
124                 } else {
125                         shuttle_fract = speed/shuttle_max_speed;
126                 }
127         }
128
129         queue_draw ();
130 }
131
132 void
133 ShuttleControl::build_shuttle_context_menu ()
134 {
135         using namespace Menu_Helpers;
136
137         shuttle_context_menu = new Menu();
138         MenuList& items = shuttle_context_menu->items();
139
140         Menu* speed_menu = manage (new Menu());
141         MenuList& speed_items = speed_menu->items();
142
143         Menu* units_menu = manage (new Menu);
144         MenuList& units_items = units_menu->items();
145         RadioMenuItem::Group units_group;
146         
147         units_items.push_back (RadioMenuElem (units_group, _("Percent"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_units), Percentage)));
148         if (Config->get_shuttle_units() == Percentage) {
149                 static_cast<RadioMenuItem*>(&units_items.back())->set_active();
150         }
151         units_items.push_back (RadioMenuElem (units_group, _("Semitones"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_units), Semitones)));
152         if (Config->get_shuttle_units() == Semitones) {
153                 static_cast<RadioMenuItem*>(&units_items.back())->set_active();
154         }
155         items.push_back (MenuElem (_("Units"), *units_menu));
156         
157         Menu* style_menu = manage (new Menu);
158         MenuList& style_items = style_menu->items();
159         RadioMenuItem::Group style_group;
160
161         style_items.push_back (RadioMenuElem (style_group, _("Sprung"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_style), Sprung)));
162         if (Config->get_shuttle_behaviour() == Sprung) {
163                 static_cast<RadioMenuItem*>(&style_items.back())->set_active();
164         }
165         style_items.push_back (RadioMenuElem (style_group, _("Wheel"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_style), Wheel)));
166         if (Config->get_shuttle_behaviour() == Wheel) {
167                 static_cast<RadioMenuItem*>(&style_items.back())->set_active();
168         }
169         
170         items.push_back (MenuElem (_("Mode"), *style_menu));
171
172         RadioMenuItem::Group speed_group;
173
174         speed_items.push_back (RadioMenuElem (speed_group, "8", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 8.0f)));
175         if (shuttle_max_speed == 8.0) {
176                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
177         }
178         speed_items.push_back (RadioMenuElem (speed_group, "6", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 6.0f)));
179         if (shuttle_max_speed == 6.0) {
180                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
181         }
182         speed_items.push_back (RadioMenuElem (speed_group, "4", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 4.0f)));
183         if (shuttle_max_speed == 4.0) {
184                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
185         }
186         speed_items.push_back (RadioMenuElem (speed_group, "3", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 3.0f)));
187         if (shuttle_max_speed == 3.0) {
188                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
189         }
190         speed_items.push_back (RadioMenuElem (speed_group, "2", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 2.0f)));
191         if (shuttle_max_speed == 2.0) {
192                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
193         }
194         speed_items.push_back (RadioMenuElem (speed_group, "1.5", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 1.5f)));
195         if (shuttle_max_speed == 1.5) {
196                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
197         }
198
199         items.push_back (MenuElem (_("Maximum speed"), *speed_menu));
200         
201 }
202
203 void
204 ShuttleControl::show_shuttle_context_menu ()
205 {
206         if (shuttle_context_menu == 0) {
207                 build_shuttle_context_menu ();
208         }
209
210         shuttle_context_menu->popup (1, gtk_get_current_event_time());
211 }
212
213 void
214 ShuttleControl::set_shuttle_max_speed (float speed)
215 {
216         shuttle_max_speed = speed;
217 }
218
219 bool
220 ShuttleControl::on_button_press_event (GdkEventButton* ev)
221 {
222         if (!_session) {
223                 return true;
224         }
225
226         if (binding_proxy.button_press_handler (ev)) {
227                 return true;
228         }
229
230         if (Keyboard::is_context_menu_event (ev)) {
231                 show_shuttle_context_menu ();
232                 return true;
233         }
234
235         switch (ev->button) {
236         case 1:
237                 add_modal_grab ();
238                 shuttle_grabbed = true;
239                 mouse_shuttle (ev->x, true);
240                 break;
241
242         case 2:
243         case 3:
244                 return true;
245                 break;
246         }
247
248         return true;
249 }
250
251 bool
252 ShuttleControl::on_button_release_event (GdkEventButton* ev)
253 {
254         if (!_session) {
255                 return true;
256         }
257
258         switch (ev->button) {
259         case 1:
260                 shuttle_grabbed = false;
261                 remove_modal_grab ();
262
263                 if (Config->get_shuttle_behaviour() == Sprung) {
264                         if (_session->config.get_auto_play()) {
265                                 _session->request_transport_speed (1.0);
266                         } else {
267                                 _session->request_transport_speed (0.0);
268                         }
269                 } else {
270                         mouse_shuttle (ev->x, true);
271                 }
272
273                 return true;
274
275         case 2:
276                 if (_session->transport_rolling()) {
277                         _session->request_transport_speed (1.0);
278                 } 
279                 return true;
280
281         case 3:
282         default:
283                 return true;
284
285         }
286
287         return true;
288 }
289
290 bool
291 ShuttleControl::on_query_tooltip (int, int, bool, const Glib::RefPtr<Gtk::Tooltip>&)
292 {
293         return false;
294 }
295
296 bool
297 ShuttleControl::on_scroll_event (GdkEventScroll* ev)
298 {
299         if (!_session || Config->get_shuttle_behaviour() != Wheel) {
300                 return true;
301         }
302
303         switch (ev->direction) {
304         case GDK_SCROLL_UP:
305         case GDK_SCROLL_RIGHT:
306                 shuttle_fract += 0.005;
307                 break;
308         case GDK_SCROLL_DOWN:
309         case GDK_SCROLL_LEFT:
310                 shuttle_fract -= 0.005;
311                 break;
312         default:
313                 return false;
314         }
315         
316         if (Config->get_shuttle_units() == Semitones) {
317
318                 float lower_side_of_dead_zone = semitones_as_fract (-24, true);
319                 float upper_side_of_dead_zone = semitones_as_fract (-24, false);
320
321                 /* if we entered the "dead zone" (-24 semitones in forward or reverse), jump
322                    to the far side of it.
323                 */
324
325                 if (shuttle_fract > lower_side_of_dead_zone && shuttle_fract < upper_side_of_dead_zone) {
326                         switch (ev->direction) {
327                         case GDK_SCROLL_UP:
328                         case GDK_SCROLL_RIGHT:
329                                 shuttle_fract = upper_side_of_dead_zone;
330                                 break;
331                         case GDK_SCROLL_DOWN:
332                         case GDK_SCROLL_LEFT:
333                                 shuttle_fract = lower_side_of_dead_zone;
334                                 break;
335                         default:
336                                 /* impossible, checked above */
337                                 return false;
338                         }
339                 }
340         }
341
342         use_shuttle_fract (true);
343
344         return true;
345 }
346
347 bool
348 ShuttleControl::on_motion_notify_event (GdkEventMotion* ev)
349 {
350         if (!_session || !shuttle_grabbed) {
351                 return true;
352         }
353
354         return mouse_shuttle (ev->x, false);
355 }
356
357 gint
358 ShuttleControl::mouse_shuttle (double x, bool force)
359 {
360         double const center = get_width() / 2.0;
361         double distance_from_center = x - center;
362
363         if (distance_from_center > 0) {
364                 distance_from_center = min (distance_from_center, center);
365         } else {
366                 distance_from_center = max (distance_from_center, -center);
367         }
368
369         /* compute shuttle fract as expressing how far between the center
370            and the edge we are. positive values indicate we are right of 
371            center, negative values indicate left of center
372         */
373
374         shuttle_fract = distance_from_center / center; // center == half the width
375         use_shuttle_fract (force);
376         return true;
377 }
378
379 void
380 ShuttleControl::set_shuttle_fract (double f)
381 {
382         shuttle_fract = f;
383         use_shuttle_fract (false);
384 }
385
386 int
387 ShuttleControl::speed_as_semitones (float speed, bool& reverse)
388 {
389         assert (speed != 0.0);
390
391         if (speed < 0.0) {
392                 reverse = true;
393                 return (int) round (12.0 * fast_log2 (-speed));
394         } else {
395                 reverse = false;
396                 return (int) round (12.0 * fast_log2 (speed));
397         }
398 }        
399
400 float
401 ShuttleControl::semitones_as_speed (int semi, bool reverse)
402 {
403         if (reverse) {
404                 return -pow (2.0, (semi / 12.0));
405         } else {
406                 return pow (2.0, (semi / 12.0));
407         }
408 }
409
410 float
411 ShuttleControl::semitones_as_fract (int semi, bool reverse)
412 {
413         float speed = semitones_as_speed (semi, reverse);
414         return speed/4.0; /* 4.0 is the maximum speed for a 24 semitone shift */
415 }
416
417 int
418 ShuttleControl::fract_as_semitones (float fract, bool& reverse)
419 {
420         assert (fract != 0.0);
421         return speed_as_semitones (fract * 4.0, reverse);
422 }
423
424 void
425 ShuttleControl::use_shuttle_fract (bool force)
426 {
427         microseconds_t now = get_microseconds();
428
429         shuttle_fract = max (-1.0f, shuttle_fract);
430         shuttle_fract = min (1.0f, shuttle_fract);
431
432         /* do not attempt to submit a motion-driven transport speed request
433            more than once per process cycle.
434          */
435
436         if (!force && (last_shuttle_request - now) < (microseconds_t) AudioEngine::instance()->usecs_per_cycle()) {
437                 return;
438         }
439
440         last_shuttle_request = now;
441
442         double speed = 0;
443
444         if (Config->get_shuttle_units() == Semitones) {
445                 if (shuttle_fract != 0.0) {
446                         bool reverse;
447                         int semi = fract_as_semitones (shuttle_fract, reverse);
448                         speed = semitones_as_speed (semi, reverse);
449                 } else {
450                         speed = 0.0;
451                 }
452         } else {
453                 speed = shuttle_max_speed * shuttle_fract;
454         }
455
456         _session->request_transport_speed_nonzero (speed);
457 }
458
459 bool
460 ShuttleControl::on_expose_event (GdkEventExpose* event)
461 {
462         cairo_text_extents_t extents;
463         Glib::RefPtr<Gdk::Window> win (get_window());
464         Glib::RefPtr<Gtk::Style> style (get_style());
465
466         cairo_t* cr = gdk_cairo_create (win->gobj());   
467
468         cairo_set_source (cr, pattern);
469         cairo_rectangle (cr, 0.0, 0.0, get_width(), get_height());
470         cairo_fill_preserve (cr);
471
472         cairo_set_source_rgb (cr, 0, 0, 0.0);
473         cairo_stroke (cr);
474
475         float speed = 0.0;
476
477         if (_session) {
478                 speed = _session->transport_speed ();
479         }
480
481         /* Marker */
482
483         double visual_fraction = std::min (1.0f, speed/shuttle_max_speed);
484         double x = (get_width() / 2.0) + (0.5 * (get_width() * visual_fraction));
485         cairo_move_to (cr, x, 1);
486         cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
487         cairo_line_to (cr, x, get_height()-1);
488         cairo_stroke (cr);
489
490         /* speed text */
491
492         char buf[32];
493
494         if (speed != 0) {
495
496                 if (Config->get_shuttle_units() == Percentage) {
497
498                         if (speed == 1.0) {
499                                 snprintf (buf, sizeof (buf), _("Playing"));
500                         } else {
501                                 if (speed < 0.0) {
502                                         snprintf (buf, sizeof (buf), "<<< %d%%", (int) round (-speed * 100));
503                                 } else {
504                                         snprintf (buf, sizeof (buf), ">>> %d%%", (int) round (speed * 100));
505                                 }
506                         }
507
508                 } else {
509
510                         bool reversed;
511                         int semi = speed_as_semitones (speed, reversed);
512
513                         if (reversed) {
514                                 snprintf (buf, sizeof (buf), _("<<< %+d semitones"), semi);
515                         } else {
516                                 snprintf (buf, sizeof (buf), _(">>> %+d semitones"), semi);
517                         }
518                 }
519
520         } else {
521                 snprintf (buf, sizeof (buf), _("Stopped"));
522         }
523
524         last_speed_displayed = speed;
525
526         cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
527         cairo_text_extents (cr, buf, &extents);
528         cairo_move_to (cr, 10, extents.height + 2);
529         cairo_show_text (cr, buf);
530
531         /* style text */
532
533
534         switch (Config->get_shuttle_behaviour()) {
535         case Sprung:
536                 snprintf (buf, sizeof (buf), _("Sprung"));
537                 break;
538         case Wheel:
539                 snprintf (buf, sizeof (buf), _("Wheel"));
540                 break;
541         }
542
543         cairo_text_extents (cr, buf, &extents);
544
545         cairo_move_to (cr, get_width() - (fabs(extents.x_advance) + 5), extents.height + 2);
546         cairo_show_text (cr, buf);
547
548         cairo_destroy (cr);
549
550         return true;
551 }
552
553 void
554 ShuttleControl::shuttle_unit_clicked ()
555 {
556         if (shuttle_unit_menu == 0) {
557                 shuttle_unit_menu = dynamic_cast<Menu*> (ActionManager::get_widget ("/ShuttleUnitPopup"));
558         }
559         shuttle_unit_menu->popup (1, gtk_get_current_event_time());
560 }
561
562 void
563 ShuttleControl::set_shuttle_style (ShuttleBehaviour s)
564 {
565         Config->set_shuttle_behaviour (s);
566 }
567
568 void
569 ShuttleControl::set_shuttle_units (ShuttleUnits s)
570 {
571         Config->set_shuttle_units (s);
572 }
573
574 void
575 ShuttleControl::update_speed_display ()
576 {
577         if (_session->transport_speed() != last_speed_displayed) {
578                 queue_draw ();
579         }
580 }
581       
582 ShuttleControl::ShuttleControllable::ShuttleControllable (ShuttleControl& s)
583         : PBD::Controllable (X_("Shuttle")) 
584         , sc (s)
585 {
586 }
587
588 void
589 ShuttleControl::ShuttleControllable::set_id (const std::string& str)
590 {
591         _id = str;
592 }
593
594 void
595 ShuttleControl::ShuttleControllable::set_value (double val)
596 {
597         double fract;
598         
599         if (val == 0.5) {
600                 fract = 0.0;
601         } else {
602                 if (val < 0.5) {
603                         fract = -((0.5 - val)/0.5);
604                 } else {
605                         fract = ((val - 0.5)/0.5);
606                 }
607         }
608
609         sc.set_shuttle_fract (fract);
610 }
611
612 double 
613 ShuttleControl::ShuttleControllable::get_value () const
614 {
615         return sc.get_shuttle_fract ();
616 }
617
618 void
619 ShuttleControl::parameter_changed (std::string p)
620 {
621         if (p == "shuttle-behaviour") {
622                 switch (Config->get_shuttle_behaviour ()) {
623                 case Sprung:
624                         /* back to Sprung - reset to speed = 1.0 if playing
625                          */
626                         if (_session) {
627                                 if (_session->transport_rolling()) {
628                                         if (_session->transport_speed() == 1.0) {
629                                                 queue_draw ();
630                                         } else {
631                                                 _session->request_transport_speed (1.0);
632                                                 /* redraw when speed changes */
633                                         }
634                                 } else {
635                                         queue_draw ();
636                                 }
637                         }
638                         break;
639
640                 case Wheel:
641                         queue_draw ();
642                         break;
643                 }
644                         
645         } else if (p == "shuttle-units") {
646                 queue_draw ();
647         }
648 }