2 * Copyright (C) 2009-2011 Carl Hetherington <carl@carlh.net>
3 * Copyright (C) 2009-2012 David Robillard <d@drobilla.net>
4 * Copyright (C) 2009-2017 Paul Davis <paul@linuxaudiosystems.com>
5 * Copyright (C) 2013-2019 Robin Gareus <robin@gareus.org>
6 * Copyright (C) 2015 Tim Mayberry <mojofunk@gmail.com>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License along
19 * with this program; if not, write to the Free Software Foundation, Inc.,
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 #include <gtkmm/stock.h>
25 #include "ardour/session.h"
26 #include "ardour/route_group.h"
27 #include "ardour/route.h"
28 #include "ardour/vca_manager.h"
29 #include "ardour/vca.h"
31 #include "gtkmm2ext/doi.h"
33 #include "gui_thread.h"
34 #include "route_group_dialog.h"
35 #include "group_tabs.h"
38 #include "ardour_ui.h"
39 #include "rgb_macros.h"
40 #include "ui_config.h"
45 using namespace ARDOUR;
46 using namespace ARDOUR_UI_UTILS;
47 using Gtkmm2ext::Keyboard;
49 list<Gdk::Color> GroupTabs::_used_colors;
51 GroupTabs::GroupTabs ()
54 , _dragging_new_tab (0)
56 add_events (Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK|Gdk::POINTER_MOTION_MASK);
57 UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &GroupTabs::queue_draw));
60 GroupTabs::~GroupTabs ()
66 GroupTabs::set_session (Session* s)
68 SessionHandlePtr::set_session (s);
71 _session->RouteGroupPropertyChanged.connect (
72 _session_connections, invalidator (*this), boost::bind (&GroupTabs::route_group_property_changed, this, _1), gui_context()
74 _session->RouteAddedToRouteGroup.connect (
75 _session_connections, invalidator (*this), boost::bind (&GroupTabs::route_added_to_route_group, this, _1, _2), gui_context()
77 _session->RouteRemovedFromRouteGroup.connect (
78 _session_connections, invalidator (*this), boost::bind (&GroupTabs::route_removed_from_route_group, this, _1, _2), gui_context()
81 _session->route_group_removed.connect (_session_connections, invalidator (*this), boost::bind (&GroupTabs::set_dirty, this, (cairo_rectangle_t*)0), gui_context());
86 /** Handle a size request.
87 * @param req GTK requisition
90 GroupTabs::on_size_request (Gtk::Requisition *req)
92 req->width = std::max (16.f, rintf (16.f * UIConfiguration::instance().get_ui_scale()));
93 req->height = std::max (16.f, rintf (16.f * UIConfiguration::instance().get_ui_scale()));
97 GroupTabs::on_button_press_event (GdkEventButton* ev)
99 using namespace Menu_Helpers;
101 double const p = primary_coordinate (ev->x, ev->y);
103 list<Tab>::iterator prev;
104 list<Tab>::iterator next;
105 Tab* t = click_to_tab (p, &prev, &next);
107 _drag_min = prev != _tabs.end() ? prev->to : 0;
108 _drag_max = next != _tabs.end() ? next->from : extent ();
110 if (ev->button == 1) {
115 _dragging_new_tab = true;
117 if (next == _tabs.end()) {
121 list<Tab>::iterator j = _tabs.insert (next, n);
126 _dragging_new_tab = false;
127 _initial_dragging_routes = routes_for_tab (t);
134 double const h = (t->from + t->to) / 2;
136 _drag_moving = t->from;
138 _drag_offset = p - t->from;
140 _drag_moving = t->to;
141 _drag_fixed = t->from;
142 _drag_offset = p - t->to;
145 } else if (ev->button == 3) {
147 RouteGroup* g = t ? t->group : 0;
149 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier) && g) {
151 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier) && g) {
154 Menu* m = get_menu (g, true);
156 m->popup (ev->button, ev->time);
166 GroupTabs::on_motion_notify_event (GdkEventMotion* ev)
168 if (_dragging == 0) {
172 double const p = primary_coordinate (ev->x, ev->y);
174 if (p != _drag_first) {
178 _drag_moving = p - _drag_offset;
180 _dragging->from = min (_drag_moving, _drag_fixed);
181 _dragging->to = max (_drag_moving, _drag_fixed);
183 _dragging->from = max (_dragging->from, _drag_min);
184 _dragging->to = min (_dragging->to, _drag_max);
189 gdk_event_request_motions(ev);
196 GroupTabs::on_button_release_event (GdkEventButton*)
198 if (_dragging == 0) {
204 if (_dragging->group) {
205 /* toggle active state */
206 _dragging->group->set_active (!_dragging->group->is_active (), this);
211 RouteList routes = routes_for_tab (_dragging);
213 if (!routes.empty()) {
214 if (_dragging_new_tab) {
215 run_new_group_dialog (&routes, false);
217 boost::shared_ptr<RouteList> r = _session->get_routes ();
218 /* First add new ones, then remove old ones.
219 * We cannot allow the group to become temporarily empty, because
220 * Session::route_removed_from_route_group() will delete empty groups.
222 for (RouteList::const_iterator i = routes.begin(); i != routes.end(); ++i) {
223 /* RouteGroup::add () ignores routes already present in the set */
224 _dragging->group->add (*i);
226 for (RouteList::const_iterator i = r->begin(); i != r->end(); ++i) {
228 bool const was_in_tab = find (
229 _initial_dragging_routes.begin(), _initial_dragging_routes.end(), *i
230 ) != _initial_dragging_routes.end ();
232 bool const now_in_tab = find (routes.begin(), routes.end(), *i) != routes.end();
234 if (was_in_tab && !now_in_tab) {
235 _dragging->group->remove (*i);
247 _initial_dragging_routes.clear ();
253 GroupTabs::render (Cairo::RefPtr<Cairo::Context> const& ctx, cairo_rectangle_t*)
255 cairo_t* cr = ctx->cobj();
256 if (_dragging == 0) {
257 _tabs = compute_tabs ();
262 Gdk::Color c = get_style()->get_base (Gtk::STATE_NORMAL);
264 cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p());
265 cairo_rectangle (cr, 0, 0, get_width(), get_height());
270 for (list<Tab>::const_iterator i = _tabs.begin(); i != _tabs.end(); ++i) {
276 /** Convert a click position to a tab.
277 * @param c Click position.
278 * @param prev Filled in with the previous tab to the click, or _tabs.end().
279 * @param next Filled in with the next tab after the click, or _tabs.end().
280 * @return Tab under the click, or 0.
284 GroupTabs::click_to_tab (double c, list<Tab>::iterator* prev, list<Tab>::iterator* next)
286 *prev = *next = _tabs.end ();
289 list<Tab>::iterator i = _tabs.begin ();
290 while (i != _tabs.end()) {
303 if (i->from <= c && c < i->to) {
314 GroupTabs::add_new_from_items (Menu_Helpers::MenuList& items)
316 using namespace Menu_Helpers;
319 new_from = manage (new Menu);
321 MenuList& f = new_from->items ();
322 f.push_back (MenuElem (_("Selection..."), sigc::bind (sigc::mem_fun (*this, &GroupTabs::new_from_selection), false)));
323 f.push_back (MenuElem (_("Record Enabled..."), sigc::bind (sigc::mem_fun (*this, &GroupTabs::new_from_rec_enabled), false)));
324 f.push_back (MenuElem (_("Soloed..."), sigc::bind (sigc::mem_fun (*this, &GroupTabs::new_from_soloed), false)));
326 items.push_back (MenuElem (_("Create New Group From..."), *new_from));
328 new_from = manage (new Menu);
330 MenuList& f = new_from->items ();
331 f.push_back (MenuElem (_("Selection..."), sigc::bind (sigc::mem_fun (*this, &GroupTabs::new_from_selection), true)));
332 f.push_back (MenuElem (_("Record Enabled..."), sigc::bind (sigc::mem_fun (*this, &GroupTabs::new_from_rec_enabled), true)));
333 f.push_back (MenuElem (_("Soloed..."), sigc::bind (sigc::mem_fun (*this, &GroupTabs::new_from_soloed), true)));
335 items.push_back (MenuElem (_("Create New Group with Master From..."), *new_from));
339 GroupTabs::get_menu (RouteGroup* g, bool in_tab_area)
341 using namespace Menu_Helpers;
346 _menu->set_name ("ArdourContextMenu");
348 MenuList& items = _menu->items();
351 const VCAList vcas = _session->vca_manager().vcas ();
354 /* context menu is not for a group tab, show the "create new
357 add_new_from_items (items);
361 items.push_back (SeparatorElem());
362 items.push_back (MenuElem (_("Edit Group..."), sigc::bind (sigc::mem_fun (*this, &GroupTabs::edit_group), g)));
363 items.push_back (MenuElem (_("Collect Group"), sigc::bind (sigc::mem_fun (*this, &GroupTabs::collect), g)));
364 items.push_back (MenuElem (_("Remove Group"), sigc::bind (sigc::mem_fun (*this, &GroupTabs::remove_group), g)));
366 items.push_back (SeparatorElem());
368 if (g->has_control_master()) {
369 items.push_back (MenuElem (_("Drop Group from VCA..."), sigc::bind (sigc::mem_fun (*this, &GroupTabs::unassign_group_to_master), g->group_master_number(), g)));
371 vca_menu = manage (new Menu);
372 MenuList& f (vca_menu->items());
373 f.push_back (MenuElem ("New", sigc::bind (sigc::mem_fun (*this, &GroupTabs::assign_group_to_master), 0, g, true)));
375 for (VCAList::const_iterator v = vcas.begin(); v != vcas.end(); ++v) {
376 f.push_back (MenuElem ((*v)->name().empty() ? string_compose ("VCA %1", (*v)->number()) : (*v)->name(), sigc::bind (sigc::mem_fun (*this, &GroupTabs::assign_group_to_master), (*v)->number(), g, true)));
378 items.push_back (MenuElem (_("Assign Group to VCA..."), *vca_menu));
381 items.push_back (SeparatorElem());
383 bool can_subgroup = true;
384 boost::shared_ptr<RouteList> rl = g->route_list();
385 for (RouteList::const_iterator i = rl->begin(); i != rl->end(); ++i) {
387 if ((*i)->mixbus ()) {
388 can_subgroup = false;
392 if ((*i)->output()->n_ports().n_midi() != 0) {
393 can_subgroup = false;
398 if (g->has_subgroup ()) {
399 items.push_back (MenuElem (_("Remove Subgroup Bus"), sigc::bind (sigc::mem_fun (*this, &GroupTabs::un_subgroup), g)));
400 } else if (can_subgroup) {
401 items.push_back (MenuElem (_("Add New Subgroup Bus"), sigc::bind (sigc::mem_fun (*this, &GroupTabs::subgroup), g, false, PreFader)));
405 items.push_back (MenuElem (_("Add New Aux Bus (pre-fader)"), sigc::bind (sigc::mem_fun (*this, &GroupTabs::subgroup), g, true, PreFader)));
406 items.push_back (MenuElem (_("Add New Aux Bus (post-fader)"), sigc::bind (sigc::mem_fun (*this, &GroupTabs::subgroup), g, true, PostFader)));
409 if (can_subgroup || g->has_subgroup ()) {
410 items.push_back (SeparatorElem());
414 add_menu_items (_menu, g);
417 /* context menu is for a group tab, show the "create new
420 add_new_from_items (items);
423 items.push_back (SeparatorElem());
425 vca_menu = manage (new Menu);
427 MenuList& f (vca_menu->items());
428 f.push_back (MenuElem ("New", sigc::bind (sigc::mem_fun (*this, &GroupTabs::assign_selection_to_master), 0)));
429 for (VCAList::const_iterator v = vcas.begin(); v != vcas.end(); ++v) {
430 f.push_back (MenuElem ((*v)->name().empty() ? string_compose ("VCA %1", (*v)->number()) : (*v)->name(), sigc::bind (sigc::mem_fun (*this, &GroupTabs::assign_selection_to_master), (*v)->number())));
434 items.push_back (MenuElem (_("Assign Selection to VCA..."), *vca_menu));
436 vca_menu = manage (new Menu);
438 MenuList& f (vca_menu->items());
439 f.push_back (MenuElem ("New", sigc::bind (sigc::mem_fun (*this, &GroupTabs::assign_recenabled_to_master), 0)));
440 for (VCAList::const_iterator v = vcas.begin(); v != vcas.end(); ++v) {
441 f.push_back (MenuElem ((*v)->name().empty() ? string_compose ("VCA %1", (*v)->number()) : (*v)->name(), sigc::bind (sigc::mem_fun (*this, &GroupTabs::assign_recenabled_to_master), (*v)->number())));
445 items.push_back (MenuElem (_("Assign Record Enabled to VCA..."), *vca_menu));
447 vca_menu = manage (new Menu);
449 MenuList& f (vca_menu->items());
450 f.push_back (MenuElem ("New", sigc::bind (sigc::mem_fun (*this, &GroupTabs::assign_soloed_to_master), 0)));
451 for (VCAList::const_iterator v = vcas.begin(); v != vcas.end(); ++v) {
452 f.push_back (MenuElem ((*v)->name().empty() ? string_compose ("VCA %1", (*v)->number()) : (*v)->name(), sigc::bind (sigc::mem_fun (*this, &GroupTabs::assign_soloed_to_master), (*v)->number())));
456 items.push_back (MenuElem (_("Assign Soloed to VCA..."), *vca_menu));
458 items.push_back (SeparatorElem());
459 items.push_back (MenuElem (_("Enable All Groups"), sigc::mem_fun(*this, &GroupTabs::activate_all)));
460 items.push_back (MenuElem (_("Disable All Groups"), sigc::mem_fun(*this, &GroupTabs::disable_all)));
466 GroupTabs::assign_group_to_master (uint32_t which, RouteGroup* group, bool rename_master) const
468 if (!_session || !group) {
472 boost::shared_ptr<VCA> master;
475 if (_session->vca_manager().create_vca (1).empty ()) {
480 /* Get most recently created VCA... */
481 which = _session->vca_manager().vcas().back()->number();
484 master = _session->vca_manager().vca_by_number (which);
487 /* should never happen; if it does, basically something deeply
488 odd happened, no reason to tell user because there's no
489 sensible explanation.
494 group->assign_master (master);
497 master->set_name (group->name());
502 GroupTabs::unassign_group_to_master (uint32_t which, RouteGroup* group) const
504 if (!_session || !group) {
508 boost::shared_ptr<VCA> master = _session->vca_manager().vca_by_number (which);
511 /* should never happen; if it does, basically something deeply
512 odd happened, no reason to tell user because there's no
513 sensible explanation.
518 group->unassign_master (master);
522 GroupTabs::assign_some_to_master (uint32_t which, RouteList rl, std::string vcaname)
528 boost::shared_ptr<VCA> master;
529 bool set_name = false;
532 if (_session->vca_manager().create_vca (1).empty ()) {
538 /* Get most recently created VCA... */
539 which = _session->vca_manager().vcas().back()->number();
542 master = _session->vca_manager().vca_by_number (which);
545 /* should never happen; if it does, basically something deeply
546 odd happened, no reason to tell user because there's no
547 sensible explanation.
557 for (RouteList::iterator r = rl.begin(); r != rl.end(); ++r) {
558 (*r)->assign (master);
560 if (set_name && !vcaname.empty()) {
561 master->set_name (vcaname);
566 GroupTabs::get_rec_enabled ()
568 RouteList rec_enabled;
574 boost::shared_ptr<RouteList> rl = _session->get_routes ();
576 for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
577 boost::shared_ptr<Track> trk (boost::dynamic_pointer_cast<Track> (*i));
578 if (trk && trk->rec_enable_control()->get_value()) {
579 rec_enabled.push_back (*i);
588 GroupTabs::get_soloed ()
590 RouteList rl = _session->get_routelist ();
593 for (RouteList::iterator i = rl.begin(); i != rl.end(); ++i) {
594 if (!(*i)->is_master() && (*i)->soloed()) {
595 soloed.push_back (*i);
603 GroupTabs::assign_selection_to_master (uint32_t which)
605 assign_some_to_master (which, selected_routes (), _("Selection"));
609 GroupTabs::assign_recenabled_to_master (uint32_t which)
611 assign_some_to_master (which, get_rec_enabled());
615 GroupTabs::assign_soloed_to_master (uint32_t which)
617 assign_some_to_master (which, get_soloed());
621 GroupTabs::new_from_selection (bool with_master)
623 RouteList rl (selected_routes());
624 run_new_group_dialog (&rl, with_master);
628 GroupTabs::new_from_rec_enabled (bool with_master)
630 RouteList rl (get_rec_enabled());
631 run_new_group_dialog (&rl, with_master);
635 GroupTabs::new_from_soloed (bool with_master)
637 RouteList rl (get_soloed());
638 run_new_group_dialog (&rl, with_master);
642 GroupTabs::run_new_group_dialog (RouteList const * rl, bool with_master)
644 if (rl && rl->empty()) {
648 RouteGroup* g = new RouteGroup (*_session, "");
649 RouteGroupDialog* d = new RouteGroupDialog (g, true);
651 d->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &GroupTabs::new_group_dialog_finished), d, rl ? new RouteList (*rl): 0, with_master));
656 GroupTabs::new_group_dialog_finished (int r, RouteGroupDialog* d, RouteList const * rl, bool with_master) const
658 if (r == RESPONSE_OK) {
660 if (!d->name_check()) {
664 _session->add_route_group (d->group());
667 for (RouteList::const_iterator i = rl->begin(); i != rl->end(); ++i) {
668 d->group()->add (*i);
672 assign_group_to_master (0, d->group(), true); /* zero => new master */
680 delete_when_idle (d);
684 GroupTabs::edit_group (RouteGroup* g)
686 RouteGroupDialog* d = new RouteGroupDialog (g, false);
687 d->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &GroupTabs::edit_group_dialog_finished), d));
692 GroupTabs::edit_group_dialog_finished (int r, RouteGroupDialog* d) const
694 delete_when_idle (d);
698 GroupTabs::subgroup (RouteGroup* g, bool aux, Placement placement)
700 g->make_subgroup (aux, placement);
704 GroupTabs::un_subgroup (RouteGroup* g)
706 g->destroy_subgroup ();
709 /** Collect all members of a RouteGroup so that they are together in the Editor or Mixer.
710 * @param g Group to collect.
713 GroupTabs::collect (RouteGroup* g)
715 boost::shared_ptr<RouteList> group_routes = g->route_list ();
716 group_routes->sort (Stripable::Sorter());
717 int const N = group_routes->size ();
719 RouteList::iterator i = group_routes->begin ();
720 RouteList routes = _session->get_routelist ();
721 routes.sort (Stripable::Sorter());
722 RouteList::const_iterator j = routes.begin ();
727 PresentationInfo::ChangeSuspender cs;
729 while (i != group_routes->end() && j != routes.end()) {
731 PresentationInfo::order_t const k = (*j)->presentation_info ().order();
742 (*j)->set_presentation_order (coll);
749 (*j)->set_presentation_order (k + diff);
758 GroupTabs::activate_all ()
760 _session->foreach_route_group (
761 sigc::bind (sigc::mem_fun (*this, &GroupTabs::set_activation), true)
766 GroupTabs::disable_all ()
768 _session->foreach_route_group (
769 sigc::bind (sigc::mem_fun (*this, &GroupTabs::set_activation), false)
774 GroupTabs::set_activation (RouteGroup* g, bool a)
776 g->set_active (a, this);
780 GroupTabs::remove_group (RouteGroup* g)
782 boost::shared_ptr<RouteList> rl (g->route_list ());
783 _session->remove_route_group (*g);
785 emit_gui_changed_for_members (rl);
788 /** Set the color of the tab of a route group */
790 GroupTabs::set_group_color (RouteGroup* group, uint32_t color)
793 PresentationInfo::ChangeSuspender cs;
794 group->set_rgba (color);
797 /** @return the ID string to use for the GUI state of a route group */
799 GroupTabs::group_gui_id (RouteGroup* group)
804 snprintf (buf, sizeof (buf), "route_group %s", group->id().to_s().c_str ());
809 /** @return the color to use for a route group tab */
811 GroupTabs::group_color (RouteGroup* group)
815 /* prefer libardour color, if set */
816 uint32_t rgba = group->rgba ();
821 /* backwards compatibility, load old color */
823 GUIObjectState& gui_state = *ARDOUR_UI::instance()->gui_object_state;
824 string const gui_id = group_gui_id (group);
826 string const color = gui_state.get_string (gui_id, "color", &empty);
829 /* no color has yet been set, so use a random one */
830 uint32_t c = gdk_color_to_rgba (unique_random_color (_used_colors));
831 set_group_color (group, c);
837 /* for historical reasons, colors are stored as 16 bit values. */
839 sscanf (color.c_str(), "%d:%d:%d", &r, &g, &b);
845 group->migrate_rgba (RGBA_TO_UINT (r, g, b, 255));
846 gui_state.remove_node (gui_id);
848 return RGBA_TO_UINT (r, g, b, 255);
852 GroupTabs::route_group_property_changed (RouteGroup* rg)
854 /* This is a bit of a hack, but this might change
855 our route's effective color, so emit gui_changed
859 emit_gui_changed_for_members (rg->route_list ());
865 GroupTabs::route_added_to_route_group (RouteGroup*, boost::weak_ptr<Route> w)
867 /* Similarly-spirited hack as in route_group_property_changed */
869 boost::shared_ptr<Route> r = w.lock ();
874 r->presentation_info().PropertyChanged (Properties::color);
880 GroupTabs::route_removed_from_route_group (RouteGroup*, boost::weak_ptr<Route> w)
882 /* Similarly-spirited hack as in route_group_property_changed */
884 boost::shared_ptr<Route> r = w.lock ();
889 r->presentation_info().PropertyChanged (Properties::color);
895 GroupTabs::emit_gui_changed_for_members (boost::shared_ptr<RouteList> rl)
897 PresentationInfo::ChangeSuspender cs;
899 for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
900 (*i)->presentation_info().PropertyChanged (Properties::color);