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