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.RecordStateChanged.connect (mem_fun (*this, &RouteUI::session_rec_enable_changed));
81 rec_enable_button = manage (new BindableToggleButton (& at->midi_rec_enable_control(), r_name ));
82 rec_enable_button->set_bind_button_state (2, GDK_CONTROL_MASK);
85 rec_enable_button = manage (new BindableToggleButton (0, r_name ));
88 mute_button->unset_flags (Gtk::CAN_FOCUS);
89 solo_button->unset_flags (Gtk::CAN_FOCUS);
90 rec_enable_button->unset_flags (Gtk::CAN_FOCUS);
92 /* map the current state */
94 update_rec_display ();
104 RouteUI::mute_press(GdkEventButton* ev)
106 if (!ignore_toggle) {
108 if (Keyboard::is_context_menu_event (ev)) {
114 mute_menu->popup(0,0);
118 if (ev->button == 2) {
119 // ctrl-button2 click is the midi binding click
120 // button2-click is "momentary"
122 if (!Keyboard::modifier_state_equals (ev->state, Keyboard::ModifierMask (Keyboard::Control))) {
123 wait_for_release = true;
127 if (ev->button == 1 || ev->button == 2) {
129 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ModifierMask (Keyboard::Control|Keyboard::Shift))) {
131 /* ctrl-shift-click applies change to all routes */
133 _session.begin_reversible_command (_("mute change"));
134 _session.add_undo (_session.global_mute_memento(this));
135 _session.set_all_mute (!_route.muted());
136 _session.add_redo_no_execute (_session.global_mute_memento(this));
137 _session.commit_reversible_command ();
139 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::Control)) {
141 /* ctrl-click applies change to the mix group.
142 ctrl-button2 is MIDI learn.
145 if (ev->button == 1) {
146 set_mix_group_mute (_route, !_route.muted());
151 /* plain click applies change to this route */
153 reversibly_apply_route_boolean ("mute change", &Route::set_mute, !_route.muted(), this);
164 RouteUI::mute_release(GdkEventButton* ev)
166 if (!ignore_toggle) {
167 if (wait_for_release){
168 wait_for_release = false;
170 // because the press was the last undoable thing we did
178 RouteUI::solo_press(GdkEventButton* ev)
180 if (!ignore_toggle) {
182 if (Keyboard::is_context_menu_event (ev)) {
184 if (solo_menu == 0) {
188 solo_menu->popup (1, 0);
192 if (ev->button == 2) {
194 // ctrl-button2 click is the midi binding click
195 // button2-click is "momentary"
197 if (!Keyboard::modifier_state_equals (ev->state, Keyboard::ModifierMask (Keyboard::Control))) {
198 wait_for_release = true;
202 if (ev->button == 1 || ev->button == 2) {
204 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ModifierMask (Keyboard::Control|Keyboard::Shift))) {
206 /* ctrl-shift-click applies change to all routes */
208 _session.begin_reversible_command (_("solo change"));
209 _session.add_undo (_session.global_solo_memento(this));
210 _session.set_all_solo (!_route.soloed());
211 _session.add_redo_no_execute (_session.global_solo_memento(this));
212 _session.commit_reversible_command ();
214 } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::ModifierMask (Keyboard::Control|Keyboard::Alt))) {
216 // ctrl-alt-click: exclusively solo this track, not a toggle */
218 _session.begin_reversible_command (_("solo change"));
219 _session.add_undo (_session.global_solo_memento(this));
220 _session.set_all_solo (false);
221 _route.set_solo (true, this);
222 _session.add_redo_no_execute (_session.global_solo_memento(this));
223 _session.commit_reversible_command ();
225 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::Shift)) {
227 // shift-click: set this route to solo safe
229 _route.set_solo_safe (!_route.solo_safe(), this);
230 wait_for_release = false;
232 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::Control)) {
234 /* ctrl-click: solo mix group.
235 ctrl-button2 is MIDI learn.
238 if (ev->button == 1) {
239 set_mix_group_solo (_route, !_route.soloed());
244 /* click: solo this route */
246 reversibly_apply_route_boolean ("solo change", &Route::set_solo, !_route.soloed(), this);
252 return stop_signal (*solo_button, "button-press-event");
256 RouteUI::solo_release(GdkEventButton* ev)
259 if (wait_for_release){
260 wait_for_release = false;
262 // because the press was the last undoable thing we did
266 stop_signal (*solo_button, "button-release-event");
273 RouteUI::rec_enable_press(GdkEventButton* ev)
275 if (!ignore_toggle && is_audio_track()) {
277 if (ev->button == 2 && Keyboard::modifier_state_equals (ev->state, Keyboard::Control)) {
278 // do nothing on midi bind event
280 else if (Keyboard::modifier_state_equals (ev->state, Keyboard::ModifierMask (Keyboard::Control|Keyboard::Shift))) {
282 _session.begin_reversible_command (_("rec-enable change"));
283 _session.add_undo (_session.global_record_enable_memento(this));
285 if (rec_enable_button->get_active()) {
286 _session.record_disenable_all ();
288 _session.record_enable_all ();
291 _session.add_redo_no_execute (_session.global_record_enable_memento(this));
292 _session.commit_reversible_command ();
294 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::Control)) {
296 set_mix_group_rec_enable (_route, !_route.record_enabled());
300 reversibly_apply_audio_track_boolean ("rec-enable change", &AudioTrack::set_record_enable, !audio_track()->record_enabled(), this);
302 ignore_toggle = true;
303 rec_enable_button->set_active(audio_track()->record_enabled());
304 ignore_toggle = false;
307 stop_signal (*rec_enable_button, "button-press-event");
314 RouteUI::solo_changed(void* src)
316 Gtkmm2ext::UI::instance()->call_slot (mem_fun (*this, &RouteUI::update_solo_display));
320 RouteUI::update_solo_display ()
324 if (solo_button->get_active() != (x = _route.soloed())){
325 ignore_toggle = true;
326 solo_button->set_active(x);
327 ignore_toggle = false;
332 if (_route.solo_safe()){
333 solo_button->set_name(safe_solo_button_name());
335 solo_button->set_name(solo_button_name());
340 RouteUI::mute_changed(void* src)
342 Gtkmm2ext::UI::instance()->call_slot (mem_fun (*this, &RouteUI::update_mute_display));
346 RouteUI::update_mute_display ()
350 if (mute_button->get_active() != (x = _route.muted())){
351 ignore_toggle = true;
352 mute_button->set_active(x);
353 ignore_toggle = false;
358 RouteUI::route_rec_enable_changed (void *src)
360 Gtkmm2ext::UI::instance()->call_slot (mem_fun (*this, &RouteUI::update_rec_display));
364 RouteUI::session_rec_enable_changed ()
366 Gtkmm2ext::UI::instance()->call_slot (mem_fun (*this, &RouteUI::update_rec_display));
370 RouteUI::update_rec_display ()
372 bool model = _route.record_enabled();
373 bool view = rec_enable_button->get_active();
375 /* first make sure the button's "depressed" visual
380 ignore_toggle = true;
381 rec_enable_button->set_active (model);
382 ignore_toggle = false;
385 /* now make sure its color state is correct */
389 switch (_session.record_status ()) {
390 case Session::Disabled:
391 case Session::Enabled:
392 if (rec_enable_button->get_state() != Gtk::STATE_ACTIVE) {
393 rec_enable_button->set_state (Gtk::STATE_ACTIVE);
397 case Session::Recording:
398 if (rec_enable_button->get_state() != Gtk::STATE_SELECTED) {
399 rec_enable_button->set_state (Gtk::STATE_SELECTED);
405 if (rec_enable_button->get_state() != Gtk::STATE_NORMAL) {
406 rec_enable_button->set_state (Gtk::STATE_NORMAL);
412 RouteUI::build_remote_control_menu ()
414 remote_control_menu = manage (new Menu);
415 refresh_remote_control_menu ();
419 RouteUI::refresh_remote_control_menu ()
421 using namespace Menu_Helpers;
423 RadioMenuItem::Group rc_group;
424 CheckMenuItem* rc_active;
425 uint32_t limit = _session.ntracks();
428 MenuList& rc_items = remote_control_menu->items();
431 /* note that this menu list starts at zero, not 1, because zero
432 is a valid, if useless, ID.
435 limit += 4; /* leave some breathing room */
437 for (uint32_t i = 0; i < limit; ++i) {
438 snprintf (buf, sizeof (buf), "%u", i);
439 rc_items.push_back (RadioMenuElem (rc_group, buf));
440 rc_active = dynamic_cast<RadioMenuItem*>(&rc_items.back());
441 if (_route.remote_control_id() == i) {
442 rc_active = dynamic_cast<CheckMenuItem*> (&rc_items.back());
443 rc_active->set_active ();
445 rc_active->signal_activate().connect (bind (mem_fun (*this, &RouteUI::set_remote_control_id), i, rc_active));
450 RouteUI::set_remote_control_id (uint32_t id, CheckMenuItem* item)
452 /* this is called when the radio menu item is toggled, and so
453 is actually invoked twice per menu selection. we only
454 care about the invocation for the item that was being
458 if (item->get_active()) {
459 _route.set_remote_control_id (id);
464 RouteUI::build_solo_menu (void)
466 using namespace Menu_Helpers;
468 solo_menu = new Menu;
469 solo_menu->set_name ("ArdourContextMenu");
470 MenuList& items = solo_menu->items();
471 CheckMenuItem* check;
473 check = new CheckMenuItem(_("Solo-safe"));
474 check->set_active (_route.solo_safe());
475 check->signal_toggled().connect (bind (mem_fun (*this, &RouteUI::toggle_solo_safe), check));
476 _route.solo_safe_changed.connect(bind (mem_fun (*this, &RouteUI::solo_safe_toggle), check));
477 items.push_back (CheckMenuElem(*check));
480 items.push_back (SeparatorElem());
481 items.push_back (MenuElem (_("MIDI Bind"), mem_fun (*mute_button, &BindableToggleButton::midi_learn)));
486 RouteUI::build_mute_menu(void)
488 using namespace Menu_Helpers;
490 mute_menu = new Menu;
491 mute_menu->set_name ("ArdourContextMenu");
492 MenuList& items = mute_menu->items();
493 CheckMenuItem* check;
495 check = new CheckMenuItem(_("Pre Fader"));
496 init_mute_menu(PRE_FADER, check);
497 check->signal_toggled().connect(bind (mem_fun (*this, &RouteUI::toggle_mute_menu), PRE_FADER, check));
498 _route.pre_fader_changed.connect(bind (mem_fun (*this, &RouteUI::pre_fader_toggle), check));
499 items.push_back (CheckMenuElem(*check));
502 check = new CheckMenuItem(_("Post Fader"));
503 init_mute_menu(POST_FADER, check);
504 check->signal_toggled().connect(bind (mem_fun (*this, &RouteUI::toggle_mute_menu), POST_FADER, check));
505 _route.post_fader_changed.connect(bind (mem_fun (*this, &RouteUI::post_fader_toggle), check));
506 items.push_back (CheckMenuElem(*check));
509 check = new CheckMenuItem(_("Control Outs"));
510 init_mute_menu(CONTROL_OUTS, check);
511 check->signal_toggled().connect(bind (mem_fun (*this, &RouteUI::toggle_mute_menu), CONTROL_OUTS, check));
512 _route.control_outs_changed.connect(bind (mem_fun (*this, &RouteUI::control_outs_toggle), check));
513 items.push_back (CheckMenuElem(*check));
516 check = new CheckMenuItem(_("Main Outs"));
517 init_mute_menu(MAIN_OUTS, check);
518 check->signal_toggled().connect(bind (mem_fun (*this, &RouteUI::toggle_mute_menu), MAIN_OUTS, check));
519 _route.main_outs_changed.connect(bind (mem_fun (*this, &RouteUI::main_outs_toggle), check));
520 items.push_back (CheckMenuElem(*check));
523 items.push_back (SeparatorElem());
524 items.push_back (MenuElem (_("MIDI Bind"), mem_fun (*mute_button, &BindableToggleButton::midi_learn)));
528 RouteUI::init_mute_menu(mute_type type, CheckMenuItem* check)
530 if (_route.get_mute_config (type)) {
531 check->set_active (true);
536 RouteUI::toggle_mute_menu(mute_type type, Gtk::CheckMenuItem* check)
538 _route.set_mute_config(type, check->get_active(), this);
542 RouteUI::toggle_solo_safe (Gtk::CheckMenuItem* check)
544 _route.set_solo_safe (check->get_active(), this);
548 RouteUI::set_mix_group_solo(Route& route, bool yn)
550 RouteGroup* mix_group;
552 if((mix_group = route.mix_group()) != 0){
553 _session.begin_reversible_command (_("mix group solo change"));
554 _session.add_undo (_session.global_solo_memento (this));
555 mix_group->apply(&Route::set_solo, yn, this);
556 _session.add_redo_no_execute (_session.global_solo_memento(this));
557 _session.commit_reversible_command ();
559 reversibly_apply_route_boolean ("solo change", &Route::set_solo, !route.soloed(), this);
564 RouteUI::reversibly_apply_route_boolean (string name, void (Route::*func)(bool, void *), bool yn, void *arg)
566 _session.begin_reversible_command (name);
567 _session.add_undo (bind (mem_fun (_route, func), !yn, (void *) arg));
568 _session.add_redo (bind (mem_fun (_route, func), yn, (void *) arg));
569 _session.commit_reversible_command ();
573 RouteUI::reversibly_apply_audio_track_boolean (string name, void (AudioTrack::*func)(bool, void *), bool yn, void *arg)
575 _session.begin_reversible_command (name);
576 _session.add_undo (bind (mem_fun (*audio_track(), func), !yn, (void *) arg));
577 _session.add_redo (bind (mem_fun (*audio_track(), func), yn, (void *) arg));
578 _session.commit_reversible_command ();
582 RouteUI::set_mix_group_mute(Route& route, bool yn)
584 RouteGroup* mix_group;
586 if((mix_group = route.mix_group()) != 0){
587 _session.begin_reversible_command (_("mix group mute change"));
588 _session.add_undo (_session.global_mute_memento (this));
589 mix_group->apply(&Route::set_mute, yn, this);
590 _session.add_redo_no_execute (_session.global_mute_memento(this));
591 _session.commit_reversible_command ();
593 reversibly_apply_route_boolean ("mute change", &Route::set_mute, !route.muted(), this);
598 RouteUI::set_mix_group_rec_enable(Route& route, bool yn)
600 RouteGroup* mix_group;
602 if((mix_group = route.mix_group()) != 0){
603 _session.begin_reversible_command (_("mix group rec-enable change"));
604 _session.add_undo (_session.global_record_enable_memento (this));
605 mix_group->apply (&Route::set_record_enable, yn, this);
606 _session.add_redo_no_execute (_session.global_record_enable_memento(this));
607 _session.commit_reversible_command ();
609 reversibly_apply_route_boolean ("rec-enable change", &Route::set_record_enable, !_route.record_enabled(), this);
615 RouteUI::choose_color()
620 color = Gtkmm2ext::UI::instance()->get_color (_("ardour: color selection"), picked, &_color);
630 RouteUI::set_color (const Gdk::Color & c)
637 snprintf (buf, sizeof (buf), "%d:%d:%d", c.get_red(), c.get_green(), c.get_blue());
638 xml_node->add_property ("color", buf);
640 _route.gui_changed ("color", (void *) 0); /* EMIT_SIGNAL */
645 RouteUI::ensure_xml_node ()
648 if ((xml_node = _route.extra_xml ("GUI")) == 0) {
649 xml_node = new XMLNode ("GUI");
650 _route.add_extra_xml (*xml_node);
656 RouteUI::get_child_xml_node (const string & childname)
663 if ((child = find_named_node (*xml_node, childname)) == 0) {
664 child = new XMLNode (childname);
665 xml_node->add_child_nocopy (*child);
672 RouteUI::set_color_from_route ()
676 RouteUI::ensure_xml_node ();
678 if ((prop = xml_node->property ("color")) != 0) {
680 sscanf (prop->value().c_str(), "%d:%d:%d", &r, &g, &b);
690 RouteUI::remove_this_route ()
692 vector<string> choices;
695 if (is_audio_track()) {
696 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());
698 prompt = string_compose (_("Do you really want to remove bus \"%1\" ?\n(cannot be undone)"), _route.name());
701 choices.push_back (_("Yes, remove it."));
702 choices.push_back (_("No, do nothing."));
704 Choice prompter (prompt, choices);
706 prompter.chosen.connect(sigc::ptr_fun(Gtk::Main::quit));
707 prompter.show_all ();
711 if (prompter.get_choice() == 0) {
712 Glib::signal_idle().connect (bind (sigc::ptr_fun (&RouteUI::idle_remove_this_route), this));
717 RouteUI::idle_remove_this_route (RouteUI *rui)
719 rui->_session.remove_route (rui->_route);
724 RouteUI::route_removed ()
726 ENSURE_GUI_THREAD(mem_fun (*this, &RouteUI::route_removed));
732 RouteUI::route_rename ()
734 ArdourPrompter name_prompter (true);
736 name_prompter.set_prompt (_("new name: "));
737 name_prompter.set_initial_text (_route.name());
738 name_prompter.show_all ();
740 switch (name_prompter.run ()) {
742 case Gtk::RESPONSE_ACCEPT:
743 name_prompter.get_result (result);
744 if (result.length()) {
745 _route.set_name (result, this);
755 RouteUI::name_changed (void *src)
757 ENSURE_GUI_THREAD(bind (mem_fun (*this, &RouteUI::name_changed), src));
759 name_label.set_text (_route.name());
763 RouteUI::toggle_route_active ()
767 if (route_active_menu_item) {
768 if (route_active_menu_item->get_active() != (yn = _route.active())) {
769 _route.set_active (!yn);
775 RouteUI::route_active_changed ()
777 if (route_active_menu_item) {
778 Gtkmm2ext::UI::instance()->call_slot (bind (mem_fun (*route_active_menu_item, &CheckMenuItem::set_active), _route.active()));
783 RouteUI::solo_safe_toggle(void* src, Gtk::CheckMenuItem* check)
785 bool yn = _route.solo_safe ();
787 if (check->get_active() != yn) {
788 check->set_active (yn);
792 RouteUI::pre_fader_toggle(void* src, Gtk::CheckMenuItem* check)
794 ENSURE_GUI_THREAD(bind (mem_fun (*this, &RouteUI::pre_fader_toggle), src, check));
796 bool yn = _route.get_mute_config(PRE_FADER);
797 if (check->get_active() != yn) {
798 check->set_active (yn);
803 RouteUI::post_fader_toggle(void* src, Gtk::CheckMenuItem* check)
805 ENSURE_GUI_THREAD(bind (mem_fun (*this, &RouteUI::post_fader_toggle), src, check));
807 bool yn = _route.get_mute_config(POST_FADER);
808 if (check->get_active() != yn) {
809 check->set_active (yn);
814 RouteUI::control_outs_toggle(void* src, Gtk::CheckMenuItem* check)
816 ENSURE_GUI_THREAD(bind (mem_fun (*this, &RouteUI::control_outs_toggle), src, check));
818 bool yn = _route.get_mute_config(CONTROL_OUTS);
819 if (check->get_active() != yn) {
820 check->set_active (yn);
825 RouteUI::main_outs_toggle(void* src, Gtk::CheckMenuItem* check)
827 ENSURE_GUI_THREAD(bind (mem_fun (*this, &RouteUI::main_outs_toggle), src, check));
829 bool yn = _route.get_mute_config(MAIN_OUTS);
830 if (check->get_active() != yn) {
831 check->set_active (yn);
836 RouteUI::disconnect_input ()
838 _route.disconnect_inputs (this);
842 RouteUI::disconnect_output ()
844 _route.disconnect_outputs (this);
848 RouteUI::is_audio_track () const
850 return dynamic_cast<AudioTrack*>(&_route) != 0;
854 RouteUI::get_diskstream () const
858 if ((at = dynamic_cast<AudioTrack*>(&_route)) != 0) {
859 return &at->disk_stream();
866 RouteUI::audio_track() const
868 return dynamic_cast<AudioTrack*>(&_route);
871 RouteUI::name() const
873 return _route.name();
877 RouteUI::map_frozen ()
879 ENSURE_GUI_THREAD (mem_fun (*this, &RouteUI::map_frozen));
881 AudioTrack* at = dynamic_cast<AudioTrack*>(&_route);
884 switch (at->freeze_state()) {
885 case AudioTrack::Frozen:
886 rec_enable_button->set_sensitive (false);
889 rec_enable_button->set_sensitive (true);