13
0
livetrax/gtk2_ardour/route_time_axis.cc
David Robillard 332a3d9813 Selection of visible note range (full range vs fit contents, selectable from midi track menu).
Added note pencil tool, mock note adding (notes can be added visually but don't yet play).
Reworked MidiModel to be notes w/ duration instead of realtime style MIDI events.
Moved layering (stacked/overlaid) from auto time axis down to route time axis since it applies
to MIDI tracks as well.


git-svn-id: svn://localhost/ardour2/trunk@2128 d708f5d6-7413-0410-9779-e7cbd77b26cf
2007-07-15 01:56:11 +00:00

2001 lines
50 KiB
C++

/*
Copyright (C) 2006 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 <cstdlib>
#include <cmath>
#include <cassert>
#include <algorithm>
#include <string>
#include <vector>
#include <utility>
#include <sigc++/bind.h>
#include <pbd/error.h>
#include <pbd/stl_delete.h>
#include <pbd/whitespace.h>
#include <pbd/memento_command.h>
#include <gtkmm/menu.h>
#include <gtkmm/menuitem.h>
#include <gtkmm2ext/gtk_ui.h>
#include <gtkmm2ext/selector.h>
#include <gtkmm2ext/stop_signal.h>
#include <gtkmm2ext/bindable_button.h>
#include <gtkmm2ext/utils.h>
#include <ardour/playlist.h>
#include <ardour/audioplaylist.h>
#include <ardour/diskstream.h>
#include <ardour/processor.h>
#include <ardour/ladspa_plugin.h>
#include <ardour/location.h>
#include <ardour/panner.h>
#include <ardour/playlist.h>
#include <ardour/session.h>
#include <ardour/session_playlist.h>
#include <ardour/utils.h>
#include <ardour/parameter.h>
#include "ardour_ui.h"
#include "route_time_axis.h"
#include "automation_time_axis.h"
#include "canvas_impl.h"
#include "crossfade_view.h"
#include "enums.h"
#include "gui_thread.h"
#include "keyboard.h"
#include "playlist_selector.h"
#include "point_selection.h"
#include "prompter.h"
#include "public_editor.h"
#include "region_view.h"
#include "rgb_macros.h"
#include "selection.h"
#include "simplerect.h"
#include "streamview.h"
#include "utils.h"
#include <ardour/track.h>
#include "i18n.h"
using namespace ARDOUR;
using namespace PBD;
using namespace Gtk;
using namespace Editing;
using namespace sigc;
RouteTimeAxisView::RouteTimeAxisView (PublicEditor& ed, Session& sess, boost::shared_ptr<Route> rt, Canvas& canvas)
: AxisView(sess),
RouteUI(rt, sess, _("m"), _("s"), _("r")), // mute, solo, and record
TimeAxisView(sess,ed,(TimeAxisView*) 0, canvas),
parent_canvas (canvas),
button_table (3, 3),
edit_group_button (_("g")), // group
playlist_button (_("p")),
size_button (_("h")), // height
automation_button (_("a")),
visual_button (_("v"))
{
_has_state = true;
playlist_menu = 0;
playlist_action_menu = 0;
automation_action_menu = 0;
_view = 0;
timestretch_rect = 0;
no_redraw = false;
ignore_toggle = false;
edit_group_button.set_name ("TrackGroupButton");
playlist_button.set_name ("TrackPlaylistButton");
automation_button.set_name ("TrackAutomationButton");
size_button.set_name ("TrackSizeButton");
visual_button.set_name ("TrackVisualButton");
hide_button.set_name ("TrackRemoveButton");
hide_button.add (*(manage (new Image (::get_icon("hide")))));
hide_button.show_all ();
edit_group_button.signal_button_release_event().connect (mem_fun(*this, &RouteTimeAxisView::edit_click), false);
playlist_button.signal_clicked().connect (mem_fun(*this, &RouteTimeAxisView::playlist_click));
automation_button.signal_clicked().connect (mem_fun(*this, &RouteTimeAxisView::automation_click));
size_button.signal_button_release_event().connect (mem_fun(*this, &RouteTimeAxisView::size_click), false);
visual_button.signal_clicked().connect (mem_fun(*this, &RouteTimeAxisView::visual_click));
hide_button.signal_clicked().connect (mem_fun(*this, &RouteTimeAxisView::hide_click));
solo_button->signal_button_press_event().connect (mem_fun(*this, &RouteUI::solo_press), false);
solo_button->signal_button_release_event().connect (mem_fun(*this, &RouteUI::solo_release));
mute_button->signal_button_press_event().connect (mem_fun(*this, &RouteUI::mute_press), false);
mute_button->signal_button_release_event().connect (mem_fun(*this, &RouteUI::mute_release));
if (is_track()) {
/* use icon */
rec_enable_button->remove ();
switch (track()->mode()) {
case ARDOUR::Normal:
rec_enable_button->add (*(manage (new Image (::get_icon (X_("record_normal_red"))))));
break;
case ARDOUR::Destructive:
rec_enable_button->add (*(manage (new Image (::get_icon (X_("record_tape_red"))))));
break;
}
rec_enable_button->show_all ();
rec_enable_button->signal_button_press_event().connect (mem_fun(*this, &RouteUI::rec_enable_press), false);
rec_enable_button->signal_button_release_event().connect (mem_fun(*this, &RouteUI::rec_enable_release));
controls_table.attach (*rec_enable_button, 5, 6, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND, 0, 0);
ARDOUR_UI::instance()->tooltips().set_tip(*rec_enable_button, _("Record"));
}
controls_table.attach (*mute_button, 6, 7, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND, 0, 0);
controls_table.attach (*solo_button, 7, 8, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::FILL|Gtk::EXPAND, 0, 0);
controls_table.attach (edit_group_button, 6, 7, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND, 0, 0);
ARDOUR_UI::instance()->tooltips().set_tip(*solo_button,_("Solo"));
ARDOUR_UI::instance()->tooltips().set_tip(*mute_button,_("Mute"));
ARDOUR_UI::instance()->tooltips().set_tip(edit_group_button,_("Edit Group"));
ARDOUR_UI::instance()->tooltips().set_tip(size_button,_("Display Height"));
ARDOUR_UI::instance()->tooltips().set_tip(playlist_button,_("Playlist"));
ARDOUR_UI::instance()->tooltips().set_tip(automation_button, _("Automation"));
ARDOUR_UI::instance()->tooltips().set_tip(visual_button, _("Visual options"));
ARDOUR_UI::instance()->tooltips().set_tip(hide_button, _("Hide this track"));
label_view ();
controls_table.attach (hide_button, 0, 1, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
controls_table.attach (visual_button, 1, 2, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
controls_table.attach (size_button, 2, 3, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
controls_table.attach (automation_button, 3, 4, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
if (is_track() && track()->mode() == ARDOUR::Normal) {
controls_table.attach (playlist_button, 5, 6, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
}
/* remove focus from the buttons */
y_position = -1;
_route->mute_changed.connect (mem_fun(*this, &RouteUI::mute_changed));
_route->solo_changed.connect (mem_fun(*this, &RouteUI::solo_changed));
_route->processors_changed.connect (mem_fun(*this, &RouteTimeAxisView::processors_changed));
_route->NameChanged.connect (mem_fun(*this, &RouteTimeAxisView::route_name_changed));
_route->solo_safe_changed.connect (mem_fun(*this, &RouteUI::solo_changed));
if (is_track()) {
track()->TrackModeChanged.connect (mem_fun(*this, &RouteTimeAxisView::track_mode_changed));
track()->FreezeChange.connect (mem_fun(*this, &RouteTimeAxisView::map_frozen));
track()->DiskstreamChanged.connect (mem_fun(*this, &RouteTimeAxisView::diskstream_changed));
get_diskstream()->SpeedChanged.connect (mem_fun(*this, &RouteTimeAxisView::speed_changed));
/* pick up the correct freeze state */
map_frozen ();
}
editor.ZoomChanged.connect (mem_fun(*this, &RouteTimeAxisView::reset_samples_per_unit));
ColorsChanged.connect (mem_fun (*this, &RouteTimeAxisView::color_handler));
}
RouteTimeAxisView::~RouteTimeAxisView ()
{
GoingAway (); /* EMIT_SIGNAL */
for (list<ProcessorAutomationInfo*>::iterator i = processor_automation.begin(); i != processor_automation.end(); ++i) {
delete *i;
}
if (playlist_menu) {
delete playlist_menu;
playlist_menu = 0;
}
if (playlist_action_menu) {
delete playlist_action_menu;
playlist_action_menu = 0;
}
if (_view) {
delete _view;
_view = 0;
}
}
void
RouteTimeAxisView::post_construct ()
{
/* map current state of the route */
update_diskstream_display ();
_route->foreach_processor (this, &RouteTimeAxisView::add_processor_to_subplugin_menu);
_route->foreach_processor (this, &RouteTimeAxisView::add_existing_processor_automation_curves);
reset_processor_automation_curves ();
}
void
RouteTimeAxisView::set_playlist (boost::shared_ptr<Playlist> newplaylist)
{
boost::shared_ptr<Playlist> pl = playlist();
assert(pl);
modified_connection.disconnect ();
modified_connection = pl->Modified.connect (mem_fun(*this, &RouteTimeAxisView::playlist_modified));
}
void
RouteTimeAxisView::playlist_modified ()
{
}
void
RouteTimeAxisView::set_state (const XMLNode& node)
{
const XMLProperty *prop;
TimeAxisView::set_state (node);
if ((prop = node.property ("shown_editor")) != 0) {
if (prop->value() == "no") {
_marked_for_display = false;
} else {
_marked_for_display = true;
}
} else {
_marked_for_display = true;
}
XMLNodeList nlist = node.children();
XMLNodeConstIterator niter;
XMLNode *child_node;
_show_automation.clear();
for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
child_node = *niter;
if (child_node->name() != AutomationTimeAxisView::state_node_name)
continue;
XMLProperty* prop = child_node->property ("automation-id");
if (!prop)
continue;
Parameter param(prop->value());
if (!param)
continue;
bool show = false;
prop = child_node->property ("shown");
if (prop && prop->value() == "yes") {
show = true;
_show_automation.insert(param);
}
if (_automation_tracks.find(param) == _automation_tracks.end())
create_automation_child(param, show);
}
}
XMLNode*
RouteTimeAxisView::get_automation_child_xml_node (Parameter param)
{
return RouteUI::get_automation_child_xml_node (param);
}
gint
RouteTimeAxisView::edit_click (GdkEventButton *ev)
{
if (Keyboard::modifier_state_equals (ev->state, Keyboard::Control)) {
_route->set_edit_group (0, this);
return FALSE;
}
using namespace Menu_Helpers;
MenuList& items = edit_group_menu.items ();
RadioMenuItem::Group group;
items.clear ();
items.push_back (RadioMenuElem (group, _("No group"),
bind (mem_fun(*this, &RouteTimeAxisView::set_edit_group_from_menu), (RouteGroup *) 0)));
if (_route->edit_group() == 0) {
static_cast<RadioMenuItem*>(&items.back())->set_active ();
}
_session.foreach_edit_group (bind (mem_fun (*this, &RouteTimeAxisView::add_edit_group_menu_item), &group));
edit_group_menu.popup (ev->button, ev->time);
return FALSE;
}
void
RouteTimeAxisView::add_edit_group_menu_item (RouteGroup *eg, RadioMenuItem::Group* group)
{
using namespace Menu_Helpers;
MenuList &items = edit_group_menu.items();
items.push_back (RadioMenuElem (*group, eg->name(), bind (mem_fun(*this, &RouteTimeAxisView::set_edit_group_from_menu), eg)));
if (_route->edit_group() == eg) {
static_cast<RadioMenuItem*>(&items.back())->set_active ();
}
}
void
RouteTimeAxisView::set_edit_group_from_menu (RouteGroup *eg)
{
_route->set_edit_group (eg, this);
}
void
RouteTimeAxisView::playlist_changed ()
{
label_view ();
if (is_track()) {
set_playlist (get_diskstream()->playlist());
}
}
void
RouteTimeAxisView::label_view ()
{
string x = _route->name();
if (x != name_entry.get_text()) {
name_entry.set_text (x);
}
ARDOUR_UI::instance()->tooltips().set_tip (name_entry, x);
}
void
RouteTimeAxisView::route_name_changed ()
{
editor.route_name_changed (this);
label_view ();
}
void
RouteTimeAxisView::take_name_changed (void *src)
{
if (src != this) {
label_view ();
}
}
void
RouteTimeAxisView::playlist_click ()
{
// always build a new action menu
if (playlist_action_menu != 0) {
delete playlist_action_menu;
}
playlist_action_menu = new Menu;
playlist_action_menu->set_name ("ArdourContextMenu");
build_playlist_menu (playlist_action_menu);
editor.set_selected_track (*this, Selection::Add);
playlist_action_menu->popup (1, gtk_get_current_event_time());
}
void
RouteTimeAxisView::automation_click ()
{
if (automation_action_menu == 0) {
/* this seems odd, but the automation action
menu is built as part of the display menu.
*/
build_display_menu ();
}
editor.set_selected_track (*this, Selection::Add);
automation_action_menu->popup (1, gtk_get_current_event_time());
}
void
RouteTimeAxisView::build_automation_action_menu ()
{
using namespace Menu_Helpers;
automation_action_menu = manage (new Menu);
MenuList& automation_items = automation_action_menu->items();
automation_action_menu->set_name ("ArdourContextMenu");
automation_items.push_back (MenuElem (_("Show all automation"),
mem_fun(*this, &RouteTimeAxisView::show_all_automation)));
automation_items.push_back (MenuElem (_("Show existing automation"),
mem_fun(*this, &RouteTimeAxisView::show_existing_automation)));
automation_items.push_back (MenuElem (_("Hide all automation"),
mem_fun(*this, &RouteTimeAxisView::hide_all_automation)));
if (subplugin_menu.get_parent())
subplugin_menu.detach();
automation_items.push_back (MenuElem (_("Plugins"), subplugin_menu));
map<ARDOUR::Parameter, RouteAutomationNode*>::iterator i;
for (i = _automation_tracks.begin(); i != _automation_tracks.end(); ++i) {
automation_items.push_back (SeparatorElem());
if (i->second->menu_item)
delete i->second->menu_item;
automation_items.push_back(CheckMenuElem (_route->describe_parameter(i->second->param),
bind (mem_fun(*this, &RouteTimeAxisView::toggle_automation_track), i->second->param)));
i->second->menu_item = static_cast<Gtk::CheckMenuItem*>(&automation_items.back());
i->second->menu_item->set_active(show_automation(i->second->param));
//i->second->menu_item->set_active(false);
}
}
void
RouteTimeAxisView::build_display_menu ()
{
using namespace Menu_Helpers;
/* get the size menu ready */
build_size_menu ();
/* prepare it */
TimeAxisView::build_display_menu ();
/* now fill it with our stuff */
MenuList& items = display_menu->items();
display_menu->set_name ("ArdourContextMenu");
items.push_back (MenuElem (_("Height"), *size_menu));
items.push_back (MenuElem (_("Color"), mem_fun(*this, &RouteTimeAxisView::select_track_color)));
items.push_back (SeparatorElem());
build_remote_control_menu ();
items.push_back (MenuElem (_("Remote Control ID"), *remote_control_menu));
build_automation_action_menu ();
items.push_back (MenuElem (_("Automation"), *automation_action_menu));
// Hook for derived classes to add type specific stuff
items.push_back (SeparatorElem());
append_extra_display_menu_items ();
items.push_back (SeparatorElem());
if (is_track()) {
Menu *layers_menu = manage(new Menu);
MenuList &layers_items = layers_menu->items();
layers_menu->set_name("ArdourContextMenu");
RadioMenuItem::Group layers_group;
layers_items.push_back(RadioMenuElem (layers_group, _("Overlaid"),
bind (mem_fun (*this, &RouteTimeAxisView::set_layer_display), Overlaid)));
layers_items.push_back(RadioMenuElem (layers_group, _("Stacked"),
bind (mem_fun (*this, &RouteTimeAxisView::set_layer_display), Stacked)));
items.push_back (MenuElem (_("Layers"), *layers_menu));
Menu* alignment_menu = manage (new Menu);
MenuList& alignment_items = alignment_menu->items();
alignment_menu->set_name ("ArdourContextMenu");
RadioMenuItem::Group align_group;
alignment_items.push_back (RadioMenuElem (align_group, _("Align with existing material"),
bind (mem_fun(*this, &RouteTimeAxisView::set_align_style), ExistingMaterial)));
align_existing_item = dynamic_cast<RadioMenuItem*>(&alignment_items.back());
if (get_diskstream()->alignment_style() == ExistingMaterial)
align_existing_item->set_active();
alignment_items.push_back (RadioMenuElem (align_group, _("Align with capture time"),
bind (mem_fun(*this, &RouteTimeAxisView::set_align_style), CaptureTime)));
align_capture_item = dynamic_cast<RadioMenuItem*>(&alignment_items.back());
if (get_diskstream()->alignment_style() == CaptureTime)
align_capture_item->set_active();
items.push_back (MenuElem (_("Alignment"), *alignment_menu));
get_diskstream()->AlignmentStyleChanged.connect (
mem_fun(*this, &RouteTimeAxisView::align_style_changed));
mode_menu = build_mode_menu();
if (mode_menu)
items.push_back (MenuElem (_("Mode"), *mode_menu));
}
items.push_back (SeparatorElem());
items.push_back (CheckMenuElem (_("Active"), mem_fun(*this, &RouteUI::toggle_route_active)));
route_active_menu_item = dynamic_cast<CheckMenuItem *> (&items.back());
route_active_menu_item->set_active (_route->active());
items.push_back (SeparatorElem());
items.push_back (MenuElem (_("Remove"), mem_fun(*this, &RouteUI::remove_this_route)));
}
static bool __reset_item (RadioMenuItem* item)
{
item->set_active ();
return false;
}
void
RouteTimeAxisView::set_track_mode (TrackMode mode)
{
RadioMenuItem* item;
RadioMenuItem* other_item;
switch (mode) {
case ARDOUR::Normal:
item = normal_track_mode_item;
other_item = destructive_track_mode_item;
break;
case ARDOUR::Destructive:
item = destructive_track_mode_item;
other_item = normal_track_mode_item;
break;
default:
fatal << string_compose (_("programming error: %1 %2"), "illegal track mode in RouteTimeAxisView::set_track_mode", mode) << endmsg;
/*NOTREACHED*/
return;
}
if (item->get_active () && track()->mode() != mode) {
_set_track_mode (track().get(), mode, other_item);
}
}
void
RouteTimeAxisView::_set_track_mode (Track* track, TrackMode mode, RadioMenuItem* reset_item)
{
bool needs_bounce;
if (!track->can_use_mode (mode, needs_bounce)) {
if (!needs_bounce) {
/* cannot be done */
Glib::signal_idle().connect (bind (sigc::ptr_fun (__reset_item), reset_item));
return;
} else {
cerr << "would bounce this one\n";
return;
}
}
track->set_mode (mode);
rec_enable_button->remove ();
switch (mode) {
case ARDOUR::Normal:
rec_enable_button->add (*(manage (new Image (::get_icon (X_("record_normal_red"))))));
break;
case ARDOUR::Destructive:
rec_enable_button->add (*(manage (new Image (::get_icon (X_("record_tape_red"))))));
break;
}
rec_enable_button->show_all ();
}
void
RouteTimeAxisView::track_mode_changed ()
{
RadioMenuItem* item;
switch (track()->mode()) {
case ARDOUR::Normal:
item = normal_track_mode_item;
break;
case ARDOUR::Destructive:
item = destructive_track_mode_item;
break;
default:
fatal << string_compose (_("programming error: %1 %2"), "illegal track mode in RouteTimeAxisView::set_track_mode", track()->mode()) << endmsg;
/*NOTREACHED*/
return;
}
item->set_active ();
}
void
RouteTimeAxisView::show_timestretch (nframes_t start, nframes_t end)
{
double x1;
double x2;
double y2;
TimeAxisView::show_timestretch (start, end);
hide_timestretch ();
#if 0
if (ts.empty()) {
return;
}
/* check that the time selection was made in our route, or our edit group.
remember that edit_group() == 0 implies the route is *not* in a edit group.
*/
if (!(ts.track == this || (ts.group != 0 && ts.group == _route->edit_group()))) {
/* this doesn't apply to us */
return;
}
/* ignore it if our edit group is not active */
if ((ts.track != this) && _route->edit_group() && !_route->edit_group()->is_active()) {
return;
}
#endif
if (timestretch_rect == 0) {
timestretch_rect = new SimpleRect (*canvas_display);
timestretch_rect->property_x1() = 0.0;
timestretch_rect->property_y1() = 0.0;
timestretch_rect->property_x2() = 0.0;
timestretch_rect->property_y2() = 0.0;
timestretch_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_TimeStretchFill.get();
timestretch_rect->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_TimeStretchOutline.get();
}
timestretch_rect->show ();
timestretch_rect->raise_to_top ();
x1 = start / editor.get_current_zoom();
x2 = (end - 1) / editor.get_current_zoom();
y2 = height - 2;
timestretch_rect->property_x1() = x1;
timestretch_rect->property_y1() = 1.0;
timestretch_rect->property_x2() = x2;
timestretch_rect->property_y2() = y2;
}
void
RouteTimeAxisView::hide_timestretch ()
{
TimeAxisView::hide_timestretch ();
if (timestretch_rect) {
timestretch_rect->hide ();
}
}
void
RouteTimeAxisView::show_selection (TimeSelection& ts)
{
#if 0
/* ignore it if our edit group is not active or if the selection was started
in some other track or edit group (remember that edit_group() == 0 means
that the track is not in an edit group).
*/
if (((ts.track != this && !is_child (ts.track)) && _route->edit_group() && !_route->edit_group()->is_active()) ||
(!(ts.track == this || is_child (ts.track) || (ts.group != 0 && ts.group == _route->edit_group())))) {
hide_selection ();
return;
}
#endif
TimeAxisView::show_selection (ts);
}
void
RouteTimeAxisView::set_height (TrackHeight h)
{
bool height_changed = (height == 0) || (h != height_style);
TimeAxisView::set_height (h);
ensure_xml_node ();
if (_view) {
_view->set_height ((double) height);
}
switch (height_style) {
case Largest:
xml_node->add_property ("track_height", "largest");
break;
case Large:
xml_node->add_property ("track_height", "large");
break;
case Larger:
xml_node->add_property ("track_height", "larger");
break;
case Normal:
xml_node->add_property ("track_height", "normal");
break;
case Smaller:
xml_node->add_property ("track_height", "smaller");
break;
case Small:
xml_node->add_property ("track_height", "small");
break;
}
switch (height_style) {
case Largest:
case Large:
case Larger:
case Normal:
show_name_entry ();
hide_name_label ();
mute_button->show();
solo_button->show();
if (rec_enable_button)
rec_enable_button->show();
edit_group_button.show();
hide_button.show();
visual_button.show();
size_button.show();
automation_button.show();
if (is_track() && track()->mode() == ARDOUR::Normal) {
playlist_button.show();
}
break;
case Smaller:
show_name_entry ();
hide_name_label ();
mute_button->show();
solo_button->show();
if (rec_enable_button)
rec_enable_button->show();
edit_group_button.hide ();
hide_button.hide ();
visual_button.hide ();
size_button.hide ();
automation_button.hide ();
if (is_track() && track()->mode() == ARDOUR::Normal) {
playlist_button.hide ();
}
break;
case Small:
hide_name_entry ();
show_name_label ();
mute_button->hide();
solo_button->hide();
if (rec_enable_button)
rec_enable_button->hide();
edit_group_button.hide ();
hide_button.hide ();
visual_button.hide ();
size_button.hide ();
automation_button.hide ();
playlist_button.hide ();
name_label.set_text (_route->name());
break;
}
if (height_changed) {
/* only emit the signal if the height really changed */
_route->gui_changed ("track_height", (void *) 0); /* EMIT_SIGNAL */
}
}
void
RouteTimeAxisView::select_track_color ()
{
if (RouteUI::choose_color ()) {
if (_view) {
_view->apply_color (_color, StreamView::RegionColor);
}
}
}
void
RouteTimeAxisView::reset_samples_per_unit ()
{
set_samples_per_unit (editor.get_current_zoom());
}
void
RouteTimeAxisView::set_samples_per_unit (double spu)
{
double speed = 1.0;
if (get_diskstream() != 0) {
speed = get_diskstream()->speed();
}
if (_view) {
_view->set_samples_per_unit (spu * speed);
}
TimeAxisView::set_samples_per_unit (spu * speed);
}
void
RouteTimeAxisView::align_style_changed ()
{
switch (get_diskstream()->alignment_style()) {
case ExistingMaterial:
if (!align_existing_item->get_active()) {
align_existing_item->set_active();
}
break;
case CaptureTime:
if (!align_capture_item->get_active()) {
align_capture_item->set_active();
}
break;
}
}
void
RouteTimeAxisView::set_align_style (AlignStyle style)
{
RadioMenuItem* item;
switch (style) {
case ExistingMaterial:
item = align_existing_item;
break;
case CaptureTime:
item = align_capture_item;
break;
default:
fatal << string_compose (_("programming error: %1 %2"), "illegal align style in RouteTimeAxisView::set_align_style", style) << endmsg;
/*NOTREACHED*/
return;
}
if (item->get_active()) {
get_diskstream()->set_align_style (style);
}
}
void
RouteTimeAxisView::rename_current_playlist ()
{
ArdourPrompter prompter (true);
string name;
boost::shared_ptr<Diskstream> ds = get_diskstream();
if (!ds || ds->destructive())
return;
boost::shared_ptr<Playlist> pl = ds->playlist();
if (!pl)
return;
prompter.set_prompt (_("Name for playlist"));
prompter.set_initial_text (pl->name());
prompter.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT);
prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, false);
switch (prompter.run ()) {
case Gtk::RESPONSE_ACCEPT:
prompter.get_result (name);
if (name.length()) {
pl->set_name (name);
}
break;
default:
break;
}
}
void
RouteTimeAxisView::use_copy_playlist (bool prompt)
{
string name;
boost::shared_ptr<Diskstream> ds = get_diskstream();
if (!ds || ds->destructive())
return;
boost::shared_ptr<const Playlist> pl = ds->playlist();
if (!pl)
return;
name = pl->name();
do {
name = Playlist::bump_name (name, _session);
} while (_session.playlist_by_name(name));
// TODO: The prompter "new" button should be de-activated if the user
// specifies a playlist name which already exists in the session.
if (prompt) {
ArdourPrompter prompter (true);
prompter.set_prompt (_("Name for Playlist"));
prompter.set_initial_text (name);
prompter.add_button (Gtk::Stock::NEW, Gtk::RESPONSE_ACCEPT);
prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, true);
prompter.show_all ();
switch (prompter.run ()) {
case Gtk::RESPONSE_ACCEPT:
prompter.get_result (name);
break;
default:
return;
}
}
if (name.length()) {
ds->use_copy_playlist ();
ds->playlist()->set_name (name);
}
}
void
RouteTimeAxisView::use_new_playlist (bool prompt)
{
string name;
boost::shared_ptr<Diskstream> ds = get_diskstream();
if (!ds || ds->destructive())
return;
boost::shared_ptr<const Playlist> pl = ds->playlist();
if (!pl)
return;
name = pl->name();
do {
name = Playlist::bump_name (name, _session);
} while (_session.playlist_by_name(name));
if (prompt) {
ArdourPrompter prompter (true);
prompter.set_prompt (_("Name for Playlist"));
prompter.set_initial_text (name);
prompter.add_button (Gtk::Stock::NEW, Gtk::RESPONSE_ACCEPT);
prompter.set_response_sensitive (Gtk::RESPONSE_ACCEPT, true);
switch (prompter.run ()) {
case Gtk::RESPONSE_ACCEPT:
prompter.get_result (name);
break;
default:
return;
}
}
if (name.length()) {
ds->use_new_playlist ();
ds->playlist()->set_name (name);
}
}
void
RouteTimeAxisView::clear_playlist ()
{
boost::shared_ptr<Diskstream> ds = get_diskstream();
if (!ds || ds->destructive())
return;
boost::shared_ptr<Playlist> pl = ds->playlist();
if (!pl)
return;
editor.clear_playlist (pl);
}
void
RouteTimeAxisView::speed_changed ()
{
Gtkmm2ext::UI::instance()->call_slot (mem_fun(*this, &RouteTimeAxisView::reset_samples_per_unit));
}
void
RouteTimeAxisView::diskstream_changed ()
{
Gtkmm2ext::UI::instance()->call_slot (mem_fun(*this, &RouteTimeAxisView::update_diskstream_display));
}
void
RouteTimeAxisView::update_diskstream_display ()
{
if (!get_diskstream()) // bus
return;
set_playlist (get_diskstream()->playlist());
map_frozen ();
}
void
RouteTimeAxisView::selection_click (GdkEventButton* ev)
{
if (Keyboard::modifier_state_equals (ev->state, (Keyboard::Shift|Keyboard::Control))) {
/* special case: select/deselect all tracks */
if (editor.get_selection().selected (this)) {
editor.get_selection().clear_tracks ();
} else {
editor.select_all_tracks ();
}
return;
}
PublicEditor::TrackViewList* tracks = editor.get_valid_views (this, _route->edit_group());
switch (Keyboard::selection_type (ev->state)) {
case Selection::Toggle:
editor.get_selection().toggle (*tracks);
break;
case Selection::Set:
editor.get_selection().set (*tracks);
break;
case Selection::Extend:
if (tracks->size() > 1) {
/* add each one, do not "extend" */
editor.get_selection().add (*tracks);
} else {
/* extend to the single track */
editor.extend_selection_to_track (*tracks->front());
}
break;
case Selection::Add:
editor.get_selection().add (*tracks);
break;
}
delete tracks;
}
void
RouteTimeAxisView::set_selected_points (PointSelection& points)
{
for (Children::iterator i = children.begin(); i != children.end(); ++i) {
(*i)->set_selected_points (points);
}
}
void
RouteTimeAxisView::set_selected_regionviews (RegionSelection& regions)
{
if (_view) {
_view->set_selected_regionviews (regions);
}
}
/** Add the selectable things that we have to a list.
* @param results List to add things to.
*/
void
RouteTimeAxisView::get_selectables (nframes_t start, nframes_t end, double top, double bot, list<Selectable*>& results)
{
double speed = 1.0;
if (get_diskstream() != 0) {
speed = get_diskstream()->speed();
}
nframes_t start_adjusted = session_frame_to_track_frame(start, speed);
nframes_t end_adjusted = session_frame_to_track_frame(end, speed);
if (_view && ((top < 0.0 && bot < 0.0)) || touched (top, bot)) {
_view->get_selectables (start_adjusted, end_adjusted, results);
}
/* pick up visible automation tracks */
for (Children::iterator i = children.begin(); i != children.end(); ++i) {
if (!(*i)->hidden()) {
(*i)->get_selectables (start_adjusted, end_adjusted, top, bot, results);
}
}
}
void
RouteTimeAxisView::get_inverted_selectables (Selection& sel, list<Selectable*>& results)
{
if (_view) {
_view->get_inverted_selectables (sel, results);
}
for (Children::iterator i = children.begin(); i != children.end(); ++i) {
if (!(*i)->hidden()) {
(*i)->get_inverted_selectables (sel, results);
}
}
return;
}
bool
RouteTimeAxisView::show_automation(Parameter param)
{
return (_show_automation.find(param) != _show_automation.end());
}
/** Retuns NULL if track for \a param doesn't exist.
*/
RouteTimeAxisView::RouteAutomationNode*
RouteTimeAxisView::automation_track(Parameter param)
{
map<ARDOUR::Parameter, RouteAutomationNode*>::iterator i = _automation_tracks.find(param);
if (i != _automation_tracks.end())
return i->second;
else
return NULL;
}
/** Shorthand for GainAutomation, etc.
*/
RouteTimeAxisView::RouteAutomationNode*
RouteTimeAxisView::automation_track(AutomationType type)
{
return automation_track(Parameter(type));
}
RouteGroup*
RouteTimeAxisView::edit_group() const
{
return _route->edit_group();
}
string
RouteTimeAxisView::name() const
{
return _route->name();
}
boost::shared_ptr<Playlist>
RouteTimeAxisView::playlist () const
{
boost::shared_ptr<Diskstream> ds;
if ((ds = get_diskstream()) != 0) {
return ds->playlist();
} else {
return boost::shared_ptr<Playlist> ();
}
}
void
RouteTimeAxisView::name_entry_changed ()
{
string x;
x = name_entry.get_text ();
if (x == _route->name()) {
return;
}
strip_whitespace_edges(x);
if (x.length() == 0) {
name_entry.set_text (_route->name());
return;
}
if (_session.route_name_unique (x)) {
_route->set_name (x);
} else {
ARDOUR_UI::instance()->popup_error (_("A track already exists with that name"));
name_entry.set_text (_route->name());
}
}
void
RouteTimeAxisView::visual_click ()
{
popup_display_menu (0);
}
void
RouteTimeAxisView::hide_click ()
{
// LAME fix for hide_button refresh fix
hide_button.set_sensitive(false);
editor.hide_track_in_display (*this);
hide_button.set_sensitive(true);
}
boost::shared_ptr<Region>
RouteTimeAxisView::find_next_region (nframes_t pos, RegionPoint point, int32_t dir)
{
boost::shared_ptr<Diskstream> stream;
boost::shared_ptr<Playlist> playlist;
if ((stream = get_diskstream()) != 0 && (playlist = stream->playlist()) != 0) {
return playlist->find_next_region (pos, point, dir);
}
return boost::shared_ptr<Region> ();
}
bool
RouteTimeAxisView::cut_copy_clear (Selection& selection, CutCopyOp op)
{
boost::shared_ptr<Playlist> what_we_got;
boost::shared_ptr<Diskstream> ds = get_diskstream();
boost::shared_ptr<Playlist> playlist;
bool ret = false;
if (ds == 0) {
/* route is a bus, not a track */
return false;
}
playlist = ds->playlist();
TimeSelection time (selection.time);
float speed = ds->speed();
if (speed != 1.0f) {
for (TimeSelection::iterator i = time.begin(); i != time.end(); ++i) {
(*i).start = session_frame_to_track_frame((*i).start, speed);
(*i).end = session_frame_to_track_frame((*i).end, speed);
}
}
XMLNode &before = playlist->get_state();
switch (op) {
case Cut:
if ((what_we_got = playlist->cut (time)) != 0) {
editor.get_cut_buffer().add (what_we_got);
_session.add_command( new MementoCommand<Playlist>(*playlist.get(), &before, &playlist->get_state()));
ret = true;
}
break;
case Copy:
if ((what_we_got = playlist->copy (time)) != 0) {
editor.get_cut_buffer().add (what_we_got);
}
break;
case Clear:
if ((what_we_got = playlist->cut (time)) != 0) {
_session.add_command( new MementoCommand<Playlist>(*playlist, &before, &playlist->get_state()));
what_we_got->release ();
ret = true;
}
break;
}
return ret;
}
bool
RouteTimeAxisView::paste (nframes_t pos, float times, Selection& selection, size_t nth)
{
if (!is_track()) {
return false;
}
boost::shared_ptr<Playlist> playlist = get_diskstream()->playlist();
PlaylistSelection::iterator p;
for (p = selection.playlists.begin(); p != selection.playlists.end() && nth; ++p, --nth);
if (p == selection.playlists.end()) {
return false;
}
if (get_diskstream()->speed() != 1.0f)
pos = session_frame_to_track_frame(pos, get_diskstream()->speed() );
XMLNode &before = playlist->get_state();
playlist->paste (*p, pos, times);
_session.add_command( new MementoCommand<Playlist>(*playlist, &before, &playlist->get_state()));
return true;
}
TimeAxisView::Children
RouteTimeAxisView::get_child_list()
{
TimeAxisView::Children redirect_children;
for (Children::iterator i = children.begin(); i != children.end(); ++i) {
if (!(*i)->hidden()) {
redirect_children.push_back(*i);
}
}
return redirect_children;
}
void
RouteTimeAxisView::build_playlist_menu (Gtk::Menu * menu)
{
using namespace Menu_Helpers;
if (!menu || !is_track()) {
return;
}
MenuList& playlist_items = menu->items();
menu->set_name ("ArdourContextMenu");
playlist_items.clear();
if (playlist_menu) {
delete playlist_menu;
}
playlist_menu = new Menu;
playlist_menu->set_name ("ArdourContextMenu");
vector<boost::shared_ptr<Playlist> > playlists;
boost::shared_ptr<Diskstream> ds = get_diskstream();
RadioMenuItem::Group playlist_group;
_session.get_playlists (playlists);
for (vector<boost::shared_ptr<Playlist> >::iterator i = playlists.begin(); i != playlists.end(); ++i) {
if ((*i)->get_orig_diskstream_id() == ds->id()) {
playlist_items.push_back (RadioMenuElem (playlist_group, (*i)->name(), bind (mem_fun (*this, &RouteTimeAxisView::use_playlist),
boost::weak_ptr<Playlist> (*i))));
if (ds->playlist()->id() == (*i)->id()) {
static_cast<RadioMenuItem*>(&playlist_items.back())->set_active();
}
} else if (ds->playlist()->id() == (*i)->id()) {
playlist_items.push_back (RadioMenuElem (playlist_group, (*i)->name(), bind (mem_fun (*this, &RouteTimeAxisView::use_playlist),
boost::weak_ptr<Playlist>(*i))));
static_cast<RadioMenuItem*>(&playlist_items.back())->set_active();
}
}
playlist_items.push_back (SeparatorElem());
playlist_items.push_back (MenuElem (_("Rename"), mem_fun(*this, &RouteTimeAxisView::rename_current_playlist)));
playlist_items.push_back (SeparatorElem());
playlist_items.push_back (MenuElem (_("New"), mem_fun(editor, &PublicEditor::new_playlists)));
playlist_items.push_back (MenuElem (_("New Copy"), mem_fun(editor, &PublicEditor::copy_playlists)));
playlist_items.push_back (SeparatorElem());
playlist_items.push_back (MenuElem (_("Clear Current"), mem_fun(editor, &PublicEditor::clear_playlists)));
playlist_items.push_back (SeparatorElem());
playlist_items.push_back (MenuElem(_("Select from all ..."), mem_fun(*this, &RouteTimeAxisView::show_playlist_selector)));
}
void
RouteTimeAxisView::use_playlist (boost::weak_ptr<Playlist> wpl)
{
assert (is_track());
boost::shared_ptr<Playlist> pl (wpl.lock());
if (!pl) {
return;
}
boost::shared_ptr<AudioPlaylist> apl = boost::dynamic_pointer_cast<AudioPlaylist> (pl);
if (apl) {
get_diskstream()->use_playlist (apl);
}
}
void
RouteTimeAxisView::show_playlist_selector ()
{
editor.playlist_selector().show_for (this);
}
void
RouteTimeAxisView::map_frozen ()
{
if (!is_track()) {
return;
}
ENSURE_GUI_THREAD (mem_fun(*this, &RouteTimeAxisView::map_frozen));
switch (track()->freeze_state()) {
case Track::Frozen:
playlist_button.set_sensitive (false);
rec_enable_button->set_sensitive (false);
break;
default:
playlist_button.set_sensitive (true);
rec_enable_button->set_sensitive (true);
break;
}
}
void
RouteTimeAxisView::color_handler ()
{
//case cTimeStretchOutline:
if (timestretch_rect) {
timestretch_rect->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_TimeStretchOutline.get();
}
//case cTimeStretchFill:
if (timestretch_rect) {
timestretch_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_TimeStretchFill.get();
}
}
void
RouteTimeAxisView::toggle_automation_track (Parameter param)
{
RouteAutomationNode* node = automation_track(param);
if (!node)
return;
bool showit = node->menu_item->get_active();
if (showit != node->track->marked_for_display()) {
if (showit) {
node->track->set_marked_for_display (true);
node->track->canvas_display->show();
node->track->get_state_node()->add_property ("shown", X_("yes"));
} else {
node->track->set_marked_for_display (false);
node->track->hide ();
node->track->get_state_node()->add_property ("shown", X_("no"));
}
/* now trigger a redisplay */
if (!no_redraw) {
_route->gui_changed (X_("track_height"), (void *) 0); /* EMIT_SIGNAL */
}
}
}
void
RouteTimeAxisView::automation_track_hidden (Parameter param)
{
RouteAutomationNode* ran = automation_track(param);
if (!ran)
return;
_show_automation.erase(param);
ran->track->get_state_node()->add_property (X_("shown"), X_("no"));
if (ran->menu_item && !_hidden) {
ran->menu_item->set_active (false);
}
_route->gui_changed ("track_height", (void *) 0); /* EMIT_SIGNAL */
}
void
RouteTimeAxisView::show_all_automation ()
{
no_redraw = true;
/* Show our automation */
map<ARDOUR::Parameter, RouteAutomationNode*>::iterator i;
for (i = _automation_tracks.begin(); i != _automation_tracks.end(); ++i) {
i->second->track->set_marked_for_display (true);
i->second->track->canvas_display->show();
i->second->track->get_state_node()->add_property ("shown", X_("yes"));
i->second->menu_item->set_active(true);
}
/* Show processor automation */
for (list<ProcessorAutomationInfo*>::iterator i = processor_automation.begin(); i != processor_automation.end(); ++i) {
for (vector<ProcessorAutomationNode*>::iterator ii = (*i)->lines.begin(); ii != (*i)->lines.end(); ++ii) {
if ((*ii)->view == 0) {
add_processor_automation_curve ((*i)->processor, (*ii)->what);
}
(*ii)->menu_item->set_active (true);
}
}
no_redraw = false;
/* Redraw */
_route->gui_changed ("track_height", (void *) 0); /* EMIT_SIGNAL */
}
void
RouteTimeAxisView::show_existing_automation ()
{
no_redraw = true;
/* Show our automation */
map<ARDOUR::Parameter, RouteAutomationNode*>::iterator i;
for (i = _automation_tracks.begin(); i != _automation_tracks.end(); ++i) {
if (i->second->track->line() && i->second->track->line()->npoints() > 0) {
i->second->track->set_marked_for_display (true);
i->second->track->canvas_display->show();
i->second->track->get_state_node()->add_property ("shown", X_("yes"));
i->second->menu_item->set_active(true);
}
}
/* Show processor automation */
for (list<ProcessorAutomationInfo*>::iterator i = processor_automation.begin(); i != processor_automation.end(); ++i) {
for (vector<ProcessorAutomationNode*>::iterator ii = (*i)->lines.begin(); ii != (*i)->lines.end(); ++ii) {
if ((*ii)->view != 0 && (*i)->processor->control((*ii)->what)->list()->size() > 0) {
(*ii)->menu_item->set_active (true);
}
}
}
no_redraw = false;
_route->gui_changed ("track_height", (void *) 0); /* EMIT_SIGNAL */
}
void
RouteTimeAxisView::hide_all_automation ()
{
no_redraw = true;
for (list<ProcessorAutomationInfo*>::iterator i = processor_automation.begin(); i != processor_automation.end(); ++i) {
for (vector<ProcessorAutomationNode*>::iterator ii = (*i)->lines.begin(); ii != (*i)->lines.end(); ++ii) {
(*ii)->menu_item->set_active (false);
}
}
_show_automation.clear();
no_redraw = false;
_route->gui_changed ("track_height", (void *) 0); /* EMIT_SIGNAL */
}
void
RouteTimeAxisView::region_view_added (RegionView* rv)
{
for (Children::iterator i = children.begin(); i != children.end(); ++i) {
boost::shared_ptr<AutomationTimeAxisView> atv;
if ((atv = boost::dynamic_pointer_cast<AutomationTimeAxisView> (*i)) != 0) {
rv->add_ghost (*atv.get());
}
}
}
void
RouteTimeAxisView::add_ghost_to_processor (RegionView* rv, boost::shared_ptr<AutomationTimeAxisView> atv)
{
rv->add_ghost (*atv.get());
}
RouteTimeAxisView::ProcessorAutomationInfo::~ProcessorAutomationInfo ()
{
for (vector<ProcessorAutomationNode*>::iterator i = lines.begin(); i != lines.end(); ++i) {
delete *i;
}
}
RouteTimeAxisView::ProcessorAutomationNode::~ProcessorAutomationNode ()
{
parent.remove_processor_automation_node (this);
}
void
RouteTimeAxisView::remove_processor_automation_node (ProcessorAutomationNode* pan)
{
if (pan->view) {
remove_child (pan->view);
}
}
RouteTimeAxisView::ProcessorAutomationNode*
RouteTimeAxisView::find_processor_automation_node (boost::shared_ptr<Processor> processor, Parameter what)
{
for (list<ProcessorAutomationInfo*>::iterator i = processor_automation.begin(); i != processor_automation.end(); ++i) {
if ((*i)->processor == processor) {
for (vector<ProcessorAutomationNode*>::iterator ii = (*i)->lines.begin(); ii != (*i)->lines.end(); ++ii) {
if ((*ii)->what == what) {
return *ii;
}
}
}
}
return 0;
}
static string
legalize_for_xml_node (string str)
{
string::size_type pos;
string legal_chars = "abcdefghijklmnopqrtsuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_+=:";
string legal;
legal = str;
pos = 0;
while ((pos = legal.find_first_not_of (legal_chars, pos)) != string::npos) {
legal.replace (pos, 1, "_");
pos += 1;
}
return legal;
}
void
RouteTimeAxisView::add_processor_automation_curve (boost::shared_ptr<Processor> processor, Parameter what)
{
string name;
ProcessorAutomationNode* pan;
if ((pan = find_processor_automation_node (processor, what)) == 0) {
fatal << _("programming error: ")
<< string_compose (X_("processor automation curve for %1:%2 not registered with track!"),
processor->name(), what)
<< endmsg;
/*NOTREACHED*/
return;
}
if (pan->view) {
return;
}
name = processor->describe_parameter (what);
/* create a string that is a legal XML node name that can be used to refer to this redirect+port combination */
/* FIXME: ew */
char state_name[256];
snprintf (state_name, sizeof (state_name), "Redirect-%s-%" PRIu32, legalize_for_xml_node (processor->name()).c_str(), what.id());
boost::shared_ptr<AutomationControl> control = processor->control(what, true);
pan->view = boost::shared_ptr<AutomationTimeAxisView>(
new AutomationTimeAxisView (_session, _route, processor, control,
editor, *this, parent_canvas, name, state_name));
pan->view->Hiding.connect (bind (mem_fun(*this, &RouteTimeAxisView::processor_automation_track_hidden), pan, processor));
if (!pan->view->marked_for_display()) {
pan->view->hide ();
} else {
pan->menu_item->set_active (true);
}
add_child (pan->view);
if (_view) {
_view->foreach_regionview (bind (mem_fun(*this, &RouteTimeAxisView::add_ghost_to_processor), pan->view));
}
processor->mark_automation_visible (what, true);
}
void
RouteTimeAxisView::processor_automation_track_hidden (RouteTimeAxisView::ProcessorAutomationNode* pan, boost::shared_ptr<Processor> i)
{
if (!_hidden) {
pan->menu_item->set_active (false);
}
i->mark_automation_visible (pan->what, false);
_route->gui_changed ("track_height", (void *) 0); /* EMIT_SIGNAL */
}
void
RouteTimeAxisView::add_existing_processor_automation_curves (boost::shared_ptr<Processor> processor)
{
set<Parameter> s;
boost::shared_ptr<AutomationLine> al;
processor->what_has_visible_automation (s);
for (set<Parameter>::iterator i = s.begin(); i != s.end(); ++i) {
if ((al = find_processor_automation_curve (processor, *i)) != 0) {
al->queue_reset ();
} else {
add_processor_automation_curve (processor, (*i));
}
}
}
void
RouteTimeAxisView::add_automation_child(Parameter param, boost::shared_ptr<AutomationTimeAxisView> track, bool show)
{
using namespace Menu_Helpers;
XMLProperty* prop;
add_child (track);
track->Hiding.connect (bind (mem_fun (*this, &RouteTimeAxisView::automation_track_hidden), param));
bool hideit = (!show);
XMLNode* node;
if ((node = track->get_state_node()) != 0) {
if ((prop = node->property ("shown")) != 0) {
if (prop->value() == "yes") {
hideit = false;
}
}
}
_automation_tracks.insert(std::make_pair(param, new RouteAutomationNode(param, NULL, track)));
if (hideit) {
track->hide ();
} else {
_show_automation.insert(param);
_route->gui_changed ("track_height", (void *) 0); /* EMIT_SIGNAL */
}
build_display_menu();
}
void
RouteTimeAxisView::add_processor_to_subplugin_menu (boost::shared_ptr<Processor> processor)
{
using namespace Menu_Helpers;
ProcessorAutomationInfo *rai;
list<ProcessorAutomationInfo*>::iterator x;
const std::set<Parameter>& automatable = processor->what_can_be_automated ();
std::set<Parameter> has_visible_automation;
processor->what_has_visible_automation(has_visible_automation);
if (automatable.empty()) {
return;
}
for (x = processor_automation.begin(); x != processor_automation.end(); ++x) {
if ((*x)->processor == processor) {
break;
}
}
if (x == processor_automation.end()) {
rai = new ProcessorAutomationInfo (processor);
processor_automation.push_back (rai);
} else {
rai = *x;
}
/* any older menu was deleted at the top of processors_changed()
when we cleared the subplugin menu.
*/
rai->menu = manage (new Menu);
MenuList& items = rai->menu->items();
rai->menu->set_name ("ArdourContextMenu");
items.clear ();
for (std::set<Parameter>::const_iterator i = automatable.begin(); i != automatable.end(); ++i) {
ProcessorAutomationNode* pan;
CheckMenuItem* mitem;
string name = processor->describe_parameter (*i);
items.push_back (CheckMenuElem (name));
mitem = dynamic_cast<CheckMenuItem*> (&items.back());
if (has_visible_automation.find((*i)) != has_visible_automation.end()) {
mitem->set_active(true);
}
if ((pan = find_processor_automation_node (processor, *i)) == 0) {
/* new item */
pan = new ProcessorAutomationNode (*i, mitem, *this);
rai->lines.push_back (pan);
} else {
pan->menu_item = mitem;
}
mitem->signal_toggled().connect (bind (mem_fun(*this, &RouteTimeAxisView::processor_menu_item_toggled), rai, pan));
}
/* add the menu for this processor, because the subplugin
menu is always cleared at the top of processors_changed().
this is the result of some poor design in gtkmm and/or
GTK+.
*/
subplugin_menu.items().push_back (MenuElem (processor->name(), *rai->menu));
rai->valid = true;
}
void
RouteTimeAxisView::processor_menu_item_toggled (RouteTimeAxisView::ProcessorAutomationInfo* rai,
RouteTimeAxisView::ProcessorAutomationNode* pan)
{
bool showit = pan->menu_item->get_active();
bool redraw = false;
if (pan->view == 0 && showit) {
add_processor_automation_curve (rai->processor, pan->what);
redraw = true;
}
if (pan->view && showit != pan->view->marked_for_display()) {
if (showit) {
pan->view->set_marked_for_display (true);
pan->view->canvas_display->show();
} else {
rai->processor->mark_automation_visible (pan->what, true);
pan->view->set_marked_for_display (false);
pan->view->hide ();
}
redraw = true;
}
if (redraw && !no_redraw) {
/* now trigger a redisplay */
_route->gui_changed ("track_height", (void *) 0); /* EMIT_SIGNAL */
}
}
void
RouteTimeAxisView::processors_changed ()
{
using namespace Menu_Helpers;
for (list<ProcessorAutomationInfo*>::iterator i = processor_automation.begin(); i != processor_automation.end(); ++i) {
(*i)->valid = false;
}
subplugin_menu.items().clear ();
_route->foreach_processor (this, &RouteTimeAxisView::add_processor_to_subplugin_menu);
_route->foreach_processor (this, &RouteTimeAxisView::add_existing_processor_automation_curves);
for (list<ProcessorAutomationInfo*>::iterator i = processor_automation.begin(); i != processor_automation.end(); ) {
list<ProcessorAutomationInfo*>::iterator tmp;
tmp = i;
++tmp;
if (!(*i)->valid) {
delete *i;
processor_automation.erase (i);
}
i = tmp;
}
/* change in visibility was possible */
_route->gui_changed ("track_height", this);
}
boost::shared_ptr<AutomationLine>
RouteTimeAxisView::find_processor_automation_curve (boost::shared_ptr<Processor> processor, Parameter what)
{
ProcessorAutomationNode* pan;
if ((pan = find_processor_automation_node (processor, what)) != 0) {
if (pan->view) {
pan->view->line();
}
}
return boost::shared_ptr<AutomationLine>();
}
void
RouteTimeAxisView::reset_processor_automation_curves ()
{
for (ProcessorAutomationCurves::iterator i = processor_automation_curves.begin(); i != processor_automation_curves.end(); ++i) {
(*i)->reset();
}
}
void
RouteTimeAxisView::update_rec_display ()
{
RouteUI::update_rec_display ();
name_entry.set_sensitive (!_route->record_enabled());
}
void
RouteTimeAxisView::set_layer_display (LayerDisplay d)
{
_view->set_layer_display (d);
}