/*
- 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.
-
-*/
+ * Copyright (C) 2005-2006 Taybin Rutkin <taybin@taybin.com>
+ * Copyright (C) 2005-2009 Sampo Savolainen <v2@iki.fi>
+ * Copyright (C) 2005-2018 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2005 Karsten Wiese <fzuuzf@googlemail.com>
+ * Copyright (C) 2006-2015 David Robillard <d@drobilla.net>
+ * Copyright (C) 2007-2012 Carl Hetherington <carl@carlh.net>
+ * Copyright (C) 2007-2017 Tim Mayberry <mojofunk@gmail.com>
+ * Copyright (C) 2013-2016 Colin Fletcher <colin.m.fletcher@googlemail.com>
+ * Copyright (C) 2013-2017 John Emmas <john@creativepost.co.uk>
+ * Copyright (C) 2013-2017 Nick Mainsbridge <mainsbridge@gmail.com>
+ * Copyright (C) 2013-2019 Robin Gareus <robin@gareus.org>
+ * Copyright (C) 2014-2019 Ben Loftis <ben@harrisonconsoles.com>
+ * Copyright (C) 2015 André Nusser <andre.nusser@googlemail.com>
+ *
+ * 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
/* Note: public Editor methods are documented in public_editor.h */
#include "ardour/legatize.h"
#include "ardour/region_factory.h"
#include "ardour/reverse.h"
+#include "ardour/selection.h"
#include "ardour/session.h"
#include "ardour/session_playlists.h"
+#include "ardour/source.h"
#include "ardour/strip_silence.h"
#include "ardour/transient_detector.h"
+#include "ardour/transport_master_manager.h"
#include "ardour/transpose.h"
#include "ardour/vca_manager.h"
#include "canvas/canvas.h"
#include "actions.h"
+#include "ardour_message.h"
+#include "ardour_ui.h"
#include "audio_region_view.h"
#include "audio_streamview.h"
#include "audio_time_axis.h"
#include "editor_cursors.h"
#include "editor_drag.h"
#include "editor_regions.h"
+#include "editor_sources.h"
#include "editor_routes.h"
#include "gui_thread.h"
#include "insert_remove_time_dialog.h"
#include "transpose_dialog.h"
#include "transform_dialog.h"
#include "ui_config.h"
+#include "utils.h"
#include "vca_time_axis.h"
#include "pbd/i18n.h"
if (_drags->active ()) {
_drags->abort ();
}
+ paste_count = 0;
if (_session) {
_session->undo (n);
if (_drags->active ()) {
_drags->abort ();
}
+ paste_count = 0;
if (_session) {
_session->redo (n);
}
void
-Editor::split_regions_at (MusicSample where, RegionSelection& regions, bool snap_sample)
+Editor::split_regions_at (MusicSample where, RegionSelection& regions)
{
bool frozen = false;
- RegionSelection pre_selected_regions = selection->regions;
- bool working_on_selection = !pre_selected_regions.empty();
-
list<boost::shared_ptr<Playlist> > used_playlists;
list<RouteTimeAxisView*> used_trackviews;
begin_reversible_command (_("split"));
- // if splitting a single region, and snap-to is using
- // region boundaries, don't pay attention to them
if (regions.size() == 1) {
- switch (_snap_type) {
- case SnapToRegionStart:
- case SnapToRegionSync:
- case SnapToRegionEnd:
- break;
- default:
- if (snap_sample) {
- snap_to (where);
- }
- }
+ /* TODO: if splitting a single region, and snap-to is using
+ region boundaries, mabye we shouldn't pay attention to them? */
} else {
- if (snap_sample) {
- snap_to (where);
- }
-
frozen = true;
EditorFreeze(); /* Emit Signal */
}
EditorThaw(); /* Emit Signal */
}
- if (working_on_selection) {
- // IFF we were working on selected regions, try to reinstate the other region selections that existed before the freeze/thaw.
-
- RegionSelectionAfterSplit rsas = Config->get_region_selection_after_split();
- /* There are three classes of regions that we might want selected after
- splitting selected regions:
- - regions selected before the split operation, and unaffected by it
- - newly-created regions before the split
- - newly-created regions after the split
- */
-
- if (rsas & Existing) {
- // region selections that existed before the split.
- selection->add ( pre_selected_regions );
- }
+ RegionSelectionAfterSplit rsas = Config->get_region_selection_after_split();
+ //if the user has "Clear Selection" as their post-split behavior, then clear the selection
+ if (!latest_regionviews.empty() && (rsas == None)) {
+ selection->clear_objects();
+ selection->clear_time();
+ //but leave track selection intact
+ }
+
+ //if the user doesn't want to preserve the "Existing" selection, then clear the selection
+ if (!(rsas & Existing)) {
+ selection->clear_objects();
+ selection->clear_time();
+ }
+
+ //if the user wants newly-created regions to be selected, then select them:
+ if (mouse_mode == MouseObject) {
for (RegionSelection::iterator ri = latest_regionviews.begin(); ri != latest_regionviews.end(); ri++) {
if ((*ri)->region()->position() < where.sample) {
// new regions created before the split
}
}
}
- } else {
- if( working_on_selection ) {
- selection->add (latest_regionviews); //these are the new regions created after the split
- }
}
commit_reversible_command ();
loc->set_end (max_samplepos, false, true, divisions);
}
if (loc->is_session_range()) {
- _session->set_end_is_free (false);
+ _session->set_session_range_is_free (false);
}
}
if (!in_command) {
loc->set_end (loc->length(), false, true, get_grid_music_divisions(0));
}
if (loc->is_session_range()) {
- _session->set_end_is_free (false);
+ _session->set_session_range_is_free (false);
}
}
if (!in_command) {
void
Editor::build_region_boundary_cache ()
{
+
+ /* TODO: maybe set a timer so we don't recalutate when lots of changes are coming in */
+ /* TODO: maybe somehow defer this until session is fully loaded. */
+
+ if (!_region_boundary_cache_dirty)
+ return;
+
samplepos_t pos = 0;
vector<RegionPoint> interesting_points;
boost::shared_ptr<Region> r;
bool maybe_first_sample = false;
- switch (_snap_type) {
- case SnapToRegionStart:
+ if (UIConfiguration::instance().get_snap_to_region_start()) {
interesting_points.push_back (Start);
maybe_first_sample = true;
- break;
- case SnapToRegionEnd:
+ }
+
+ if (UIConfiguration::instance().get_snap_to_region_end()) {
interesting_points.push_back (End);
- break;
- case SnapToRegionSync:
+ }
+
+ if (UIConfiguration::instance().get_snap_to_region_sync()) {
interesting_points.push_back (SyncPoint);
- break;
- case SnapToRegionBoundary:
- interesting_points.push_back (Start);
- interesting_points.push_back (End);
- maybe_first_sample = true;
- break;
- default:
- fatal << string_compose (_("build_region_boundary_cache called with snap_type = %1"), _snap_type) << endmsg;
- abort(); /*NOTREACHED*/
+ }
+
+ /* if no snap selections are set, boundary cache should be left empty */
+ if ( interesting_points.empty() ) {
+ _region_boundary_cache_dirty = false;
return;
}
TimeAxisView *ontrack = 0;
TrackViewList tlist;
- if (!selection->tracks.empty()) {
- tlist = selection->tracks.filter_to_unique_playlists ();
- } else {
- tlist = track_views.filter_to_unique_playlists ();
- }
+ tlist = track_views.filter_to_unique_playlists ();
if (maybe_first_sample) {
TrackViewList::const_iterator i;
}
}
- while (pos < _session->current_end_sample() && !at_end) {
+ //allow regions to snap to the video start (if any) as if it were a "region"
+ if (ARDOUR_UI::instance()->video_timeline) {
+ region_boundary_cache.push_back (ARDOUR_UI::instance()->video_timeline->get_video_start_offset());
+ }
+
+ std::pair<samplepos_t, samplepos_t> ext = session_gui_extents (false);
+ samplepos_t session_end = ext.second;
+
+ while (pos < session_end && !at_end) {
samplepos_t rpos;
- samplepos_t lpos = max_samplepos;
+ samplepos_t lpos = session_end;
for (vector<RegionPoint>::iterator p = interesting_points.begin(); p != interesting_points.end(); ++p) {
/* finally sort to be sure that the order is correct */
sort (region_boundary_cache.begin(), region_boundary_cache.end());
+
+ _region_boundary_cache_dirty = false;
}
boost::shared_ptr<Region>
samplepos_t rpos = 0;
samplepos_t track_sample;
- RouteTimeAxisView *rtav;
for (i = tracks.begin(); i != tracks.end(); ++i) {
//zoom-behavior-tweaks
//limit our maximum zoom to the session gui extents value
std::pair<samplepos_t, samplepos_t> ext = session_gui_extents();
- samplecnt_t session_extents_pp = ( ext.second - ext.first ) / _visible_canvas_width;
+ samplecnt_t session_extents_pp = (ext.second - ext.first) / _visible_canvas_width;
if (nspp > session_extents_pp)
nspp = session_extents_pp;
new_page_size = (samplepos_t) floor (_visible_canvas_width * nfpp);
half_page_size = new_page_size / 2;
- switch (zoom_focus) {
+ Editing::ZoomFocus zf = zoom_focus;
+
+ if (zf == ZoomFocusEdit && _edit_point == EditAtMouse) {
+ zf = ZoomFocusMouse;
+ }
+
+ switch (zf) {
case ZoomFocusLeft:
leftmost_after_zoom = current_leftmost;
break;
case ZoomFocusEdit:
/* try to keep the edit point in the same place */
where = get_preferred_edit_position ();
-
- if (where > 0) {
-
+ {
double l = - ((new_page_size * ((where - current_leftmost)/(double)current_page)) - where);
if (l < 0) {
} else {
leftmost_after_zoom = (samplepos_t) l;
}
-
- } else {
- /* edit point not defined */
- return;
}
break;
//ToDo: if control points are selected, set extents to that selection
- if ( !selection->regions.empty() ) {
+ if (!selection->regions.empty()) {
RegionSelection rs = get_regions_from_selection_and_entered ();
for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) {
{
if (!selection) return;
+ if (selection->regions.empty() && selection->time.empty()) {
+ if (axes == Horizontal || axes == Both) {
+ temporal_zoom_step(true);
+ }
+ if (axes == Vertical || axes == Both) {
+ if (!track_views.empty()) {
+
+ TrackViewList tvl;
+
+ //implicit hack: by extending the top & bottom check outside the current view limits, we include the trackviews immediately above & below what is visible
+ const double top = vertical_adjustment.get_value() - 10;
+ const double btm = top + _visible_canvas_height + 10;
+
+ for (TrackViewList::iterator iter = track_views.begin(); iter != track_views.end(); ++iter) {
+ if ((*iter)->covered_by_y_range (top, btm)) {
+ tvl.push_back(*iter);
+ }
+ }
+
+ fit_tracks (tvl);
+ }
+ }
+ return;
+ }
+
//ToDo: if notes are selected, zoom to that
//ToDo: if control points are selected, zoom to that
if (axes == Vertical || axes == Both) {
fit_selection ();
}
+
+ //normally, we don't do anything "automatic" to the user's selection.
+ //but in this case, we will clear the selection after a zoom-to-selection.
+ selection->clear();
}
void
samplecnt_t start = _session->current_start_sample();
samplecnt_t end = _session->current_end_sample();
- if (_session->actively_recording () ) {
+ if (_session->actively_recording ()) {
samplepos_t cur = playhead_cursor->current_sample ();
if (cur > end) {
/* recording beyond the end marker; zoom out
ENSURE_GUI_THREAD (*this, &Editor::temporal_zoom_extents)
if (_session) {
- std::pair<samplepos_t, samplepos_t> ext = session_gui_extents( false ); //in this case we want to zoom to the extents explicitly; ignore the users prefs for extra padding
+ std::pair<samplepos_t, samplepos_t> ext = session_gui_extents (false); //in this case we want to zoom to the extents explicitly; ignore the users prefs for extra padding
samplecnt_t start = ext.first;
samplecnt_t end = ext.second;
- if (_session->actively_recording () ) {
+ if (_session->actively_recording ()) {
samplepos_t cur = playhead_cursor->current_sample ();
if (cur > end) {
/* recording beyond the end marker; zoom out
bool
-Editor::choose_new_marker_name(string &name) {
+Editor::choose_new_marker_name(string &name, bool is_range) {
if (!UIConfiguration::instance().get_name_new_markers()) {
/* don't prompt user for a new name */
dialog.set_prompt (_("New Name:"));
- dialog.set_title (_("New Location Marker"));
+ if (is_range) {
+ dialog.set_title(_("New Range"));
+ } else {
+ dialog.set_title (_("New Location Marker"));
+ }
dialog.set_name ("MarkNameWindow");
dialog.set_size_request (250, -1);
samplepos_t end = selection->time[clicked_selection].end;
_session->locations()->next_available_name(rangename,"selection");
+ if (!choose_new_marker_name(rangename, true)) {
+ return;
+ }
Location *location = new Location (*_session, start, end, rangename, Location::IsRangeMarker, get_grid_music_divisions(0));
begin_reversible_command (_("add marker"));
Location* loc;
if ((loc = _session->locations()->session_range_location()) == 0) { //should never happen
- _session->set_session_extents ( _session->audible_sample(), _session->audible_sample() );
+ _session->set_session_extents (_session->audible_sample(), _session->audible_sample());
} else {
XMLNode &before = loc->get_state();
- _session->set_session_extents ( _session->audible_sample(), loc->end() );
+ _session->set_session_extents (_session->audible_sample(), loc->end());
XMLNode &after = loc->get_state();
commit_reversible_command ();
}
+
+ _session->set_session_range_is_free (false);
}
void
Location* loc;
if ((loc = _session->locations()->session_range_location()) == 0) { //should never happen
- _session->set_session_extents ( _session->audible_sample(), _session->audible_sample() );
+ _session->set_session_extents (_session->audible_sample(), _session->audible_sample());
} else {
XMLNode &before = loc->get_state();
- _session->set_session_extents ( loc->start(), _session->audible_sample() );
+ _session->set_session_extents (loc->start(), _session->audible_sample());
XMLNode &after = loc->get_state();
commit_reversible_command ();
}
- _session->set_end_is_free (false);
+ _session->set_session_range_is_free (false);
}
samplepos_t pos = _session->locations()->first_mark_before (playhead_cursor->current_sample());
//handle the case where we are rolling, and we're less than one-half second past the mark, we want to go to the prior mark...
- if ( _session->transport_rolling() ) {
- if ( (playhead_cursor->current_sample() - pos) < _session->sample_rate()/2 ) {
- samplepos_t prior = _session->locations()->first_mark_before ( pos );
+ if (_session->transport_rolling()) {
+ if ((playhead_cursor->current_sample() - pos) < _session->sample_rate()/2) {
+ samplepos_t prior = _session->locations()->first_mark_before (pos);
pos = prior;
}
}
/* INSERT/REPLACE */
void
-Editor::insert_region_list_selection (float times)
+Editor::insert_source_list_selection (float times)
{
RouteTimeAxisView *tv = 0;
boost::shared_ptr<Playlist> playlist;
return;
}
- boost::shared_ptr<Region> region = _regions->get_single_selection ();
+ boost::shared_ptr<Region> region = _sources->get_single_selection ();
if (region == 0) {
return;
}
}
if (_session->config.get_external_sync()) {
- switch (Config->get_sync_source()) {
+ switch (TransportMasterManager::instance().current()->type()) {
case Engine:
break;
default:
samplepos_t start_sample;
samplepos_t return_sample;
- start_sample = get_preferred_edit_position ( EDIT_IGNORE_PHEAD );
+ start_sample = get_preferred_edit_position (EDIT_IGNORE_PHEAD);
if (_session->transport_rolling()) {
_session->request_locate (start_sample, false);
Editor::play_selection ()
{
samplepos_t start, end;
- if (!get_selection_extents ( start, end))
+ if (!get_selection_extents (start, end))
return;
AudioRange ar (start, end, 0);
void
Editor::maybe_locate_with_edit_preroll (samplepos_t location)
{
- if ( _session->transport_rolling() || !UIConfiguration::instance().get_follow_edits() || _session->config.get_external_sync() )
+ if (_session->transport_rolling() || !UIConfiguration::instance().get_follow_edits() || _session->config.get_external_sync())
return;
location -= _session->preroll_samples (location);
}
//if follow_playhead is on, keep the playhead on the screen
- if ( _follow_playhead )
- if ( location < _leftmost_sample )
+ if (_follow_playhead)
+ if (location < _leftmost_sample)
location = _leftmost_sample;
- _session->request_locate( location );
+ _session->request_locate (location);
}
void
Editor::play_with_preroll ()
{
samplepos_t start, end;
- if ( UIConfiguration::instance().get_follow_edits() && get_selection_extents ( start, end) ) {
+ if (UIConfiguration::instance().get_follow_edits() && get_selection_extents (start, end)) {
const samplepos_t preroll = _session->preroll_samples (start);
samplepos_t ret = start;
}
}
- if (in_command) {
-// selection->set (new_selection);
+ if (in_command) {
+
+ RangeSelectionAfterSplit rsas = Config->get_range_selection_after_split();
+
+ //if our config preference says to clear the selection, clear the Range selection
+ if (rsas == ClearSel) {
+ selection->clear_time();
+ //but leave track selection intact
+ } else if (rsas == ForceSel) {
+ //note: forcing the regions to be selected *might* force a tool-change to Object here
+ selection->set(new_selection);
+ }
commit_reversible_command ();
}
playlist->partition ((*rl)->first_sample() - 1, (*rl)->last_sample() + 1, true);
//Re-add region that was just removed due to the partition operation
- playlist->add_region( (*rl), (*rl)->first_sample() );
+ playlist->add_region ((*rl), (*rl)->first_sample());
}
vector<PlaylistState>::iterator pl;
}
if (!clicked_routeview->track()->bounceable (clicked_routeview->track()->main_outs(), true)) {
- MessageDialog d (
+ ArdourMessageDialog d (
_("This track/bus cannot be frozen because the signal adds or loses channels before reaching the outputs.\n"
"This is typically caused by plugins that generate stereo output from mono input or vice versa.")
);
}
if (clicked_routeview->track()->has_external_redirects()) {
- MessageDialog d (string_compose (_("<b>%1</b>\n\nThis track has at least one send/insert/return as part of its signal flow.\n\n"
- "Freezing will only process the signal as far as the first send/insert/return."),
- clicked_routeview->track()->name()), true, MESSAGE_INFO, BUTTONS_NONE, true);
+ ArdourMessageDialog d (string_compose (_("<b>%1</b>\n\nThis track has at least one send/insert/return as part of its signal flow.\n\n"
+ "Freezing will only process the signal as far as the first send/insert/return."),
+ clicked_routeview->track()->name()), true, MESSAGE_INFO, BUTTONS_NONE, true);
d.add_button (_("Freeze anyway"), Gtk::RESPONSE_OK);
d.add_button (_("Don't freeze"), Gtk::RESPONSE_CANCEL);
RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
if (rtv && rtv->track() && replace && enable_processing && !rtv->track()->bounceable (rtv->track()->main_outs(), false)) {
- MessageDialog d (
+ ArdourMessageDialog d (
_("You can't perform this operation because the processing of the signal "
"will cause one or more of the tracks to end up with a region with more channels than this track has inputs.\n\n"
"You can do this without processing, which is a different operation.")
//special case: if the user is pointing in the editor/mixer strip, they may be trying to delete a plugin.
//we need this because the editor-mixer strip is in the editor window, so it doesn't get the bindings from the mix window
bool deleted = false;
- if ( current_mixer_strip && current_mixer_strip == MixerStrip::entered_mixer_strip() )
+ if (current_mixer_strip && current_mixer_strip == MixerStrip::entered_mixer_strip())
deleted = current_mixer_strip->delete_processors ();
if (!deleted)
}
}
- if ( op != Delete ) { //"Delete" doesn't change copy/paste buf
+ if (op != Delete) { //"Delete" doesn't change copy/paste buf
cut_buffer->clear ();
}
begin_reversible_command (_("remove region"));
boost::shared_ptr<Playlist> playlist = clicked_routeview->playlist();
+ boost::shared_ptr<Region> region = clicked_regionview->region();
playlist->clear_changes ();
playlist->clear_owned_changes ();
- playlist->remove_region (clicked_regionview->region());
- if (Config->get_edit_mode() == Ripple)
- playlist->ripple (clicked_regionview->region()->position(), -clicked_regionview->region()->length(), boost::shared_ptr<Region>());
+ playlist->remove_region (region);
+
+ if (Config->get_edit_mode() == Ripple) {
+ playlist->ripple (region->position(), - region->length(), boost::shared_ptr<Region>());
+ }
/* We might have removed regions, which alters other regions' layering_index,
so we need to do a recursive diff here.
}
+void
+Editor::recover_regions (ARDOUR::RegionList regions)
+{
+#ifdef RECOVER_REGIONS_IS_WORKING
+ begin_reversible_command (_("recover regions"));
+
+ for (RegionList::iterator i = regions.begin(); i != regions.end(); ++i) {
+ boost::shared_ptr<ARDOUR::Source> source = (*i)->source();
+
+ RouteList routes = _session->get_routelist();
+ for (RouteList::iterator it = routes.begin(); it != routes.end(); ++it) {
+ boost::shared_ptr<ARDOUR::Track> track = boost::dynamic_pointer_cast<Track>(*it);
+ if (track) {
+ //ToDo
+ if (source->captured_for() == track->) {
+ //_session->add_command(new StatefulDiffCommand (playlist));
+ }
+ }
+ }
+ }
+
+ commit_reversible_command ();
+#endif
+}
+
+
/** Remove the selected regions */
void
Editor::remove_selected_regions ()
DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("preferred edit position is %1\n", position));
}
- if (position == last_paste_pos) {
- /* repeated paste in the same position */
- ++paste_count;
- } else {
+ if (position != last_paste_pos) {
/* paste in new location, reset repeated paste state */
paste_count = 0;
last_paste_pos = position;
}
}
+ ++paste_count;
+
commit_reversible_command ();
}
}
boost::shared_ptr<Playlist> playlist;
+ std::set<boost::shared_ptr<Playlist> > playlists; // list of unique playlists affected by duplication
RegionSelection sel = regions; // clear (below) may clear the argument list if its the current region selection
RegionSelection foo;
samplepos_t const start_sample = regions.start ();
samplepos_t const end_sample = regions.end_sample ();
- samplecnt_t const gap = end_sample - start_sample + 1;
+ samplecnt_t const span = end_sample - start_sample + 1;
begin_reversible_command (Operations::duplicate_region);
selection->clear_regions ();
+ /* ripple first so that we don't move the duplicates that will be added */
+
+ if (Config->get_edit_mode() == Ripple) {
+
+ /* convert RegionSelection into RegionList so that we can pass it to ripple and exclude the regions we will duplicate */
+
+ RegionList exclude;
+
+ for (RegionSelection::iterator i = sel.begin(); i != sel.end(); ++i) {
+ exclude.push_back ((*i)->region());
+ playlist = (*i)->region()->playlist();
+ if (playlists.insert (playlist).second) {
+ /* successfully inserted into set, so it's the first time we've seen this playlist */
+ playlist->clear_changes ();
+ }
+ }
+
+ for (set<boost::shared_ptr<Playlist> >::iterator p = playlists.begin(); p != playlists.end(); ++p) {
+ (*p)->ripple (start_sample, span * times, &exclude);
+ }
+ }
+
for (RegionSelection::iterator i = sel.begin(); i != sel.end(); ++i) {
boost::shared_ptr<Region> r ((*i)->region());
samplepos_t const position = end_sample + (r->first_sample() - start_sample + 1);
playlist = (*i)->region()->playlist();
- playlist->clear_changes ();
- playlist->duplicate (r, position, gap, times);
- _session->add_command(new StatefulDiffCommand (playlist));
+
+ if (Config->get_edit_mode() != Ripple) {
+ if (playlists.insert (playlist).second) {
+ playlist->clear_changes ();
+ }
+ }
+
+ playlist->duplicate (r, position, span, times);
c.disconnect ();
foo.insert (foo.end(), latest_regionviews.begin(), latest_regionviews.end());
}
+ for (set<boost::shared_ptr<Playlist> >::iterator p = playlists.begin(); p != playlists.end(); ++p) {
+ _session->add_command (new StatefulDiffCommand (*p));
+ vector<Command*> cmds;
+ (*p)->rdiff (cmds);
+ _session->add_commands (cmds);
+ }
+
if (!foo.empty()) {
selection->set (foo);
}
}
}
+void
+Editor::tag_regions (RegionList regions)
+{
+ ArdourDialog d (_("Tag Last Capture"), true, false);
+ Entry entry;
+ Label label (_("Tag:"));
+ HBox hbox;
+
+ hbox.set_spacing (6);
+ hbox.pack_start (label, false, false);
+ hbox.pack_start (entry, true, true);
+
+ d.get_vbox()->set_border_width (12);
+ d.get_vbox()->pack_start (hbox, false, false);
+
+ d.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
+ d.add_button(Gtk::Stock::OK, Gtk::RESPONSE_OK);
+
+ d.set_size_request (300, -1);
+
+ entry.set_text (_("Good"));
+ entry.select_region (0, -1);
+
+ entry.signal_activate().connect (sigc::bind (sigc::mem_fun (d, &Dialog::response), RESPONSE_OK));
+
+ d.show_all ();
+
+ entry.grab_focus();
+
+ int const ret = d.run();
+
+ d.hide ();
+
+ if (ret != RESPONSE_OK) {
+ return;
+ }
+
+ std::string tagstr = entry.get_text();
+ strip_whitespace_edges (tagstr);
+
+ if (!tagstr.empty()) {
+ for (RegionList::iterator r = regions.begin(); r != regions.end(); r++) {
+ (*r)->set_tags(tagstr);
+ }
+
+ _regions->redisplay ();
+ }
+}
+
+void
+Editor::tag_selected_region ()
+{
+ std::list<boost::shared_ptr<Region> > rlist;
+
+ RegionSelection rs = get_regions_from_selection_and_entered ();
+ for (RegionSelection::iterator r = rs.begin(); r != rs.end(); r++) {
+ rlist.push_back((*r)->region());
+ }
+
+ tag_regions(rlist);
+}
+
+void
+Editor::tag_last_capture ()
+{
+ if (!_session) {
+ return;
+ }
+
+ std::list<boost::shared_ptr<Region> > rlist;
+
+ std::list<boost::shared_ptr<Source> > srcs;
+ _session->get_last_capture_sources (srcs);
+ for (std::list<boost::shared_ptr<Source> >::iterator i = srcs.begin(); i != srcs.end(); ++i) {
+ boost::shared_ptr<ARDOUR::Source> source = (*i);
+ if (source) {
+
+ set<boost::shared_ptr<Region> > regions;
+ RegionFactory::get_regions_using_source (source, regions);
+ for (set<boost::shared_ptr<Region> >::iterator r = regions.begin(); r != regions.end(); r++) {
+ rlist.push_back(*r);
+ }
+
+ }
+ }
+
+ tag_regions(rlist);
+}
+
void
Editor::normalize_region ()
{
}
}
+StripableList
+tracklist_to_stripables (TrackViewList list)
+{
+ StripableList ret;
+
+ for (TrackSelection::iterator i = list.begin(); i != list.end(); ++i) {
+ RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> ((*i));
+
+ if (rtv && rtv->is_track()) {
+ ret.push_back (rtv->track());
+ }
+ }
+
+ return ret;
+}
+
+void
+Editor::play_solo_selection (bool restart)
+{
+ //note: session::solo_selection takes care of invalidating the region playlist
+
+ if ((!selection->tracks.empty()) && selection->time.length() > 0) { //a range is selected; solo the tracks and roll
+
+ StripableList sl = tracklist_to_stripables (selection->tracks);
+ _session->solo_selection (sl, true);
+
+ if (restart) {
+ samplepos_t start = selection->time.start();
+ samplepos_t end = selection->time.end_sample();
+ _session->request_bounded_roll (start, end);
+ }
+ } else if (! selection->tracks.empty()) { //no range is selected, but tracks are selected; solo the tracks and roll
+ StripableList sl = tracklist_to_stripables (selection->tracks);
+ _session->solo_selection (sl, true);
+ _session->request_cancel_play_range();
+ transition_to_rolling (true);
+
+ } else if (! selection->regions.empty()) { //solo any tracks with selected regions, and roll
+ StripableList sl = tracklist_to_stripables (get_tracks_for_range_action());
+ _session->solo_selection (sl, true);
+ _session->request_cancel_play_range();
+ transition_to_rolling (true);
+ } else {
+ _session->request_cancel_play_range();
+ transition_to_rolling (true); //no selection. just roll.
+ }
+}
+
void
Editor::toggle_solo ()
{
first = false;
}
- cl->push_back (stav->stripable()->mute_control());
+ boost::shared_ptr<MuteControl> mc = stav->stripable()->mute_control();
+ cl->push_back (mc);
+ mc->start_touch (_session->audible_sample ());
}
_session->set_controls (cl, new_state, Controllable::UseGroup);
void
Editor::split_region ()
{
- if (_drags->active ()) {
+ if (_dragging_playhead) {
+ /*continue*/
+ } else if (_drags->active ()) {
+ /*any other kind of drag, bail out so we avoid Undo snafu*/
return;
}
//if a range is selected, separate it
- if ( !selection->time.empty()) {
+ if (!selection->time.empty()) {
separate_regions_between (selection->time);
return;
}
//if no range was selected, try to find some regions to split
- if (current_mouse_mode() == MouseObject) { //don't try this for Internal Edit, Stretch, Draw, etc.
+ if (current_mouse_mode() == MouseObject || current_mouse_mode() == MouseRange ) { //don't try this for Internal Edit, Stretch, Draw, etc.
+
+ RegionSelection rs;
+
+ //new behavior: the Split action will prioritize the entered_regionview rather than selected regions.
+ //this fixes the unexpected case where you point at a region, but
+ // * nothing happens OR
+ // * some other region (maybe off-screen) is split.
+ //NOTE: if the entered_regionview is /part of the selection/ then we should operate on the selection as usual
+ if (_edit_point == EditAtMouse && entered_regionview && !entered_regionview->selected()) {
+ rs.add (entered_regionview);
+ } else {
+ rs = selection->regions; //might be empty
+ }
+
+ if (rs.empty()) {
+ TrackViewList tracks = selection->tracks;
+
+ if (!tracks.empty()) {
+ /* no region selected or entered, but some selected tracks:
+ * act on all regions on the selected tracks at the edit point
+ */
+ samplepos_t const where = get_preferred_edit_position (Editing::EDIT_IGNORE_NONE, false, false);
+ get_regions_at(rs, where, tracks);
+ }
+ }
- RegionSelection rs = get_regions_from_selection_and_edit_point ();
const samplepos_t pos = get_preferred_edit_position();
const int32_t division = get_grid_music_divisions (0);
MusicSample where (pos, division);
}
split_regions_at (where, rs);
-
}
}
void
Editor::select_next_stripable (bool routes_only)
{
- if (selection->tracks.empty()) {
- selection->set (track_views.front());
- return;
- }
-
- TimeAxisView* current = selection->tracks.front();
-
- bool valid;
- do {
- for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
-
- if (*i == current) {
- ++i;
- if (i != track_views.end()) {
- current = (*i);
- } else {
- current = (*(track_views.begin()));
- //selection->set (*(track_views.begin()));
- }
- break;
- }
- }
-
- if (routes_only) {
- RouteUI* rui = dynamic_cast<RouteUI *>(current);
- valid = rui && rui->route()->active();
- } else {
- valid = 0 != current->stripable ().get();
- }
-
- } while (current->hidden() || !valid);
-
- selection->set (current);
-
- ensure_time_axis_view_is_visible (*current, false);
+ _session->selection().select_next_stripable (false, routes_only);
}
void
Editor::select_prev_stripable (bool routes_only)
{
- if (selection->tracks.empty()) {
- selection->set (track_views.front());
- return;
- }
-
- TimeAxisView* current = selection->tracks.front();
-
- bool valid;
- do {
- for (TrackViewList::reverse_iterator i = track_views.rbegin(); i != track_views.rend(); ++i) {
-
- if (*i == current) {
- ++i;
- if (i != track_views.rend()) {
- current = (*i);
- } else {
- current = *(track_views.rbegin());
- }
- break;
- }
- }
- if (routes_only) {
- RouteUI* rui = dynamic_cast<RouteUI *>(current);
- valid = rui && rui->route()->active();
- } else {
- valid = 0 != current->stripable ().get();
- }
-
- } while (current->hidden() || !valid);
-
- selection->set (current);
-
- ensure_time_axis_view_is_visible (*current, false);
+ _session->selection().select_prev_stripable (false, routes_only);
}
void
}
samplepos_t start, end;
- if (!get_selection_extents ( start, end))
+ if (!get_selection_extents (start, end))
return;
set_loop_range (start, end, _("set loop range from selection"));
Editor::set_loop_from_region (bool play)
{
samplepos_t start, end;
- if (!get_selection_extents ( start, end))
+ if (!get_selection_extents (start, end))
return;
set_loop_range (start, end, _("set loop range from region"));
}
samplepos_t start, end;
- if (!get_selection_extents ( start, end))
+ if (!get_selection_extents (start, end))
return;
set_punch_range (start, end, _("set punch range from selection"));
set_punch_range (tpl->start(), now, _("Auto Punch In/Out"));
_session->config.set_punch_out(true);
}
- } else {
+ } else {
if (_session->config.get_punch_out()) {
_session->config.set_punch_out(false);
}
}
samplepos_t start, end;
- if (!get_selection_extents ( start, end))
+ if (!get_selection_extents (start, end))
return;
Location* loc;
commit_reversible_command ();
}
- _session->set_end_is_free (false);
+ _session->set_session_range_is_free (false);
}
void
start.sample = get_preferred_edit_position();
}
- //snap the selection start/end
- snap_to(start);
-
//if there's not already a sensible selection endpoint, go "forever"
- if (start.sample > end ) {
+ if (start.sample > end) {
end = max_samplepos;
}
end.sample = get_preferred_edit_position();
}
- //snap the selection start/end
- snap_to (end);
-
set_punch_range (start, end.sample, _("set punch end from EP"));
}
start.sample = get_preferred_edit_position();
}
- //snap the selection start/end
- snap_to (start);
-
//if there's not already a sensible selection endpoint, go "forever"
- if (start.sample > end ) {
+ if (start.sample > end) {
end = max_samplepos;
}
end.sample = get_preferred_edit_position();
}
- //snap the selection start/end
- snap_to(end);
-
set_loop_range (start, end.sample, _("set loop end from EP"));
}
}
Editor::set_punch_from_region ()
{
samplepos_t start, end;
- if (!get_selection_extents ( start, end))
+ if (!get_selection_extents (start, end))
return;
set_punch_range (start, end, _("set punch range from region"));
if (positions.size() > 20 && can_ferret) {
std::string msgstr = string_compose (_("You are about to split\n%1\ninto %2 pieces.\nThis could take a long time."), r->name(), positions.size() + 1);
- MessageDialog msg (msgstr,
- false,
- Gtk::MESSAGE_INFO,
- Gtk::BUTTONS_OK_CANCEL);
+ ArdourMessageDialog msg (msgstr,
+ false,
+ Gtk::MESSAGE_INFO,
+ Gtk::BUTTONS_OK_CANCEL);
if (can_ferret) {
msg.add_button (_("Call for the Ferret!"), RESPONSE_APPLY);
}
msg.set_title (_("Excessive split?"));
- msg.present ();
-
int response = msg.run();
msg.hide ();
(*r)->region()->clear_changes ();
MusicSample start ((*r)->region()->first_sample (), 0);
- snap_to (start);
+ snap_to (start, RoundNearest, SnapToGrid_Unscaled, true);
(*r)->region()->set_position (start.sample, start.division);
_session->add_command(new StatefulDiffCommand ((*r)->region()));
}
}
(*r)->region()->clear_changes ();
- (*r)->region()->trim_front( (position - pull_back_samples));
+ (*r)->region()->trim_front((position - pull_back_samples));
last_region->clear_changes ();
- last_region->trim_end( (position - pull_back_samples + crossfade_len));
+ last_region->trim_end ((position - pull_back_samples + crossfade_len));
_session->add_command (new StatefulDiffCommand ((*r)->region()));
_session->add_command (new StatefulDiffCommand (last_region));
return;
}
- MusicSample pos (playhead_cursor->current_sample (), 0);
+ MusicSample pos (playhead_cursor->current_sample (), 0);
+
+ if ( _grid_type == GridTypeNone) {
+ if (pos.sample < max_samplepos - current_page_samples()*0.1) {
+ pos.sample += current_page_samples()*0.1;
+ _session->request_locate (pos.sample);
+ } else {
+ _session->request_locate (0);
+ }
+ } else {
+
+ if (pos.sample < max_samplepos - 1) {
+ pos.sample += 2;
+ pos = snap_to_grid (pos, RoundUpAlways, SnapToGrid_Scaled);
+ _session->request_locate (pos.sample);
+ }
+ }
+
- if (pos.sample < max_samplepos - 1) {
- pos.sample += 2;
- snap_to_internal (pos, RoundUpAlways, false, true);
- _session->request_locate (pos.sample);
+ /* keep PH visible in window */
+ if (pos.sample > (_leftmost_sample + current_page_samples() *0.9)) {
+ reset_x_origin (pos.sample - (current_page_samples()*0.9));
}
}
MusicSample pos (playhead_cursor->current_sample (), 0);
- if (pos.sample > 2) {
- pos.sample -= 2;
- snap_to_internal (pos, RoundDownAlways, false, true);
- _session->request_locate (pos.sample);
+ if ( _grid_type == GridTypeNone) {
+ if ( pos.sample > current_page_samples()*0.1 ) {
+ pos.sample -= current_page_samples()*0.1;
+ _session->request_locate (pos.sample);
+ } else {
+ _session->request_locate (0);
+ }
+ } else {
+
+ if (pos.sample > 2) {
+ pos.sample -= 2;
+ pos = snap_to_grid (pos, RoundDownAlways, SnapToGrid_Scaled);
+ }
+
+ //handle the case where we are rolling, and we're less than one-half second past the mark, we want to go to the prior mark...
+ //also see: jump_backward_to_mark
+ if (_session->transport_rolling()) {
+ if ((playhead_cursor->current_sample() - pos.sample) < _session->sample_rate()/2) {
+ pos = snap_to_grid (pos, RoundDownAlways, SnapToGrid_Scaled);
+ }
+ }
+
+ _session->request_locate (pos.sample, _session->transport_rolling());
+ }
+
+ /* keep PH visible in window */
+ if (pos.sample < (_leftmost_sample + current_page_samples() *0.1)) {
+ reset_x_origin (pos.sample - (current_page_samples()*0.1));
}
}
return;
}
+ if (!ARDOUR_UI_UTILS::engine_is_running ()) {
+ return;
+ }
+
vector<string> choices;
string prompt;
int ntracks = 0;
}
if (special_bus && !Config->get_allow_special_bus_removal()) {
- MessageDialog msg (_("That would be bad news ...."),
- false,
- Gtk::MESSAGE_INFO,
- Gtk::BUTTONS_OK);
- msg.set_secondary_text (string_compose (_(
- "Removing the master or monitor bus is such a bad idea\n\
+ ArdourMessageDialog msg (_("That would be bad news ...."),
+ false,
+ Gtk::MESSAGE_INFO,
+ Gtk::BUTTONS_OK);
+ msg.set_secondary_text (string_compose (_("Removing the master or monitor bus is such a bad idea\n\
that %1 is not going to allow it.\n\
\n\
If you really want to do this sort of thing\n\
edit your ardour.rc file to set the\n\
\"allow-special-bus-removal\" option to be \"yes\""), PROGRAM_NAME));
- msg.present ();
msg.run ();
return;
}
/* Route deletion calls Editor::timeaxisview_deleted() iteratively (for each deleted
* route). If the deleted route is currently displayed in the Editor-Mixer (highly
* likely because deletion requires selection) this will call
- * Editor::set_selected_mixer_strip () which is expensive ( MixerStrip::set_route() ).
+ * Editor::set_selected_mixer_strip () which is expensive (MixerStrip::set_route()).
* It's likewise likely that the route that has just been displayed in the
* Editor-Mixer will be next in line for deletion.
*
Editor::do_insert_time ()
{
if (selection->tracks.empty()) {
+ ArdourMessageDialog msg (_("You must first select some tracks to Insert Time."),
+ true, MESSAGE_INFO, BUTTONS_OK, true);
+ msg.run ();
+ return;
+ }
+
+ if (Config->get_edit_mode() == Lock) {
+ ArdourMessageDialog msg (_("You cannot insert time in Lock Edit mode."),
+ true, MESSAGE_INFO, BUTTONS_OK, true);
+ msg.run ();
return;
}
if (all_playlists) {
RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (*x);
if (rtav && rtav->track ()) {
- vector<boost::shared_ptr<Playlist> > all = _session->playlists->playlists_for_track (rtav->track ());
+ vector<boost::shared_ptr<Playlist> > all = _session->playlists()->playlists_for_track (rtav->track ());
for (vector<boost::shared_ptr<Playlist> >::iterator p = all.begin(); p != all.end(); ++p) {
pl.insert (*p);
}
Editor::do_remove_time ()
{
if (selection->tracks.empty()) {
+ ArdourMessageDialog msg (_("You must first select some tracks to Remove Time."),
+ true, MESSAGE_INFO, BUTTONS_OK, true);
+ msg.run ();
+ return;
+ }
+
+ if (Config->get_edit_mode() == Lock) {
+ ArdourMessageDialog msg (_("You cannot remove time in Lock Edit mode."),
+ true, MESSAGE_INFO, BUTTONS_OK, true);
+ msg.run ();
return;
}
}
}
- } else if ((*i)->start() >= pos && (*i)->start() < pos+samples ) {
+ } else if ((*i)->start() >= pos && (*i)->start() < pos+samples) {
loc_kill_list.push_back(*i);
moved = true;
} else if ((*i)->start() >= pos) {
}
for (list<Location*>::iterator i = loc_kill_list.begin(); i != loc_kill_list.end(); ++i) {
- _session->locations()->remove( *i );
+ _session->locations()->remove (*i);
}
if (moved) {
if (tempo_too) {
XMLNode& before (_session->tempo_map().get_state());
- if (_session->tempo_map().remove_time (pos, samples) ) {
+ if (_session->tempo_map().remove_time (pos, samples)) {
if (!in_command) {
begin_reversible_command (_("remove time"));
in_command = true;
double first_y_pos = DBL_MAX;
if (h < TimeAxisView::preset_height (HeightSmall)) {
- MessageDialog msg (_("There are too many tracks to fit in the current window"));
+ ArdourMessageDialog msg (_("There are too many tracks to fit in the current window"));
+ msg.run ();
/* too small to be displayed */
return;
}