13
0
livetrax/gtk2_ardour/group_tabs.cc
Paul Davis 653ae4acd6 universal change in the design of the way Route/Track controls are designed and used. The controls now own their own state, rather than proxy for state in their owners.
Massive changes all over the code to accomodate this. Many things are not finished. Consider this a backup safety commit
2016-05-31 15:30:40 -04:00

675 lines
15 KiB
C++

/*
Copyright (C) 2009 Paul Davis
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <gtkmm/stock.h>
#include "ardour/session.h"
#include "ardour/route_group.h"
#include "ardour/route.h"
#include "gui_thread.h"
#include "route_group_dialog.h"
#include "group_tabs.h"
#include "keyboard.h"
#include "i18n.h"
#include "ardour_ui.h"
#include "rgb_macros.h"
#include "ui_config.h"
#include "utils.h"
using namespace std;
using namespace Gtk;
using namespace ARDOUR;
using namespace ARDOUR_UI_UTILS;
using Gtkmm2ext::Keyboard;
list<Gdk::Color> GroupTabs::_used_colors;
GroupTabs::GroupTabs ()
: _menu (0)
, _dragging (0)
, _dragging_new_tab (0)
{
add_events (Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK|Gdk::POINTER_MOTION_MASK);
UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &GroupTabs::queue_draw));
}
GroupTabs::~GroupTabs ()
{
delete _menu;
}
void
GroupTabs::set_session (Session* s)
{
SessionHandlePtr::set_session (s);
if (_session) {
_session->RouteGroupPropertyChanged.connect (
_session_connections, invalidator (*this), boost::bind (&GroupTabs::route_group_property_changed, this, _1), gui_context()
);
_session->RouteAddedToRouteGroup.connect (
_session_connections, invalidator (*this), boost::bind (&GroupTabs::route_added_to_route_group, this, _1, _2), gui_context()
);
_session->RouteRemovedFromRouteGroup.connect (
_session_connections, invalidator (*this), boost::bind (&GroupTabs::route_removed_from_route_group, this, _1, _2), gui_context()
);
_session->route_group_removed.connect (_session_connections, invalidator (*this), boost::bind (&GroupTabs::set_dirty, this, (cairo_rectangle_t*)0), gui_context());
}
}
/** Handle a size request.
* @param req GTK requisition
*/
void
GroupTabs::on_size_request (Gtk::Requisition *req)
{
/* Use a dummy, small width and the actual height that we want */
req->width = 16;
req->height = 16;
}
bool
GroupTabs::on_button_press_event (GdkEventButton* ev)
{
using namespace Menu_Helpers;
double const p = primary_coordinate (ev->x, ev->y);
list<Tab>::iterator prev;
list<Tab>::iterator next;
Tab* t = click_to_tab (p, &prev, &next);
_drag_min = prev != _tabs.end() ? prev->to : 0;
_drag_max = next != _tabs.end() ? next->from : extent ();
if (ev->button == 1) {
if (t == 0) {
Tab n;
n.from = n.to = p;
_dragging_new_tab = true;
if (next == _tabs.end()) {
_tabs.push_back (n);
t = &_tabs.back ();
} else {
list<Tab>::iterator j = _tabs.insert (next, n);
t = &(*j);
}
} else {
_dragging_new_tab = false;
_initial_dragging_routes = routes_for_tab (t);
}
_dragging = t;
_drag_moved = false;
_drag_first = p;
double const h = (t->from + t->to) / 2;
if (p < h) {
_drag_moving = t->from;
_drag_fixed = t->to;
_drag_offset = p - t->from;
} else {
_drag_moving = t->to;
_drag_fixed = t->from;
_drag_offset = p - t->to;
}
} else if (ev->button == 3) {
RouteGroup* g = t ? t->group : 0;
if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier) && g) {
/* edit */
RouteGroupDialog d (g, false);
d.do_run ();
} else {
Menu* m = get_menu (g, true);
if (m) {
m->popup (ev->button, ev->time);
}
}
}
return true;
}
bool
GroupTabs::on_motion_notify_event (GdkEventMotion* ev)
{
if (_dragging == 0) {
return false;
}
double const p = primary_coordinate (ev->x, ev->y);
if (p != _drag_first) {
_drag_moved = true;
}
_drag_moving = p - _drag_offset;
_dragging->from = min (_drag_moving, _drag_fixed);
_dragging->to = max (_drag_moving, _drag_fixed);
_dragging->from = max (_dragging->from, _drag_min);
_dragging->to = min (_dragging->to, _drag_max);
set_dirty ();
queue_draw ();
gdk_event_request_motions(ev);
return true;
}
bool
GroupTabs::on_button_release_event (GdkEventButton*)
{
if (_dragging == 0) {
return false;
}
if (!_drag_moved) {
if (_dragging->group) {
/* toggle active state */
_dragging->group->set_active (!_dragging->group->is_active (), this);
}
} else {
/* finish drag */
RouteList routes = routes_for_tab (_dragging);
if (!routes.empty()) {
if (_dragging_new_tab) {
RouteGroup* g = create_and_add_group ();
if (g) {
for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) {
g->add (*i);
}
}
} else {
boost::shared_ptr<RouteList> r = _session->get_routes ();
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
bool const was_in_tab = find (
_initial_dragging_routes.begin(), _initial_dragging_routes.end(), *i
) != _initial_dragging_routes.end ();
bool const now_in_tab = find (routes.begin(), routes.end(), *i) != routes.end();
if (was_in_tab && !now_in_tab) {
_dragging->group->remove (*i);
} else if (!was_in_tab && now_in_tab) {
_dragging->group->add (*i);
}
}
}
}
set_dirty ();
queue_draw ();
}
_dragging = 0;
_initial_dragging_routes.clear ();
return true;
}
void
GroupTabs::render (cairo_t* cr, cairo_rectangle_t*)
{
if (_dragging == 0) {
_tabs = compute_tabs ();
}
/* background */
Gdk::Color c = get_style()->get_base (Gtk::STATE_NORMAL);
cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p());
cairo_rectangle (cr, 0, 0, get_width(), get_height());
cairo_fill (cr);
/* tabs */
for (list<Tab>::const_iterator i = _tabs.begin(); i != _tabs.end(); ++i) {
draw_tab (cr, *i);
}
}
/** Convert a click position to a tab.
* @param c Click position.
* @param prev Filled in with the previous tab to the click, or _tabs.end().
* @param next Filled in with the next tab after the click, or _tabs.end().
* @return Tab under the click, or 0.
*/
GroupTabs::Tab *
GroupTabs::click_to_tab (double c, list<Tab>::iterator* prev, list<Tab>::iterator* next)
{
*prev = *next = _tabs.end ();
Tab* under = 0;
list<Tab>::iterator i = _tabs.begin ();
while (i != _tabs.end()) {
if (i->from > c) {
*next = i;
break;
}
if (i->to < c) {
*prev = i;
++i;
continue;
}
if (i->from <= c && c < i->to) {
under = &(*i);
}
++i;
}
return under;
}
Gtk::Menu*
GroupTabs::get_menu (RouteGroup* g, bool TabArea)
{
using namespace Menu_Helpers;
delete _menu;
Menu* new_from = new Menu;
MenuList& f = new_from->items ();
f.push_back (MenuElem (_("Selection..."), sigc::mem_fun (*this, &GroupTabs::new_from_selection)));
f.push_back (MenuElem (_("Record Enabled..."), sigc::mem_fun (*this, &GroupTabs::new_from_rec_enabled)));
f.push_back (MenuElem (_("Soloed..."), sigc::mem_fun (*this, &GroupTabs::new_from_soloed)));
_menu = new Menu;
_menu->set_name ("ArdourContextMenu");
MenuList& items = _menu->items();
if (!TabArea) {
items.push_back (MenuElem (_("Create New Group ..."), hide_return (sigc::mem_fun(*this, &GroupTabs::create_and_add_group))));
}
items.push_back (MenuElem (_("Create New Group From"), *new_from));
if (g) {
items.push_back (MenuElem (_("Edit Group..."), sigc::bind (sigc::mem_fun (*this, &GroupTabs::edit_group), g)));
items.push_back (MenuElem (_("Collect Group"), sigc::bind (sigc::mem_fun (*this, &GroupTabs::collect), g)));
items.push_back (MenuElem (_("Remove Group"), sigc::bind (sigc::mem_fun (*this, &GroupTabs::remove_group), g)));
items.push_back (SeparatorElem());
if (g->has_subgroup()) {
items.push_back (MenuElem (_("Remove Subgroup Bus"), sigc::bind (sigc::mem_fun (*this, &GroupTabs::un_subgroup), g)));
} else {
items.push_back (MenuElem (_("Add New Subgroup Bus"), sigc::bind (sigc::mem_fun (*this, &GroupTabs::subgroup), g, false, PreFader)));
}
items.push_back (MenuElem (_("Add New Aux Bus (pre-fader)"), sigc::bind (sigc::mem_fun (*this, &GroupTabs::subgroup), g, true, PreFader)));
items.push_back (MenuElem (_("Add New Aux Bus (post-fader)"), sigc::bind (sigc::mem_fun (*this, &GroupTabs::subgroup), g, true, PostFader)));
}
add_menu_items (_menu, g);
items.push_back (SeparatorElem());
items.push_back (MenuElem (_("Enable All Groups"), sigc::mem_fun(*this, &GroupTabs::activate_all)));
items.push_back (MenuElem (_("Disable All Groups"), sigc::mem_fun(*this, &GroupTabs::disable_all)));
return _menu;
}
void
GroupTabs::new_from_selection ()
{
RouteList rl = selected_routes ();
if (rl.empty()) {
return;
}
run_new_group_dialog (rl);
}
void
GroupTabs::new_from_rec_enabled ()
{
boost::shared_ptr<RouteList> rl = _session->get_routes ();
RouteList rec_enabled;
for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
boost::shared_ptr<Track> trk (boost::dynamic_pointer_cast<Track> (*i));
if (trk && trk->rec_enable_control()->get_value()) {
rec_enabled.push_back (*i);
}
}
if (rec_enabled.empty()) {
return;
}
run_new_group_dialog (rec_enabled);
}
void
GroupTabs::new_from_soloed ()
{
boost::shared_ptr<RouteList> rl = _session->get_routes ();
RouteList soloed;
for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
if (!(*i)->is_master() && (*i)->soloed()) {
soloed.push_back (*i);
}
}
if (soloed.empty()) {
return;
}
run_new_group_dialog (soloed);
}
void
GroupTabs::run_new_group_dialog (RouteList const & rl)
{
RouteGroup* g = new RouteGroup (*_session, "");
RouteGroupDialog d (g, true);
if (d.do_run ()) {
delete g;
} else {
_session->add_route_group (g);
for (RouteList::const_iterator i = rl.begin(); i != rl.end(); ++i) {
g->add (*i);
}
}
}
RouteGroup *
GroupTabs::create_and_add_group () const
{
RouteGroup* g = new RouteGroup (*_session, "");
RouteGroupDialog d (g, true);
if (d.do_run ()) {
delete g;
return 0;
}
_session->add_route_group (g);
return g;
}
void
GroupTabs::edit_group (RouteGroup* g)
{
RouteGroupDialog d (g, false);
d.do_run ();
}
void
GroupTabs::subgroup (RouteGroup* g, bool aux, Placement placement)
{
g->make_subgroup (aux, placement);
}
void
GroupTabs::un_subgroup (RouteGroup* g)
{
g->destroy_subgroup ();
}
struct CollectSorter {
bool operator () (boost::shared_ptr<Route> a, boost::shared_ptr<Route> b) {
return a->order_key () < b->order_key ();
}
};
struct OrderSorter {
bool operator() (boost::shared_ptr<Route> a, boost::shared_ptr<Route> b) {
return a->order_key () < b->order_key ();
}
};
/** Collect all members of a RouteGroup so that they are together in the Editor or Mixer.
* @param g Group to collect.
*/
void
GroupTabs::collect (RouteGroup* g)
{
boost::shared_ptr<RouteList> group_routes = g->route_list ();
group_routes->sort (CollectSorter ());
int const N = group_routes->size ();
RouteList::iterator i = group_routes->begin ();
boost::shared_ptr<RouteList> routes = _session->get_routes ();
routes->sort (OrderSorter ());
RouteList::const_iterator j = routes->begin ();
int diff = 0;
int coll = -1;
while (i != group_routes->end() && j != routes->end()) {
int const k = (*j)->order_key ();
if (*i == *j) {
if (coll == -1) {
coll = k;
diff = N - 1;
} else {
--diff;
}
(*j)->set_order_key (coll);
++coll;
++i;
} else {
(*j)->set_order_key (k + diff);
}
++j;
}
_session->sync_order_keys ();
}
void
GroupTabs::activate_all ()
{
_session->foreach_route_group (
sigc::bind (sigc::mem_fun (*this, &GroupTabs::set_activation), true)
);
}
void
GroupTabs::disable_all ()
{
_session->foreach_route_group (
sigc::bind (sigc::mem_fun (*this, &GroupTabs::set_activation), false)
);
}
void
GroupTabs::set_activation (RouteGroup* g, bool a)
{
g->set_active (a, this);
}
void
GroupTabs::remove_group (RouteGroup* g)
{
_session->remove_route_group (*g);
}
/** Set the color of the tab of a route group */
void
GroupTabs::set_group_color (RouteGroup* group, uint32_t color)
{
assert (group);
uint32_t r, g, b, a;
UINT_TO_RGBA (color, &r, &g, &b, &a);
/* Hack to disallow black route groups; force a dark grey instead */
if (r == 0 && g == 0 && b == 0) {
r = 25;
g = 25;
b = 25;
}
GUIObjectState& gui_state = *ARDOUR_UI::instance()->gui_object_state;
char buf[64];
/* for historical reasons the colors must be stored as 16 bit color
* values. Ugh.
*/
snprintf (buf, sizeof (buf), "%d:%d:%d", (r<<8), (g<<8), (b<<8));
gui_state.set_property (group_gui_id (group), "color", buf);
/* the group color change notification */
PBD::PropertyChange change;
change.add (Properties::color);
group->PropertyChanged (change);
/* This is a bit of a hack, but this might change
our route's effective color, so emit gui_changed
for our routes.
*/
emit_gui_changed_for_members (group);
}
/** @return the ID string to use for the GUI state of a route group */
string
GroupTabs::group_gui_id (RouteGroup* group)
{
assert (group);
char buf[64];
snprintf (buf, sizeof (buf), "route_group %s", group->id().to_s().c_str ());
return buf;
}
/** @return the color to use for a route group tab */
uint32_t
GroupTabs::group_color (RouteGroup* group)
{
assert (group);
GUIObjectState& gui_state = *ARDOUR_UI::instance()->gui_object_state;
string const gui_id = group_gui_id (group);
bool empty;
string const color = gui_state.get_string (gui_id, "color", &empty);
if (empty) {
/* no color has yet been set, so use a random one */
uint32_t c = gdk_color_to_rgba (unique_random_color (_used_colors));
set_group_color (group, c);
return c;
}
int r, g, b;
/* for historical reasons, colors are stored as 16 bit values.
*/
sscanf (color.c_str(), "%d:%d:%d", &r, &g, &b);
r /= 256;
g /= 256;
b /= 256;
return RGBA_TO_UINT (r, g, b, 255);
}
void
GroupTabs::route_group_property_changed (RouteGroup* rg)
{
/* This is a bit of a hack, but this might change
our route's effective color, so emit gui_changed
for our routes.
*/
emit_gui_changed_for_members (rg);
set_dirty ();
}
void
GroupTabs::route_added_to_route_group (RouteGroup*, boost::weak_ptr<Route> w)
{
/* Similarly-spirited hack as in route_group_property_changed */
boost::shared_ptr<Route> r = w.lock ();
if (!r) {
return;
}
r->gui_changed (X_("color"), 0);
set_dirty ();
}
void
GroupTabs::route_removed_from_route_group (RouteGroup*, boost::weak_ptr<Route> w)
{
/* Similarly-spirited hack as in route_group_property_changed */
boost::shared_ptr<Route> r = w.lock ();
if (!r) {
return;
}
r->gui_changed (X_("color"), 0);
set_dirty ();
}
void
GroupTabs::emit_gui_changed_for_members (RouteGroup* rg)
{
for (RouteList::iterator i = rg->route_list()->begin(); i != rg->route_list()->end(); ++i) {
(*i)->gui_changed (X_("color"), 0);
}
}