Paul Davis
5d6dc388f7
The code relied on the idea that the order-key resync that occurs after deletion would change the order keys and thus cause a redisplay. But since both the editor and mixer can initiate an order-key resync, the other window's resync will actually do nothing (the order keys will already be correct). This led to the incorrect placement of material in the tracks canvas, because the first resync triggered a redisplay while the route still existed, and then the second resync didn't cause a redisplay (repositioning) but the canvas elements representing the track went away. Fixed by forcing a redisplay in both editor and mixer if a route deletion is believed to be triggering a row deletion in their underlying data models.
1993 lines
47 KiB
C++
1993 lines
47 KiB
C++
/*
|
|
Copyright (C) 2000-2004 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.
|
|
|
|
*/
|
|
|
|
#ifdef WAF_BUILD
|
|
#include "gtk2ardour-config.h"
|
|
#endif
|
|
|
|
#include <algorithm>
|
|
#include <map>
|
|
#include <sigc++/bind.h>
|
|
|
|
#include <gtkmm/accelmap.h>
|
|
|
|
#include "pbd/convert.h"
|
|
#include "pbd/unwind.h"
|
|
|
|
#include <glibmm/threads.h>
|
|
|
|
#include <gtkmm2ext/gtk_ui.h>
|
|
#include <gtkmm2ext/utils.h>
|
|
#include <gtkmm2ext/tearoff.h>
|
|
#include <gtkmm2ext/window_title.h>
|
|
|
|
#include "ardour/debug.h"
|
|
#include "ardour/midi_track.h"
|
|
#include "ardour/plugin_manager.h"
|
|
#include "ardour/route_group.h"
|
|
#include "ardour/session.h"
|
|
|
|
#include "keyboard.h"
|
|
#include "mixer_ui.h"
|
|
#include "mixer_strip.h"
|
|
#include "monitor_section.h"
|
|
#include "plugin_selector.h"
|
|
#include "public_editor.h"
|
|
#include "ardour_ui.h"
|
|
#include "prompter.h"
|
|
#include "utils.h"
|
|
#include "route_sorter.h"
|
|
#include "actions.h"
|
|
#include "gui_thread.h"
|
|
#include "mixer_group_tabs.h"
|
|
|
|
#include "i18n.h"
|
|
|
|
using namespace ARDOUR;
|
|
using namespace PBD;
|
|
using namespace Gtk;
|
|
using namespace Glib;
|
|
using namespace Gtkmm2ext;
|
|
using namespace std;
|
|
|
|
using PBD::atoi;
|
|
using PBD::Unwinder;
|
|
|
|
Mixer_UI* Mixer_UI::_instance = 0;
|
|
|
|
Mixer_UI*
|
|
Mixer_UI::instance ()
|
|
{
|
|
if (!_instance) {
|
|
_instance = new Mixer_UI;
|
|
}
|
|
|
|
return _instance;
|
|
}
|
|
|
|
Mixer_UI::Mixer_UI ()
|
|
: Window (Gtk::WINDOW_TOPLEVEL)
|
|
, VisibilityTracker (*((Gtk::Window*) this))
|
|
, _visible (false)
|
|
, no_track_list_redisplay (false)
|
|
, in_group_row_change (false)
|
|
, track_menu (0)
|
|
, _monitor_section (0)
|
|
, _strip_width (Config->get_default_narrow_ms() ? Narrow : Wide)
|
|
, ignore_reorder (false)
|
|
, _following_editor_selection (false)
|
|
, _in_group_rebuild_or_clear (false)
|
|
, _route_deletion_in_progress (false)
|
|
, _maximised (false)
|
|
{
|
|
/* allow this window to become the key focus window */
|
|
set_flags (CAN_FOCUS);
|
|
|
|
Route::SyncOrderKeys.connect (*this, invalidator (*this), boost::bind (&Mixer_UI::sync_treeview_from_order_keys, this), gui_context());
|
|
|
|
scroller.set_can_default (true);
|
|
set_default (scroller);
|
|
|
|
scroller_base.set_flags (Gtk::CAN_FOCUS);
|
|
scroller_base.add_events (Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK);
|
|
scroller_base.set_name ("MixerWindow");
|
|
scroller_base.signal_button_release_event().connect (sigc::mem_fun(*this, &Mixer_UI::strip_scroller_button_release));
|
|
// add as last item of strip packer
|
|
strip_packer.pack_end (scroller_base, true, true);
|
|
|
|
_group_tabs = new MixerGroupTabs (this);
|
|
VBox* b = manage (new VBox);
|
|
b->pack_start (*_group_tabs, PACK_SHRINK);
|
|
b->pack_start (strip_packer);
|
|
b->show_all ();
|
|
|
|
scroller.add (*b);
|
|
scroller.set_policy (Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
|
|
|
|
setup_track_display ();
|
|
|
|
group_model = ListStore::create (group_columns);
|
|
group_display.set_model (group_model);
|
|
group_display.append_column (_("Group"), group_columns.text);
|
|
group_display.append_column (_("Show"), group_columns.visible);
|
|
group_display.get_column (0)->set_data (X_("colnum"), GUINT_TO_POINTER(0));
|
|
group_display.get_column (1)->set_data (X_("colnum"), GUINT_TO_POINTER(1));
|
|
group_display.get_column (0)->set_expand(true);
|
|
group_display.get_column (1)->set_expand(false);
|
|
group_display.set_name ("EditGroupList");
|
|
group_display.get_selection()->set_mode (Gtk::SELECTION_SINGLE);
|
|
group_display.set_reorderable (true);
|
|
group_display.set_headers_visible (true);
|
|
group_display.set_rules_hint (true);
|
|
|
|
/* name is directly editable */
|
|
|
|
CellRendererText* name_cell = dynamic_cast<CellRendererText*>(group_display.get_column_cell_renderer (0));
|
|
name_cell->property_editable() = true;
|
|
name_cell->signal_edited().connect (sigc::mem_fun (*this, &Mixer_UI::route_group_name_edit));
|
|
|
|
/* use checkbox for the active column */
|
|
|
|
CellRendererToggle* active_cell = dynamic_cast<CellRendererToggle*>(group_display.get_column_cell_renderer (1));
|
|
active_cell->property_activatable() = true;
|
|
active_cell->property_radio() = false;
|
|
|
|
group_model->signal_row_changed().connect (sigc::mem_fun (*this, &Mixer_UI::route_group_row_change));
|
|
/* We use this to notice drag-and-drop reorders of the group list */
|
|
group_model->signal_row_deleted().connect (sigc::mem_fun (*this, &Mixer_UI::route_group_row_deleted));
|
|
group_display.signal_button_press_event().connect (sigc::mem_fun (*this, &Mixer_UI::group_display_button_press), false);
|
|
|
|
group_display_scroller.add (group_display);
|
|
group_display_scroller.set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
|
|
|
|
HBox* route_group_display_button_box = manage (new HBox());
|
|
|
|
Button* route_group_add_button = manage (new Button ());
|
|
Button* route_group_remove_button = manage (new Button ());
|
|
|
|
Widget* w;
|
|
|
|
w = manage (new Image (Stock::ADD, ICON_SIZE_BUTTON));
|
|
w->show();
|
|
route_group_add_button->add (*w);
|
|
|
|
w = manage (new Image (Stock::REMOVE, ICON_SIZE_BUTTON));
|
|
w->show();
|
|
route_group_remove_button->add (*w);
|
|
|
|
route_group_display_button_box->set_homogeneous (true);
|
|
|
|
route_group_add_button->signal_clicked().connect (sigc::mem_fun (*this, &Mixer_UI::new_route_group));
|
|
route_group_remove_button->signal_clicked().connect (sigc::mem_fun (*this, &Mixer_UI::remove_selected_route_group));
|
|
|
|
route_group_display_button_box->add (*route_group_add_button);
|
|
route_group_display_button_box->add (*route_group_remove_button);
|
|
|
|
group_display_vbox.pack_start (group_display_scroller, true, true);
|
|
group_display_vbox.pack_start (*route_group_display_button_box, false, false);
|
|
|
|
group_display_frame.set_name ("BaseFrame");
|
|
group_display_frame.set_shadow_type (Gtk::SHADOW_IN);
|
|
group_display_frame.add (group_display_vbox);
|
|
|
|
rhs_pane1.pack1 (track_display_frame);
|
|
rhs_pane1.pack2 (group_display_frame);
|
|
|
|
list_vpacker.pack_start (rhs_pane1, true, true);
|
|
|
|
global_hpacker.pack_start (scroller, true, true);
|
|
#ifdef GTKOSX
|
|
/* current gtk-quartz has dirty updates on borders like this one */
|
|
global_hpacker.pack_start (out_packer, false, false, 0);
|
|
#else
|
|
global_hpacker.pack_start (out_packer, false, false, 12);
|
|
#endif
|
|
list_hpane.pack1(list_vpacker, true, true);
|
|
list_hpane.pack2(global_hpacker, true, false);
|
|
|
|
rhs_pane1.signal_size_allocate().connect (sigc::bind (sigc::mem_fun(*this, &Mixer_UI::pane_allocation_handler),
|
|
static_cast<Gtk::Paned*> (&rhs_pane1)));
|
|
list_hpane.signal_size_allocate().connect (sigc::bind (sigc::mem_fun(*this, &Mixer_UI::pane_allocation_handler),
|
|
static_cast<Gtk::Paned*> (&list_hpane)));
|
|
|
|
global_vpacker.pack_start (list_hpane, true, true);
|
|
|
|
add (global_vpacker);
|
|
set_name ("MixerWindow");
|
|
|
|
update_title ();
|
|
|
|
set_wmclass (X_("ardour_mixer"), PROGRAM_NAME);
|
|
|
|
signal_delete_event().connect (sigc::mem_fun (*this, &Mixer_UI::hide_window));
|
|
add_events (Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK);
|
|
|
|
signal_configure_event().connect (sigc::mem_fun (*ARDOUR_UI::instance(), &ARDOUR_UI::configure_handler));
|
|
|
|
route_group_display_button_box->show();
|
|
route_group_add_button->show();
|
|
route_group_remove_button->show();
|
|
|
|
global_hpacker.show();
|
|
global_vpacker.show();
|
|
scroller.show();
|
|
scroller_base.show();
|
|
scroller_hpacker.show();
|
|
mixer_scroller_vpacker.show();
|
|
list_vpacker.show();
|
|
group_display_button_label.show();
|
|
group_display_button.show();
|
|
group_display_scroller.show();
|
|
group_display_vbox.show();
|
|
group_display_frame.show();
|
|
rhs_pane1.show();
|
|
strip_packer.show();
|
|
out_packer.show();
|
|
list_hpane.show();
|
|
group_display.show();
|
|
|
|
MixerStrip::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&Mixer_UI::remove_strip, this, _1), gui_context());
|
|
|
|
MonitorSection::setup_knob_images ();
|
|
|
|
#ifndef DEFER_PLUGIN_SELECTOR_LOAD
|
|
_plugin_selector = new PluginSelector (PluginManager::instance ());
|
|
#endif
|
|
}
|
|
|
|
Mixer_UI::~Mixer_UI ()
|
|
{
|
|
if (_monitor_section) {
|
|
delete _monitor_section;
|
|
}
|
|
}
|
|
|
|
void
|
|
Mixer_UI::track_editor_selection ()
|
|
{
|
|
PublicEditor::instance().get_selection().TracksChanged.connect (sigc::mem_fun (*this, &Mixer_UI::follow_editor_selection));
|
|
}
|
|
|
|
|
|
void
|
|
Mixer_UI::ensure_float (Window& win)
|
|
{
|
|
win.set_transient_for (*this);
|
|
}
|
|
|
|
void
|
|
Mixer_UI::show_window ()
|
|
{
|
|
present ();
|
|
if (!_visible) {
|
|
set_window_pos_and_size ();
|
|
|
|
/* show/hide group tabs as required */
|
|
parameter_changed ("show-group-tabs");
|
|
|
|
/* now reset each strips width so the right widgets are shown */
|
|
MixerStrip* ms;
|
|
|
|
TreeModel::Children rows = track_model->children();
|
|
TreeModel::Children::iterator ri;
|
|
|
|
for (ri = rows.begin(); ri != rows.end(); ++ri) {
|
|
ms = (*ri)[track_columns.strip];
|
|
ms->set_width_enum (ms->get_width_enum (), ms->width_owner());
|
|
/* Fix visibility of mixer strip stuff */
|
|
ms->parameter_changed (X_("mixer-strip-visibility"));
|
|
}
|
|
}
|
|
|
|
/* force focus into main area */
|
|
scroller_base.grab_focus ();
|
|
|
|
_visible = true;
|
|
}
|
|
|
|
bool
|
|
Mixer_UI::hide_window (GdkEventAny *ev)
|
|
{
|
|
get_window_pos_and_size ();
|
|
|
|
_visible = false;
|
|
return just_hide_it(ev, static_cast<Gtk::Window *>(this));
|
|
}
|
|
|
|
|
|
void
|
|
Mixer_UI::add_strips (RouteList& routes)
|
|
{
|
|
bool from_scratch = track_model->children().size() == 0;
|
|
Gtk::TreeModel::Children::iterator insert_iter = track_model->children().end();
|
|
|
|
for (Gtk::TreeModel::Children::iterator it = track_model->children().begin(); it != track_model->children().end(); ++it) {
|
|
boost::shared_ptr<Route> r = (*it)[track_columns.route];
|
|
|
|
if (r->order_key() == (routes.front()->order_key() + routes.size())) {
|
|
insert_iter = it;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!from_scratch) {
|
|
_selection.clear_routes ();
|
|
}
|
|
|
|
MixerStrip* strip;
|
|
|
|
try {
|
|
no_track_list_redisplay = true;
|
|
track_display.set_model (Glib::RefPtr<ListStore>());
|
|
|
|
for (RouteList::iterator x = routes.begin(); x != routes.end(); ++x) {
|
|
boost::shared_ptr<Route> route = (*x);
|
|
|
|
if (route->is_auditioner()) {
|
|
continue;
|
|
}
|
|
|
|
if (route->is_monitor()) {
|
|
|
|
if (!_monitor_section) {
|
|
_monitor_section = new MonitorSection (_session);
|
|
|
|
XMLNode* mnode = ARDOUR_UI::instance()->tearoff_settings (X_("monitor-section"));
|
|
if (mnode) {
|
|
_monitor_section->tearoff().set_state (*mnode);
|
|
}
|
|
}
|
|
|
|
out_packer.pack_end (_monitor_section->tearoff(), false, false);
|
|
_monitor_section->set_session (_session);
|
|
_monitor_section->tearoff().show_all ();
|
|
|
|
route->DropReferences.connect (*this, invalidator(*this), boost::bind (&Mixer_UI::monitor_section_going_away, this), gui_context());
|
|
|
|
/* no regular strip shown for control out */
|
|
|
|
continue;
|
|
}
|
|
|
|
strip = new MixerStrip (*this, _session, route);
|
|
strips.push_back (strip);
|
|
|
|
Config->get_default_narrow_ms() ? _strip_width = Narrow : _strip_width = Wide;
|
|
|
|
if (strip->width_owner() != strip) {
|
|
strip->set_width_enum (_strip_width, this);
|
|
}
|
|
|
|
show_strip (strip);
|
|
|
|
TreeModel::Row row = *(track_model->insert(insert_iter));
|
|
row[track_columns.text] = route->name();
|
|
row[track_columns.visible] = strip->route()->is_master() ? true : strip->marked_for_display();
|
|
row[track_columns.route] = route;
|
|
row[track_columns.strip] = strip;
|
|
|
|
if (!from_scratch) {
|
|
_selection.add (strip);
|
|
}
|
|
|
|
route->PropertyChanged.connect (*this, invalidator (*this), boost::bind (&Mixer_UI::strip_property_changed, this, _1, strip), gui_context());
|
|
|
|
strip->WidthChanged.connect (sigc::mem_fun(*this, &Mixer_UI::strip_width_changed));
|
|
strip->signal_button_release_event().connect (sigc::bind (sigc::mem_fun(*this, &Mixer_UI::strip_button_release_event), strip));
|
|
}
|
|
|
|
} catch (...) {
|
|
}
|
|
|
|
no_track_list_redisplay = false;
|
|
track_display.set_model (track_model);
|
|
|
|
sync_order_keys_from_treeview ();
|
|
redisplay_track_list ();
|
|
}
|
|
|
|
void
|
|
Mixer_UI::remove_strip (MixerStrip* strip)
|
|
{
|
|
if (_session && _session->deletion_in_progress()) {
|
|
/* its all being taken care of */
|
|
return;
|
|
}
|
|
|
|
TreeModel::Children rows = track_model->children();
|
|
TreeModel::Children::iterator ri;
|
|
list<MixerStrip *>::iterator i;
|
|
|
|
if ((i = find (strips.begin(), strips.end(), strip)) != strips.end()) {
|
|
strips.erase (i);
|
|
}
|
|
|
|
for (ri = rows.begin(); ri != rows.end(); ++ri) {
|
|
if ((*ri)[track_columns.strip] == strip) {
|
|
PBD::Unwinder<bool> uw (_route_deletion_in_progress, true);
|
|
track_model->erase (ri);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
Mixer_UI::reset_remote_control_ids ()
|
|
{
|
|
if (Config->get_remote_model() == UserOrdered || !_session || _session->deletion_in_progress()) {
|
|
return;
|
|
}
|
|
|
|
TreeModel::Children rows = track_model->children();
|
|
|
|
if (rows.empty()) {
|
|
return;
|
|
}
|
|
|
|
DEBUG_TRACE (DEBUG::OrderKeys, "mixer resets remote control ids after remote model change\n");
|
|
|
|
TreeModel::Children::iterator ri;
|
|
bool rid_change = false;
|
|
uint32_t rid = 1;
|
|
uint32_t invisible_key = UINT32_MAX;
|
|
|
|
for (ri = rows.begin(); ri != rows.end(); ++ri) {
|
|
boost::shared_ptr<Route> route = (*ri)[track_columns.route];
|
|
bool visible = (*ri)[track_columns.visible];
|
|
|
|
if (!route->is_master() && !route->is_monitor()) {
|
|
|
|
uint32_t new_rid = (visible ? rid : invisible_key--);
|
|
|
|
if (new_rid != route->remote_control_id()) {
|
|
route->set_remote_control_id_explicit (new_rid);
|
|
rid_change = true;
|
|
}
|
|
|
|
if (visible) {
|
|
rid++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (rid_change) {
|
|
/* tell the world that we changed the remote control IDs */
|
|
_session->notify_remote_id_change ();
|
|
}
|
|
}
|
|
|
|
void
|
|
Mixer_UI::sync_order_keys_from_treeview ()
|
|
{
|
|
if (ignore_reorder || !_session || _session->deletion_in_progress()) {
|
|
return;
|
|
}
|
|
|
|
TreeModel::Children rows = track_model->children();
|
|
|
|
if (rows.empty()) {
|
|
return;
|
|
}
|
|
|
|
DEBUG_TRACE (DEBUG::OrderKeys, "mixer sync order keys from model\n");
|
|
|
|
TreeModel::Children::iterator ri;
|
|
bool changed = false;
|
|
bool rid_change = false;
|
|
uint32_t order = 0;
|
|
uint32_t rid = 1;
|
|
uint32_t invisible_key = UINT32_MAX;
|
|
|
|
for (ri = rows.begin(); ri != rows.end(); ++ri) {
|
|
boost::shared_ptr<Route> route = (*ri)[track_columns.route];
|
|
bool visible = (*ri)[track_columns.visible];
|
|
|
|
uint32_t old_key = route->order_key ();
|
|
|
|
if (order != old_key) {
|
|
route->set_order_key (order);
|
|
changed = true;
|
|
}
|
|
|
|
if ((Config->get_remote_model() == MixerOrdered) && !route->is_master() && !route->is_monitor()) {
|
|
|
|
uint32_t new_rid = (visible ? rid : invisible_key--);
|
|
|
|
if (new_rid != route->remote_control_id()) {
|
|
route->set_remote_control_id_explicit (new_rid);
|
|
rid_change = true;
|
|
}
|
|
|
|
if (visible) {
|
|
rid++;
|
|
}
|
|
|
|
}
|
|
|
|
++order;
|
|
}
|
|
|
|
if (changed) {
|
|
/* tell everyone that we changed the mixer sort keys */
|
|
_session->sync_order_keys ();
|
|
}
|
|
|
|
if (rid_change) {
|
|
/* tell the world that we changed the remote control IDs */
|
|
_session->notify_remote_id_change ();
|
|
}
|
|
}
|
|
|
|
void
|
|
Mixer_UI::sync_treeview_from_order_keys ()
|
|
{
|
|
if (!_session || _session->deletion_in_progress()) {
|
|
return;
|
|
}
|
|
|
|
DEBUG_TRACE (DEBUG::OrderKeys, "mixer sync model from order keys.\n");
|
|
|
|
/* we could get here after either a change in the Mixer or Editor sort
|
|
* order, but either way, the mixer order keys reflect the intended
|
|
* order for the GUI, so reorder the treeview model to match it.
|
|
*/
|
|
|
|
vector<int> neworder;
|
|
TreeModel::Children rows = track_model->children();
|
|
uint32_t old_order = 0;
|
|
bool changed = false;
|
|
|
|
if (rows.empty()) {
|
|
return;
|
|
}
|
|
|
|
OrderKeySortedRoutes sorted_routes;
|
|
|
|
for (TreeModel::Children::iterator ri = rows.begin(); ri != rows.end(); ++ri, ++old_order) {
|
|
boost::shared_ptr<Route> route = (*ri)[track_columns.route];
|
|
sorted_routes.push_back (RoutePlusOrderKey (route, old_order, route->order_key ()));
|
|
}
|
|
|
|
SortByNewDisplayOrder cmp;
|
|
|
|
sort (sorted_routes.begin(), sorted_routes.end(), cmp);
|
|
neworder.assign (sorted_routes.size(), 0);
|
|
|
|
uint32_t n = 0;
|
|
|
|
for (OrderKeySortedRoutes::iterator sr = sorted_routes.begin(); sr != sorted_routes.end(); ++sr, ++n) {
|
|
|
|
neworder[n] = sr->old_display_order;
|
|
|
|
if (sr->old_display_order != n) {
|
|
changed = true;
|
|
}
|
|
|
|
DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("MIXER change order for %1 from %2 to %3\n",
|
|
sr->route->name(), sr->old_display_order, n));
|
|
}
|
|
|
|
if (changed) {
|
|
Unwinder<bool> uw (ignore_reorder, true);
|
|
track_model->reorder (neworder);
|
|
}
|
|
|
|
redisplay_track_list ();
|
|
}
|
|
|
|
void
|
|
Mixer_UI::follow_editor_selection ()
|
|
{
|
|
if (!Config->get_link_editor_and_mixer_selection() || _following_editor_selection) {
|
|
return;
|
|
}
|
|
|
|
_following_editor_selection = true;
|
|
_selection.block_routes_changed (true);
|
|
|
|
TrackSelection& s (PublicEditor::instance().get_selection().tracks);
|
|
|
|
_selection.clear_routes ();
|
|
|
|
for (TrackViewList::iterator i = s.begin(); i != s.end(); ++i) {
|
|
RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (*i);
|
|
if (rtav) {
|
|
MixerStrip* ms = strip_by_route (rtav->route());
|
|
if (ms) {
|
|
_selection.add (ms);
|
|
}
|
|
}
|
|
}
|
|
|
|
_following_editor_selection = false;
|
|
_selection.block_routes_changed (false);
|
|
}
|
|
|
|
|
|
MixerStrip*
|
|
Mixer_UI::strip_by_route (boost::shared_ptr<Route> r)
|
|
{
|
|
for (list<MixerStrip *>::iterator i = strips.begin(); i != strips.end(); ++i) {
|
|
if ((*i)->route() == r) {
|
|
return (*i);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool
|
|
Mixer_UI::strip_button_release_event (GdkEventButton *ev, MixerStrip *strip)
|
|
{
|
|
if (ev->button == 1) {
|
|
if (_selection.selected (strip)) {
|
|
/* primary-click: toggle selection state of strip */
|
|
if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
|
|
_selection.remove (strip);
|
|
}
|
|
} else {
|
|
if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
|
|
_selection.add (strip);
|
|
} else if (Keyboard::modifier_state_equals (ev->state, Keyboard::RangeSelectModifier)) {
|
|
|
|
if (!_selection.selected(strip)) {
|
|
|
|
/* extend selection */
|
|
|
|
vector<MixerStrip*> tmp;
|
|
bool accumulate = false;
|
|
|
|
tmp.push_back (strip);
|
|
|
|
for (list<MixerStrip*>::iterator i = strips.begin(); i != strips.end(); ++i) {
|
|
if ((*i) == strip) {
|
|
/* hit clicked strip, start accumulating till we hit the first
|
|
selected strip
|
|
*/
|
|
if (accumulate) {
|
|
/* done */
|
|
break;
|
|
} else {
|
|
accumulate = true;
|
|
}
|
|
} else if (_selection.selected (*i)) {
|
|
/* hit selected strip. if currently accumulating others,
|
|
we're done. if not accumulating others, start doing so.
|
|
*/
|
|
if (accumulate) {
|
|
/* done */
|
|
break;
|
|
} else {
|
|
accumulate = true;
|
|
}
|
|
} else {
|
|
if (accumulate) {
|
|
tmp.push_back (*i);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (vector<MixerStrip*>::iterator i = tmp.begin(); i != tmp.end(); ++i) {
|
|
_selection.add (*i);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
_selection.set (strip);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
Mixer_UI::set_session (Session* sess)
|
|
{
|
|
SessionHandlePtr::set_session (sess);
|
|
|
|
if (_plugin_selector) {
|
|
_plugin_selector->set_session (_session);
|
|
}
|
|
|
|
_group_tabs->set_session (sess);
|
|
|
|
if (!_session) {
|
|
return;
|
|
}
|
|
|
|
XMLNode* node = ARDOUR_UI::instance()->mixer_settings();
|
|
set_state (*node);
|
|
|
|
update_title ();
|
|
|
|
initial_track_display ();
|
|
|
|
_session->RouteAdded.connect (_session_connections, invalidator (*this), boost::bind (&Mixer_UI::add_strips, this, _1), gui_context());
|
|
_session->route_group_added.connect (_session_connections, invalidator (*this), boost::bind (&Mixer_UI::add_route_group, this, _1), gui_context());
|
|
_session->route_group_removed.connect (_session_connections, invalidator (*this), boost::bind (&Mixer_UI::route_groups_changed, this), gui_context());
|
|
_session->route_groups_reordered.connect (_session_connections, invalidator (*this), boost::bind (&Mixer_UI::route_groups_changed, this), gui_context());
|
|
_session->config.ParameterChanged.connect (_session_connections, invalidator (*this), boost::bind (&Mixer_UI::parameter_changed, this, _1), gui_context());
|
|
_session->DirtyChanged.connect (_session_connections, invalidator (*this), boost::bind (&Mixer_UI::update_title, this), gui_context());
|
|
_session->StateSaved.connect (_session_connections, invalidator (*this), boost::bind (&Mixer_UI::update_title, this), gui_context());
|
|
|
|
Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&Mixer_UI::parameter_changed, this, _1), gui_context ());
|
|
|
|
route_groups_changed ();
|
|
|
|
if (_visible) {
|
|
show_window();
|
|
|
|
/* Bit of a hack; if we're here, we're opening the mixer because of our
|
|
instant XML state having a show-mixer property. Fix up the corresponding
|
|
action state.
|
|
*/
|
|
ActionManager::check_toggleaction ("<Actions>/Common/toggle-mixer");
|
|
}
|
|
|
|
start_updating ();
|
|
}
|
|
|
|
void
|
|
Mixer_UI::session_going_away ()
|
|
{
|
|
ENSURE_GUI_THREAD (*this, &Mixer_UI::session_going_away);
|
|
|
|
_in_group_rebuild_or_clear = true;
|
|
group_model->clear ();
|
|
_in_group_rebuild_or_clear = false;
|
|
|
|
_selection.clear ();
|
|
track_model->clear ();
|
|
|
|
for (list<MixerStrip *>::iterator i = strips.begin(); i != strips.end(); ++i) {
|
|
delete (*i);
|
|
}
|
|
|
|
if (_monitor_section) {
|
|
_monitor_section->tearoff().hide_visible ();
|
|
}
|
|
|
|
strips.clear ();
|
|
|
|
stop_updating ();
|
|
|
|
SessionHandlePtr::session_going_away ();
|
|
|
|
_session = 0;
|
|
update_title ();
|
|
}
|
|
|
|
void
|
|
Mixer_UI::track_visibility_changed (std::string const & path)
|
|
{
|
|
if (_session && _session->deletion_in_progress()) {
|
|
return;
|
|
}
|
|
|
|
TreeIter iter;
|
|
|
|
if ((iter = track_model->get_iter (path))) {
|
|
MixerStrip* strip = (*iter)[track_columns.strip];
|
|
if (strip) {
|
|
bool visible = (*iter)[track_columns.visible];
|
|
|
|
if (strip->set_marked_for_display (!visible)) {
|
|
update_track_visibility ();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
Mixer_UI::update_track_visibility ()
|
|
{
|
|
TreeModel::Children rows = track_model->children();
|
|
TreeModel::Children::iterator i;
|
|
|
|
{
|
|
Unwinder<bool> uw (no_track_list_redisplay, true);
|
|
|
|
for (i = rows.begin(); i != rows.end(); ++i) {
|
|
MixerStrip *strip = (*i)[track_columns.strip];
|
|
(*i)[track_columns.visible] = strip->marked_for_display ();
|
|
}
|
|
|
|
/* force route order keys catch up with visibility changes
|
|
*/
|
|
|
|
sync_order_keys_from_treeview ();
|
|
}
|
|
|
|
redisplay_track_list ();
|
|
}
|
|
|
|
void
|
|
Mixer_UI::show_strip (MixerStrip* ms)
|
|
{
|
|
TreeModel::Children rows = track_model->children();
|
|
TreeModel::Children::iterator i;
|
|
|
|
for (i = rows.begin(); i != rows.end(); ++i) {
|
|
|
|
MixerStrip* strip = (*i)[track_columns.strip];
|
|
if (strip == ms) {
|
|
(*i)[track_columns.visible] = true;
|
|
redisplay_track_list ();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
Mixer_UI::hide_strip (MixerStrip* ms)
|
|
{
|
|
TreeModel::Children rows = track_model->children();
|
|
TreeModel::Children::iterator i;
|
|
|
|
for (i = rows.begin(); i != rows.end(); ++i) {
|
|
|
|
MixerStrip* strip = (*i)[track_columns.strip];
|
|
if (strip == ms) {
|
|
(*i)[track_columns.visible] = false;
|
|
redisplay_track_list ();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
gint
|
|
Mixer_UI::start_updating ()
|
|
{
|
|
fast_screen_update_connection = ARDOUR_UI::instance()->SuperRapidScreenUpdate.connect (sigc::mem_fun(*this, &Mixer_UI::fast_update_strips));
|
|
return 0;
|
|
}
|
|
|
|
gint
|
|
Mixer_UI::stop_updating ()
|
|
{
|
|
fast_screen_update_connection.disconnect();
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
Mixer_UI::fast_update_strips ()
|
|
{
|
|
if (is_mapped () && _session) {
|
|
for (list<MixerStrip *>::iterator i = strips.begin(); i != strips.end(); ++i) {
|
|
(*i)->fast_update ();
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
Mixer_UI::set_all_strips_visibility (bool yn)
|
|
{
|
|
TreeModel::Children rows = track_model->children();
|
|
TreeModel::Children::iterator i;
|
|
|
|
{
|
|
Unwinder<bool> uw (no_track_list_redisplay, true);
|
|
|
|
for (i = rows.begin(); i != rows.end(); ++i) {
|
|
|
|
TreeModel::Row row = (*i);
|
|
MixerStrip* strip = row[track_columns.strip];
|
|
|
|
if (strip == 0) {
|
|
continue;
|
|
}
|
|
|
|
if (strip->route()->is_master() || strip->route()->is_monitor()) {
|
|
continue;
|
|
}
|
|
|
|
(*i)[track_columns.visible] = yn;
|
|
}
|
|
}
|
|
|
|
redisplay_track_list ();
|
|
}
|
|
|
|
|
|
void
|
|
Mixer_UI::set_all_audio_visibility (int tracks, bool yn)
|
|
{
|
|
TreeModel::Children rows = track_model->children();
|
|
TreeModel::Children::iterator i;
|
|
|
|
{
|
|
Unwinder<bool> uw (no_track_list_redisplay, true);
|
|
|
|
for (i = rows.begin(); i != rows.end(); ++i) {
|
|
TreeModel::Row row = (*i);
|
|
MixerStrip* strip = row[track_columns.strip];
|
|
|
|
if (strip == 0) {
|
|
continue;
|
|
}
|
|
|
|
if (strip->route()->is_master() || strip->route()->is_monitor()) {
|
|
continue;
|
|
}
|
|
|
|
boost::shared_ptr<AudioTrack> at = strip->audio_track();
|
|
|
|
switch (tracks) {
|
|
case 0:
|
|
(*i)[track_columns.visible] = yn;
|
|
break;
|
|
|
|
case 1:
|
|
if (at) { /* track */
|
|
(*i)[track_columns.visible] = yn;
|
|
}
|
|
break;
|
|
|
|
case 2:
|
|
if (!at) { /* bus */
|
|
(*i)[track_columns.visible] = yn;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
redisplay_track_list ();
|
|
}
|
|
|
|
void
|
|
Mixer_UI::hide_all_routes ()
|
|
{
|
|
set_all_strips_visibility (false);
|
|
}
|
|
|
|
void
|
|
Mixer_UI::show_all_routes ()
|
|
{
|
|
set_all_strips_visibility (true);
|
|
}
|
|
|
|
void
|
|
Mixer_UI::show_all_audiobus ()
|
|
{
|
|
set_all_audio_visibility (2, true);
|
|
}
|
|
void
|
|
Mixer_UI::hide_all_audiobus ()
|
|
{
|
|
set_all_audio_visibility (2, false);
|
|
}
|
|
|
|
void
|
|
Mixer_UI::show_all_audiotracks()
|
|
{
|
|
set_all_audio_visibility (1, true);
|
|
}
|
|
void
|
|
Mixer_UI::hide_all_audiotracks ()
|
|
{
|
|
set_all_audio_visibility (1, false);
|
|
}
|
|
|
|
void
|
|
Mixer_UI::track_list_reorder (const TreeModel::Path&, const TreeModel::iterator&, int* /*new_order*/)
|
|
{
|
|
DEBUG_TRACE (DEBUG::OrderKeys, "mixer UI treeview reordered\n");
|
|
sync_order_keys_from_treeview ();
|
|
}
|
|
|
|
void
|
|
Mixer_UI::track_list_delete (const Gtk::TreeModel::Path&)
|
|
{
|
|
/* this happens as the second step of a DnD within the treeview as well
|
|
as when a row/route is actually deleted.
|
|
|
|
if it was a deletion then we have to force a redisplay because
|
|
order keys may not have changed.
|
|
*/
|
|
|
|
DEBUG_TRACE (DEBUG::OrderKeys, "mixer UI treeview row deleted\n");
|
|
sync_order_keys_from_treeview ();
|
|
|
|
if (_route_deletion_in_progress) {
|
|
redisplay_track_list ();
|
|
}
|
|
}
|
|
|
|
void
|
|
Mixer_UI::redisplay_track_list ()
|
|
{
|
|
TreeModel::Children rows = track_model->children();
|
|
TreeModel::Children::iterator i;
|
|
|
|
if (no_track_list_redisplay) {
|
|
return;
|
|
}
|
|
|
|
for (i = rows.begin(); i != rows.end(); ++i) {
|
|
|
|
MixerStrip* strip = (*i)[track_columns.strip];
|
|
|
|
if (strip == 0) {
|
|
/* we're in the middle of changing a row, don't worry */
|
|
continue;
|
|
}
|
|
|
|
bool const visible = (*i)[track_columns.visible];
|
|
|
|
if (visible) {
|
|
strip->set_gui_property ("visible", true);
|
|
|
|
if (strip->packed()) {
|
|
|
|
if (strip->route()->is_master() || strip->route()->is_monitor()) {
|
|
out_packer.reorder_child (*strip, -1);
|
|
|
|
} else {
|
|
strip_packer.reorder_child (*strip, -1); /* put at end */
|
|
}
|
|
|
|
} else {
|
|
|
|
if (strip->route()->is_master() || strip->route()->is_monitor()) {
|
|
out_packer.pack_start (*strip, false, false);
|
|
} else {
|
|
strip_packer.pack_start (*strip, false, false);
|
|
}
|
|
strip->set_packed (true);
|
|
}
|
|
|
|
} else {
|
|
|
|
strip->set_gui_property ("visible", false);
|
|
|
|
if (strip->route()->is_master() || strip->route()->is_monitor()) {
|
|
/* do nothing, these cannot be hidden */
|
|
} else {
|
|
if (strip->packed()) {
|
|
strip_packer.remove (*strip);
|
|
strip->set_packed (false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
_group_tabs->set_dirty ();
|
|
}
|
|
|
|
void
|
|
Mixer_UI::strip_width_changed ()
|
|
{
|
|
_group_tabs->set_dirty ();
|
|
|
|
#ifdef GTKOSX
|
|
TreeModel::Children rows = track_model->children();
|
|
TreeModel::Children::iterator i;
|
|
long order;
|
|
|
|
for (order = 0, i = rows.begin(); i != rows.end(); ++i, ++order) {
|
|
MixerStrip* strip = (*i)[track_columns.strip];
|
|
|
|
if (strip == 0) {
|
|
continue;
|
|
}
|
|
|
|
bool visible = (*i)[track_columns.visible];
|
|
|
|
if (visible) {
|
|
strip->queue_draw();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
struct SignalOrderRouteSorter {
|
|
bool operator() (boost::shared_ptr<Route> a, boost::shared_ptr<Route> b) {
|
|
if (a->is_master() || a->is_monitor()) {
|
|
/* "a" is a special route (master, monitor, etc), and comes
|
|
* last in the mixer ordering
|
|
*/
|
|
return false;
|
|
} else if (b->is_master() || b->is_monitor()) {
|
|
/* everything comes before b */
|
|
return true;
|
|
}
|
|
return a->order_key () < b->order_key ();
|
|
|
|
}
|
|
};
|
|
|
|
void
|
|
Mixer_UI::initial_track_display ()
|
|
{
|
|
boost::shared_ptr<RouteList> routes = _session->get_routes();
|
|
RouteList copy (*routes);
|
|
SignalOrderRouteSorter sorter;
|
|
|
|
copy.sort (sorter);
|
|
|
|
{
|
|
Unwinder<bool> uw1 (no_track_list_redisplay, true);
|
|
Unwinder<bool> uw2 (ignore_reorder, true);
|
|
|
|
track_model->clear ();
|
|
add_strips (copy);
|
|
}
|
|
|
|
_session->sync_order_keys ();
|
|
|
|
redisplay_track_list ();
|
|
}
|
|
|
|
void
|
|
Mixer_UI::show_track_list_menu ()
|
|
{
|
|
if (track_menu == 0) {
|
|
build_track_menu ();
|
|
}
|
|
|
|
track_menu->popup (1, gtk_get_current_event_time());
|
|
}
|
|
|
|
bool
|
|
Mixer_UI::track_display_button_press (GdkEventButton* ev)
|
|
{
|
|
if (Keyboard::is_context_menu_event (ev)) {
|
|
show_track_list_menu ();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void
|
|
Mixer_UI::build_track_menu ()
|
|
{
|
|
using namespace Menu_Helpers;
|
|
using namespace Gtk;
|
|
|
|
track_menu = new Menu;
|
|
track_menu->set_name ("ArdourContextMenu");
|
|
MenuList& items = track_menu->items();
|
|
|
|
items.push_back (MenuElem (_("Show All"), sigc::mem_fun(*this, &Mixer_UI::show_all_routes)));
|
|
items.push_back (MenuElem (_("Hide All"), sigc::mem_fun(*this, &Mixer_UI::hide_all_routes)));
|
|
items.push_back (MenuElem (_("Show All Audio Tracks"), sigc::mem_fun(*this, &Mixer_UI::show_all_audiotracks)));
|
|
items.push_back (MenuElem (_("Hide All Audio Tracks"), sigc::mem_fun(*this, &Mixer_UI::hide_all_audiotracks)));
|
|
items.push_back (MenuElem (_("Show All Audio Busses"), sigc::mem_fun(*this, &Mixer_UI::show_all_audiobus)));
|
|
items.push_back (MenuElem (_("Hide All Audio Busses"), sigc::mem_fun(*this, &Mixer_UI::hide_all_audiobus)));
|
|
|
|
}
|
|
|
|
void
|
|
Mixer_UI::strip_property_changed (const PropertyChange& what_changed, MixerStrip* mx)
|
|
{
|
|
if (!what_changed.contains (ARDOUR::Properties::name)) {
|
|
return;
|
|
}
|
|
|
|
ENSURE_GUI_THREAD (*this, &Mixer_UI::strip_name_changed, what_changed, mx)
|
|
|
|
TreeModel::Children rows = track_model->children();
|
|
TreeModel::Children::iterator i;
|
|
|
|
for (i = rows.begin(); i != rows.end(); ++i) {
|
|
if ((*i)[track_columns.strip] == mx) {
|
|
(*i)[track_columns.text] = mx->route()->name();
|
|
return;
|
|
}
|
|
}
|
|
|
|
error << _("track display list item for renamed strip not found!") << endmsg;
|
|
}
|
|
|
|
bool
|
|
Mixer_UI::group_display_button_press (GdkEventButton* ev)
|
|
{
|
|
TreeModel::Path path;
|
|
TreeViewColumn* column;
|
|
int cellx;
|
|
int celly;
|
|
|
|
if (!group_display.get_path_at_pos ((int)ev->x, (int)ev->y, path, column, cellx, celly)) {
|
|
return false;
|
|
}
|
|
|
|
TreeIter iter = group_model->get_iter (path);
|
|
if (!iter) {
|
|
return false;
|
|
}
|
|
|
|
RouteGroup* group = (*iter)[group_columns.group];
|
|
|
|
if (Keyboard::is_context_menu_event (ev)) {
|
|
_group_tabs->get_menu(group)->popup (1, ev->time);
|
|
return true;
|
|
}
|
|
|
|
switch (GPOINTER_TO_UINT (column->get_data (X_("colnum")))) {
|
|
case 0:
|
|
if (Keyboard::is_edit_event (ev)) {
|
|
if (group) {
|
|
// edit_route_group (group);
|
|
#ifdef GTKOSX
|
|
group_display.queue_draw();
|
|
#endif
|
|
return true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 1:
|
|
{
|
|
bool visible = (*iter)[group_columns.visible];
|
|
(*iter)[group_columns.visible] = !visible;
|
|
#ifdef GTKOSX
|
|
group_display.queue_draw();
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void
|
|
Mixer_UI::activate_all_route_groups ()
|
|
{
|
|
_session->foreach_route_group (sigc::bind (sigc::mem_fun (*this, &Mixer_UI::set_route_group_activation), true));
|
|
}
|
|
|
|
void
|
|
Mixer_UI::disable_all_route_groups ()
|
|
{
|
|
_session->foreach_route_group (sigc::bind (sigc::mem_fun (*this, &Mixer_UI::set_route_group_activation), false));
|
|
}
|
|
|
|
void
|
|
Mixer_UI::route_groups_changed ()
|
|
{
|
|
ENSURE_GUI_THREAD (*this, &Mixer_UI::route_groups_changed);
|
|
|
|
_in_group_rebuild_or_clear = true;
|
|
|
|
/* just rebuild the while thing */
|
|
|
|
group_model->clear ();
|
|
|
|
{
|
|
TreeModel::Row row;
|
|
row = *(group_model->append());
|
|
row[group_columns.visible] = true;
|
|
row[group_columns.text] = (_("-all-"));
|
|
row[group_columns.group] = 0;
|
|
}
|
|
|
|
_session->foreach_route_group (sigc::mem_fun (*this, &Mixer_UI::add_route_group));
|
|
|
|
_group_tabs->set_dirty ();
|
|
_in_group_rebuild_or_clear = false;
|
|
}
|
|
|
|
void
|
|
Mixer_UI::new_route_group ()
|
|
{
|
|
RouteList rl;
|
|
|
|
_group_tabs->run_new_group_dialog (rl);
|
|
}
|
|
|
|
void
|
|
Mixer_UI::remove_selected_route_group ()
|
|
{
|
|
Glib::RefPtr<TreeSelection> selection = group_display.get_selection();
|
|
TreeView::Selection::ListHandle_Path rows = selection->get_selected_rows ();
|
|
|
|
if (rows.empty()) {
|
|
return;
|
|
}
|
|
|
|
TreeView::Selection::ListHandle_Path::iterator i = rows.begin();
|
|
TreeIter iter;
|
|
|
|
/* selection mode is single, so rows.begin() is it */
|
|
|
|
if ((iter = group_model->get_iter (*i))) {
|
|
|
|
RouteGroup* rg = (*iter)[group_columns.group];
|
|
|
|
if (rg) {
|
|
_session->remove_route_group (*rg);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
Mixer_UI::route_group_property_changed (RouteGroup* group, const PropertyChange& change)
|
|
{
|
|
if (in_group_row_change) {
|
|
return;
|
|
}
|
|
|
|
/* force an update of any mixer strips that are using this group,
|
|
otherwise mix group names don't change in mixer strips
|
|
*/
|
|
|
|
for (list<MixerStrip *>::iterator i = strips.begin(); i != strips.end(); ++i) {
|
|
if ((*i)->route_group() == group) {
|
|
(*i)->route_group_changed();
|
|
}
|
|
}
|
|
|
|
TreeModel::iterator i;
|
|
TreeModel::Children rows = group_model->children();
|
|
Glib::RefPtr<TreeSelection> selection = group_display.get_selection();
|
|
|
|
in_group_row_change = true;
|
|
|
|
for (i = rows.begin(); i != rows.end(); ++i) {
|
|
if ((*i)[group_columns.group] == group) {
|
|
(*i)[group_columns.visible] = !group->is_hidden ();
|
|
(*i)[group_columns.text] = group->name ();
|
|
break;
|
|
}
|
|
}
|
|
|
|
in_group_row_change = false;
|
|
|
|
if (change.contains (Properties::name)) {
|
|
_group_tabs->set_dirty ();
|
|
}
|
|
|
|
for (list<MixerStrip*>::iterator j = strips.begin(); j != strips.end(); ++j) {
|
|
if ((*j)->route_group() == group) {
|
|
if (group->is_hidden ()) {
|
|
hide_strip (*j);
|
|
} else {
|
|
show_strip (*j);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
Mixer_UI::route_group_name_edit (const std::string& path, const std::string& new_text)
|
|
{
|
|
RouteGroup* group;
|
|
TreeIter iter;
|
|
|
|
if ((iter = group_model->get_iter (path))) {
|
|
|
|
if ((group = (*iter)[group_columns.group]) == 0) {
|
|
return;
|
|
}
|
|
|
|
if (new_text != group->name()) {
|
|
group->set_name (new_text);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
Mixer_UI::route_group_row_change (const Gtk::TreeModel::Path&, const Gtk::TreeModel::iterator& iter)
|
|
{
|
|
RouteGroup* group;
|
|
|
|
if (in_group_row_change) {
|
|
return;
|
|
}
|
|
|
|
if ((group = (*iter)[group_columns.group]) == 0) {
|
|
return;
|
|
}
|
|
|
|
std::string name = (*iter)[group_columns.text];
|
|
|
|
if (name != group->name()) {
|
|
group->set_name (name);
|
|
}
|
|
|
|
bool hidden = !(*iter)[group_columns.visible];
|
|
|
|
if (hidden != group->is_hidden ()) {
|
|
group->set_hidden (hidden, this);
|
|
}
|
|
}
|
|
|
|
/** Called when a group model row is deleted, but also when the model is
|
|
* reordered by a user drag-and-drop; the latter is what we are
|
|
* interested in here.
|
|
*/
|
|
void
|
|
Mixer_UI::route_group_row_deleted (Gtk::TreeModel::Path const &)
|
|
{
|
|
if (_in_group_rebuild_or_clear) {
|
|
return;
|
|
}
|
|
|
|
/* Re-write the session's route group list so that the new order is preserved */
|
|
|
|
list<RouteGroup*> new_list;
|
|
|
|
Gtk::TreeModel::Children children = group_model->children();
|
|
for (Gtk::TreeModel::Children::iterator i = children.begin(); i != children.end(); ++i) {
|
|
RouteGroup* g = (*i)[group_columns.group];
|
|
if (g) {
|
|
new_list.push_back (g);
|
|
}
|
|
}
|
|
|
|
_session->reorder_route_groups (new_list);
|
|
}
|
|
|
|
|
|
void
|
|
Mixer_UI::add_route_group (RouteGroup* group)
|
|
{
|
|
ENSURE_GUI_THREAD (*this, &Mixer_UI::add_route_group, group)
|
|
bool focus = false;
|
|
|
|
in_group_row_change = true;
|
|
|
|
TreeModel::Row row = *(group_model->append());
|
|
row[group_columns.visible] = !group->is_hidden ();
|
|
row[group_columns.group] = group;
|
|
if (!group->name().empty()) {
|
|
row[group_columns.text] = group->name();
|
|
} else {
|
|
row[group_columns.text] = _("unnamed");
|
|
focus = true;
|
|
}
|
|
|
|
group->PropertyChanged.connect (*this, invalidator (*this), boost::bind (&Mixer_UI::route_group_property_changed, this, group, _1), gui_context());
|
|
|
|
if (focus) {
|
|
TreeViewColumn* col = group_display.get_column (0);
|
|
CellRendererText* name_cell = dynamic_cast<CellRendererText*>(group_display.get_column_cell_renderer (0));
|
|
group_display.set_cursor (group_model->get_path (row), *col, *name_cell, true);
|
|
}
|
|
|
|
_group_tabs->set_dirty ();
|
|
|
|
in_group_row_change = false;
|
|
}
|
|
|
|
bool
|
|
Mixer_UI::strip_scroller_button_release (GdkEventButton* ev)
|
|
{
|
|
using namespace Menu_Helpers;
|
|
|
|
if (Keyboard::is_context_menu_event (ev)) {
|
|
ARDOUR_UI::instance()->add_route (this);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void
|
|
Mixer_UI::set_strip_width (Width w, bool save)
|
|
{
|
|
_strip_width = w;
|
|
|
|
for (list<MixerStrip*>::iterator i = strips.begin(); i != strips.end(); ++i) {
|
|
(*i)->set_width_enum (w, save ? (*i)->width_owner() : this);
|
|
}
|
|
}
|
|
|
|
void
|
|
Mixer_UI::set_window_pos_and_size ()
|
|
{
|
|
resize (m_width, m_height);
|
|
move (m_root_x, m_root_y);
|
|
}
|
|
|
|
void
|
|
Mixer_UI::get_window_pos_and_size ()
|
|
{
|
|
get_position(m_root_x, m_root_y);
|
|
get_size(m_width, m_height);
|
|
}
|
|
|
|
int
|
|
Mixer_UI::set_state (const XMLNode& node)
|
|
{
|
|
const XMLProperty* prop;
|
|
XMLNode* geometry;
|
|
|
|
m_width = default_width;
|
|
m_height = default_height;
|
|
m_root_x = 1;
|
|
m_root_y = 1;
|
|
|
|
if ((geometry = find_named_node (node, "geometry")) != 0) {
|
|
|
|
XMLProperty* prop;
|
|
|
|
if ((prop = geometry->property("x_size")) == 0) {
|
|
prop = geometry->property ("x-size");
|
|
}
|
|
if (prop) {
|
|
m_width = atoi(prop->value());
|
|
}
|
|
if ((prop = geometry->property("y_size")) == 0) {
|
|
prop = geometry->property ("y-size");
|
|
}
|
|
if (prop) {
|
|
m_height = atoi(prop->value());
|
|
}
|
|
|
|
if ((prop = geometry->property ("x_pos")) == 0) {
|
|
prop = geometry->property ("x-pos");
|
|
}
|
|
if (prop) {
|
|
m_root_x = atoi (prop->value());
|
|
|
|
}
|
|
if ((prop = geometry->property ("y_pos")) == 0) {
|
|
prop = geometry->property ("y-pos");
|
|
}
|
|
if (prop) {
|
|
m_root_y = atoi (prop->value());
|
|
}
|
|
}
|
|
|
|
set_window_pos_and_size ();
|
|
|
|
if ((prop = node.property ("narrow-strips"))) {
|
|
if (string_is_affirmative (prop->value())) {
|
|
set_strip_width (Narrow);
|
|
} else {
|
|
set_strip_width (Wide);
|
|
}
|
|
}
|
|
|
|
if ((prop = node.property ("show-mixer"))) {
|
|
if (string_is_affirmative (prop->value())) {
|
|
_visible = true;
|
|
}
|
|
}
|
|
|
|
if ((prop = node.property ("maximised"))) {
|
|
bool yn = string_is_affirmative (prop->value());
|
|
Glib::RefPtr<Action> act = ActionManager::get_action (X_("Common"), X_("ToggleMaximalMixer"));
|
|
assert (act);
|
|
Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic(act);
|
|
bool fs = tact && tact->get_active();
|
|
if (yn ^ fs) {
|
|
ActionManager::do_action ("Common",
|
|
"ToggleMaximalMixer");
|
|
}
|
|
}
|
|
|
|
|
|
return 0;
|
|
}
|
|
|
|
XMLNode&
|
|
Mixer_UI::get_state (void)
|
|
{
|
|
XMLNode* node = new XMLNode ("Mixer");
|
|
|
|
if (is_realized()) {
|
|
Glib::RefPtr<Gdk::Window> win = get_window();
|
|
|
|
get_window_pos_and_size ();
|
|
|
|
XMLNode* geometry = new XMLNode ("geometry");
|
|
char buf[32];
|
|
snprintf(buf, sizeof(buf), "%d", m_width);
|
|
geometry->add_property(X_("x_size"), string(buf));
|
|
snprintf(buf, sizeof(buf), "%d", m_height);
|
|
geometry->add_property(X_("y_size"), string(buf));
|
|
snprintf(buf, sizeof(buf), "%d", m_root_x);
|
|
geometry->add_property(X_("x_pos"), string(buf));
|
|
snprintf(buf, sizeof(buf), "%d", m_root_y);
|
|
geometry->add_property(X_("y_pos"), string(buf));
|
|
|
|
// written only for compatibility, they are not used.
|
|
snprintf(buf, sizeof(buf), "%d", 0);
|
|
geometry->add_property(X_("x_off"), string(buf));
|
|
snprintf(buf, sizeof(buf), "%d", 0);
|
|
geometry->add_property(X_("y_off"), string(buf));
|
|
|
|
snprintf(buf,sizeof(buf), "%d",gtk_paned_get_position (static_cast<Paned*>(&rhs_pane1)->gobj()));
|
|
geometry->add_property(X_("mixer_rhs_pane1_pos"), string(buf));
|
|
snprintf(buf,sizeof(buf), "%d",gtk_paned_get_position (static_cast<Paned*>(&list_hpane)->gobj()));
|
|
geometry->add_property(X_("mixer_list_hpane_pos"), string(buf));
|
|
|
|
node->add_child_nocopy (*geometry);
|
|
}
|
|
|
|
node->add_property ("narrow-strips", _strip_width == Narrow ? "yes" : "no");
|
|
|
|
node->add_property ("show-mixer", _visible ? "yes" : "no");
|
|
|
|
node->add_property ("maximised", _maximised ? "yes" : "no");
|
|
|
|
return *node;
|
|
}
|
|
|
|
|
|
void
|
|
Mixer_UI::pane_allocation_handler (Allocation&, Gtk::Paned* which)
|
|
{
|
|
int pos;
|
|
XMLProperty* prop = 0;
|
|
char buf[32];
|
|
XMLNode* node = ARDOUR_UI::instance()->mixer_settings();
|
|
XMLNode* geometry;
|
|
int height;
|
|
static int32_t done[3] = { 0, 0, 0 };
|
|
|
|
height = default_height;
|
|
|
|
if ((geometry = find_named_node (*node, "geometry")) != 0) {
|
|
|
|
if ((prop = geometry->property ("y_size")) == 0) {
|
|
prop = geometry->property ("y-size");
|
|
}
|
|
if (prop) {
|
|
height = atoi (prop->value());
|
|
}
|
|
}
|
|
|
|
if (which == static_cast<Gtk::Paned*> (&rhs_pane1)) {
|
|
|
|
if (done[0]) {
|
|
return;
|
|
}
|
|
|
|
if (!geometry || (prop = geometry->property("mixer-rhs-pane1-pos")) == 0) {
|
|
pos = height / 3;
|
|
snprintf (buf, sizeof(buf), "%d", pos);
|
|
} else {
|
|
pos = atoi (prop->value());
|
|
}
|
|
|
|
if ((done[0] = GTK_WIDGET(rhs_pane1.gobj())->allocation.height > pos)) {
|
|
rhs_pane1.set_position (pos);
|
|
}
|
|
|
|
} else if (which == static_cast<Gtk::Paned*> (&list_hpane)) {
|
|
|
|
if (done[2]) {
|
|
return;
|
|
}
|
|
|
|
if (!geometry || (prop = geometry->property("mixer-list-hpane-pos")) == 0) {
|
|
pos = 75;
|
|
snprintf (buf, sizeof(buf), "%d", pos);
|
|
} else {
|
|
pos = atoi (prop->value());
|
|
}
|
|
|
|
if ((done[2] = GTK_WIDGET(list_hpane.gobj())->allocation.width > pos)) {
|
|
list_hpane.set_position (pos);
|
|
}
|
|
}
|
|
}
|
|
void
|
|
Mixer_UI::scroll_left ()
|
|
{
|
|
if (!scroller.get_hscrollbar()) return;
|
|
Adjustment* adj = scroller.get_hscrollbar()->get_adjustment();
|
|
/* stupid GTK: can't rely on clamping across versions */
|
|
scroller.get_hscrollbar()->set_value (max (adj->get_lower(), adj->get_value() - adj->get_step_increment()));
|
|
}
|
|
|
|
void
|
|
Mixer_UI::scroll_right ()
|
|
{
|
|
if (!scroller.get_hscrollbar()) return;
|
|
Adjustment* adj = scroller.get_hscrollbar()->get_adjustment();
|
|
/* stupid GTK: can't rely on clamping across versions */
|
|
scroller.get_hscrollbar()->set_value (min (adj->get_upper(), adj->get_value() + adj->get_step_increment()));
|
|
}
|
|
|
|
bool
|
|
Mixer_UI::on_key_press_event (GdkEventKey* ev)
|
|
{
|
|
/* focus widget gets first shot, then bindings, otherwise
|
|
forward to main window
|
|
*/
|
|
|
|
if (gtk_window_propagate_key_event (GTK_WINDOW(gobj()), ev)) {
|
|
return true;
|
|
}
|
|
|
|
KeyboardKey k (ev->state, ev->keyval);
|
|
|
|
if (bindings.activate (k, Bindings::Press)) {
|
|
return true;
|
|
}
|
|
|
|
return forward_key_press (ev);
|
|
}
|
|
|
|
bool
|
|
Mixer_UI::on_key_release_event (GdkEventKey* ev)
|
|
{
|
|
if (gtk_window_propagate_key_event (GTK_WINDOW(gobj()), ev)) {
|
|
return true;
|
|
}
|
|
|
|
KeyboardKey k (ev->state, ev->keyval);
|
|
|
|
if (bindings.activate (k, Bindings::Release)) {
|
|
return true;
|
|
}
|
|
|
|
/* don't forward releases */
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
Mixer_UI::on_scroll_event (GdkEventScroll* ev)
|
|
{
|
|
switch (ev->direction) {
|
|
case GDK_SCROLL_LEFT:
|
|
scroll_left ();
|
|
return true;
|
|
case GDK_SCROLL_UP:
|
|
if (ev->state & Keyboard::TertiaryModifier) {
|
|
scroll_left ();
|
|
return true;
|
|
}
|
|
return false;
|
|
|
|
case GDK_SCROLL_RIGHT:
|
|
scroll_right ();
|
|
return true;
|
|
|
|
case GDK_SCROLL_DOWN:
|
|
if (ev->state & Keyboard::TertiaryModifier) {
|
|
scroll_right ();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
void
|
|
Mixer_UI::parameter_changed (string const & p)
|
|
{
|
|
if (p == "show-group-tabs") {
|
|
bool const s = _session->config.get_show_group_tabs ();
|
|
if (s) {
|
|
_group_tabs->show ();
|
|
} else {
|
|
_group_tabs->hide ();
|
|
}
|
|
} else if (p == "default-narrow_ms") {
|
|
bool const s = Config->get_default_narrow_ms ();
|
|
for (list<MixerStrip*>::iterator i = strips.begin(); i != strips.end(); ++i) {
|
|
(*i)->set_width_enum (s ? Narrow : Wide, this);
|
|
}
|
|
} else if (p == "remote-model") {
|
|
reset_remote_control_ids ();
|
|
}
|
|
}
|
|
|
|
void
|
|
Mixer_UI::set_route_group_activation (RouteGroup* g, bool a)
|
|
{
|
|
g->set_active (a, this);
|
|
}
|
|
|
|
PluginSelector*
|
|
Mixer_UI::plugin_selector()
|
|
{
|
|
#ifdef DEFER_PLUGIN_SELECTOR_LOAD
|
|
if (!_plugin_selector)
|
|
_plugin_selector = new PluginSelector (PluginManager::instance());
|
|
#endif
|
|
|
|
return _plugin_selector;
|
|
}
|
|
|
|
void
|
|
Mixer_UI::setup_track_display ()
|
|
{
|
|
track_model = ListStore::create (track_columns);
|
|
track_display.set_model (track_model);
|
|
track_display.append_column (_("Strips"), track_columns.text);
|
|
track_display.append_column (_("Show"), track_columns.visible);
|
|
track_display.get_column (0)->set_data (X_("colnum"), GUINT_TO_POINTER(0));
|
|
track_display.get_column (1)->set_data (X_("colnum"), GUINT_TO_POINTER(1));
|
|
track_display.get_column (0)->set_expand(true);
|
|
track_display.get_column (1)->set_expand(false);
|
|
track_display.set_name (X_("EditGroupList"));
|
|
track_display.get_selection()->set_mode (Gtk::SELECTION_NONE);
|
|
track_display.set_reorderable (true);
|
|
track_display.set_headers_visible (true);
|
|
|
|
track_model->signal_row_deleted().connect (sigc::mem_fun (*this, &Mixer_UI::track_list_delete));
|
|
track_model->signal_rows_reordered().connect (sigc::mem_fun (*this, &Mixer_UI::track_list_reorder));
|
|
|
|
CellRendererToggle* track_list_visible_cell = dynamic_cast<CellRendererToggle*>(track_display.get_column_cell_renderer (1));
|
|
track_list_visible_cell->property_activatable() = true;
|
|
track_list_visible_cell->property_radio() = false;
|
|
track_list_visible_cell->signal_toggled().connect (sigc::mem_fun (*this, &Mixer_UI::track_visibility_changed));
|
|
|
|
track_display.signal_button_press_event().connect (sigc::mem_fun (*this, &Mixer_UI::track_display_button_press), false);
|
|
|
|
track_display_scroller.add (track_display);
|
|
track_display_scroller.set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
|
|
|
|
VBox* v = manage (new VBox);
|
|
v->show ();
|
|
v->pack_start (track_display_scroller, true, true);
|
|
|
|
Button* b = manage (new Button);
|
|
b->show ();
|
|
Widget* w = manage (new Image (Stock::ADD, ICON_SIZE_BUTTON));
|
|
w->show ();
|
|
b->add (*w);
|
|
|
|
b->signal_clicked().connect (sigc::mem_fun (*this, &Mixer_UI::new_track_or_bus));
|
|
|
|
v->pack_start (*b, false, false);
|
|
|
|
track_display_frame.set_name("BaseFrame");
|
|
track_display_frame.set_shadow_type (Gtk::SHADOW_IN);
|
|
track_display_frame.add (*v);
|
|
|
|
track_display_scroller.show();
|
|
track_display_frame.show();
|
|
track_display.show();
|
|
}
|
|
|
|
void
|
|
Mixer_UI::new_track_or_bus ()
|
|
{
|
|
ARDOUR_UI::instance()->add_route (this);
|
|
}
|
|
|
|
|
|
void
|
|
Mixer_UI::update_title ()
|
|
{
|
|
if (_session) {
|
|
string n;
|
|
|
|
if (_session->snap_name() != _session->name()) {
|
|
n = _session->snap_name ();
|
|
} else {
|
|
n = _session->name ();
|
|
}
|
|
|
|
if (_session->dirty ()) {
|
|
n = "*" + n;
|
|
}
|
|
|
|
WindowTitle title (n);
|
|
title += S_("Window|Mixer");
|
|
title += Glib::get_application_name ();
|
|
set_title (title.get_string());
|
|
|
|
} else {
|
|
|
|
WindowTitle title (S_("Window|Mixer"));
|
|
title += Glib::get_application_name ();
|
|
set_title (title.get_string());
|
|
}
|
|
}
|
|
|
|
MixerStrip*
|
|
Mixer_UI::strip_by_x (int x)
|
|
{
|
|
for (list<MixerStrip*>::iterator i = strips.begin(); i != strips.end(); ++i) {
|
|
int x1, x2, y;
|
|
|
|
(*i)->translate_coordinates (*this, 0, 0, x1, y);
|
|
x2 = x1 + (*i)->get_width();
|
|
|
|
if (x >= x1 && x <= x2) {
|
|
return (*i);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
Mixer_UI::set_route_targets_for_operation ()
|
|
{
|
|
_route_targets.clear ();
|
|
|
|
if (!_selection.empty()) {
|
|
_route_targets = _selection.routes;
|
|
return;
|
|
}
|
|
|
|
/* nothing selected ... try to get mixer strip at mouse */
|
|
|
|
int x, y;
|
|
get_pointer (x, y);
|
|
|
|
MixerStrip* ms = strip_by_x (x);
|
|
|
|
if (ms) {
|
|
_route_targets.insert (ms);
|
|
}
|
|
}
|
|
|
|
void
|
|
Mixer_UI::monitor_section_going_away ()
|
|
{
|
|
if (_monitor_section) {
|
|
out_packer.remove (_monitor_section->tearoff());
|
|
_monitor_section->set_session (0);
|
|
}
|
|
}
|
|
|
|
void
|
|
Mixer_UI::toggle_midi_input_active (bool flip_others)
|
|
{
|
|
boost::shared_ptr<RouteList> rl (new RouteList);
|
|
bool onoff = false;
|
|
|
|
set_route_targets_for_operation ();
|
|
|
|
for (RouteUISelection::iterator r = _route_targets.begin(); r != _route_targets.end(); ++r) {
|
|
boost::shared_ptr<MidiTrack> mt = (*r)->midi_track();
|
|
|
|
if (mt) {
|
|
rl->push_back ((*r)->route());
|
|
onoff = !mt->input_active();
|
|
}
|
|
}
|
|
|
|
_session->set_exclusive_input_active (rl, onoff, flip_others);
|
|
}
|
|
|
|
void
|
|
Mixer_UI::maximise_mixer_space ()
|
|
{
|
|
if (_maximised) {
|
|
return;
|
|
}
|
|
|
|
fullscreen ();
|
|
|
|
_maximised = true;
|
|
}
|
|
|
|
void
|
|
Mixer_UI::restore_mixer_space ()
|
|
{
|
|
if (!_maximised) {
|
|
return;
|
|
}
|
|
|
|
unfullscreen();
|
|
|
|
_maximised = false;
|
|
}
|