mp4chaps Lua script: don't clutter global environment
[ardour.git] / gtk2_ardour / panner_ui.cc
1 /*
2   Copyright (C) 2004 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 <limits.h>
20
21 #include <gtkmm2ext/utils.h>
22
23 #include "pbd/fastlog.h"
24
25 #include "ardour/pannable.h"
26 #include "ardour/panner.h"
27 #include "ardour/panner_shell.h"
28 #include "ardour/session.h"
29
30 #include "widgets/tooltips.h"
31
32 #include "panner_ui.h"
33 #include "panner2d.h"
34 #include "gui_thread.h"
35 #include "stereo_panner.h"
36 #include "timers.h"
37 #include "mono_panner.h"
38 #include "ui_config.h"
39
40 #include "pbd/i18n.h"
41
42 using namespace std;
43 using namespace ARDOUR;
44 using namespace PBD;
45 using namespace Gtkmm2ext;
46 using namespace Gtk;
47
48 PannerUI::PannerUI (Session* s)
49         : _current_nouts (-1)
50         , _current_nins (-1)
51         , _current_uri ("")
52         , _send_mode (false)
53         , pan_automation_state_button ("")
54         , _panner_list()
55 {
56         set_session (s);
57
58         ignore_toggle = false;
59         pan_menu = 0;
60         pan_astate_menu = 0;
61         pan_astyle_menu = 0;
62         in_pan_update = false;
63         _stereo_panner = 0;
64         _mono_panner = 0;
65         _ignore_width_change = false;
66         _ignore_position_change = false;
67
68         pan_automation_state_button.set_name ("MixerAutomationPlaybackButton");
69
70         ArdourWidgets::set_tooltip (pan_automation_state_button, _("Pan automation mode"));
71
72         //set_size_request_to_display_given_text (pan_automation_state_button, X_("O"), 2, 2);
73
74         pan_automation_state_button.unset_flags (Gtk::CAN_FOCUS);
75
76         pan_automation_state_button.signal_button_press_event().connect (sigc::mem_fun(*this, &PannerUI::pan_automation_state_button_event), false);
77
78         pan_vbox.set_spacing (2);
79         pack_start (pan_vbox, true, true);
80
81         twod_panner = 0;
82         big_window = 0;
83
84         set_width(Narrow);
85 }
86
87 void
88 PannerUI::set_panner (boost::shared_ptr<PannerShell> ps, boost::shared_ptr<Panner> p)
89 {
90         /* note that the panshell might not change here (i.e. ps == _panshell)
91          */
92
93         connections.drop_connections ();
94
95         delete pan_astyle_menu;
96         pan_astyle_menu = 0;
97
98         delete pan_astate_menu;
99         pan_astate_menu = 0;
100
101         _panshell = ps;
102         _panner = p;
103
104         delete twod_panner;
105         twod_panner = 0;
106
107         delete _stereo_panner;
108         _stereo_panner = 0;
109
110         delete _mono_panner;
111         _mono_panner = 0;
112
113         if (!_panner) {
114                 return;
115         }
116
117         _panshell->Changed.connect (connections, invalidator (*this), boost::bind (&PannerUI::panshell_changed, this), gui_context());
118
119         /* new panner object, force complete reset of panner GUI
120          */
121
122         _current_nouts = 0;
123         _current_nins = 0;
124
125         setup_pan ();
126         update_pan_sensitive ();
127         pan_automation_state_changed ();
128 }
129
130 void
131 PannerUI::build_astate_menu ()
132 {
133         using namespace Menu_Helpers;
134
135         if (pan_astate_menu == 0) {
136                 pan_astate_menu = new Menu;
137                 pan_astate_menu->set_name ("ArdourContextMenu");
138         } else {
139                 pan_astate_menu->items().clear ();
140         }
141
142         /** TRANSLATORS: this is `Manual' in the sense of automation not being played,
143             so that changes to pan must be done by hand.
144         */
145         pan_astate_menu->items().push_back (MenuElem (S_("Automation|Manual"), sigc::bind (
146                         sigc::mem_fun (_panner.get(), &Panner::set_automation_state),
147                         (AutoState) ARDOUR::Off)));
148         pan_astate_menu->items().push_back (MenuElem (_("Play"), sigc::bind (
149                         sigc::mem_fun (_panner.get(), &Panner::set_automation_state),
150                         (AutoState) Play)));
151         pan_astate_menu->items().push_back (MenuElem (_("Write"), sigc::bind (
152                         sigc::mem_fun (_panner.get(), &Panner::set_automation_state),
153                         (AutoState) Write)));
154         pan_astate_menu->items().push_back (MenuElem (_("Touch"), sigc::bind (
155                         sigc::mem_fun (_panner.get(), &Panner::set_automation_state),
156                         (AutoState) Touch)));
157
158 }
159
160 void
161 PannerUI::build_astyle_menu ()
162 {
163         using namespace Menu_Helpers;
164
165         if (pan_astyle_menu == 0) {
166                 pan_astyle_menu = new Menu;
167                 pan_astyle_menu->set_name ("ArdourContextMenu");
168         } else {
169                 pan_astyle_menu->items().clear();
170         }
171
172         pan_astyle_menu->items().push_back (MenuElem (_("Trim")));
173         pan_astyle_menu->items().push_back (MenuElem (_("Abs")));
174 }
175
176 void
177 PannerUI::on_size_allocate (Allocation& a)
178 {
179         HBox::on_size_allocate (a);
180 }
181
182 void
183 PannerUI::set_width (Width w)
184 {
185         _width = w;
186 }
187
188 PannerUI::~PannerUI ()
189 {
190         delete twod_panner;
191         delete big_window;
192         delete pan_menu;
193         delete pan_astyle_menu;
194         delete pan_astate_menu;
195         delete _stereo_panner;
196         delete _mono_panner;
197 }
198
199 void
200 PannerUI::panshell_changed ()
201 {
202         set_panner (_panshell, _panshell->panner());
203         setup_pan ();
204 }
205
206 void
207 PannerUI::setup_pan ()
208 {
209         int const nouts = _panner ? _panner->out().n_audio() : -1;
210         int const nins = _panner ? _panner->in().n_audio() : -1;
211
212         if (nouts == _current_nouts
213                         && nins == _current_nins
214                         && _current_uri == _panshell->panner_gui_uri()
215                         )
216         {
217                 return;
218         }
219
220         _current_nins = nins;
221         _current_nouts = nouts;
222         _current_uri = _panshell->panner_gui_uri();
223
224         container_clear (pan_vbox);
225
226         delete twod_panner;
227         twod_panner = 0;
228         delete _stereo_panner;
229         _stereo_panner = 0;
230         delete _mono_panner;
231         _mono_panner = 0;
232
233         if (!_panner) {
234                 delete big_window;
235                 big_window = 0;
236                 return;
237         }
238
239         const float scale = std::max (1.f, UIConfiguration::instance().get_ui_scale());
240
241         if (_current_uri == "http://ardour.org/plugin/panner_2in2out#ui")
242         {
243                 delete big_window;
244                 big_window = 0;
245
246                 boost::shared_ptr<Pannable> pannable = _panner->pannable();
247
248                 _stereo_panner = new StereoPanner (_panshell);
249                 _stereo_panner->set_size_request (-1, 5 * ceilf(7.f * scale));
250                 _stereo_panner->set_send_drawing_mode (_send_mode);
251                 pan_vbox.pack_start (*_stereo_panner, false, false);
252
253                 boost::shared_ptr<AutomationControl> ac;
254
255                 ac = pannable->pan_azimuth_control;
256                 _stereo_panner->StartPositionGesture.connect (sigc::bind (sigc::mem_fun (*this, &PannerUI::start_touch),
257                                         boost::weak_ptr<AutomationControl> (ac)));
258                 _stereo_panner->StopPositionGesture.connect (sigc::bind (sigc::mem_fun (*this, &PannerUI::stop_touch),
259                                         boost::weak_ptr<AutomationControl>(ac)));
260
261                 ac = pannable->pan_width_control;
262                 _stereo_panner->StartWidthGesture.connect (sigc::bind (sigc::mem_fun (*this, &PannerUI::start_touch),
263                                         boost::weak_ptr<AutomationControl> (ac)));
264                 _stereo_panner->StopWidthGesture.connect (sigc::bind (sigc::mem_fun (*this, &PannerUI::stop_touch),
265                                         boost::weak_ptr<AutomationControl>(ac)));
266                 _stereo_panner->signal_button_release_event().connect (sigc::mem_fun(*this, &PannerUI::pan_button_event));
267         }
268         else if (_current_uri == "http://ardour.org/plugin/panner_1in2out#ui"
269                         || _current_uri == "http://ardour.org/plugin/panner_balance#ui")
270         {
271                 delete big_window;
272                 big_window = 0;
273                 boost::shared_ptr<Pannable> pannable = _panner->pannable();
274                 boost::shared_ptr<AutomationControl> ac = pannable->pan_azimuth_control;
275
276                 _mono_panner = new MonoPanner (_panshell);
277
278                 _mono_panner->StartGesture.connect (sigc::bind (sigc::mem_fun (*this, &PannerUI::start_touch),
279                                         boost::weak_ptr<AutomationControl> (ac)));
280                 _mono_panner->StopGesture.connect (sigc::bind (sigc::mem_fun (*this, &PannerUI::stop_touch),
281                                         boost::weak_ptr<AutomationControl>(ac)));
282
283                 _mono_panner->signal_button_release_event().connect (sigc::mem_fun(*this, &PannerUI::pan_button_event));
284
285                 _mono_panner->set_size_request (-1, 5 * ceilf(7.f * scale));
286                 _mono_panner->set_send_drawing_mode (_send_mode);
287
288                 update_pan_sensitive ();
289                 pan_vbox.pack_start (*_mono_panner, false, false);
290         }
291         else if (_current_uri == "http://ardour.org/plugin/panner_vbap#ui")
292         {
293                 if (!twod_panner) {
294                         twod_panner = new Panner2d (_panshell, rintf(61.f * scale));
295                         twod_panner->set_name ("MixerPanZone");
296                         twod_panner->show ();
297                         twod_panner->signal_button_press_event().connect (sigc::mem_fun(*this, &PannerUI::pan_button_event), false);
298                 }
299
300                 update_pan_sensitive ();
301                 twod_panner->reset (nins);
302                 if (big_window) {
303                         big_window->reset (nins);
304                 }
305                 twod_panner->set_size_request (-1, rintf(61.f * scale));
306                 twod_panner->set_send_drawing_mode (_send_mode);
307
308                 /* and finally, add it to the panner frame */
309
310                 pan_vbox.pack_start (*twod_panner, false, false);
311         }
312         else
313         {
314                 /* stick something into the panning viewport so that it redraws */
315                 EventBox* eb = manage (new EventBox());
316                 pan_vbox.pack_start (*eb, false, false);
317
318                 delete big_window;
319                 big_window = 0;
320         }
321
322         pan_vbox.show_all ();
323 }
324
325 void
326 PannerUI::set_send_drawing_mode (bool onoff)
327 {
328         if (_stereo_panner) {
329                 _stereo_panner->set_send_drawing_mode (onoff);
330         } else if (_mono_panner) {
331                 _mono_panner->set_send_drawing_mode (onoff);
332         } else if (twod_panner) {
333                 twod_panner->set_send_drawing_mode (onoff);
334         }
335         _send_mode = onoff;
336 }
337
338 void
339 PannerUI::start_touch (boost::weak_ptr<AutomationControl> wac)
340 {
341         boost::shared_ptr<AutomationControl> ac = wac.lock();
342         if (!ac) {
343                 return;
344         }
345         ac->start_touch (ac->session().transport_frame());
346 }
347
348 void
349 PannerUI::stop_touch (boost::weak_ptr<AutomationControl> wac)
350 {
351         boost::shared_ptr<AutomationControl> ac = wac.lock();
352         if (!ac) {
353                 return;
354         }
355         ac->stop_touch (ac->session().transport_frame());
356 }
357
358 bool
359 PannerUI::pan_button_event (GdkEventButton* ev)
360 {
361         switch (ev->button) {
362         case 1:
363                 if (twod_panner && ev->type == GDK_2BUTTON_PRESS) {
364                         if (!big_window) {
365                                 big_window = new Panner2dWindow (_panshell, 400, _panner->in().n_audio());
366                         }
367                         big_window->show ();
368                         return true;
369                 }
370                 break;
371
372         case 3:
373                 if (pan_menu == 0) {
374                         pan_menu = manage (new Menu);
375                         pan_menu->set_name ("ArdourContextMenu");
376                 }
377                 build_pan_menu ();
378                 pan_menu->popup (1, ev->time);
379                 return true;
380                 break;
381         default:
382                 return false;
383         }
384
385         return false; // what's wrong with gcc?
386 }
387
388 void
389 PannerUI::build_pan_menu ()
390 {
391         using namespace Menu_Helpers;
392         MenuList& items (pan_menu->items());
393
394         items.clear ();
395
396         items.push_back (CheckMenuElem (_("Bypass"), sigc::mem_fun(*this, &PannerUI::pan_bypass_toggle)));
397         bypass_menu_item = static_cast<Gtk::CheckMenuItem*> (&items.back());
398
399         /* set state first, connect second */
400
401         bypass_menu_item->set_active (_panshell->bypassed());
402         bypass_menu_item->signal_toggled().connect (sigc::mem_fun(*this, &PannerUI::pan_bypass_toggle));
403
404         if (!_panshell->bypassed()) {
405                 items.push_back (MenuElem (_("Reset"), sigc::mem_fun (*this, &PannerUI::pan_reset)));
406                 items.push_back (MenuElem (_("Edit..."), sigc::mem_fun (*this, &PannerUI::pan_edit)));
407         }
408
409         if (_panner_list.size() > 1 && !_panshell->bypassed()) {
410                 RadioMenuItem::Group group;
411                 items.push_back (SeparatorElem());
412
413                 _suspend_menu_callbacks = true;
414                 for (std::map<std::string,std::string>::const_iterator p = _panner_list.begin(); p != _panner_list.end(); ++p) {
415                         items.push_back (RadioMenuElem (group, p->second,
416                                                 sigc::bind(sigc::mem_fun (*this, &PannerUI::pan_set_custom_type), p->first)));
417                         RadioMenuItem* i = dynamic_cast<RadioMenuItem *> (&items.back ());
418                         i->set_active (_panshell->current_panner_uri() == p->first);
419                 }
420                 _suspend_menu_callbacks = false;
421         }
422 }
423
424 void
425 PannerUI::pan_bypass_toggle ()
426 {
427         if (bypass_menu_item && (_panshell->bypassed() != bypass_menu_item->get_active())) {
428                 _panshell->set_bypassed (!_panshell->bypassed());
429         }
430 }
431
432 void
433 PannerUI::pan_edit ()
434 {
435         if (_panshell->bypassed()) {
436                 return;
437         }
438         if (_mono_panner) {
439                 _mono_panner->edit ();
440         } else if (_stereo_panner) {
441                 _stereo_panner->edit ();
442         } else if (twod_panner) {
443                 if (!big_window) {
444                         big_window = new Panner2dWindow (_panshell, 400, _panner->in().n_audio());
445                 }
446                 big_window->show ();
447         }
448 }
449
450 void
451 PannerUI::pan_reset ()
452 {
453         if (_panshell->bypassed()) {
454                 return;
455         }
456         _panner->reset ();
457 }
458
459 void
460 PannerUI::pan_set_custom_type (std::string uri) {
461         if (_suspend_menu_callbacks) return;
462         _panshell->select_panner_by_uri(uri);
463 }
464
465 void
466 PannerUI::effective_pan_display ()
467 {
468         if (_stereo_panner) {
469                 _stereo_panner->queue_draw ();
470         } else if (_mono_panner) {
471                 _mono_panner->queue_draw ();
472         } else if (twod_panner) {
473                 twod_panner->queue_draw ();
474         }
475 }
476
477 void
478 PannerUI::update_pan_sensitive ()
479 {
480         bool const sensitive = !(_panner->pannable()->automation_state() & Play);
481
482         pan_vbox.set_sensitive (sensitive);
483
484         if (big_window) {
485                 big_window->set_sensitive (sensitive);
486         }
487 }
488
489 gint
490 PannerUI::pan_automation_state_button_event (GdkEventButton *ev)
491 {
492         using namespace Menu_Helpers;
493
494         if (ev->type == GDK_BUTTON_RELEASE) {
495                 return TRUE;
496         }
497
498         switch (ev->button) {
499         case 1:
500                 if (pan_astate_menu == 0) {
501                         build_astate_menu ();
502                 }
503                 pan_astate_menu->popup (1, ev->time);
504                 break;
505         default:
506                 break;
507         }
508
509         return TRUE;
510 }
511
512 void
513 PannerUI::pan_automation_state_changed ()
514 {
515         boost::shared_ptr<Pannable> pannable (_panner->pannable());
516
517         switch (_width) {
518         case Wide:
519                 pan_automation_state_button.set_label (astate_string(pannable->automation_state()));
520                 break;
521         case Narrow:
522                 pan_automation_state_button.set_label (short_astate_string(pannable->automation_state()));
523                 break;
524         }
525
526         bool x = (pannable->automation_state() != ARDOUR::Off);
527
528         if (pan_automation_state_button.get_active() != x) {
529                 ignore_toggle = true;
530                 pan_automation_state_button.set_active (x);
531                 ignore_toggle = false;
532         }
533
534         update_pan_sensitive ();
535
536         /* start watching automation so that things move */
537
538         pan_watching.disconnect();
539
540         if (x) {
541                 pan_watching = Timers::rapid_connect (sigc::mem_fun (*this, &PannerUI::effective_pan_display));
542         }
543 }
544
545 string
546 PannerUI::astate_string (AutoState state)
547 {
548         return _astate_string (state, false);
549 }
550
551 string
552 PannerUI::short_astate_string (AutoState state)
553 {
554         return _astate_string (state, true);
555 }
556
557 string
558 PannerUI::_astate_string (AutoState state, bool shrt)
559 {
560         string sstr;
561
562         switch (state) {
563         case ARDOUR::Off:
564                 sstr = (shrt ? "M" : S_("Manual|M"));
565                 break;
566         case Play:
567                 sstr = (shrt ? "P" : S_("Play|P"));
568                 break;
569         case Touch:
570                 sstr = (shrt ? "T" : S_("Touch|T"));
571                 break;
572         case Write:
573                 sstr = (shrt ? "W" : S_("Write|W"));
574                 break;
575         }
576
577         return sstr;
578 }
579
580 void
581 PannerUI::show_width ()
582 {
583 }
584
585 void
586 PannerUI::width_adjusted ()
587 {
588 }
589
590 void
591 PannerUI::show_position ()
592 {
593 }
594
595 void
596 PannerUI::position_adjusted ()
597 {
598 }
599
600 void
601 PannerUI::set_available_panners(std::map<std::string,std::string> p)
602 {
603         _panner_list = p;
604 }