2 Copyright (C) 2002 Paul Davis
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.
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.
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.
21 #include <gtkmm2ext/gtk_ui.h>
22 #include <gtkmm2ext/stop_signal.h>
23 #include <gtkmm2ext/choice.h>
24 #include <gtkmm2ext/bindable_button.h>
25 #include <gtkmm2ext/doi.h>
27 #include <ardour/route_group.h>
33 #include "gui_thread.h"
35 #include <ardour/route.h>
36 #include <ardour/audio_track.h>
37 #include <ardour/diskstream.h>
42 using namespace Gtkmm2ext;
43 using namespace ARDOUR;
46 RouteUI::RouteUI (ARDOUR::Route& rt, ARDOUR::Session& sess, const char* m_name,
47 const char* s_name, const char* r_name)
57 remote_control_menu = 0;
58 ignore_toggle = false;
59 wait_for_release = false;
60 route_active_menu_item = 0;
62 if (set_color_from_route()) {
63 set_color (unique_random_color());
66 _route.GoingAway.connect (mem_fun (*this, &RouteUI::route_removed));
67 _route.active_changed.connect (mem_fun (*this, &RouteUI::route_active_changed));
69 mute_button = manage (new BindableToggleButton (& _route.midi_mute_control(), m_name ));
70 mute_button->set_bind_button_state (2, GDK_CONTROL_MASK);
71 solo_button = manage (new BindableToggleButton (& _route.midi_solo_control(), s_name ));
72 solo_button->set_bind_button_state (2, GDK_CONTROL_MASK);
74 if (is_audio_track()) {
75 AudioTrack* at = dynamic_cast<AudioTrack*>(&_route);
77 get_diskstream()->record_enable_changed.connect (mem_fun (*this, &RouteUI::route_rec_enable_changed));
79 _session.RecordEnabled.connect (mem_fun (*this, &RouteUI::session_rec_enable_changed));
80 _session.RecordDisabled.connect (mem_fun (*this, &RouteUI::session_rec_enable_changed));
82 rec_enable_button = manage (new BindableToggleButton (& at->midi_rec_enable_control(), r_name ));
83 rec_enable_button->set_bind_button_state (2, GDK_CONTROL_MASK);
86 rec_enable_button = manage (new BindableToggleButton (0, r_name ));
89 mute_button->unset_flags (Gtk::CAN_FOCUS);
90 solo_button->unset_flags (Gtk::CAN_FOCUS);
91 rec_enable_button->unset_flags (Gtk::CAN_FOCUS);
93 /* map the current state */
95 update_rec_display ();
105 RouteUI::mute_press(GdkEventButton* ev)
107 if (!ignore_toggle) {
109 if (Keyboard::is_context_menu_event (ev)) {
115 mute_menu->popup(0,0);
119 if (ev->button == 2) {
120 // ctrl-button2 click is the midi binding click
121 // button2-click is "momentary"
123 if (!Keyboard::modifier_state_equals (ev->state, Keyboard::ModifierMask (Keyboard::Control))) {
124 wait_for_release = true;
128 if (ev->button == 1 || ev->button == 2) {
130 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ModifierMask (Keyboard::Control|Keyboard::Shift))) {
132 /* ctrl-shift-click applies change to all routes */
134 _session.begin_reversible_command (_("mute change"));
135 _session.add_undo (_session.global_mute_memento(this));
136 _session.set_all_mute (!_route.muted());
137 _session.add_redo_no_execute (_session.global_mute_memento(this));
138 _session.commit_reversible_command ();
140 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::Control)) {
142 /* ctrl-click applies change to the mix group.
143 ctrl-button2 is MIDI learn.
146 if (ev->button == 1) {
147 set_mix_group_mute (_route, !_route.muted());
152 /* plain click applies change to this route */
154 reversibly_apply_route_boolean ("mute change", &Route::set_mute, !_route.muted(), this);
165 RouteUI::mute_release(GdkEventButton* ev)
167 if (!ignore_toggle) {
168 if (wait_for_release){
169 wait_for_release = false;
171 // because the press was the last undoable thing we did
179 RouteUI::solo_press(GdkEventButton* ev)
181 if (!ignore_toggle) {
183 if (Keyboard::is_context_menu_event (ev)) {
185 if (solo_menu == 0) {
189 solo_menu->popup (1, 0);
193 if (ev->button == 2) {
195 // ctrl-button2 click is the midi binding click
196 // button2-click is "momentary"
198 if (!Keyboard::modifier_state_equals (ev->state, Keyboard::ModifierMask (Keyboard::Control))) {
199 wait_for_release = true;
203 if (ev->button == 1 || ev->button == 2) {
205 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ModifierMask (Keyboard::Control|Keyboard::Shift))) {
207 /* ctrl-shift-click applies change to all routes */
209 _session.begin_reversible_command (_("solo change"));
210 _session.add_undo (_session.global_solo_memento(this));
211 _session.set_all_solo (!_route.soloed());
212 _session.add_redo_no_execute (_session.global_solo_memento(this));
213 _session.commit_reversible_command ();
215 } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::ModifierMask (Keyboard::Control|Keyboard::Alt))) {
217 // ctrl-alt-click: exclusively solo this track, not a toggle */
219 _session.begin_reversible_command (_("solo change"));
220 _session.add_undo (_session.global_solo_memento(this));
221 _session.set_all_solo (false);
222 _route.set_solo (true, this);
223 _session.add_redo_no_execute (_session.global_solo_memento(this));
224 _session.commit_reversible_command ();
226 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::Shift)) {
228 // shift-click: set this route to solo safe
230 _route.set_solo_safe (!_route.solo_safe(), this);
231 wait_for_release = false;
233 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::Control)) {
235 /* ctrl-click: solo mix group.
236 ctrl-button2 is MIDI learn.
239 if (ev->button == 1) {
240 set_mix_group_solo (_route, !_route.soloed());
245 /* click: solo this route */
247 reversibly_apply_route_boolean ("solo change", &Route::set_solo, !_route.soloed(), this);
253 return stop_signal (*solo_button, "button-press-event");
257 RouteUI::solo_release(GdkEventButton* ev)
260 if (wait_for_release){
261 wait_for_release = false;
263 // because the press was the last undoable thing we did
267 stop_signal (*solo_button, "button-release-event");
274 RouteUI::rec_enable_press(GdkEventButton* ev)
276 if (!ignore_toggle && is_audio_track()) {
278 if (ev->button == 2 && Keyboard::modifier_state_equals (ev->state, Keyboard::Control)) {
279 // do nothing on midi bind event
281 else if (Keyboard::modifier_state_equals (ev->state, Keyboard::ModifierMask (Keyboard::Control|Keyboard::Shift))) {
283 _session.begin_reversible_command (_("rec-enable change"));
284 _session.add_undo (_session.global_record_enable_memento(this));
286 if (rec_enable_button->get_active()) {
287 _session.record_disenable_all ();
289 _session.record_enable_all ();
292 _session.add_redo_no_execute (_session.global_record_enable_memento(this));
293 _session.commit_reversible_command ();
295 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::Control)) {
297 set_mix_group_rec_enable (_route, !_route.record_enabled());
301 reversibly_apply_audio_track_boolean ("rec-enable change", &AudioTrack::set_record_enable, !audio_track()->record_enabled(), this);
303 ignore_toggle = true;
304 rec_enable_button->set_active(audio_track()->record_enabled());
305 ignore_toggle = false;
308 stop_signal (*rec_enable_button, "button-press-event");
315 RouteUI::solo_changed(void* src)
317 Gtkmm2ext::UI::instance()->call_slot (mem_fun (*this, &RouteUI::update_solo_display));
321 RouteUI::update_solo_display ()
325 if (solo_button->get_active() != (x = _route.soloed())){
326 ignore_toggle = true;
327 solo_button->set_active(x);
328 ignore_toggle = false;
333 if (_route.solo_safe()){
334 solo_button->set_name(safe_solo_button_name());
336 solo_button->set_name(solo_button_name());
341 RouteUI::mute_changed(void* src)
343 Gtkmm2ext::UI::instance()->call_slot (mem_fun (*this, &RouteUI::update_mute_display));
347 RouteUI::update_mute_display ()
351 if (mute_button->get_active() != (x = _route.muted())){
352 ignore_toggle = true;
353 mute_button->set_active(x);
354 ignore_toggle = false;
359 RouteUI::route_rec_enable_changed (void *src)
361 Gtkmm2ext::UI::instance()->call_slot (mem_fun (*this, &RouteUI::update_rec_display));
365 RouteUI::session_rec_enable_changed ()
367 Gtkmm2ext::UI::instance()->call_slot (mem_fun (*this, &RouteUI::update_rec_display));
371 RouteUI::update_rec_display ()
373 bool model = _route.record_enabled();
374 bool view = rec_enable_button->get_active();
376 /* first make sure the button's "depressed" visual
381 ignore_toggle = true;
382 rec_enable_button->set_active (model);
383 ignore_toggle = false;
386 /* now make sure its color state is correct */
390 switch (_session.record_status ()) {
391 case Session::Disabled:
392 case Session::Enabled:
393 if (rec_enable_button->get_state() != Gtk::STATE_ACTIVE) {
394 rec_enable_button->set_state (Gtk::STATE_ACTIVE);
398 case Session::Recording:
399 if (rec_enable_button->get_state() != Gtk::STATE_SELECTED) {
400 rec_enable_button->set_state (Gtk::STATE_SELECTED);
406 if (rec_enable_button->get_state() != Gtk::STATE_NORMAL) {
407 rec_enable_button->set_state (Gtk::STATE_NORMAL);
413 RouteUI::build_remote_control_menu ()
415 remote_control_menu = manage (new Menu);
416 refresh_remote_control_menu ();
420 RouteUI::refresh_remote_control_menu ()
422 using namespace Menu_Helpers;
424 RadioMenuItem::Group rc_group;
425 CheckMenuItem* rc_active;
426 uint32_t limit = _session.ntracks();
429 MenuList& rc_items = remote_control_menu->items();
432 /* note that this menu list starts at zero, not 1, because zero
433 is a valid, if useless, ID.
436 limit += 4; /* leave some breathing room */
438 for (uint32_t i = 0; i < limit; ++i) {
439 snprintf (buf, sizeof (buf), "%u", i);
440 rc_items.push_back (RadioMenuElem (rc_group, buf));
441 rc_active = dynamic_cast<RadioMenuItem*>(&rc_items.back());
442 if (_route.remote_control_id() == i) {
443 rc_active = dynamic_cast<CheckMenuItem*> (&rc_items.back());
444 rc_active->set_active ();
446 rc_active->signal_activate().connect (bind (mem_fun (*this, &RouteUI::set_remote_control_id), i, rc_active));
451 RouteUI::set_remote_control_id (uint32_t id, CheckMenuItem* item)
453 /* this is called when the radio menu item is toggled, and so
454 is actually invoked twice per menu selection. we only
455 care about the invocation for the item that was being
459 if (item->get_active()) {
460 _route.set_remote_control_id (id);
465 RouteUI::build_solo_menu (void)
467 using namespace Menu_Helpers;
469 solo_menu = new Menu;
470 solo_menu->set_name ("ArdourContextMenu");
471 MenuList& items = solo_menu->items();
472 CheckMenuItem* check;
474 check = new CheckMenuItem(_("Solo-safe"));
475 check->set_active (_route.solo_safe());
476 check->signal_toggled().connect (bind (mem_fun (*this, &RouteUI::toggle_solo_safe), check));
477 _route.solo_safe_changed.connect(bind (mem_fun (*this, &RouteUI::solo_safe_toggle), check));
478 items.push_back (CheckMenuElem(*check));
481 items.push_back (SeparatorElem());
482 items.push_back (MenuElem (_("MIDI Bind"), mem_fun (*mute_button, &BindableToggleButton::midi_learn)));
487 RouteUI::build_mute_menu(void)
489 using namespace Menu_Helpers;
491 mute_menu = new Menu;
492 mute_menu->set_name ("ArdourContextMenu");
493 MenuList& items = mute_menu->items();
494 CheckMenuItem* check;
496 check = new CheckMenuItem(_("Pre Fader"));
497 init_mute_menu(PRE_FADER, check);
498 check->signal_toggled().connect(bind (mem_fun (*this, &RouteUI::toggle_mute_menu), PRE_FADER, check));
499 _route.pre_fader_changed.connect(bind (mem_fun (*this, &RouteUI::pre_fader_toggle), check));
500 items.push_back (CheckMenuElem(*check));
503 check = new CheckMenuItem(_("Post Fader"));
504 init_mute_menu(POST_FADER, check);
505 check->signal_toggled().connect(bind (mem_fun (*this, &RouteUI::toggle_mute_menu), POST_FADER, check));
506 _route.post_fader_changed.connect(bind (mem_fun (*this, &RouteUI::post_fader_toggle), check));
507 items.push_back (CheckMenuElem(*check));
510 check = new CheckMenuItem(_("Control Outs"));
511 init_mute_menu(CONTROL_OUTS, check);
512 check->signal_toggled().connect(bind (mem_fun (*this, &RouteUI::toggle_mute_menu), CONTROL_OUTS, check));
513 _route.control_outs_changed.connect(bind (mem_fun (*this, &RouteUI::control_outs_toggle), check));
514 items.push_back (CheckMenuElem(*check));
517 check = new CheckMenuItem(_("Main Outs"));
518 init_mute_menu(MAIN_OUTS, check);
519 check->signal_toggled().connect(bind (mem_fun (*this, &RouteUI::toggle_mute_menu), MAIN_OUTS, check));
520 _route.main_outs_changed.connect(bind (mem_fun (*this, &RouteUI::main_outs_toggle), check));
521 items.push_back (CheckMenuElem(*check));
524 items.push_back (SeparatorElem());
525 items.push_back (MenuElem (_("MIDI Bind"), mem_fun (*mute_button, &BindableToggleButton::midi_learn)));
529 RouteUI::init_mute_menu(mute_type type, CheckMenuItem* check)
531 if (_route.get_mute_config (type)) {
532 check->set_active (true);
537 RouteUI::toggle_mute_menu(mute_type type, Gtk::CheckMenuItem* check)
539 _route.set_mute_config(type, check->get_active(), this);
543 RouteUI::toggle_solo_safe (Gtk::CheckMenuItem* check)
545 _route.set_solo_safe (check->get_active(), this);
549 RouteUI::set_mix_group_solo(Route& route, bool yn)
551 RouteGroup* mix_group;
553 if((mix_group = route.mix_group()) != 0){
554 _session.begin_reversible_command (_("mix group solo change"));
555 _session.add_undo (_session.global_solo_memento (this));
556 mix_group->apply(&Route::set_solo, yn, this);
557 _session.add_redo_no_execute (_session.global_solo_memento(this));
558 _session.commit_reversible_command ();
560 reversibly_apply_route_boolean ("solo change", &Route::set_solo, !route.soloed(), this);
565 RouteUI::reversibly_apply_route_boolean (string name, void (Route::*func)(bool, void *), bool yn, void *arg)
567 _session.begin_reversible_command (name);
568 _session.add_undo (bind (mem_fun (_route, func), !yn, (void *) arg));
569 _session.add_redo (bind (mem_fun (_route, func), yn, (void *) arg));
570 _session.commit_reversible_command ();
574 RouteUI::reversibly_apply_audio_track_boolean (string name, void (AudioTrack::*func)(bool, void *), bool yn, void *arg)
576 _session.begin_reversible_command (name);
577 _session.add_undo (bind (mem_fun (*audio_track(), func), !yn, (void *) arg));
578 _session.add_redo (bind (mem_fun (*audio_track(), func), yn, (void *) arg));
579 _session.commit_reversible_command ();
583 RouteUI::set_mix_group_mute(Route& route, bool yn)
585 RouteGroup* mix_group;
587 if((mix_group = route.mix_group()) != 0){
588 _session.begin_reversible_command (_("mix group mute change"));
589 _session.add_undo (_session.global_mute_memento (this));
590 mix_group->apply(&Route::set_mute, yn, this);
591 _session.add_redo_no_execute (_session.global_mute_memento(this));
592 _session.commit_reversible_command ();
594 reversibly_apply_route_boolean ("mute change", &Route::set_mute, !route.muted(), this);
599 RouteUI::set_mix_group_rec_enable(Route& route, bool yn)
601 RouteGroup* mix_group;
603 if((mix_group = route.mix_group()) != 0){
604 _session.begin_reversible_command (_("mix group rec-enable change"));
605 _session.add_undo (_session.global_record_enable_memento (this));
606 mix_group->apply (&Route::set_record_enable, yn, this);
607 _session.add_redo_no_execute (_session.global_record_enable_memento(this));
608 _session.commit_reversible_command ();
610 reversibly_apply_route_boolean ("rec-enable change", &Route::set_record_enable, !_route.record_enabled(), this);
616 RouteUI::choose_color()
621 color = Gtkmm2ext::UI::instance()->get_color (_("ardour: color selection"), picked, &_color);
631 RouteUI::set_color (const Gdk::Color & c)
638 snprintf (buf, sizeof (buf), "%d:%d:%d", c.get_red(), c.get_green(), c.get_blue());
639 xml_node->add_property ("color", buf);
641 _route.gui_changed ("color", (void *) 0); /* EMIT_SIGNAL */
646 RouteUI::ensure_xml_node ()
649 if ((xml_node = _route.extra_xml ("GUI")) == 0) {
650 xml_node = new XMLNode ("GUI");
651 _route.add_extra_xml (*xml_node);
657 RouteUI::get_child_xml_node (const string & childname)
664 if ((child = find_named_node (*xml_node, childname)) == 0) {
665 child = new XMLNode (childname);
666 xml_node->add_child_nocopy (*child);
673 RouteUI::set_color_from_route ()
677 RouteUI::ensure_xml_node ();
679 if ((prop = xml_node->property ("color")) != 0) {
681 sscanf (prop->value().c_str(), "%d:%d:%d", &r, &g, &b);
691 RouteUI::remove_this_route ()
693 vector<string> choices;
696 if (is_audio_track()) {
697 prompt = string_compose (_("Do you really want to remove track \"%1\" ?\nYou may also lose the playlist used by this track.\n(cannot be undone)"), _route.name());
699 prompt = string_compose (_("Do you really want to remove bus \"%1\" ?\n(cannot be undone)"), _route.name());
702 choices.push_back (_("Yes, remove it."));
703 choices.push_back (_("No, do nothing."));
705 Choice prompter (prompt, choices);
707 prompter.chosen.connect(sigc::ptr_fun(Gtk::Main::quit));
708 prompter.show_all ();
712 if (prompter.get_choice() == 0) {
713 Glib::signal_idle().connect (bind (sigc::ptr_fun (&RouteUI::idle_remove_this_route), this));
718 RouteUI::idle_remove_this_route (RouteUI *rui)
720 rui->_session.remove_route (rui->_route);
725 RouteUI::route_removed ()
727 ENSURE_GUI_THREAD(mem_fun (*this, &RouteUI::route_removed));
733 RouteUI::route_rename ()
735 ArdourPrompter name_prompter (true);
737 name_prompter.set_prompt (_("new name: "));
738 name_prompter.set_initial_text (_route.name());
739 name_prompter.show_all ();
741 switch (name_prompter.run ()) {
743 case Gtk::RESPONSE_ACCEPT:
744 name_prompter.get_result (result);
745 if (result.length()) {
746 _route.set_name (result, this);
756 RouteUI::name_changed (void *src)
758 ENSURE_GUI_THREAD(bind (mem_fun (*this, &RouteUI::name_changed), src));
760 name_label.set_text (_route.name());
764 RouteUI::toggle_route_active ()
768 if (route_active_menu_item) {
769 if (route_active_menu_item->get_active() != (yn = _route.active())) {
770 _route.set_active (!yn);
776 RouteUI::route_active_changed ()
778 if (route_active_menu_item) {
779 Gtkmm2ext::UI::instance()->call_slot (bind (mem_fun (*route_active_menu_item, &CheckMenuItem::set_active), _route.active()));
784 RouteUI::solo_safe_toggle(void* src, Gtk::CheckMenuItem* check)
786 bool yn = _route.solo_safe ();
788 if (check->get_active() != yn) {
789 check->set_active (yn);
793 RouteUI::pre_fader_toggle(void* src, Gtk::CheckMenuItem* check)
795 ENSURE_GUI_THREAD(bind (mem_fun (*this, &RouteUI::pre_fader_toggle), src, check));
797 bool yn = _route.get_mute_config(PRE_FADER);
798 if (check->get_active() != yn) {
799 check->set_active (yn);
804 RouteUI::post_fader_toggle(void* src, Gtk::CheckMenuItem* check)
806 ENSURE_GUI_THREAD(bind (mem_fun (*this, &RouteUI::post_fader_toggle), src, check));
808 bool yn = _route.get_mute_config(POST_FADER);
809 if (check->get_active() != yn) {
810 check->set_active (yn);
815 RouteUI::control_outs_toggle(void* src, Gtk::CheckMenuItem* check)
817 ENSURE_GUI_THREAD(bind (mem_fun (*this, &RouteUI::control_outs_toggle), src, check));
819 bool yn = _route.get_mute_config(CONTROL_OUTS);
820 if (check->get_active() != yn) {
821 check->set_active (yn);
826 RouteUI::main_outs_toggle(void* src, Gtk::CheckMenuItem* check)
828 ENSURE_GUI_THREAD(bind (mem_fun (*this, &RouteUI::main_outs_toggle), src, check));
830 bool yn = _route.get_mute_config(MAIN_OUTS);
831 if (check->get_active() != yn) {
832 check->set_active (yn);
837 RouteUI::disconnect_input ()
839 _route.disconnect_inputs (this);
843 RouteUI::disconnect_output ()
845 _route.disconnect_outputs (this);
849 RouteUI::is_audio_track () const
851 return dynamic_cast<AudioTrack*>(&_route) != 0;
855 RouteUI::get_diskstream () const
859 if ((at = dynamic_cast<AudioTrack*>(&_route)) != 0) {
860 return &at->disk_stream();
867 RouteUI::audio_track() const
869 return dynamic_cast<AudioTrack*>(&_route);
872 RouteUI::name() const
874 return _route.name();
878 RouteUI::map_frozen ()
880 ENSURE_GUI_THREAD (mem_fun (*this, &RouteUI::map_frozen));
882 AudioTrack* at = dynamic_cast<AudioTrack*>(&_route);
885 switch (at->freeze_state()) {
886 case AudioTrack::Frozen:
887 rec_enable_button->set_sensitive (false);
890 rec_enable_button->set_sensitive (true);