2 Copyright (C) 2002-2006 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/doi.h>
25 #include <gtkmm2ext/bindable_button.h>
27 #include <ardour/route_group.h>
28 #include <pbd/memento_command.h>
29 #include <pbd/stacktrace.h>
30 #include <pbd/shiva.h>
36 #include "gui_thread.h"
38 #include <ardour/route.h>
39 #include <ardour/audio_track.h>
40 #include <ardour/audio_diskstream.h>
45 using namespace Gtkmm2ext;
46 using namespace ARDOUR;
49 RouteUI::RouteUI (boost::shared_ptr<ARDOUR::Route> rt, ARDOUR::Session& sess, const char* m_name,
50 const char* s_name, const char* r_name)
60 remote_control_menu = 0;
61 ignore_toggle = false;
62 wait_for_release = false;
63 route_active_menu_item = 0;
65 if (set_color_from_route()) {
66 set_color (unique_random_color());
69 new PairedShiva<Route,RouteUI> (*_route, *this);
71 _route->active_changed.connect (mem_fun (*this, &RouteUI::route_active_changed));
73 mute_button = manage (new BindableToggleButton (_route->mute_control(), m_name ));
74 solo_button = manage (new BindableToggleButton (_route->solo_control(), s_name ));
76 mute_button->unset_flags (Gtk::CAN_FOCUS);
77 solo_button->unset_flags (Gtk::CAN_FOCUS);
79 _route->mute_changed.connect (mem_fun(*this, &RouteUI::mute_changed));
80 _route->solo_changed.connect (mem_fun(*this, &RouteUI::solo_changed));
81 _route->solo_safe_changed.connect (mem_fun(*this, &RouteUI::solo_changed));
83 update_solo_display ();
84 update_mute_display ();
87 boost::shared_ptr<Track> t = boost::dynamic_pointer_cast<Track>(_route);
89 t->diskstream()->RecordEnableChanged.connect (mem_fun (*this, &RouteUI::route_rec_enable_changed));
91 _session.RecordStateChanged.connect (mem_fun (*this, &RouteUI::session_rec_enable_changed));
93 rec_enable_button = manage (new BindableToggleButton (t->rec_enable_control(), r_name ));
95 rec_enable_button->unset_flags (Gtk::CAN_FOCUS);
97 update_rec_display ();
100 /* map the current state */
107 GoingAway (); /* EMIT SIGNAL */
112 RouteUI::mute_press(GdkEventButton* ev)
114 if (!ignore_toggle) {
116 if (Keyboard::is_context_menu_event (ev)) {
122 mute_menu->popup(0,0);
126 if (ev->button == 2) {
127 // ctrl-button2 click is the midi binding click
128 // button2-click is "momentary"
130 if (!Keyboard::modifier_state_equals (ev->state, Keyboard::ModifierMask (Keyboard::Control))) {
131 wait_for_release = true;
135 if (ev->button == 1 || ev->button == 2) {
137 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ModifierMask (Keyboard::Control|Keyboard::Shift))) {
139 /* ctrl-shift-click applies change to all routes */
141 _session.begin_reversible_command (_("mute change"));
142 Session::GlobalMuteStateCommand *cmd = new Session::GlobalMuteStateCommand(_session, this);
143 _session.set_all_mute (!_route->muted());
145 _session.add_command(cmd);
146 _session.commit_reversible_command ();
148 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::Control)) {
150 /* ctrl-click applies change to the mix group.
151 ctrl-button2 is MIDI learn.
154 if (ev->button == 1) {
155 set_mix_group_mute (_route, !_route->muted());
160 /* plain click applies change to this route */
162 reversibly_apply_route_boolean ("mute change", &Route::set_mute, !_route->muted(), this);
173 RouteUI::mute_release(GdkEventButton* ev)
175 if (!ignore_toggle) {
176 if (wait_for_release){
177 wait_for_release = false;
179 // because the press was the last undoable thing we did
187 RouteUI::solo_press(GdkEventButton* ev)
189 if (!ignore_toggle) {
191 if (Keyboard::is_context_menu_event (ev)) {
193 if (solo_menu == 0) {
197 solo_menu->popup (1, 0);
201 if (ev->button == 2) {
203 // ctrl-button2 click is the midi binding click
204 // button2-click is "momentary"
206 if (!Keyboard::modifier_state_equals (ev->state, Keyboard::ModifierMask (Keyboard::Control))) {
207 wait_for_release = true;
211 if (ev->button == 1 || ev->button == 2) {
213 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ModifierMask (Keyboard::Control|Keyboard::Shift))) {
215 /* ctrl-shift-click applies change to all routes */
217 _session.begin_reversible_command (_("solo change"));
218 Session::GlobalSoloStateCommand *cmd = new Session::GlobalSoloStateCommand(_session, this);
219 _session.set_all_solo (!_route->soloed());
221 _session.add_command (cmd);
222 _session.commit_reversible_command ();
224 } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::ModifierMask (Keyboard::Control|Keyboard::Alt))) {
226 // ctrl-alt-click: exclusively solo this track, not a toggle */
228 _session.begin_reversible_command (_("solo change"));
229 Session::GlobalSoloStateCommand *cmd = new Session::GlobalSoloStateCommand (_session, this);
230 _session.set_all_solo (false);
231 _route->set_solo (true, this);
233 _session.add_command(cmd);
234 _session.commit_reversible_command ();
236 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::Shift)) {
238 // shift-click: set this route to solo safe
240 _route->set_solo_safe (!_route->solo_safe(), this);
241 wait_for_release = false;
243 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::Control)) {
245 /* ctrl-click: solo mix group.
246 ctrl-button2 is MIDI learn.
249 if (ev->button == 1) {
250 set_mix_group_solo (_route, !_route->soloed());
255 /* click: solo this route */
257 reversibly_apply_route_boolean ("solo change", &Route::set_solo, !_route->soloed(), this);
267 RouteUI::solo_release(GdkEventButton* ev)
269 if (!ignore_toggle) {
270 if (wait_for_release) {
271 wait_for_release = false;
273 // because the press was the last undoable thing we did
283 RouteUI::rec_enable_press(GdkEventButton* ev)
285 if (!ignore_toggle && is_track() && rec_enable_button) {
287 if (ev->button == 2 && Keyboard::modifier_state_equals (ev->state, Keyboard::Control)) {
288 // do nothing on midi bind event
290 else if (Keyboard::modifier_state_equals (ev->state, Keyboard::ModifierMask (Keyboard::Control|Keyboard::Shift))) {
292 _session.begin_reversible_command (_("rec-enable change"));
293 Session::GlobalRecordEnableStateCommand *cmd = new Session::GlobalRecordEnableStateCommand(_session, this);
295 if (rec_enable_button->get_active()) {
296 _session.record_disenable_all ();
298 _session.record_enable_all ();
302 _session.add_command(cmd);
303 _session.commit_reversible_command ();
305 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::Control)) {
307 set_mix_group_rec_enable (_route, !_route->record_enabled());
311 reversibly_apply_audio_track_boolean ("rec-enable change", &AudioTrack::set_record_enable, !audio_track()->record_enabled(), this);
313 ignore_toggle = true;
314 rec_enable_button->set_active(audio_track()->record_enabled());
315 ignore_toggle = false;
318 stop_signal (*rec_enable_button, "button-press-event");
325 RouteUI::solo_changed(void* src)
327 Gtkmm2ext::UI::instance()->call_slot (mem_fun (*this, &RouteUI::update_solo_display));
331 RouteUI::update_solo_display ()
335 if (solo_button->get_active() != (x = _route->soloed())){
336 ignore_toggle = true;
337 solo_button->set_active(x);
338 ignore_toggle = false;
343 if (_route->solo_safe()){
344 solo_button->set_name(safe_solo_button_name());
346 solo_button->set_name(solo_button_name());
351 RouteUI::mute_changed(void* src)
353 Gtkmm2ext::UI::instance()->call_slot (mem_fun (*this, &RouteUI::update_mute_display));
357 RouteUI::update_mute_display ()
361 if (mute_button->get_active() != (x = _route->muted())){
362 ignore_toggle = true;
363 mute_button->set_active(x);
364 ignore_toggle = false;
369 RouteUI::route_rec_enable_changed ()
371 Gtkmm2ext::UI::instance()->call_slot (mem_fun (*this, &RouteUI::update_rec_display));
375 RouteUI::session_rec_enable_changed ()
377 Gtkmm2ext::UI::instance()->call_slot (mem_fun (*this, &RouteUI::update_rec_display));
381 RouteUI::update_rec_display ()
383 bool model = _route->record_enabled();
384 bool view = rec_enable_button->get_active();
386 /* first make sure the button's "depressed" visual
391 ignore_toggle = true;
392 rec_enable_button->set_active (model);
393 ignore_toggle = false;
396 /* now make sure its color state is correct */
400 switch (_session.record_status ()) {
401 case Session::Disabled:
402 case Session::Enabled:
403 if (rec_enable_button->get_state() != Gtk::STATE_ACTIVE) {
404 rec_enable_button->set_state (Gtk::STATE_ACTIVE);
408 case Session::Recording:
409 if (rec_enable_button->get_state() != Gtk::STATE_SELECTED) {
410 rec_enable_button->set_state (Gtk::STATE_SELECTED);
416 if (rec_enable_button->get_state() != Gtk::STATE_NORMAL) {
417 rec_enable_button->set_state (Gtk::STATE_NORMAL);
423 RouteUI::build_remote_control_menu ()
425 remote_control_menu = manage (new Menu);
426 refresh_remote_control_menu ();
430 RouteUI::refresh_remote_control_menu ()
432 using namespace Menu_Helpers;
434 RadioMenuItem::Group rc_group;
435 CheckMenuItem* rc_active;
436 uint32_t limit = _session.ntracks();
439 MenuList& rc_items = remote_control_menu->items();
442 /* note that this menu list starts at zero, not 1, because zero
443 is a valid, if useless, ID.
446 limit += 4; /* leave some breathing room */
448 rc_items.push_back (RadioMenuElem (rc_group, _("None")));
449 if (_route->remote_control_id() == 0) {
450 rc_active = dynamic_cast<CheckMenuItem*> (&rc_items.back());
451 rc_active->set_active ();
454 for (uint32_t i = 1; i < limit; ++i) {
455 snprintf (buf, sizeof (buf), "%u", i);
456 rc_items.push_back (RadioMenuElem (rc_group, buf));
457 rc_active = dynamic_cast<RadioMenuItem*>(&rc_items.back());
458 if (_route->remote_control_id() == i) {
459 rc_active = dynamic_cast<CheckMenuItem*> (&rc_items.back());
460 rc_active->set_active ();
462 rc_active->signal_activate().connect (bind (mem_fun (*this, &RouteUI::set_remote_control_id), i, rc_active));
467 RouteUI::set_remote_control_id (uint32_t id, CheckMenuItem* item)
469 /* this is called when the radio menu item is toggled, and so
470 is actually invoked twice per menu selection. we only
471 care about the invocation for the item that was being
475 if (item->get_active()) {
476 _route->set_remote_control_id (id);
481 RouteUI::build_solo_menu (void)
483 using namespace Menu_Helpers;
485 solo_menu = new Menu;
486 solo_menu->set_name ("ArdourContextMenu");
487 MenuList& items = solo_menu->items();
488 CheckMenuItem* check;
490 check = new CheckMenuItem(_("Solo-safe"));
491 check->set_active (_route->solo_safe());
492 check->signal_toggled().connect (bind (mem_fun (*this, &RouteUI::toggle_solo_safe), check));
493 _route->solo_safe_changed.connect(bind (mem_fun (*this, &RouteUI::solo_safe_toggle), check));
494 items.push_back (CheckMenuElem(*check));
497 items.push_back (SeparatorElem());
498 // items.push_back (MenuElem (_("MIDI Bind"), mem_fun (*mute_button, &BindableToggleButton::midi_learn)));
503 RouteUI::build_mute_menu(void)
505 using namespace Menu_Helpers;
507 mute_menu = new Menu;
508 mute_menu->set_name ("ArdourContextMenu");
509 MenuList& items = mute_menu->items();
510 CheckMenuItem* check;
512 check = new CheckMenuItem(_("Pre Fader"));
513 init_mute_menu(PRE_FADER, check);
514 check->signal_toggled().connect(bind (mem_fun (*this, &RouteUI::toggle_mute_menu), PRE_FADER, check));
515 _route->pre_fader_changed.connect(bind (mem_fun (*this, &RouteUI::pre_fader_toggle), check));
516 items.push_back (CheckMenuElem(*check));
519 check = new CheckMenuItem(_("Post Fader"));
520 init_mute_menu(POST_FADER, check);
521 check->signal_toggled().connect(bind (mem_fun (*this, &RouteUI::toggle_mute_menu), POST_FADER, check));
522 _route->post_fader_changed.connect(bind (mem_fun (*this, &RouteUI::post_fader_toggle), check));
523 items.push_back (CheckMenuElem(*check));
526 check = new CheckMenuItem(_("Control Outs"));
527 init_mute_menu(CONTROL_OUTS, check);
528 check->signal_toggled().connect(bind (mem_fun (*this, &RouteUI::toggle_mute_menu), CONTROL_OUTS, check));
529 _route->control_outs_changed.connect(bind (mem_fun (*this, &RouteUI::control_outs_toggle), check));
530 items.push_back (CheckMenuElem(*check));
533 check = new CheckMenuItem(_("Main Outs"));
534 init_mute_menu(MAIN_OUTS, check);
535 check->signal_toggled().connect(bind (mem_fun (*this, &RouteUI::toggle_mute_menu), MAIN_OUTS, check));
536 _route->main_outs_changed.connect(bind (mem_fun (*this, &RouteUI::main_outs_toggle), check));
537 items.push_back (CheckMenuElem(*check));
540 items.push_back (SeparatorElem());
541 // items.push_back (MenuElem (_("MIDI Bind"), mem_fun (*mute_button, &BindableToggleButton::midi_learn)));
545 RouteUI::init_mute_menu(mute_type type, CheckMenuItem* check)
547 if (_route->get_mute_config (type)) {
548 check->set_active (true);
553 RouteUI::toggle_mute_menu(mute_type type, Gtk::CheckMenuItem* check)
555 _route->set_mute_config(type, check->get_active(), this);
559 RouteUI::toggle_solo_safe (Gtk::CheckMenuItem* check)
561 _route->set_solo_safe (check->get_active(), this);
565 RouteUI::set_mix_group_solo(boost::shared_ptr<Route> route, bool yn)
567 RouteGroup* mix_group;
569 if((mix_group = route->mix_group()) != 0){
570 _session.begin_reversible_command (_("mix group solo change"));
571 Session::GlobalSoloStateCommand *cmd = new Session::GlobalSoloStateCommand(_session, this);
572 mix_group->apply(&Route::set_solo, yn, this);
574 _session.add_command (cmd);
575 _session.commit_reversible_command ();
577 reversibly_apply_route_boolean ("solo change", &Route::set_solo, !route->soloed(), this);
582 RouteUI::reversibly_apply_route_boolean (string name, void (Route::*func)(bool, void *), bool yn, void *arg)
584 _session.begin_reversible_command (name);
585 XMLNode &before = _route->get_state();
586 bind(mem_fun(*_route, func), yn, arg)();
587 XMLNode &after = _route->get_state();
588 _session.add_command (new MementoCommand<Route>(*_route, &before, &after));
589 _session.commit_reversible_command ();
593 RouteUI::reversibly_apply_audio_track_boolean (string name, void (AudioTrack::*func)(bool, void *), bool yn, void *arg)
595 _session.begin_reversible_command (name);
596 XMLNode &before = audio_track()->get_state();
597 bind (mem_fun (*audio_track(), func), yn, arg)();
598 XMLNode &after = audio_track()->get_state();
599 _session.add_command (new MementoCommand<AudioTrack>(*audio_track(), &before, &after));
600 _session.commit_reversible_command ();
604 RouteUI::set_mix_group_mute(boost::shared_ptr<Route> route, bool yn)
606 RouteGroup* mix_group;
608 if((mix_group = route->mix_group()) != 0){
609 _session.begin_reversible_command (_("mix group mute change"));
610 Session::GlobalMuteStateCommand *cmd = new Session::GlobalMuteStateCommand (_session, this);
611 mix_group->apply(&Route::set_mute, yn, this);
613 _session.add_command(cmd);
614 _session.commit_reversible_command ();
616 reversibly_apply_route_boolean ("mute change", &Route::set_mute, !route->muted(), this);
621 RouteUI::set_mix_group_rec_enable(boost::shared_ptr<Route> route, bool yn)
623 RouteGroup* mix_group;
625 if((mix_group = route->mix_group()) != 0){
626 _session.begin_reversible_command (_("mix group rec-enable change"));
627 Session::GlobalRecordEnableStateCommand *cmd = new Session::GlobalRecordEnableStateCommand(_session, this);
628 mix_group->apply (&Route::set_record_enable, yn, this);
630 _session.add_command(cmd);
631 _session.commit_reversible_command ();
633 reversibly_apply_route_boolean ("rec-enable change", &Route::set_record_enable, !_route->record_enabled(), this);
639 RouteUI::choose_color()
644 color = Gtkmm2ext::UI::instance()->get_color (_("ardour: color selection"), picked, &_color);
654 RouteUI::set_color (const Gdk::Color & c)
661 snprintf (buf, sizeof (buf), "%d:%d:%d", c.get_red(), c.get_green(), c.get_blue());
662 xml_node->add_property ("color", buf);
664 _route->gui_changed ("color", (void *) 0); /* EMIT_SIGNAL */
669 RouteUI::ensure_xml_node ()
672 if ((xml_node = _route->extra_xml ("GUI")) == 0) {
673 xml_node = new XMLNode ("GUI");
674 _route->add_extra_xml (*xml_node);
680 RouteUI::get_child_xml_node (const string & childname)
687 if ((child = find_named_node (*xml_node, childname)) == 0) {
688 child = new XMLNode (childname);
689 xml_node->add_child_nocopy (*child);
696 RouteUI::set_color_from_route ()
700 RouteUI::ensure_xml_node ();
702 if ((prop = xml_node->property ("color")) != 0) {
704 sscanf (prop->value().c_str(), "%d:%d:%d", &r, &g, &b);
714 RouteUI::remove_this_route ()
716 vector<string> choices;
720 prompt = string_compose (_("Do you really want to remove track \"%1\" ?\n\nYou may also lose the playlist used by this track.\n(cannot be undone)"), _route->name());
722 prompt = string_compose (_("Do you really want to remove bus \"%1\" ?\n(cannot be undone)"), _route->name());
725 choices.push_back (_("No, do nothing."));
726 choices.push_back (_("Yes, remove it."));
728 Choice prompter (prompt, choices);
730 if (prompter.run () == 1) {
731 Glib::signal_idle().connect (bind (sigc::ptr_fun (&RouteUI::idle_remove_this_route), this));
736 RouteUI::idle_remove_this_route (RouteUI *rui)
738 rui->_session.remove_route (rui->_route);
743 RouteUI::route_rename ()
745 ArdourPrompter name_prompter (true);
747 name_prompter.set_prompt (_("New Name: "));
748 name_prompter.set_initial_text (_route->name());
749 name_prompter.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT);
750 name_prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
751 name_prompter.show_all ();
753 switch (name_prompter.run ()) {
755 case Gtk::RESPONSE_ACCEPT:
756 name_prompter.get_result (result);
757 if (result.length()) {
758 _route->set_name (result, this);
768 RouteUI::name_changed (void *src)
770 ENSURE_GUI_THREAD(bind (mem_fun (*this, &RouteUI::name_changed), src));
772 name_label.set_text (_route->name());
776 RouteUI::toggle_route_active ()
780 if (route_active_menu_item) {
781 if (route_active_menu_item->get_active() != (yn = _route->active())) {
782 _route->set_active (!yn);
788 RouteUI::route_active_changed ()
790 if (route_active_menu_item) {
791 Gtkmm2ext::UI::instance()->call_slot (bind (mem_fun (*route_active_menu_item, &CheckMenuItem::set_active), _route->active()));
796 RouteUI::toggle_polarity ()
798 if (polarity_menu_item) {
802 ENSURE_GUI_THREAD(mem_fun (*this, &RouteUI::toggle_polarity));
804 if ((x = polarity_menu_item->get_active()) != _route->phase_invert()) {
805 _route->set_phase_invert (x, this);
807 name_label.set_text (X_("Ø ") + name_label.get_text());
809 name_label.set_text (_route->name());
816 RouteUI::polarity_changed ()
818 /* no signal for this yet */
822 RouteUI::solo_safe_toggle(void* src, Gtk::CheckMenuItem* check)
824 bool yn = _route->solo_safe ();
826 if (check->get_active() != yn) {
827 check->set_active (yn);
831 RouteUI::pre_fader_toggle(void* src, Gtk::CheckMenuItem* check)
833 ENSURE_GUI_THREAD(bind (mem_fun (*this, &RouteUI::pre_fader_toggle), src, check));
835 bool yn = _route->get_mute_config(PRE_FADER);
836 if (check->get_active() != yn) {
837 check->set_active (yn);
842 RouteUI::post_fader_toggle(void* src, Gtk::CheckMenuItem* check)
844 ENSURE_GUI_THREAD(bind (mem_fun (*this, &RouteUI::post_fader_toggle), src, check));
846 bool yn = _route->get_mute_config(POST_FADER);
847 if (check->get_active() != yn) {
848 check->set_active (yn);
853 RouteUI::control_outs_toggle(void* src, Gtk::CheckMenuItem* check)
855 ENSURE_GUI_THREAD(bind (mem_fun (*this, &RouteUI::control_outs_toggle), src, check));
857 bool yn = _route->get_mute_config(CONTROL_OUTS);
858 if (check->get_active() != yn) {
859 check->set_active (yn);
864 RouteUI::main_outs_toggle(void* src, Gtk::CheckMenuItem* check)
866 ENSURE_GUI_THREAD(bind (mem_fun (*this, &RouteUI::main_outs_toggle), src, check));
868 bool yn = _route->get_mute_config(MAIN_OUTS);
869 if (check->get_active() != yn) {
870 check->set_active (yn);
875 RouteUI::disconnect_input ()
877 _route->disconnect_inputs (this);
881 RouteUI::disconnect_output ()
883 _route->disconnect_outputs (this);
887 RouteUI::is_track () const
889 return dynamic_cast<Track*>(_route.get()) != 0;
893 RouteUI::track() const
895 return dynamic_cast<Track*>(_route.get());
899 RouteUI::is_audio_track () const
901 return dynamic_cast<AudioTrack*>(_route.get()) != 0;
905 RouteUI::audio_track() const
907 return dynamic_cast<AudioTrack*>(_route.get());
910 boost::shared_ptr<Diskstream>
911 RouteUI::get_diskstream () const
913 boost::shared_ptr<Track> t;
915 if ((t = boost::dynamic_pointer_cast<Track>(_route)) != 0) {
916 return t->diskstream();
918 return boost::shared_ptr<Diskstream> ((Diskstream*) 0);
923 RouteUI::name() const
925 return _route->name();
929 RouteUI::map_frozen ()
931 ENSURE_GUI_THREAD (mem_fun (*this, &RouteUI::map_frozen));
933 AudioTrack* at = dynamic_cast<AudioTrack*>(_route.get());
936 switch (at->freeze_state()) {
937 case AudioTrack::Frozen:
938 rec_enable_button->set_sensitive (false);
941 rec_enable_button->set_sensitive (true);