X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=gtk2_ardour%2Feditor_ops.cc;h=0dfaecf4fd75b759eb8de02a3e55dedf8a9539c2;hb=ccf58b8de23619db9983a45f696fd97e13a15cce;hp=a84832d73e01cc45544be20c9aadccf56b1e6dbc;hpb=2d62ded1aa92b82d696fb37c952732ae1a309011;p=ardour.git diff --git a/gtk2_ardour/editor_ops.cc b/gtk2_ardour/editor_ops.cc index a84832d73e..0dfaecf4fd 100644 --- a/gtk2_ardour/editor_ops.cc +++ b/gtk2_ardour/editor_ops.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2000-2004 Paul Davis + 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 @@ -15,51 +15,69 @@ along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - $Id$ */ +/* Note: public Editor methods are documented in public_editor.h */ + #include #include #include #include #include +#include -#include -#include -#include -#include +#include "pbd/error.h" +#include "pbd/basename.h" +#include "pbd/pthread_utils.h" +#include "pbd/memento_command.h" +#include "pbd/whitespace.h" #include #include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include + +#include "ardour/audioengine.h" +#include "ardour/session.h" +#include "ardour/audioplaylist.h" +#include "ardour/audioregion.h" +#include "ardour/audio_diskstream.h" +#include "ardour/utils.h" +#include "ardour/location.h" +#include "ardour/audio_track.h" +#include "ardour/audioplaylist.h" +#include "ardour/region_factory.h" +#include "ardour/playlist_factory.h" +#include "ardour/reverse.h" +#include "ardour/transient_detector.h" +#include "ardour/dB.h" +#include "ardour/quantize.h" +#include "ardour/strip_silence.h" +#include "ardour/route_group.h" #include "ardour_ui.h" #include "editor.h" #include "time_axis_view.h" +#include "route_time_axis.h" #include "audio_time_axis.h" #include "automation_time_axis.h" #include "streamview.h" +#include "audio_streamview.h" #include "audio_region_view.h" +#include "midi_region_view.h" #include "rgb_macros.h" #include "selection_templates.h" #include "selection.h" #include "editing.h" #include "gtk-custom-hruler.h" #include "gui_thread.h" +#include "keyboard.h" +#include "utils.h" +#include "editor_drag.h" +#include "strip_silence_dialog.h" +#include "editor_routes.h" +#include "editor_regions.h" +#include "quantize_dialog.h" #include "i18n.h" @@ -68,6 +86,7 @@ using namespace ARDOUR; using namespace PBD; using namespace sigc; using namespace Gtk; +using namespace Gtkmm2ext; using namespace Editing; /*********************************************************************** @@ -90,134 +109,100 @@ Editor::redo (uint32_t n) } } -int -Editor::ensure_cursor (nframes_t *pos) -{ - *pos = edit_cursor->current_frame; - return 0; -} - void -Editor::split_region () +Editor::split_regions_at (nframes64_t where, RegionSelection& regions) { - split_region_at (edit_cursor->current_frame); -} + list > used_playlists; -void -Editor::split_region_at (nframes_t where) -{ - split_regions_at (where, selection->regions); -} + if (regions.empty()) { + return; + } -void -Editor::split_regions_at (nframes_t where, RegionSelection& regions) -{ begin_reversible_command (_("split")); - snap_to (where); + // 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: + snap_to (where); + } + } else { + snap_to (where); + } + for (RegionSelection::iterator a = regions.begin(); a != regions.end(); ) { RegionSelection::iterator tmp; - + + /* XXX this test needs to be more complicated, to make sure we really + have something to split. + */ + + if (!(*a)->region()->covers (where)) { + ++a; + continue; + } + tmp = a; ++tmp; boost::shared_ptr pl = (*a)->region()->playlist(); + if (! pl->frozen()) { + /* we haven't seen this playlist before */ + + /* remember used playlists so we can thaw them later */ + used_playlists.push_back(pl); + pl->freeze(); + } + AudioRegionView* const arv = dynamic_cast(*a); - if (arv) + if (arv) { _new_regionviews_show_envelope = arv->envelope_visible(); - + } + if (pl) { - XMLNode &before = pl->get_state(); + XMLNode &before = pl->get_state(); pl->split_region ((*a)->region(), where); - XMLNode &after = pl->get_state(); - session->add_command(new MementoCommand(*pl, &before, &after)); + XMLNode &after = pl->get_state(); + session->add_command(new MementoCommand(*pl, &before, &after)); } a = tmp; - } - - commit_reversible_command (); - _new_regionviews_show_envelope = false; -} - -void -Editor::remove_clicked_region () -{ - if (clicked_audio_trackview == 0 || clicked_regionview == 0) { - return; - } - - boost::shared_ptr playlist = clicked_audio_trackview->playlist(); - - begin_reversible_command (_("remove region")); - XMLNode &before = playlist->get_state(); - playlist->remove_region (clicked_regionview->region()); - XMLNode &after = playlist->get_state(); - session->add_command(new MementoCommand(*playlist, &before, &after)); - commit_reversible_command (); -} - -void -Editor::destroy_clicked_region () -{ - uint32_t selected = selection->regions.size(); - - if (!session || !selected) { - return; - } - - vector choices; - string prompt; - - prompt = string_compose (_(" This is destructive, will possibly delete audio files\n\ -It cannot be undone\n\ -Do you really want to destroy %1 ?"), - (selected > 1 ? - _("these regions") : _("this region"))); - - choices.push_back (_("No, do nothing.")); - - if (selected > 1) { - choices.push_back (_("Yes, destroy them.")); - } else { - choices.push_back (_("Yes, destroy it.")); } - Gtkmm2ext::Choice prompter (prompt, choices); - - if (prompter.run() == 0) { /* first choice */ - return; + while (used_playlists.size() > 0) { + list >::iterator i = used_playlists.begin(); + (*i)->thaw(); + used_playlists.pop_front(); } - if (selected) { - list > r; - - for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) { - r.push_back ((*i)->region()); - } - - session->destroy_regions (r); - } + commit_reversible_command (); + _new_regionviews_show_envelope = false; } boost::shared_ptr -Editor::select_region_for_operation (int dir, TimeAxisView **tv) +Editor::select_region_for_operation (int /*dir*/, TimeAxisView **tv) { RegionView* rv; boost::shared_ptr region; - nframes_t start = 0; + nframes64_t start = 0; if (selection->time.start () == selection->time.end_frame ()) { - + /* no current selection-> is there a selected regionview? */ if (selection->regions.empty()) { return region; } - } + } if (!selection->regions.empty()) { @@ -233,24 +218,24 @@ Editor::select_region_for_operation (int dir, TimeAxisView **tv) if ((rtv = dynamic_cast (*tv)) != 0) { boost::shared_ptr pl; - + if ((pl = rtv->playlist()) == 0) { return region; } - + region = pl->top_region_at (start); } - } - + } + return region; } - + void Editor::extend_selection_to_end_of_region (bool next) { TimeAxisView *tv; boost::shared_ptr region; - nframes_t start; + nframes64_t start; if ((region = select_region_for_operation (next ? 1 : 0, &tv)) == 0) { return; @@ -278,7 +263,7 @@ Editor::extend_selection_to_start_of_region (bool previous) { TimeAxisView *tv; boost::shared_ptr region; - nframes_t end; + nframes64_t end; if ((region = select_region_for_operation (previous ? -1 : 0, &tv)) == 0) { return; @@ -291,7 +276,7 @@ Editor::extend_selection_to_start_of_region (bool previous) } /* Try to leave the selection with the same route if possible */ - + if ((tv = selection->time.track) == 0) { return; } @@ -301,22 +286,47 @@ Editor::extend_selection_to_start_of_region (bool previous) commit_reversible_command (); } +bool +Editor::nudge_forward_release (GdkEventButton* ev) +{ + if (ev->state & Keyboard::PrimaryModifier) { + nudge_forward (false, true); + } else { + nudge_forward (false, false); + } + return false; +} + +bool +Editor::nudge_backward_release (GdkEventButton* ev) +{ + if (ev->state & Keyboard::PrimaryModifier) { + nudge_backward (false, true); + } else { + nudge_backward (false, false); + } + return false; +} + void -Editor::nudge_forward (bool next) +Editor::nudge_forward (bool next, bool force_playhead) { - nframes_t distance; - nframes_t next_distance; + nframes64_t distance; + nframes64_t next_distance; + RegionSelection rs; + + get_regions_for_action (rs); if (!session) return; - - if (!selection->regions.empty()) { - begin_reversible_command (_("nudge forward")); + if (!force_playhead && !rs.empty()) { - for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) { + begin_reversible_command (_("nudge regions forward")); + + for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) { boost::shared_ptr r ((*i)->region()); - + distance = get_nudge_distance (r->position(), next_distance); if (next) { @@ -331,35 +341,81 @@ Editor::nudge_forward (bool next) commit_reversible_command (); + + } else if (!force_playhead && !selection->markers.empty()) { + + bool is_start; + + begin_reversible_command (_("nudge location forward")); + + for (MarkerSelection::iterator i = selection->markers.begin(); i != selection->markers.end(); ++i) { + + Location* loc = find_location_from_marker ((*i), is_start); + + if (loc) { + + XMLNode& before (loc->get_state()); + + if (is_start) { + distance = get_nudge_distance (loc->start(), next_distance); + if (next) { + distance = next_distance; + } + if (max_frames - distance > loc->start() + loc->length()) { + loc->set_start (loc->start() + distance); + } else { + loc->set_start (max_frames - loc->length()); + } + } else { + distance = get_nudge_distance (loc->end(), next_distance); + if (next) { + distance = next_distance; + } + if (max_frames - distance > loc->end()) { + loc->set_end (loc->end() + distance); + } else { + loc->set_end (max_frames); + } + } + XMLNode& after (loc->get_state()); + session->add_command (new MementoCommand(*loc, &before, &after)); + } + } + + commit_reversible_command (); + } else { distance = get_nudge_distance (playhead_cursor->current_frame, next_distance); session->request_locate (playhead_cursor->current_frame + distance); } } - + void -Editor::nudge_backward (bool next) +Editor::nudge_backward (bool next, bool force_playhead) { - nframes_t distance; - nframes_t next_distance; + nframes64_t distance; + nframes64_t next_distance; + RegionSelection rs; + + get_regions_for_action (rs); if (!session) return; - - if (!selection->regions.empty()) { - begin_reversible_command (_("nudge forward")); + if (!force_playhead && !rs.empty()) { - for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) { + begin_reversible_command (_("nudge regions backward")); + + for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) { boost::shared_ptr r ((*i)->region()); distance = get_nudge_distance (r->position(), next_distance); - + if (next) { distance = next_distance; } XMLNode &before = r->playlist()->get_state(); - + if (r->position() > distance) { r->set_position (r->position() - distance, this); } else { @@ -371,6 +427,51 @@ Editor::nudge_backward (bool next) commit_reversible_command (); + } else if (!force_playhead && !selection->markers.empty()) { + + bool is_start; + + begin_reversible_command (_("nudge location forward")); + + for (MarkerSelection::iterator i = selection->markers.begin(); i != selection->markers.end(); ++i) { + + Location* loc = find_location_from_marker ((*i), is_start); + + if (loc) { + + XMLNode& before (loc->get_state()); + + if (is_start) { + distance = get_nudge_distance (loc->start(), next_distance); + if (next) { + distance = next_distance; + } + if (distance < loc->start()) { + loc->set_start (loc->start() - distance); + } else { + loc->set_start (0); + } + } else { + distance = get_nudge_distance (loc->end(), next_distance); + + if (next) { + distance = next_distance; + } + + if (distance < loc->end() - loc->length()) { + loc->set_end (loc->end() - distance); + } else { + loc->set_end (loc->length()); + } + } + + XMLNode& after (loc->get_state()); + session->add_command (new MementoCommand(*loc, &before, &after)); + } + } + + commit_reversible_command (); + } else { distance = get_nudge_distance (playhead_cursor->current_frame, next_distance); @@ -386,19 +487,22 @@ Editor::nudge_backward (bool next) void Editor::nudge_forward_capture_offset () { - nframes_t distance; + nframes64_t distance; + RegionSelection rs; + + get_regions_for_action (rs); if (!session) return; - - if (!selection->regions.empty()) { + + if (!rs.empty()) { begin_reversible_command (_("nudge forward")); distance = session->worst_output_latency(); - for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) { + for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) { boost::shared_ptr r ((*i)->region()); - + XMLNode &before = r->playlist()->get_state(); r->set_position (r->position() + distance, this); XMLNode &after = r->playlist()->get_state(); @@ -407,27 +511,30 @@ Editor::nudge_forward_capture_offset () commit_reversible_command (); - } + } } - + void Editor::nudge_backward_capture_offset () { - nframes_t distance; + nframes64_t distance; + RegionSelection rs; + + get_regions_for_action (rs); if (!session) return; - - if (!selection->regions.empty()) { + + if (!rs.empty()) { begin_reversible_command (_("nudge forward")); distance = session->worst_output_latency(); - for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) { + for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) { boost::shared_ptr r ((*i)->region()); XMLNode &before = r->playlist()->get_state(); - + if (r->position() > distance) { r->set_position (r->position() - distance, this); } else { @@ -459,125 +566,141 @@ Editor::move_to_end () void Editor::build_region_boundary_cache () { - nframes_t pos = 0; - RegionPoint point; + nframes64_t pos = 0; + vector interesting_points; boost::shared_ptr r; TrackViewList tracks; + bool at_end = false; region_boundary_cache.clear (); if (session == 0) { return; } - - switch (snap_type) { + + switch (_snap_type) { case SnapToRegionStart: - point = Start; + interesting_points.push_back (Start); break; case SnapToRegionEnd: - point = End; - break; + interesting_points.push_back (End); + break; case SnapToRegionSync: - point = SyncPoint; - break; + interesting_points.push_back (SyncPoint); + break; case SnapToRegionBoundary: - point = Start; - break; + interesting_points.push_back (Start); + interesting_points.push_back (End); + break; default: - fatal << string_compose (_("build_region_boundary_cache called with snap_type = %1"), snap_type) << endmsg; + fatal << string_compose (_("build_region_boundary_cache called with snap_type = %1"), _snap_type) << endmsg; /*NOTREACHED*/ return; } - + TimeAxisView *ontrack = 0; + TrackViewList tlist; - while (pos < session->current_end_frame()) { + if (!selection->tracks.empty()) { + tlist = selection->tracks; + } else { + tlist = track_views; + } - if (!selection->tracks.empty()) { + while (pos < session->current_end_frame() && !at_end) { - if ((r = find_next_region (pos, point, 1, selection->tracks, &ontrack)) == 0) { - break; - } + nframes64_t rpos; + nframes64_t lpos = max_frames; - } else if (clicked_trackview) { + for (vector::iterator p = interesting_points.begin(); p != interesting_points.end(); ++p) { + + if ((r = find_next_region (pos, *p, 1, tlist, &ontrack)) == 0) { + if (*p == interesting_points.back()) { + at_end = true; + } + /* move to next point type */ + continue; + } - TrackViewList t; - t.push_back (clicked_trackview); + switch (*p) { + case Start: + rpos = r->first_frame(); + break; - if ((r = find_next_region (pos, point, 1, t, &ontrack)) == 0) { + case End: + rpos = r->last_frame(); break; - } - } else { + case SyncPoint: + rpos = r->sync_position (); + //r->adjust_to_sync (r->first_frame()); + break; - if ((r = find_next_region (pos, point, 1, track_views, &ontrack)) == 0) { + default: break; } - } - nframes_t rpos; - - switch (snap_type) { - case SnapToRegionStart: - rpos = r->first_frame(); - break; - case SnapToRegionEnd: - rpos = r->last_frame(); - break; - case SnapToRegionSync: - rpos = r->adjust_to_sync (r->first_frame()); - break; + float speed = 1.0f; + RouteTimeAxisView *rtav; - case SnapToRegionBoundary: - rpos = r->last_frame(); - break; - default: - break; - } - - float speed = 1.0f; - AudioTimeAxisView *atav; + if (ontrack != 0 && (rtav = dynamic_cast(ontrack)) != 0 ) { + if (rtav->get_diskstream() != 0) { + speed = rtav->get_diskstream()->speed(); + } + } + + rpos = track_frame_to_session_frame (rpos, speed); - if ( ontrack != 0 && (atav = dynamic_cast(ontrack)) != 0 ) { - if (atav->get_diskstream() != 0) { - speed = atav->get_diskstream()->speed(); + if (rpos < lpos) { + lpos = rpos; } - } - rpos = track_frame_to_session_frame(rpos, speed); + /* prevent duplicates, but we don't use set<> because we want to be able + to sort later. + */ + + vector::iterator ri; + + for (ri = region_boundary_cache.begin(); ri != region_boundary_cache.end(); ++ri) { + if (*ri == rpos) { + break; + } + } - if (region_boundary_cache.empty() || rpos != region_boundary_cache.back()) { - if (snap_type == SnapToRegionBoundary) { - region_boundary_cache.push_back (r->first_frame()); + if (ri == region_boundary_cache.end()) { + region_boundary_cache.push_back (rpos); } - region_boundary_cache.push_back (rpos); } - pos = rpos + 1; + pos = lpos + 1; } + + /* finally sort to be sure that the order is correct */ + + sort (region_boundary_cache.begin(), region_boundary_cache.end()); } boost::shared_ptr -Editor::find_next_region (nframes_t frame, RegionPoint point, int32_t dir, TrackViewList& tracks, TimeAxisView **ontrack) +Editor::find_next_region (nframes64_t frame, RegionPoint point, int32_t dir, TrackViewList& tracks, TimeAxisView **ontrack) { TrackViewList::iterator i; - nframes_t closest = max_frames; + nframes64_t closest = max_frames; boost::shared_ptr ret; - nframes_t rpos = 0; + nframes64_t rpos = 0; float track_speed; - nframes_t track_frame; - AudioTimeAxisView *atav; + nframes64_t track_frame; + RouteTimeAxisView *rtav; for (i = tracks.begin(); i != tracks.end(); ++i) { - nframes_t distance; + nframes64_t distance; boost::shared_ptr r; - + track_speed = 1.0f; - if ( (atav = dynamic_cast(*i)) != 0 ) { - if (atav->get_diskstream()!=0) - track_speed = atav->get_diskstream()->speed(); + if ( (rtav = dynamic_cast(*i)) != 0 ) { + if (rtav->get_diskstream()!=0) + track_speed = rtav->get_diskstream()->speed(); } track_frame = session_frame_to_track_frame(frame, track_speed); @@ -596,9 +719,11 @@ Editor::find_next_region (nframes_t frame, RegionPoint point, int32_t dir, Track break; case SyncPoint: - rpos = r->adjust_to_sync (r->first_frame()); + rpos = r->sync_position (); + // r->adjust_to_sync (r->first_frame()); break; } + // rpos is a "track frame", converting it to "session frame" rpos = track_frame_to_session_frame(rpos, track_speed); @@ -619,94 +744,201 @@ Editor::find_next_region (nframes_t frame, RegionPoint point, int32_t dir, Track return ret; } -void -Editor::cursor_to_region_point (Cursor* cursor, RegionPoint point, int32_t dir) +nframes64_t +Editor::find_next_region_boundary (nframes64_t pos, int32_t dir, const TrackViewList& tracks) { - boost::shared_ptr r; - nframes_t pos = cursor->current_frame; + nframes64_t distance = max_frames; + nframes64_t current_nearest = -1; - if (!session) { - return; - } - TimeAxisView *ontrack = 0; + for (TrackViewList::const_iterator i = tracks.begin(); i != tracks.end(); ++i) { + nframes64_t contender; + nframes64_t d; - // so we don't find the current region again.. - if (dir>0 || pos>0) - pos+=dir; + RouteTimeAxisView* rtv = dynamic_cast (*i); - if (!selection->tracks.empty()) { - - r = find_next_region (pos, point, dir, selection->tracks, &ontrack); - - } else if (clicked_trackview) { - - TrackViewList t; - t.push_back (clicked_trackview); - - r = find_next_region (pos, point, dir, t, &ontrack); - - } else { - - r = find_next_region (pos, point, dir, track_views, &ontrack); - } + if (!rtv) { + continue; + } - if (r == 0) { - return; - } - - switch (point){ - case Start: - pos = r->first_frame (); - break; + if ((contender = rtv->find_next_region_boundary (pos, dir)) < 0) { + continue; + } - case End: - pos = r->last_frame (); - break; + d = ::llabs (pos - contender); - case SyncPoint: - pos = r->adjust_to_sync (r->first_frame()); - break; + if (d < distance) { + current_nearest = contender; + distance = d; + } } - - float speed = 1.0f; - AudioTimeAxisView *atav; - if ( ontrack != 0 && (atav = dynamic_cast(ontrack)) != 0 ) { - if (atav->get_diskstream() != 0) { - speed = atav->get_diskstream()->speed(); + return current_nearest; +} + +nframes64_t +Editor::get_region_boundary (nframes64_t pos, int32_t dir, bool with_selection, bool only_onscreen) +{ + nframes64_t target; + TrackViewList tvl; + + if (with_selection && Config->get_region_boundaries_from_selected_tracks()) { + + if (!selection->tracks.empty()) { + + target = find_next_region_boundary (pos, dir, selection->tracks); + + } else { + + if (only_onscreen || Config->get_region_boundaries_from_onscreen_tracks()) { + get_onscreen_tracks (tvl); + target = find_next_region_boundary (pos, dir, tvl); + } else { + target = find_next_region_boundary (pos, dir, track_views); + } } - } - pos = track_frame_to_session_frame(pos, speed); - - if (cursor == playhead_cursor) { - session->request_locate (pos); } else { - cursor->set_position (pos); + + if (only_onscreen || Config->get_region_boundaries_from_onscreen_tracks()) { + get_onscreen_tracks (tvl); + target = find_next_region_boundary (pos, dir, tvl); + } else { + target = find_next_region_boundary (pos, dir, track_views); + } } -} -void -Editor::cursor_to_next_region_point (Cursor* cursor, RegionPoint point) -{ - cursor_to_region_point (cursor, point, 1); + return target; } void -Editor::cursor_to_previous_region_point (Cursor* cursor, RegionPoint point) +Editor::cursor_to_region_boundary (bool with_selection, int32_t dir) +{ + nframes64_t pos = playhead_cursor->current_frame; + nframes64_t target; + + if (!session) { + return; + } + + // so we don't find the current region again.. + if (dir > 0 || pos > 0) { + pos += dir; + } + + if ((target = get_region_boundary (pos, dir, with_selection, false)) < 0) { + return; + } + + + session->request_locate (target); +} + +void +Editor::cursor_to_next_region_boundary (bool with_selection) +{ + cursor_to_region_boundary (with_selection, 1); +} + +void +Editor::cursor_to_previous_region_boundary (bool with_selection) +{ + cursor_to_region_boundary (with_selection, -1); +} + +void +Editor::cursor_to_region_point (EditorCursor* cursor, RegionPoint point, int32_t dir) +{ + boost::shared_ptr r; + nframes64_t pos = cursor->current_frame; + + if (!session) { + return; + } + + TimeAxisView *ontrack = 0; + + // so we don't find the current region again.. + if (dir>0 || pos>0) + pos+=dir; + + if (!selection->tracks.empty()) { + + r = find_next_region (pos, point, dir, selection->tracks, &ontrack); + + } else if (clicked_axisview) { + + TrackViewList t; + t.push_back (clicked_axisview); + + r = find_next_region (pos, point, dir, t, &ontrack); + + } else { + + r = find_next_region (pos, point, dir, track_views, &ontrack); + } + + if (r == 0) { + return; + } + + switch (point){ + case Start: + pos = r->first_frame (); + break; + + case End: + pos = r->last_frame (); + break; + + case SyncPoint: + pos = r->sync_position (); + // r->adjust_to_sync (r->first_frame()); + break; + } + + float speed = 1.0f; + RouteTimeAxisView *rtav; + + if ( ontrack != 0 && (rtav = dynamic_cast(ontrack)) != 0 ) { + if (rtav->get_diskstream() != 0) { + speed = rtav->get_diskstream()->speed(); + } + } + + pos = track_frame_to_session_frame(pos, speed); + + if (cursor == playhead_cursor) { + session->request_locate (pos); + } else { + cursor->set_position (pos); + } +} + +void +Editor::cursor_to_next_region_point (EditorCursor* cursor, RegionPoint point) +{ + cursor_to_region_point (cursor, point, 1); +} + +void +Editor::cursor_to_previous_region_point (EditorCursor* cursor, RegionPoint point) { cursor_to_region_point (cursor, point, -1); } void -Editor::cursor_to_selection_start (Cursor *cursor) +Editor::cursor_to_selection_start (EditorCursor *cursor) { - nframes_t pos = 0; + nframes64_t pos = 0; + RegionSelection rs; + + get_regions_for_action (rs); + switch (mouse_mode) { case MouseObject: - if (!selection->regions.empty()) { - pos = selection->regions.start(); + if (!rs.empty()) { + pos = rs.start(); } break; @@ -728,14 +960,17 @@ Editor::cursor_to_selection_start (Cursor *cursor) } void -Editor::cursor_to_selection_end (Cursor *cursor) +Editor::cursor_to_selection_end (EditorCursor *cursor) { - nframes_t pos = 0; + nframes64_t pos = 0; + RegionSelection rs; + + get_regions_for_action (rs); switch (mouse_mode) { case MouseObject: - if (!selection->regions.empty()) { - pos = selection->regions.end_frame(); + if (!rs.empty()) { + pos = rs.end_frame(); } break; @@ -757,194 +992,457 @@ Editor::cursor_to_selection_end (Cursor *cursor) } void -Editor::playhead_backward () +Editor::selected_marker_to_region_boundary (bool with_selection, int32_t dir) { - nframes_t pos; - nframes_t cnt; - float prefix; - bool was_floating; + nframes64_t target; + Location* loc; + bool ignored; - if (get_prefix (prefix, was_floating)) { - cnt = 1; - } else { - if (was_floating) { - cnt = (nframes_t) floor (prefix * session->frame_rate ()); - } else { - cnt = (nframes_t) prefix; - } + if (!session) { + return; } - pos = playhead_cursor->current_frame; + if (selection->markers.empty()) { + nframes64_t mouse; + bool ignored; - if ((nframes_t) pos < cnt) { - pos = 0; - } else { - pos -= cnt; + if (!mouse_frame (mouse, ignored)) { + return; + } + + add_location_mark (mouse); } - - /* XXX this is completely insane. with the current buffering - design, we'll force a complete track buffer flush and - reload, just to move 1 sample !!! - */ - session->request_locate (pos); -} + if ((loc = find_location_from_marker (selection->markers.front(), ignored)) == 0) { + return; + } -void -Editor::playhead_forward () -{ - nframes_t pos; - nframes_t cnt; - bool was_floating; - float prefix; + nframes64_t pos = loc->start(); - if (get_prefix (prefix, was_floating)) { - cnt = 1; - } else { - if (was_floating) { - cnt = (nframes_t) floor (prefix * session->frame_rate ()); - } else { - cnt = (nframes_t) floor (prefix); - } + // so we don't find the current region again.. + if (dir > 0 || pos > 0) { + pos += dir; } - pos = playhead_cursor->current_frame; - - /* XXX this is completely insane. with the current buffering - design, we'll force a complete track buffer flush and - reload, just to move 1 sample !!! - */ + if ((target = get_region_boundary (pos, dir, with_selection, false)) < 0) { + return; + } - session->request_locate (pos+cnt); + loc->move_to (target); } void -Editor::cursor_align (bool playhead_to_edit) +Editor::selected_marker_to_next_region_boundary (bool with_selection) { - if (playhead_to_edit) { - if (session) { - session->request_locate (edit_cursor->current_frame); - } - } else { - edit_cursor->set_position (playhead_cursor->current_frame); - } + selected_marker_to_region_boundary (with_selection, 1); } void -Editor::edit_cursor_backward () +Editor::selected_marker_to_previous_region_boundary (bool with_selection) { - nframes_t pos; - nframes_t cnt; - float prefix; - bool was_floating; + selected_marker_to_region_boundary (with_selection, -1); +} - if (get_prefix (prefix, was_floating)) { - cnt = 1; - } else { - if (was_floating) { - cnt = (nframes_t) floor (prefix * session->frame_rate ()); - } else { - cnt = (nframes_t) prefix; - } +void +Editor::selected_marker_to_region_point (RegionPoint point, int32_t dir) +{ + boost::shared_ptr r; + nframes64_t pos; + Location* loc; + bool ignored; + + if (!session || selection->markers.empty()) { + return; } - pos = edit_cursor->current_frame; + if ((loc = find_location_from_marker (selection->markers.front(), ignored)) == 0) { + return; + } + + TimeAxisView *ontrack = 0; + + pos = loc->start(); + + // so we don't find the current region again.. + if (dir>0 || pos>0) + pos+=dir; + + if (!selection->tracks.empty()) { + + r = find_next_region (pos, point, dir, selection->tracks, &ontrack); - if ((nframes_t) pos < cnt) { - pos = 0; } else { - pos -= cnt; + + r = find_next_region (pos, point, dir, track_views, &ontrack); } - - edit_cursor->set_position (pos); -} -void -Editor::edit_cursor_forward () -{ - nframes_t pos; - nframes_t cnt; - bool was_floating; - float prefix; + if (r == 0) { + return; + } - if (get_prefix (prefix, was_floating)) { - cnt = 1; - } else { - if (was_floating) { - cnt = (nframes_t) floor (prefix * session->frame_rate ()); - } else { - cnt = (nframes_t) floor (prefix); + switch (point){ + case Start: + pos = r->first_frame (); + break; + + case End: + pos = r->last_frame (); + break; + + case SyncPoint: + pos = r->adjust_to_sync (r->first_frame()); + break; + } + + float speed = 1.0f; + RouteTimeAxisView *rtav; + + if (ontrack != 0 && (rtav = dynamic_cast(ontrack)) != 0) { + if (rtav->get_diskstream() != 0) { + speed = rtav->get_diskstream()->speed(); } } - pos = edit_cursor->current_frame; - edit_cursor->set_position (pos+cnt); + pos = track_frame_to_session_frame(pos, speed); + + loc->move_to (pos); } void -Editor::goto_frame () +Editor::selected_marker_to_next_region_point (RegionPoint point) { - float prefix; - bool was_floating; - nframes_t frame; + selected_marker_to_region_point (point, 1); +} - if (get_prefix (prefix, was_floating)) { +void +Editor::selected_marker_to_previous_region_point (RegionPoint point) +{ + selected_marker_to_region_point (point, -1); +} + +void +Editor::selected_marker_to_selection_start () +{ + nframes64_t pos = 0; + Location* loc; + bool ignored; + + if (!session || selection->markers.empty()) { return; } - if (was_floating) { - frame = (nframes_t) floor (prefix * session->frame_rate()); - } else { - frame = (nframes_t) floor (prefix); + if ((loc = find_location_from_marker (selection->markers.front(), ignored)) == 0) { + return; } - session->request_locate (frame); -} + RegionSelection rs; -void -Editor::scroll_backward (float pages) -{ - nframes_t frame; - nframes_t one_page = (nframes_t) rint (canvas_width * frames_per_unit); - bool was_floating; - float prefix; - nframes_t cnt; - - if (get_prefix (prefix, was_floating)) { - cnt = (nframes_t) floor (pages * one_page); - } else { - if (was_floating) { - cnt = (nframes_t) floor (prefix * session->frame_rate()); - } else { - cnt = (nframes_t) floor (prefix * one_page); + get_regions_for_action (rs); + + switch (mouse_mode) { + case MouseObject: + if (!rs.empty()) { + pos = rs.start(); } - } + break; - if (leftmost_frame < cnt) { - frame = 0; - } else { - frame = leftmost_frame - cnt; + case MouseRange: + if (!selection->time.empty()) { + pos = selection->time.start (); + } + break; + + default: + return; } - reposition_x_origin (frame); + loc->move_to (pos); } void -Editor::scroll_forward (float pages) +Editor::selected_marker_to_selection_end () { - nframes_t frame; - nframes_t one_page = (nframes_t) rint (canvas_width * frames_per_unit); - bool was_floating; - float prefix; - nframes_t cnt; - - if (get_prefix (prefix, was_floating)) { - cnt = (nframes_t) floor (pages * one_page); + nframes64_t pos = 0; + Location* loc; + bool ignored; + + if (!session || selection->markers.empty()) { + return; + } + + if ((loc = find_location_from_marker (selection->markers.front(), ignored)) == 0) { + return; + } + + RegionSelection rs; + + get_regions_for_action (rs); + + switch (mouse_mode) { + case MouseObject: + if (!rs.empty()) { + pos = rs.end_frame(); + } + break; + + case MouseRange: + if (!selection->time.empty()) { + pos = selection->time.end_frame (); + } + break; + + default: + return; + } + + loc->move_to (pos); +} + +void +Editor::scroll_playhead (bool forward) +{ + nframes64_t pos = playhead_cursor->current_frame; + nframes64_t delta = (nframes64_t) floor (current_page_frames() / 0.8); + + if (forward) { + if (pos == max_frames) { + return; + } + + if (pos < max_frames - delta) { + pos += delta ; + } else { + pos = max_frames; + } + + } else { + + if (pos == 0) { + return; + } + + if (pos > delta) { + pos -= delta; + } else { + pos = 0; + } + } + + session->request_locate (pos); +} + +void +Editor::playhead_backward () +{ + nframes64_t pos; + nframes64_t cnt; + float prefix; + bool was_floating; + + if (get_prefix (prefix, was_floating)) { + cnt = 1; + } else { + if (was_floating) { + cnt = (nframes64_t) floor (prefix * session->frame_rate ()); + } else { + cnt = (nframes64_t) prefix; + } + } + + pos = playhead_cursor->current_frame; + + if ((nframes64_t) pos < cnt) { + pos = 0; + } else { + pos -= cnt; + } + + /* XXX this is completely insane. with the current buffering + design, we'll force a complete track buffer flush and + reload, just to move 1 sample !!! + */ + + session->request_locate (pos); +} + +void +Editor::playhead_forward () +{ + nframes64_t pos; + nframes64_t cnt; + bool was_floating; + float prefix; + + if (get_prefix (prefix, was_floating)) { + cnt = 1; + } else { + if (was_floating) { + cnt = (nframes64_t) floor (prefix * session->frame_rate ()); + } else { + cnt = (nframes64_t) floor (prefix); + } + } + + pos = playhead_cursor->current_frame; + + /* XXX this is completely insane. with the current buffering + design, we'll force a complete track buffer flush and + reload, just to move 1 sample !!! + */ + + session->request_locate (pos+cnt); +} + +void +Editor::cursor_align (bool playhead_to_edit) +{ + if (!session) { + return; + } + + if (playhead_to_edit) { + + if (selection->markers.empty()) { + return; + } + + session->request_locate (selection->markers.front()->position(), session->transport_rolling()); + + } else { + /* move selected markers to playhead */ + + for (MarkerSelection::iterator i = selection->markers.begin(); i != selection->markers.end(); ++i) { + bool ignored; + + Location* loc = find_location_from_marker (*i, ignored); + + if (loc->is_mark()) { + loc->set_start (playhead_cursor->current_frame); + } else { + loc->set (playhead_cursor->current_frame, + playhead_cursor->current_frame + loc->length()); + } + } + } +} + +void +Editor::edit_cursor_backward () +{ + nframes64_t pos; + nframes64_t cnt; + float prefix; + bool was_floating; + + if (get_prefix (prefix, was_floating)) { + cnt = 1; + } else { + if (was_floating) { + cnt = (nframes64_t) floor (prefix * session->frame_rate ()); + } else { + cnt = (nframes64_t) prefix; + } + } + + if ((pos = get_preferred_edit_position()) < 0) { + return; + } + + if (pos < cnt) { + pos = 0; + } else { + pos -= cnt; + } + + // EDIT CURSOR edit_cursor->set_position (pos); +} + +void +Editor::edit_cursor_forward () +{ + //nframes64_t pos; + nframes64_t cnt; + bool was_floating; + float prefix; + + if (get_prefix (prefix, was_floating)) { + cnt = 1; + } else { + if (was_floating) { + cnt = (nframes64_t) floor (prefix * session->frame_rate ()); + } else { + cnt = (nframes64_t) floor (prefix); + } + } + + // pos = edit_cursor->current_frame; + // EDIT CURSOR edit_cursor->set_position (pos+cnt); +} + +void +Editor::goto_frame () +{ + float prefix; + bool was_floating; + nframes64_t frame; + + if (get_prefix (prefix, was_floating)) { + return; + } + + if (was_floating) { + frame = (nframes64_t) floor (prefix * session->frame_rate()); + } else { + frame = (nframes64_t) floor (prefix); + } + + session->request_locate (frame); +} + +void +Editor::scroll_backward (float pages) +{ + nframes64_t frame; + nframes64_t one_page = (nframes64_t) rint (_canvas_width * frames_per_unit); + bool was_floating; + float prefix; + nframes64_t cnt; + + if (get_prefix (prefix, was_floating)) { + cnt = (nframes64_t) floor (pages * one_page); + } else { + if (was_floating) { + cnt = (nframes64_t) floor (prefix * session->frame_rate()); + } else { + cnt = (nframes64_t) floor (prefix * one_page); + } + } + + if (leftmost_frame < cnt) { + frame = 0; + } else { + frame = leftmost_frame - cnt; + } + + reset_x_origin (frame); +} + +void +Editor::scroll_forward (float pages) +{ + nframes64_t frame; + nframes64_t one_page = (nframes64_t) rint (_canvas_width * frames_per_unit); + bool was_floating; + float prefix; + nframes64_t cnt; + + if (get_prefix (prefix, was_floating)) { + cnt = (nframes64_t) floor (pages * one_page); } else { if (was_floating) { - cnt = (nframes_t) floor (prefix * session->frame_rate()); + cnt = (nframes64_t) floor (prefix * session->frame_rate()); } else { - cnt = (nframes_t) floor (prefix * one_page); + cnt = (nframes64_t) floor (prefix * one_page); } } @@ -954,7 +1452,7 @@ Editor::scroll_forward (float pages) frame = leftmost_frame + cnt; } - reposition_x_origin (frame); + reset_x_origin (frame); } void @@ -972,8 +1470,8 @@ Editor::scroll_tracks_down () double vert_value = vertical_adjustment.get_value() + (cnt * vertical_adjustment.get_page_size()); - if (vert_value > vertical_adjustment.get_upper() - canvas_height) { - vert_value = vertical_adjustment.get_upper() - canvas_height; + if (vert_value > vertical_adjustment.get_upper() - _canvas_height) { + vert_value = vertical_adjustment.get_upper() - _canvas_height; } vertical_adjustment.set_value (vert_value); } @@ -999,10 +1497,10 @@ Editor::scroll_tracks_down_line () { Gtk::Adjustment* adj = edit_vscrollbar.get_adjustment(); - double vert_value = adj->get_value() + 20; + double vert_value = adj->get_value() + 60; - if (vert_value>adj->get_upper() - canvas_height) { - vert_value = adj->get_upper() - canvas_height; + if (vert_value>adj->get_upper() - _canvas_height) { + vert_value = adj->get_upper() - _canvas_height; } adj->set_value (vert_value); } @@ -1011,11 +1509,26 @@ void Editor::scroll_tracks_up_line () { Gtk::Adjustment* adj = edit_vscrollbar.get_adjustment(); - adj->set_value (adj->get_value() - 20); + adj->set_value (adj->get_value() - 60); } /* ZOOM */ +void +Editor::tav_zoom_step (bool coarser) +{ + ENSURE_GUI_THREAD (bind (mem_fun (*this, &Editor::temporal_zoom_step), coarser)); + + _routes->suspend_redisplay (); + + for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) { + TimeAxisView *tv = (static_cast(*i)); + tv->step_height (coarser); + } + + _routes->resume_redisplay (); +} + void Editor::temporal_zoom_step (bool coarser) { @@ -1024,97 +1537,254 @@ Editor::temporal_zoom_step (bool coarser) double nfpu; nfpu = frames_per_unit; - - if (coarser) { + + if (coarser) { nfpu *= 1.61803399; - } else { + } else { nfpu = max(1.0,(nfpu/1.61803399)); } temporal_zoom (nfpu); -} +} void Editor::temporal_zoom (gdouble fpu) { if (!session) return; - - nframes_t current_page = current_page_frames(); - nframes_t current_leftmost = leftmost_frame; - nframes_t current_rightmost; - nframes_t current_center; - nframes_t new_page; - nframes_t leftmost_after_zoom = 0; + + nframes64_t current_page = current_page_frames(); + nframes64_t current_leftmost = leftmost_frame; + nframes64_t current_rightmost; + nframes64_t current_center; + nframes64_t new_page_size; + nframes64_t half_page_size; + nframes64_t leftmost_after_zoom = 0; + nframes64_t where; + bool in_track_canvas; double nfpu; + double l; + + /* XXX this limit is also in ::set_frames_per_unit() */ + + if (frames_per_unit <= 2.0 && fpu <= frames_per_unit) { + return; + } nfpu = fpu; - - new_page = (nframes_t) floor (canvas_width * nfpu); + + new_page_size = (nframes64_t) floor (_canvas_width * nfpu); + half_page_size = new_page_size / 2; switch (zoom_focus) { case ZoomFocusLeft: leftmost_after_zoom = current_leftmost; break; - + case ZoomFocusRight: current_rightmost = leftmost_frame + current_page; - if (current_rightmost > new_page) { - leftmost_after_zoom = current_rightmost - new_page; - } else { + if (current_rightmost < new_page_size) { leftmost_after_zoom = 0; + } else { + leftmost_after_zoom = current_rightmost - new_page_size; } break; - + case ZoomFocusCenter: - current_center = current_leftmost + (current_page/2); - if (current_center > (new_page/2)) { - leftmost_after_zoom = current_center - (new_page / 2); - } else { + current_center = current_leftmost + (current_page/2); + if (current_center < half_page_size) { leftmost_after_zoom = 0; + } else { + leftmost_after_zoom = current_center - half_page_size; } break; - + case ZoomFocusPlayhead: - /* try to keep the playhead in the center */ - if (playhead_cursor->current_frame > new_page/2) { - leftmost_after_zoom = playhead_cursor->current_frame - (new_page/2); - } else { + /* try to keep the playhead in the same place */ + + where = playhead_cursor->current_frame; + + l = - ((new_page_size * ((where - current_leftmost)/(double)current_page)) - where); + + if (l < 0) { leftmost_after_zoom = 0; + } else if (l > max_frames) { + leftmost_after_zoom = max_frames - new_page_size; + } else { + leftmost_after_zoom = (nframes64_t) l; } break; - case ZoomFocusEdit: - /* try to keep the edit cursor in the center */ - if (edit_cursor->current_frame > new_page/2) { - leftmost_after_zoom = edit_cursor->current_frame - (new_page/2); + case ZoomFocusMouse: + /* try to keep the mouse over the same point in the display */ + + if (!mouse_frame (where, in_track_canvas)) { + /* use playhead instead */ + where = playhead_cursor->current_frame; + + if (where < half_page_size) { + leftmost_after_zoom = 0; + } else { + leftmost_after_zoom = where - half_page_size; + } + } else { - leftmost_after_zoom = 0; + + l = - ((new_page_size * ((where - current_leftmost)/(double)current_page)) - where); + + if (l < 0) { + leftmost_after_zoom = 0; + } else if (l > max_frames) { + leftmost_after_zoom = max_frames - new_page_size; + } else { + leftmost_after_zoom = (nframes64_t) l; + } } + break; - - } - - // leftmost_after_zoom = min (leftmost_after_zoom, session->current_end_frame()); -// begin_reversible_command (_("zoom")); -// session->add_undo (bind (mem_fun(*this, &Editor::reposition_and_zoom), current_leftmost, frames_per_unit)); -// session->add_redo (bind (mem_fun(*this, &Editor::reposition_and_zoom), leftmost_after_zoom, nfpu)); -// commit_reversible_command (); + case ZoomFocusEdit: + /* try to keep the edit point in the same place */ + where = get_preferred_edit_position (); - reposition_and_zoom (leftmost_after_zoom, nfpu); -} + if (where > 0) { -void + double l = - ((new_page_size * ((where - current_leftmost)/(double)current_page)) - where); + + if (l < 0) { + leftmost_after_zoom = 0; + } else if (l > max_frames) { + leftmost_after_zoom = max_frames - new_page_size; + } else { + leftmost_after_zoom = (nframes64_t) l; + } + + } else { + /* edit point not defined */ + return; + } + break; + + } + + // leftmost_after_zoom = min (leftmost_after_zoom, session->current_end_frame()); + + reposition_and_zoom (leftmost_after_zoom, nfpu); +} + +void +Editor::temporal_zoom_region (bool both_axes) +{ + + nframes64_t start = max_frames; + nframes64_t end = 0; + RegionSelection rs; + set tracks; + + get_regions_for_action (rs); + + if (rs.empty()) { + return; + } + + for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) { + + if ((*i)->region()->position() < start) { + start = (*i)->region()->position(); + } + + if ((*i)->region()->last_frame() + 1 > end) { + end = (*i)->region()->last_frame() + 1; + } + + tracks.insert (&((*i)->get_time_axis_view())); + } + + /* now comes an "interesting" hack ... make sure we leave a little space + at each end of the editor so that the zoom doesn't fit the region + precisely to the screen. + */ + + GdkScreen* screen = gdk_screen_get_default (); + gint pixwidth = gdk_screen_get_width (screen); + gint mmwidth = gdk_screen_get_width_mm (screen); + double pix_per_mm = (double) pixwidth/ (double) mmwidth; + double one_centimeter_in_pixels = pix_per_mm * 10.0; + + if ((start == 0 && end == 0) || end < start) { + return; + } + + nframes64_t range = end - start; + double new_fpu = (double)range / (double)_canvas_width; + nframes64_t extra_samples = (nframes64_t) floor (one_centimeter_in_pixels * new_fpu); + + if (start > extra_samples) { + start -= extra_samples; + } else { + start = 0; + } + + if (max_frames - extra_samples > end) { + end += extra_samples; + } else { + end = max_frames; + } + + if (both_axes) { + /* save visual state with track states included, and prevent + set_frames_per_unit() from doing it again. + */ + undo_visual_stack.push_back (current_visual_state(true)); + no_save_visual = true; + } + + temporal_zoom_by_frame (start, end, "zoom to region"); + + if (both_axes) { + uint32_t per_track_height = (uint32_t) floor ((_canvas_height - canvas_timebars_vsize - 10.0) / tracks.size()); + + /* set visible track heights appropriately */ + + for (set::iterator t = tracks.begin(); t != tracks.end(); ++t) { + (*t)->set_height (per_track_height); + } + + /* hide irrelevant tracks */ + + _routes->suspend_redisplay (); + + for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) { + if (find (tracks.begin(), tracks.end(), (*i)) == tracks.end()) { + hide_track_in_display (**i, true); + } + } + + _routes->resume_redisplay (); + + vertical_adjustment.set_value (0.0); + no_save_visual = false; + } + + redo_visual_stack.push_back (current_visual_state()); +} + +void +Editor::zoom_to_region (bool both_axes) +{ + temporal_zoom_region (both_axes); +} + +void Editor::temporal_zoom_selection () { if (!selection) return; - + if (selection->time.empty()) { return; } - nframes_t start = selection->time[clicked_selection].start; - nframes_t end = selection->time[clicked_selection].end; + nframes64_t start = selection->time[clicked_selection].start; + nframes64_t end = selection->time[clicked_selection].end; temporal_zoom_by_frame (start, end, "zoom to selection"); } @@ -1130,7 +1800,7 @@ Editor::temporal_zoom_session () } void -Editor::temporal_zoom_by_frame (nframes_t start, nframes_t end, const string & op) +Editor::temporal_zoom_by_frame (nframes64_t start, nframes64_t end, const string & /*op*/) { if (!session) return; @@ -1138,54 +1808,49 @@ Editor::temporal_zoom_by_frame (nframes_t start, nframes_t end, const string & o return; } - nframes_t range = end - start; - - double new_fpu = (double)range / (double)canvas_width; -// double p2 = 1.0; + nframes64_t range = end - start; -// while (p2 < new_fpu) { -// p2 *= 2.0; -// } -// new_fpu = p2; - - nframes_t new_page = (nframes_t) floor (canvas_width * new_fpu); - nframes_t middle = (nframes_t) floor( (double)start + ((double)range / 2.0f )); - nframes_t new_leftmost = (nframes_t) floor( (double)middle - ((double)new_page/2.0f)); + double new_fpu = (double)range / (double)_canvas_width; - if (new_leftmost > middle) new_leftmost = 0; + nframes64_t new_page = (nframes64_t) floor (_canvas_width * new_fpu); + nframes64_t middle = (nframes64_t) floor( (double)start + ((double)range / 2.0f )); + nframes64_t new_leftmost = (nframes64_t) floor( (double)middle - ((double)new_page/2.0f)); -// begin_reversible_command (op); -// session->add_undo (bind (mem_fun(*this, &Editor::reposition_and_zoom), leftmost_frame, frames_per_unit)); -// session->add_redo (bind (mem_fun(*this, &Editor::reposition_and_zoom), new_leftmost, new_fpu)); -// commit_reversible_command (); + if (new_leftmost > middle) { + new_leftmost = 0; + } reposition_and_zoom (new_leftmost, new_fpu); } -void -Editor::temporal_zoom_to_frame (bool coarser, nframes_t frame) +void +Editor::temporal_zoom_to_frame (bool coarser, nframes64_t frame) { - if (!session) return; - + if (!session) { + return; + } double range_before = frame - leftmost_frame; double new_fpu; - + new_fpu = frames_per_unit; - - if (coarser) { + + if (coarser) { new_fpu *= 1.61803399; range_before *= 1.61803399; - } else { + } else { new_fpu = max(1.0,(new_fpu/1.61803399)); range_before /= 1.61803399; } - if (new_fpu == frames_per_unit) return; - - nframes_t new_leftmost = frame - (nframes_t)range_before; + if (new_fpu == frames_per_unit) { + return; + } - if (new_leftmost > frame) new_leftmost = 0; + nframes64_t new_leftmost = frame - (nframes64_t)range_before; + if (new_leftmost > frame) { + new_leftmost = 0; + } // begin_reversible_command (_("zoom to frame")); // session->add_undo (bind (mem_fun(*this, &Editor::reposition_and_zoom), leftmost_frame, frames_per_unit)); // session->add_redo (bind (mem_fun(*this, &Editor::reposition_and_zoom), new_leftmost, new_fpu)); @@ -1194,6 +1859,43 @@ Editor::temporal_zoom_to_frame (bool coarser, nframes_t frame) reposition_and_zoom (new_leftmost, new_fpu); } + +bool +Editor::choose_new_marker_name(string &name) { + + if (!Config->get_name_new_markers()) { + /* don't prompt user for a new name */ + return true; + } + + ArdourPrompter dialog (true); + + dialog.set_prompt (_("New Name:")); + + dialog.set_title (_("New Location Marker")); + + dialog.set_name ("MarkNameWindow"); + dialog.set_size_request (250, -1); + dialog.set_position (Gtk::WIN_POS_MOUSE); + + dialog.add_button (Stock::OK, RESPONSE_ACCEPT); + dialog.set_initial_text (name); + + dialog.show (); + + switch (dialog.run ()) { + case RESPONSE_ACCEPT: + break; + default: + return false; + } + + dialog.get_result(name); + return true; + +} + + void Editor::add_location_from_selection () { @@ -1203,12 +1905,12 @@ Editor::add_location_from_selection () return; } - if (session == 0 || clicked_trackview == 0) { + if (session == 0 || clicked_axisview == 0) { return; } - nframes_t start = selection->time[clicked_selection].start; - nframes_t end = selection->time[clicked_selection].end; + nframes64_t start = selection->time[clicked_selection].start; + nframes64_t end = selection->time[clicked_selection].end; session->locations()->next_available_name(rangename,"selection"); Location *location = new Location (start, end, rangename, Location::IsRangeMarker); @@ -1222,33 +1924,17 @@ Editor::add_location_from_selection () } void -Editor::add_location_from_playhead_cursor () +Editor::add_location_mark (nframes64_t where) { string markername; - nframes_t where = session->audible_frame(); - - session->locations()->next_available_name(markername,"mark"); - Location *location = new Location (where, where, markername, Location::IsMark); - session->begin_reversible_command (_("add marker")); - XMLNode &before = session->locations()->get_state(); - session->locations()->add (location, true); - XMLNode &after = session->locations()->get_state(); - session->add_command(new MementoCommand(*(session->locations()), &before, &after)); - session->commit_reversible_command (); -} + select_new_marker = true; -void -Editor::add_location_from_audio_region () -{ - if (selection->regions.empty()) { + session->locations()->next_available_name(markername,"mark"); + if (!choose_new_marker_name(markername)) { return; } - - RegionView* rv = *(selection->regions.begin()); - boost::shared_ptr region = rv->region(); - - Location *location = new Location (region->position(), region->last_frame(), region->name(), Location::IsRangeMarker); + Location *location = new Location (where, where, markername, Location::IsMark); session->begin_reversible_command (_("add marker")); XMLNode &before = session->locations()->get_state(); session->locations()->add (location, true); @@ -1258,308 +1944,78 @@ Editor::add_location_from_audio_region () } void -Editor::select_all_in_track (Selection::Operation op) -{ - list touched; - - if (!clicked_trackview) { - return; - } - - clicked_trackview->get_selectables (0, max_frames, 0, DBL_MAX, touched); - - switch (op) { - case Selection::Toggle: - selection->add (touched); - break; - case Selection::Set: - selection->set (touched); - break; - case Selection::Extend: - /* not defined yet */ - break; - case Selection::Add: - selection->add (touched); - break; - } -} - -void -Editor::select_all (Selection::Operation op) -{ - list touched; - - for (TrackViewList::iterator iter = track_views.begin(); iter != track_views.end(); ++iter) { - if ((*iter)->hidden()) { - continue; - } - (*iter)->get_selectables (0, max_frames, 0, DBL_MAX, touched); - } - begin_reversible_command (_("select all")); - switch (op) { - case Selection::Add: - case Selection::Toggle: - selection->add (touched); - break; - case Selection::Set: - selection->set (touched); - break; - case Selection::Extend: - /* not defined yet */ - break; - } - commit_reversible_command (); -} - -void -Editor::invert_selection_in_track () -{ - list touched; - - if (!clicked_trackview) { - return; - } - - clicked_trackview->get_inverted_selectables (*selection, touched); - selection->set (touched); -} - -void -Editor::invert_selection () -{ - list touched; - - for (TrackViewList::iterator iter = track_views.begin(); iter != track_views.end(); ++iter) { - if ((*iter)->hidden()) { - continue; - } - (*iter)->get_inverted_selectables (*selection, touched); - } - - selection->set (touched); -} - -bool -Editor::select_all_within (nframes_t start, nframes_t end, double top, double bot, Selection::Operation op) -{ - list touched; - - for (TrackViewList::iterator iter = track_views.begin(); iter != track_views.end(); ++iter) { - if ((*iter)->hidden()) { - continue; - } - (*iter)->get_selectables (start, end, top, bot, touched); - } - - cerr << "select all within found " << touched.size() << endl; - - begin_reversible_command (_("select all within")); - switch (op) { - case Selection::Add: - case Selection::Toggle: - cerr << "toggle\n"; - selection->add (touched); - break; - case Selection::Set: - cerr << "set\n"; - selection->set (touched); - break; - case Selection::Extend: - cerr << "extend\n"; - /* not defined yet */ - break; - } - - cerr << "selection now has " << selection->points.size() << endl; - - commit_reversible_command (); - return !touched.empty(); -} - -void -Editor::set_selection_from_audio_region () +Editor::add_location_from_playhead_cursor () { - if (selection->regions.empty()) { - return; - } - - RegionView* rv = *(selection->regions.begin()); - boost::shared_ptr region = rv->region(); - - begin_reversible_command (_("set selection from region")); - selection->set (0, region->position(), region->last_frame()); - commit_reversible_command (); - - set_mouse_mode (Editing::MouseRange, false); + add_location_mark (session->audible_frame()); } void -Editor::set_selection_from_punch() +Editor::add_locations_from_audio_region () { - Location* location; - - if ((location = session->locations()->auto_punch_location()) == 0) { - return; - } - - set_selection_from_range (*location); -} + RegionSelection rs; -void -Editor::set_selection_from_loop() -{ - Location* location; + get_regions_for_action (rs); - if ((location = session->locations()->auto_loop_location()) == 0) { + if (rs.empty()) { return; } - set_selection_from_range (*location); -} - -void -Editor::set_selection_from_range (Location& loc) -{ - begin_reversible_command (_("set selection from range")); - selection->set (0, loc.start(), loc.end()); - commit_reversible_command (); - set_mouse_mode (Editing::MouseRange, false); -} + session->begin_reversible_command (rs.size () > 1 ? _("add markers") : _("add marker")); + XMLNode &before = session->locations()->get_state(); -void -Editor::select_all_selectables_using_time_selection () -{ - list touched; + cerr << "Add locations\n"; - if (selection->time.empty()) { - return; - } + for (RegionSelection::iterator i = rs.begin (); i != rs.end (); ++i) { - nframes_t start = selection->time[clicked_selection].start; - nframes_t end = selection->time[clicked_selection].end; + boost::shared_ptr region = (*i)->region (); - if (end - start < 1) { - return; - } + Location *location = new Location (region->position(), region->last_frame(), region->name(), Location::IsRangeMarker); - for (TrackViewList::iterator iter = selection->tracks.begin(); iter != selection->tracks.end(); ++iter) { - if ((*iter)->hidden()) { - continue; - } - (*iter)->get_selectables (start, end - 1, 0, DBL_MAX, touched); + session->locations()->add (location, true); } - begin_reversible_command (_("select all from range")); - selection->set (touched); - commit_reversible_command (); + XMLNode &after = session->locations()->get_state(); + session->add_command (new MementoCommand(*(session->locations()), &before, &after)); + session->commit_reversible_command (); } - void -Editor::select_all_selectables_using_punch() +Editor::add_location_from_audio_region () { - Location* location = session->locations()->auto_punch_location(); - list touched; - - if (location == 0 || (location->end() - location->start() <= 1)) { - return; - } + RegionSelection rs; - for (TrackViewList::iterator iter = track_views.begin(); iter != track_views.end(); ++iter) { - if ((*iter)->hidden()) { - continue; - } - (*iter)->get_selectables (location->start(), location->end() - 1, 0, DBL_MAX, touched); - } - begin_reversible_command (_("select all from punch")); - selection->set (touched); - commit_reversible_command (); - -} - -void -Editor::select_all_selectables_using_loop() -{ - Location* location = session->locations()->auto_loop_location(); - list touched; + get_regions_for_action (rs); - if (location == 0 || (location->end() - location->start() <= 1)) { + if (rs.empty()) { return; } - for (TrackViewList::iterator iter = track_views.begin(); iter != track_views.end(); ++iter) { - if ((*iter)->hidden()) { - continue; - } - (*iter)->get_selectables (location->start(), location->end() - 1, 0, DBL_MAX, touched); - } - begin_reversible_command (_("select all from loop")); - selection->set (touched); - commit_reversible_command (); - -} + session->begin_reversible_command (_("add marker")); + XMLNode &before = session->locations()->get_state(); -void -Editor::select_all_selectables_using_cursor (Cursor *cursor, bool after) -{ - nframes_t start; - nframes_t end; - list touched; + string markername; - if (after) { - begin_reversible_command (_("select all after cursor")); - start = cursor->current_frame ; - end = session->current_end_frame(); + if (rs.size() > 1) { // more than one region selected + session->locations()->next_available_name(markername, "regions"); } else { - if (cursor->current_frame > 0) { - begin_reversible_command (_("select all before cursor")); - start = 0; - end = cursor->current_frame - 1; - } else { - return; - } + RegionView* rv = *(rs.begin()); + boost::shared_ptr region = rv->region(); + markername = region->name(); } - for (TrackViewList::iterator iter = track_views.begin(); iter != track_views.end(); ++iter) { - if ((*iter)->hidden()) { - continue; - } - (*iter)->get_selectables (start, end, 0, DBL_MAX, touched); + if (!choose_new_marker_name(markername)) { + return; } - selection->set (touched); - commit_reversible_command (); -} -void -Editor::select_all_selectables_between_cursors (Cursor *cursor, Cursor *other_cursor) -{ - nframes_t start; - nframes_t end; - list touched; - bool other_cursor_is_first = cursor->current_frame > other_cursor->current_frame; + cerr << "Add location\n"; - if (cursor->current_frame == other_cursor->current_frame) { - return; - } + // single range spanning all selected + Location *location = new Location (rs.start(), rs.end_frame(), markername, Location::IsRangeMarker); + session->locations()->add (location, true); - begin_reversible_command (_("select all between cursors")); - if (other_cursor_is_first) { - start = other_cursor->current_frame; - end = cursor->current_frame - 1; - - } else { - start = cursor->current_frame; - end = other_cursor->current_frame - 1; - } - - for (TrackViewList::iterator iter = track_views.begin(); iter != track_views.end(); ++iter) { - if ((*iter)->hidden()) { - continue; - } - (*iter)->get_selectables (start, end, 0, DBL_MAX, touched); - } - selection->set (touched); - commit_reversible_command (); + XMLNode &after = session->locations()->get_state(); + session->add_command (new MementoCommand(*(session->locations()), &before, &after)); + session->commit_reversible_command (); } void @@ -1580,7 +2036,7 @@ Editor::amplitude_zoom_step (bool in) #ifdef FIX_FOR_CANVAS /* XXX DO SOMETHING */ #endif -} +} /* DELETION */ @@ -1623,7 +2079,7 @@ Editor::jump_forward_to_mark () if (!session) { return; } - + Location *location = session->locations()->first_location_after (playhead_cursor->current_frame); if (location) { @@ -1641,7 +2097,7 @@ Editor::jump_backward_to_mark () } Location *location = session->locations()->first_location_before (playhead_cursor->current_frame); - + if (location) { session->request_locate (location->start(), session->transport_rolling()); } else { @@ -1652,7 +2108,7 @@ Editor::jump_backward_to_mark () void Editor::set_mark () { - nframes_t pos; + nframes64_t pos; float prefix; bool was_floating; string markername; @@ -1661,13 +2117,16 @@ Editor::set_mark () pos = session->audible_frame (); } else { if (was_floating) { - pos = (nframes_t) floor (prefix * session->frame_rate ()); + pos = (nframes64_t) floor (prefix * session->frame_rate ()); } else { - pos = (nframes_t) floor (prefix); + pos = (nframes64_t) floor (prefix); } } session->locations()->next_available_name(markername,"mark"); + if (!choose_new_marker_name(markername)) { + return; + } session->locations()->add (new Location (pos, 0, markername, Location::IsMark), true); } @@ -1690,15 +2149,15 @@ Editor::clear_ranges () if (session) { session->begin_reversible_command (_("clear ranges")); XMLNode &before = session->locations()->get_state(); - + Location * looploc = session->locations()->auto_loop_location(); Location * punchloc = session->locations()->auto_punch_location(); - + session->locations()->clear_ranges (); // re-add these if (looploc) session->locations()->add (looploc); if (punchloc) session->locations()->add (punchloc); - + XMLNode &after = session->locations()->get_state(); session->add_command(new MementoCommand(*(session->locations()), &before, &after)); session->commit_reversible_command (); @@ -1733,7 +2192,7 @@ Editor::unhide_ranges () { for (LocationMarkerMap::iterator i = location_markers.begin(); i != location_markers.end(); ++i) { Location *l = (*i).first; - if (l->is_hidden() && l->is_range_marker()) { + if (l->is_hidden() && l->is_range_marker()) { l->set_hidden(false, this); } } @@ -1742,1870 +2201,4321 @@ Editor::unhide_ranges () /* INSERT/REPLACE */ void -Editor::insert_region_list_drag (boost::shared_ptr region, int x, int y) +Editor::insert_region_list_drag (boost::shared_ptr region, int x, int y) +{ + double wx, wy; + double cx, cy; + nframes64_t where; + RouteTimeAxisView *rtv = 0; + boost::shared_ptr playlist; + + track_canvas->window_to_world (x, y, wx, wy); + //wx += horizontal_adjustment.get_value(); + //wy += vertical_adjustment.get_value(); + + GdkEvent event; + event.type = GDK_BUTTON_RELEASE; + event.button.x = wx; + event.button.y = wy; + + where = event_frame (&event, &cx, &cy); + + if (where < leftmost_frame || where > leftmost_frame + current_page_frames()) { + /* clearly outside canvas area */ + return; + } + + std::pair tv = trackview_by_y_position (cy); + if (tv.first == 0) { + return; + } + + if ((rtv = dynamic_cast (tv.first)) == 0) { + return; + } + + if ((playlist = rtv->playlist()) == 0) { + return; + } + + snap_to (where); + + begin_reversible_command (_("insert dragged region")); + XMLNode &before = playlist->get_state(); + playlist->add_region (RegionFactory::create (region), where, 1.0); + session->add_command(new MementoCommand(*playlist, &before, &playlist->get_state())); + commit_reversible_command (); +} + +void +Editor::insert_route_list_drag (boost::shared_ptr route, int x, int y) { + double wx, wy; + double cx, cy; + nframes_t where; + RouteTimeAxisView *dest_rtv = 0; + RouteTimeAxisView *source_rtv = 0; + + track_canvas->window_to_world (x, y, wx, wy); + wx += horizontal_adjustment.get_value(); + wy += vertical_adjustment.get_value(); + + GdkEvent event; + event.type = GDK_BUTTON_RELEASE; + event.button.x = wx; + event.button.y = wy; + + where = event_frame (&event, &cx, &cy); + + std::pair const tv = trackview_by_y_position (cy); + if (tv.first == 0) { + return; + } + + if ((dest_rtv = dynamic_cast (tv.first)) == 0) { + return; + } + + /* use this drag source to add underlay to a track. But we really don't care + about the Route, only the view of the route, so find it first */ + for(TrackViewList::iterator it = track_views.begin(); it != track_views.end(); ++it) { + if((source_rtv = dynamic_cast(*it)) == 0) { + continue; + } + + if(source_rtv->route() == route && source_rtv != dest_rtv) { + dest_rtv->add_underlay(source_rtv->view()); + break; + } + } +} + +void +Editor::insert_region_list_selection (float times) +{ + RouteTimeAxisView *tv = 0; + boost::shared_ptr playlist; + + if (clicked_routeview != 0) { + tv = clicked_routeview; + } else if (!selection->tracks.empty()) { + if ((tv = dynamic_cast(selection->tracks.front())) == 0) { + return; + } + } else if (entered_track != 0) { + if ((tv = dynamic_cast(entered_track)) == 0) { + return; + } + } else { + return; + } + + if ((playlist = tv->playlist()) == 0) { + return; + } + + boost::shared_ptr region = _regions->get_single_selection (); + if (region == 0) { + return; + } + + begin_reversible_command (_("insert region")); + XMLNode &before = playlist->get_state(); + playlist->add_region ((RegionFactory::create (region)), get_preferred_edit_position(), times); + session->add_command(new MementoCommand(*playlist, &before, &playlist->get_state())); + commit_reversible_command (); +} + +/* BUILT-IN EFFECTS */ + +void +Editor::reverse_selection () +{ + +} + +/* GAIN ENVELOPE EDITING */ + +void +Editor::edit_envelope () +{ +} + +/* PLAYBACK */ + +void +Editor::transition_to_rolling (bool fwd) +{ + if (!session) { + return; + } + + if (session->config.get_external_sync()) { + switch (session->config.get_sync_source()) { + case JACK: + break; + default: + /* transport controlled by the master */ + return; + } + } + + if (session->is_auditioning()) { + session->cancel_audition (); + return; + } + + session->request_transport_speed (fwd ? 1.0f : -1.0f); +} + +void +Editor::play_from_start () +{ + session->request_locate (session->current_start_frame(), true); +} + +void +Editor::play_from_edit_point () +{ + session->request_locate (get_preferred_edit_position(), true); +} + +void +Editor::play_from_edit_point_and_return () +{ + nframes64_t start_frame; + nframes64_t return_frame; + + start_frame = get_preferred_edit_position (true); + + if (session->transport_rolling()) { + session->request_locate (start_frame, false); + return; + } + + /* don't reset the return frame if its already set */ + + if ((return_frame = session->requested_return_frame()) < 0) { + return_frame = session->audible_frame(); + } + + if (start_frame >= 0) { + session->request_roll_at_and_return (start_frame, return_frame); + } +} + +void +Editor::play_selection () +{ + if (selection->time.empty()) { + return; + } + + session->request_play_range (&selection->time, true); +} + +void +Editor::loop_selected_region () +{ + RegionSelection rs; + + get_regions_for_action (rs); + + if (!rs.empty()) { + RegionView *rv = *(rs.begin()); + Location* tll; + + if ((tll = transport_loop_location()) != 0) { + + tll->set (rv->region()->position(), rv->region()->last_frame()); + + // enable looping, reposition and start rolling + + session->request_play_loop (true); + session->request_locate (tll->start(), false); + session->request_transport_speed (1.0f); + } + } +} + +void +Editor::play_location (Location& location) +{ + if (location.start() <= location.end()) { + return; + } + + session->request_bounded_roll (location.start(), location.end()); +} + +void +Editor::loop_location (Location& location) +{ + if (location.start() <= location.end()) { + return; + } + + Location* tll; + + if ((tll = transport_loop_location()) != 0) { + tll->set (location.start(), location.end()); + + // enable looping, reposition and start rolling + session->request_play_loop (true); + session->request_locate (tll->start(), true); + } +} + +void +Editor::raise_region () +{ + selection->foreach_region (&Region::raise); +} + +void +Editor::raise_region_to_top () +{ + selection->foreach_region (&Region::raise_to_top); +} + +void +Editor::lower_region () +{ + selection->foreach_region (&Region::lower); +} + +void +Editor::lower_region_to_bottom () +{ + selection->foreach_region (&Region::lower_to_bottom); +} + +/** Show the region editor for the selected regions */ +void +Editor::edit_region () +{ + selection->foreach_regionview (&RegionView::show_region_editor); +} + +/** Show the midi list editor for the selected MIDI regions */ +void +Editor::show_midi_list_editor () +{ + selection->foreach_midi_regionview (&MidiRegionView::show_list_editor); +} + +void +Editor::rename_region() +{ + RegionSelection rs; + + get_regions_for_action (rs); + + if (rs.empty()) { + return; + } + + ArdourDialog d (*this, _("Rename Region"), true, false); + Entry entry; + Label label (_("New name:")); + 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::OK, Gtk::RESPONSE_OK); + d.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); + + d.set_size_request (300, -1); + d.set_position (Gtk::WIN_POS_MOUSE); + + entry.set_text (rs.front()->region()->name()); + entry.select_region (0, -1); + + entry.signal_activate().connect (bind (mem_fun (d, &Dialog::response), RESPONSE_OK)); + + d.show_all (); + + entry.grab_focus(); + + int ret = d.run(); + + d.hide (); + + if (ret == RESPONSE_OK) { + std::string str = entry.get_text(); + strip_whitespace_edges (str); + if (!str.empty()) { + rs.front()->region()->set_name (str); + _regions->redisplay (); + } + } +} + +void +Editor::audition_playlist_region_via_route (boost::shared_ptr region, Route& route) +{ + if (session->is_auditioning()) { + session->cancel_audition (); + } + + // note: some potential for creativity here, because region doesn't + // have to belong to the playlist that Route is handling + + // bool was_soloed = route.soloed(); + + route.set_solo (true, this); + + session->request_bounded_roll (region->position(), region->position() + region->length()); + + /* XXX how to unset the solo state ? */ +} + +/** Start an audition of the first selected region */ +void +Editor::play_edit_range () +{ + nframes64_t start, end; + + if (get_edit_op_range (start, end)) { + session->request_bounded_roll (start, end); + } +} + +void +Editor::play_selected_region () +{ + nframes64_t start = max_frames; + nframes64_t end = 0; + RegionSelection rs; + + get_regions_for_action (rs); + + if (rs.empty()) { + return; + } + + for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) { + if ((*i)->region()->position() < start) { + start = (*i)->region()->position(); + } + if ((*i)->region()->last_frame() + 1 > end) { + end = (*i)->region()->last_frame() + 1; + } + } + + session->request_bounded_roll (start, end); +} + +void +Editor::audition_playlist_region_standalone (boost::shared_ptr region) +{ + session->audition_region (region); +} + +void +Editor::build_interthread_progress_window () +{ + interthread_progress_window = new ArdourDialog (X_("interthread progress"), true); + + interthread_progress_bar.set_orientation (Gtk::PROGRESS_LEFT_TO_RIGHT); + + interthread_progress_window->set_border_width (12); + interthread_progress_window->get_vbox()->set_spacing (6); + + interthread_progress_label.set_alignment (0.5, 0.5); + + interthread_progress_window->get_vbox()->pack_start (interthread_progress_label, false, false); + interthread_progress_window->get_vbox()->pack_start (interthread_progress_bar,false, false); + + // GTK2FIX: this button needs a modifiable label + + Button* b = interthread_progress_window->add_button (Stock::CANCEL, RESPONSE_CANCEL); + b->signal_clicked().connect (mem_fun(*this, &Editor::interthread_cancel_clicked)); + + interthread_cancel_button.add (interthread_cancel_label); + + interthread_progress_window->set_default_size (200, 100); +} + +void +Editor::interthread_cancel_clicked () +{ + if (current_interthread_info) { + current_interthread_info->cancel = true; + } +} + +void +Editor::region_from_selection () +{ + if (clicked_axisview == 0) { + return; + } + + if (selection->time.empty()) { + return; + } + + nframes64_t start = selection->time[clicked_selection].start; + nframes64_t end = selection->time[clicked_selection].end; + + TrackSelection tracks = get_tracks_for_range_action (); + + nframes64_t selection_cnt = end - start + 1; + + for (TrackSelection::iterator i = tracks.begin(); i != tracks.end(); ++i) { + boost::shared_ptr current; + boost::shared_ptr pl; + nframes64_t internal_start; + string new_name; + + if ((pl = (*i)->playlist()) == 0) { + continue; + } + + if ((current = pl->top_region_at (start)) == 0) { + continue; + } + + internal_start = start - current->position(); + session->region_name (new_name, current->name(), true); + boost::shared_ptr region (RegionFactory::create (current, + internal_start, selection_cnt, new_name)); + } +} + +void +Editor::create_region_from_selection (vector >& new_regions) +{ + if (selection->time.empty() || selection->tracks.empty()) { + return; + } + + nframes64_t start = selection->time[clicked_selection].start; + nframes64_t end = selection->time[clicked_selection].end; + + sort_track_selection (); + + for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) { + boost::shared_ptr current; + boost::shared_ptr playlist; + nframes64_t internal_start; + string new_name; + + if ((playlist = (*i)->playlist()) == 0) { + continue; + } + + if ((current = playlist->top_region_at(start)) == 0) { + continue; + } + + internal_start = start - current->position(); + session->region_name (new_name, current->name(), true); + + new_regions.push_back (RegionFactory::create (current, + internal_start, end - start + 1, new_name)); + } +} + +void +Editor::split_multichannel_region () +{ + RegionSelection rs; + + get_regions_for_action (rs); + + if (rs.empty()) { + return; + } + + vector< boost::shared_ptr > v; + + for (list::iterator x = rs.begin(); x != rs.end(); ++x) { + (*x)->region()->separate_by_channel (*session, v); + } +} + +void +Editor::new_region_from_selection () +{ + region_from_selection (); + cancel_selection (); +} + +static void +add_if_covered (RegionView* rv, const AudioRange* ar, RegionSelection* rs) +{ + switch (rv->region()->coverage (ar->start, ar->end - 1)) { + case OverlapNone: + break; + default: + rs->push_back (rv); + } +} + +/** Return either: + * - selected tracks, or if there are none... + * - tracks containing selected regions, or if there are none... + * - all tracks + * @return tracks. + */ +TrackSelection +Editor::get_tracks_for_range_action () const +{ + TrackSelection t; + + if (selection->tracks.empty()) { + + /* use tracks with selected regions */ + + RegionSelection rs = selection->regions; + + for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) { + TimeAxisView* tv = &(*i)->get_time_axis_view(); + + if (!t.contains (tv)) { + t.push_back (tv); + } + } + + if (t.empty()) { + /* no regions and no tracks: use all tracks */ + t = track_views; + } + + } else { + + t = selection->tracks; + } + + return t; +} + +void +Editor::separate_regions_between (const TimeSelection& ts) +{ + bool in_command = false; + boost::shared_ptr playlist; + RegionSelection new_selection; + + TrackSelection tmptracks = get_tracks_for_range_action (); + sort_track_selection (&tmptracks); + + for (TrackSelection::iterator i = tmptracks.begin(); i != tmptracks.end(); ++i) { + + RouteTimeAxisView* rtv; + + if ((rtv = dynamic_cast ((*i))) != 0) { + + if (rtv->is_track()) { + + /* no edits to destructive tracks */ + + if (rtv->track()->diskstream()->destructive()) { + continue; + } + + if ((playlist = rtv->playlist()) != 0) { + + XMLNode *before; + bool got_some; + + before = &(playlist->get_state()); + got_some = false; + + /* XXX need to consider musical time selections here at some point */ + + double speed = rtv->get_diskstream()->speed(); + + + for (list::const_iterator t = ts.begin(); t != ts.end(); ++t) { + + sigc::connection c = rtv->view()->RegionViewAdded.connect ( + mem_fun(*this, &Editor::collect_new_region_view)); + latest_regionviews.clear (); + + playlist->partition ((nframes64_t)((*t).start * speed), + (nframes64_t)((*t).end * speed), true); + + c.disconnect (); + + if (!latest_regionviews.empty()) { + + got_some = true; + + rtv->view()->foreach_regionview (bind ( + sigc::ptr_fun (add_if_covered), + &(*t), &new_selection)); + + if (!in_command) { + begin_reversible_command (_("separate")); + in_command = true; + } + + session->add_command(new MementoCommand( + *playlist, before, &playlist->get_state())); + } + } + + if (!got_some) { + delete before; + } + } + } + } + } + + if (in_command) { + selection->set (new_selection); + set_mouse_mode (MouseObject); + + commit_reversible_command (); + } +} + +/** Take tracks from get_tracks_for_range_action and cut any regions + * on those tracks so that the tracks are empty over the time + * selection. + */ +void +Editor::separate_region_from_selection () +{ + /* preferentially use *all* ranges in the time selection if we're in range mode + to allow discontiguous operation, since get_edit_op_range() currently + returns a single range. + */ + + if (mouse_mode == MouseRange && !selection->time.empty()) { + + separate_regions_between (selection->time); + + } else { + + nframes64_t start; + nframes64_t end; + + if (get_edit_op_range (start, end)) { + + AudioRange ar (start, end, 1); + TimeSelection ts; + ts.push_back (ar); + + separate_regions_between (ts); + } + } +} + +void +Editor::separate_region_from_punch () +{ + Location* loc = session->locations()->auto_punch_location(); + if (loc) { + separate_regions_using_location (*loc); + } +} + +void +Editor::separate_region_from_loop () +{ + Location* loc = session->locations()->auto_loop_location(); + if (loc) { + separate_regions_using_location (*loc); + } +} + +void +Editor::separate_regions_using_location (Location& loc) +{ + if (loc.is_mark()) { + return; + } + + AudioRange ar (loc.start(), loc.end(), 1); + TimeSelection ts; + + ts.push_back (ar); + + separate_regions_between (ts); +} + +void +Editor::crop_region_to_selection () +{ + if (!selection->time.empty()) { + + crop_region_to (selection->time.start(), selection->time.end_frame()); + + } else { + + nframes64_t start; + nframes64_t end; + + if (get_edit_op_range (start, end)) { + crop_region_to (start, end); + } + } + +} + +void +Editor::crop_region_to (nframes64_t start, nframes64_t end) +{ + vector > playlists; + boost::shared_ptr playlist; + TrackSelection* ts; + + if (selection->tracks.empty()) { + ts = &track_views; + } else { + sort_track_selection (); + ts = &selection->tracks; + } + + for (TrackSelection::iterator i = ts->begin(); i != ts->end(); ++i) { + + RouteTimeAxisView* rtv; + + if ((rtv = dynamic_cast ((*i))) != 0) { + + boost::shared_ptr t = rtv->track(); + + if (t != 0 && ! t->diskstream()->destructive()) { + + if ((playlist = rtv->playlist()) != 0) { + playlists.push_back (playlist); + } + } + } + } + + if (playlists.empty()) { + return; + } + + nframes64_t the_start; + nframes64_t the_end; + nframes64_t cnt; + + begin_reversible_command (_("trim to selection")); + + for (vector >::iterator i = playlists.begin(); i != playlists.end(); ++i) { + + boost::shared_ptr region; + + the_start = start; + + if ((region = (*i)->top_region_at(the_start)) == 0) { + continue; + } + + /* now adjust lengths to that we do the right thing + if the selection extends beyond the region + */ + + the_start = max (the_start, (nframes64_t) region->position()); + if (max_frames - the_start < region->length()) { + the_end = the_start + region->length() - 1; + } else { + the_end = max_frames; + } + the_end = min (end, the_end); + cnt = the_end - the_start + 1; + + XMLNode &before = (*i)->get_state(); + region->trim_to (the_start, cnt, this); + XMLNode &after = (*i)->get_state(); + session->add_command (new MementoCommand(*(*i), &before, &after)); + } + + commit_reversible_command (); +} + +void +Editor::region_fill_track () +{ + nframes64_t end; + RegionSelection rs; + + get_regions_for_action (rs); + + if (!session || rs.empty()) { + return; + } + + end = session->current_end_frame (); + + begin_reversible_command (_("region fill")); + + for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) { + + boost::shared_ptr region ((*i)->region()); + + boost::shared_ptr pl = region->playlist(); + + if (end <= region->last_frame()) { + return; + } + + double times = (double) (end - region->last_frame()) / (double) region->length(); + + if (times == 0) { + return; + } + + XMLNode &before = pl->get_state(); + pl->add_region (RegionFactory::create (region), region->last_frame(), times); + session->add_command (new MementoCommand(*pl, &before, &pl->get_state())); + } + + commit_reversible_command (); +} + +void +Editor::region_fill_selection () +{ + if (clicked_routeview == 0 || !clicked_routeview->is_audio_track()) { + return; + } + + if (selection->time.empty()) { + return; + } + + boost::shared_ptr region = _regions->get_single_selection (); + if (region == 0) { + return; + } + + nframes64_t start = selection->time[clicked_selection].start; + nframes64_t end = selection->time[clicked_selection].end; + + boost::shared_ptr playlist; + + if (selection->tracks.empty()) { + return; + } + + nframes64_t selection_length = end - start; + float times = (float)selection_length / region->length(); + + begin_reversible_command (_("fill selection")); + + for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) { + + if ((playlist = (*i)->playlist()) == 0) { + continue; + } + + XMLNode &before = playlist->get_state(); + playlist->add_region (RegionFactory::create (region), start, times); + session->add_command (new MementoCommand(*playlist, &before, &playlist->get_state())); + } + + commit_reversible_command (); +} + +void +Editor::set_region_sync_from_edit_point () +{ + nframes64_t where = get_preferred_edit_position (); + RegionSelection rs; + get_regions_for_action (rs); + set_sync_point (where, rs); +} + +void +Editor::set_sync_point (nframes64_t where, const RegionSelection& rs) +{ + bool in_command = false; + + for (RegionSelection::const_iterator r = rs.begin(); r != rs.end(); ++r) { + + if (!(*r)->region()->covers (where)) { + continue; + } + + boost::shared_ptr region ((*r)->region()); + + if (!in_command) { + begin_reversible_command (_("set sync point")); + in_command = true; + } + + XMLNode &before = region->playlist()->get_state(); + region->set_sync_position (where); + XMLNode &after = region->playlist()->get_state(); + session->add_command(new MementoCommand(*(region->playlist()), &before, &after)); + } + + if (in_command) { + commit_reversible_command (); + } +} + +/** Remove the sync positions of the selection */ +void +Editor::remove_region_sync () +{ + RegionSelection rs; + + get_regions_for_action (rs); + + if (rs.empty()) { + return; + } + + begin_reversible_command (_("remove sync")); + for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) { + + XMLNode &before = (*i)->region()->playlist()->get_state(); + (*i)->region()->clear_sync_position (); + XMLNode &after = (*i)->region()->playlist()->get_state(); + session->add_command(new MementoCommand(*((*i)->region()->playlist()), &before, &after)); + } + commit_reversible_command (); +} + +void +Editor::naturalize () +{ + RegionSelection rs; + + get_regions_for_action (rs); + + if (rs.empty()) { + return; + } + + begin_reversible_command (_("naturalize")); + for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) { + XMLNode &before = (*i)->region()->get_state(); + (*i)->region()->move_to_natural_position (this); + XMLNode &after = (*i)->region()->get_state(); + session->add_command (new MementoCommand(*((*i)->region().get()), &before, &after)); + } + commit_reversible_command (); +} + +void +Editor::align (RegionPoint what) +{ + RegionSelection rs; + + get_regions_for_action (rs); + nframes64_t where = get_preferred_edit_position(); + + if (!rs.empty()) { + align_selection (what, where, rs); + } else { + + RegionSelection rs; + get_regions_at (rs, where, selection->tracks); + align_selection (what, where, rs); + } +} + +void +Editor::align_relative (RegionPoint what) +{ + nframes64_t where = get_preferred_edit_position(); + RegionSelection rs; + + get_regions_for_action (rs); + + if (!rs.empty()) { + align_selection_relative (what, where, rs); + } +} + +struct RegionSortByTime { + bool operator() (const RegionView* a, const RegionView* b) { + return a->region()->position() < b->region()->position(); + } +}; + +void +Editor::align_selection_relative (RegionPoint point, nframes64_t position, const RegionSelection& rs) +{ + if (rs.empty()) { + return; + } + + nframes64_t distance = 0; + nframes64_t pos = 0; + int dir = 1; + + list sorted; + rs.by_position (sorted); + + boost::shared_ptr r ((*sorted.begin())->region()); + + switch (point) { + case Start: + pos = position; + if (position > r->position()) { + distance = position - r->position(); + } else { + distance = r->position() - position; + dir = -1; + } + break; + + case End: + if (position > r->last_frame()) { + distance = position - r->last_frame(); + pos = r->position() + distance; + } else { + distance = r->last_frame() - position; + pos = r->position() - distance; + dir = -1; + } + break; + + case SyncPoint: + pos = r->adjust_to_sync (position); + if (pos > r->position()) { + distance = pos - r->position(); + } else { + distance = r->position() - pos; + dir = -1; + } + break; + } + + if (pos == r->position()) { + return; + } + + begin_reversible_command (_("align selection (relative)")); + + /* move first one specially */ + + XMLNode &before = r->playlist()->get_state(); + r->set_position (pos, this); + XMLNode &after = r->playlist()->get_state(); + session->add_command(new MementoCommand(*(r->playlist()), &before, &after)); + + /* move rest by the same amount */ + + sorted.pop_front(); + + for (list::iterator i = sorted.begin(); i != sorted.end(); ++i) { + + boost::shared_ptr region ((*i)->region()); + + XMLNode &before = region->playlist()->get_state(); + + if (dir > 0) { + region->set_position (region->position() + distance, this); + } else { + region->set_position (region->position() - distance, this); + } + + XMLNode &after = region->playlist()->get_state(); + session->add_command(new MementoCommand(*(region->playlist()), &before, &after)); + + } + + commit_reversible_command (); +} + +void +Editor::align_selection (RegionPoint point, nframes64_t position, const RegionSelection& rs) +{ + if (rs.empty()) { + return; + } + + begin_reversible_command (_("align selection")); + + for (RegionSelection::const_iterator i = rs.begin(); i != rs.end(); ++i) { + align_region_internal ((*i)->region(), point, position); + } + + commit_reversible_command (); +} + +void +Editor::align_region (boost::shared_ptr region, RegionPoint point, nframes64_t position) +{ + begin_reversible_command (_("align region")); + align_region_internal (region, point, position); + commit_reversible_command (); +} + +void +Editor::align_region_internal (boost::shared_ptr region, RegionPoint point, nframes64_t position) +{ + XMLNode &before = region->playlist()->get_state(); + + switch (point) { + case SyncPoint: + region->set_position (region->adjust_to_sync (position), this); + break; + + case End: + if (position > region->length()) { + region->set_position (position - region->length(), this); + } + break; + + case Start: + region->set_position (position, this); + break; + } + + XMLNode &after = region->playlist()->get_state(); + session->add_command(new MementoCommand(*(region->playlist()), &before, &after)); +} + +void +Editor::trim_region_front () +{ + trim_region (true); +} + +void +Editor::trim_region_back () +{ + trim_region (false); +} + +void +Editor::trim_region (bool front) +{ + nframes64_t where = get_preferred_edit_position(); + RegionSelection rs; + + get_regions_for_action (rs); + + if (rs.empty()) { + return; + } + + begin_reversible_command (front ? _("trim front") : _("trim back")); + + for (list::const_iterator i = rs.by_layer().begin(); i != rs.by_layer().end(); ++i) { + if (!(*i)->region()->locked()) { + boost::shared_ptr pl = (*i)->region()->playlist(); + XMLNode &before = pl->get_state(); + if (front) { + (*i)->region()->trim_front (where, this); + } else { + (*i)->region()->trim_end (where, this); + } + XMLNode &after = pl->get_state(); + session->add_command(new MementoCommand(*pl.get(), &before, &after)); + } + } + + commit_reversible_command (); +} + +/** Trim the end of the selected regions to the position of the edit cursor */ +void +Editor::trim_region_to_loop () +{ + Location* loc = session->locations()->auto_loop_location(); + if (!loc) { + return; + } + trim_region_to_location (*loc, _("trim to loop")); +} + +void +Editor::trim_region_to_punch () +{ + Location* loc = session->locations()->auto_punch_location(); + if (!loc) { + return; + } + trim_region_to_location (*loc, _("trim to punch")); +} +void +Editor::trim_region_to_location (const Location& loc, const char* str) +{ + RegionSelection rs; + + get_regions_for_action (rs); + + begin_reversible_command (str); + + for (RegionSelection::iterator x = rs.begin(); x != rs.end(); ++x) { + RegionView* rv = (*x); + + /* require region to span proposed trim */ + switch (rv->region()->coverage (loc.start(), loc.end())) { + case OverlapInternal: + break; + default: + continue; + } + + RouteTimeAxisView* tav = dynamic_cast (&rv->get_time_axis_view()); + if (!tav) { + return; + } + + float speed = 1.0; + nframes64_t start; + nframes64_t end; + + if (tav->get_diskstream() != 0) { + speed = tav->get_diskstream()->speed(); + } + + start = session_frame_to_track_frame (loc.start(), speed); + end = session_frame_to_track_frame (loc.end(), speed); + + XMLNode &before = rv->region()->playlist()->get_state(); + rv->region()->trim_to (start, (end - start), this); + XMLNode &after = rv->region()->playlist()->get_state(); + session->add_command(new MementoCommand( + *(rv->region()->playlist()), &before, &after)); + } + + commit_reversible_command (); +} + +void +Editor::trim_region_to_edit_point () +{ + RegionSelection rs; + + get_regions_for_action (rs); + + nframes64_t where = get_preferred_edit_position(); + + begin_reversible_command (_("trim region start to edit point")); + + for (RegionSelection::iterator x = rs.begin(); x != rs.end(); ++x) { + RegionView* rv = (*x); + + /* require region to cover trim */ + if (!rv->region()->covers (where)) { + continue; + } + + RouteTimeAxisView* tav = dynamic_cast (&rv->get_time_axis_view()); + if (!tav) { + return; + } + + float speed = 1.0; + + if (tav->get_diskstream() != 0) { + speed = tav->get_diskstream()->speed(); + } + + XMLNode &before = rv->region()->playlist()->get_state(); + rv->region()->trim_end( session_frame_to_track_frame(where, speed), this); + XMLNode &after = rv->region()->playlist()->get_state(); + session->add_command(new MementoCommand( + *(rv->region()->playlist()), &before, &after)); + } + + commit_reversible_command (); +} + +void +Editor::trim_region_from_edit_point () +{ + RegionSelection rs; + + get_regions_for_action (rs); + + nframes64_t where = get_preferred_edit_position(); + + begin_reversible_command (_("trim region end to edit point")); + + for (RegionSelection::iterator x = rs.begin(); x != rs.end(); ++x) { + RegionView* rv = (*x); + + /* require region to cover trim */ + if (!rv->region()->covers (where)) { + continue; + } + + RouteTimeAxisView* tav = dynamic_cast (&rv->get_time_axis_view()); + if (!tav) { + return; + } + + float speed = 1.0; + + if (tav->get_diskstream() != 0) { + speed = tav->get_diskstream()->speed(); + } + + XMLNode &before = rv->region()->playlist()->get_state(); + rv->region()->trim_front ( session_frame_to_track_frame(where, speed), this); + XMLNode &after = rv->region()->playlist()->get_state(); + session->add_command(new MementoCommand( + *(rv->region()->playlist()), &before, &after)); + } + + commit_reversible_command (); +} + +void +Editor::trim_region_to_previous_region_end () +{ + return trim_to_region(false); +} + +void +Editor::trim_region_to_next_region_start () +{ + return trim_to_region(true); +} + +void +Editor::trim_to_region(bool forward) +{ + RegionSelection rs; + + get_regions_for_action (rs); + + begin_reversible_command (_("trim to region")); + + boost::shared_ptr next_region; + + for (RegionSelection::iterator x = rs.begin(); x != rs.end(); ++x) { + + AudioRegionView* arv = dynamic_cast (*x); + + if (!arv) { + continue; + } + + AudioTimeAxisView* atav = dynamic_cast (&arv->get_time_axis_view()); + + if (!atav) { + return; + } + + float speed = 1.0; + + if (atav->get_diskstream() != 0) { + speed = atav->get_diskstream()->speed(); + } + + + boost::shared_ptr region = arv->region(); + boost::shared_ptr playlist (region->playlist()); + + XMLNode &before = playlist->get_state(); + + if(forward){ + + next_region = playlist->find_next_region (region->first_frame(), Start, 1); + + if(!next_region){ + continue; + } + + region->trim_end((nframes64_t) (next_region->first_frame() * speed), this); + arv->region_changed (Change (LengthChanged)); + } + else { + + next_region = playlist->find_next_region (region->first_frame(), Start, 0); + + if(!next_region){ + continue; + } + + region->trim_front((nframes64_t) ((next_region->last_frame() + 1) * speed), this); + arv->region_changed (Change (LengthChanged|PositionChanged|StartChanged)); + } + + XMLNode &after = playlist->get_state(); + session->add_command(new MementoCommand(*playlist, &before, &after)); + } + + commit_reversible_command (); +} + +void +Editor::unfreeze_route () +{ + if (clicked_routeview == 0 || !clicked_routeview->is_track()) { + return; + } + + clicked_routeview->track()->unfreeze (); +} + +void* +Editor::_freeze_thread (void* arg) +{ + PBD::notify_gui_about_thread_creation (pthread_self(), X_("Freeze")); + return static_cast(arg)->freeze_thread (); +} + +void* +Editor::freeze_thread () +{ + clicked_routeview->audio_track()->freeze (*current_interthread_info); + current_interthread_info->done = true; + return 0; +} + +gint +Editor::freeze_progress_timeout (void */*arg*/) +{ + interthread_progress_bar.set_fraction (current_interthread_info->progress); + return !(current_interthread_info->done || current_interthread_info->cancel); +} + +void +Editor::freeze_route () +{ + if (clicked_routeview == 0 || !clicked_routeview->is_audio_track()) { + return; + } + + InterThreadInfo itt; + + if (interthread_progress_window == 0) { + build_interthread_progress_window (); + } + + interthread_progress_window->set_title (_("Freeze")); + interthread_progress_window->set_position (Gtk::WIN_POS_MOUSE); + interthread_progress_window->show_all (); + interthread_progress_bar.set_fraction (0.0f); + interthread_progress_label.set_text (""); + interthread_cancel_label.set_text (_("Cancel Freeze")); + current_interthread_info = &itt; + + interthread_progress_connection = + Glib::signal_timeout().connect (bind (mem_fun(*this, &Editor::freeze_progress_timeout), (gpointer) 0), 100); + + itt.done = false; + itt.cancel = false; + itt.progress = 0.0f; + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setstacksize(&attr, 500000); + + pthread_create_and_store (X_("freezer"), &itt.thread, &attr, _freeze_thread, this); + + pthread_attr_destroy(&attr); + + track_canvas->get_window()->set_cursor (Gdk::Cursor (Gdk::WATCH)); + + while (!itt.done && !itt.cancel) { + gtk_main_iteration (); + } + + interthread_progress_connection.disconnect (); + interthread_progress_window->hide_all (); + current_interthread_info = 0; + track_canvas->get_window()->set_cursor (*current_canvas_cursor); +} + +void +Editor::bounce_range_selection (bool replace, bool enable_processing) +{ + if (selection->time.empty()) { + return; + } + + TrackSelection views = selection->tracks; + + nframes64_t start = selection->time[clicked_selection].start; + nframes64_t end = selection->time[clicked_selection].end; + nframes64_t cnt = end - start + 1; + + begin_reversible_command (_("bounce range")); + + for (TrackViewList::iterator i = views.begin(); i != views.end(); ++i) { + + RouteTimeAxisView* rtv; + + if ((rtv = dynamic_cast (*i)) == 0) { + continue; + } + + boost::shared_ptr playlist; + + if ((playlist = rtv->playlist()) == 0) { + return; + } + + InterThreadInfo itt; + + itt.done = false; + itt.cancel = false; + itt.progress = false; + + XMLNode &before = playlist->get_state(); + boost::shared_ptr r = rtv->track()->bounce_range (start, start+cnt, itt, enable_processing); + + if (replace) { + list ranges; + ranges.push_back (AudioRange (start, start+cnt, 0)); + playlist->cut (ranges); // discard result + playlist->add_region (r, start); + } + + XMLNode &after = playlist->get_state(); + session->add_command (new MementoCommand (*playlist, &before, &after)); + } + + commit_reversible_command (); +} + +/** Cut selected regions, automation points or a time range */ +void +Editor::cut () +{ + cut_copy (Cut); +} + +/** Copy selected regions, automation points or a time range */ +void +Editor::copy () +{ + cut_copy (Copy); +} + + +/** @return true if a Cut, Copy or Clear is possible */ +bool +Editor::can_cut_copy () const +{ + switch (current_mouse_mode()) { + + case MouseObject: + if (!selection->regions.empty() || !selection->points.empty()) { + return true; + } + break; + + case MouseRange: + if (!selection->time.empty()) { + return true; + } + break; + + default: + break; + } + + return false; +} + + +/** Cut, copy or clear selected regions, automation points or a time range. + * @param op Operation (Cut, Copy or Clear) + */ +void +Editor::cut_copy (CutCopyOp op) +{ + /* only cancel selection if cut/copy is successful.*/ + + string opname; + + switch (op) { + case Cut: + opname = _("cut"); + break; + case Copy: + opname = _("copy"); + break; + case Clear: + opname = _("clear"); + break; + } + + /* if we're deleting something, and the mouse is still pressed, + the thing we started a drag for will be gone when we release + the mouse button(s). avoid this. see part 2 at the end of + this function. + */ + + if (op == Cut || op == Clear) { + if (_drag) { + _drag->item()->ungrab (0); + delete _drag; + _drag = 0; + } + } + + cut_buffer->clear (); + + if (entered_marker) { + + /* cut/delete op while pointing at a marker */ + + bool ignored; + Location* loc = find_location_from_marker (entered_marker, ignored); + + if (session && loc) { + Glib::signal_idle().connect (bind (mem_fun(*this, &Editor::really_remove_marker), loc)); + } + + break_drag (); + delete _drag; + _drag = 0; + + return; + } + + if (internal_editing()) { + + switch (current_mouse_mode()) { + case MouseObject: + case MouseRange: + cut_copy_midi (op); + break; + default: + break; + } + + } else { + + RegionSelection rs; + + /* we only want to cut regions if some are selected */ + + if (!selection->regions.empty()) { + get_regions_for_action (rs, false, false); + } + + switch (current_mouse_mode()) { + case MouseObject: + if (!rs.empty() || !selection->points.empty()) { + + begin_reversible_command (opname + _(" objects")); + + if (!rs.empty()) { + cut_copy_regions (op, rs); + + if (op == Cut) { + selection->clear_regions (); + } + } + + if (!selection->points.empty()) { + cut_copy_points (op); + + if (op == Cut) { + selection->clear_points (); + } + } + + commit_reversible_command (); + break; // terminate case statement here + } + if (!selection->time.empty()) { + /* don't cause suprises */ + break; + } + // fall thru if there was nothing selected + + case MouseRange: + if (selection->time.empty()) { + nframes64_t start, end; + if (!get_edit_op_range (start, end)) { + return; + } + selection->set ((TimeAxisView*) 0, start, end); + } + + begin_reversible_command (opname + _(" range")); + cut_copy_ranges (op); + commit_reversible_command (); + + if (op == Cut) { + selection->clear_time (); + } + + break; + + default: + break; + } + } + + if (op == Cut || op == Clear) { + break_drag (); + delete _drag; + _drag = 0; + } +} + +/** Cut, copy or clear selected automation points. + * @param op Operation (Cut, Copy or Clear) + */ +void +Editor::cut_copy_points (CutCopyOp op) +{ + for (PointSelection::iterator i = selection->points.begin(); i != selection->points.end(); ++i) { + + AutomationTimeAxisView* atv = dynamic_cast(&(*i).track); + + if (atv) { + atv->cut_copy_clear_objects (selection->points, op); + } + } +} + +/** Cut, copy or clear selected automation points. + * @param op Operation (Cut, Copy or Clear) + */ +void +Editor::cut_copy_midi (CutCopyOp op) +{ + for (MidiRegionSelection::iterator i = selection->midi_regions.begin(); i != selection->midi_regions.end(); ++i) { + MidiRegionView* mrv = *i; + mrv->cut_copy_clear (op); + } +} + +struct PlaylistState { + boost::shared_ptr playlist; + XMLNode* before; +}; + +struct lt_playlist { + bool operator () (const PlaylistState& a, const PlaylistState& b) { + return a.playlist < b.playlist; + } +}; + +struct PlaylistMapping { + TimeAxisView* tv; + boost::shared_ptr pl; + + PlaylistMapping (TimeAxisView* tvp) : tv (tvp) {} +}; + +/** Remove `clicked_regionview' */ +void +Editor::remove_clicked_region () +{ + if (clicked_routeview == 0 || clicked_regionview == 0) { + return; + } + + boost::shared_ptr playlist = clicked_routeview->playlist(); + + begin_reversible_command (_("remove region")); + XMLNode &before = playlist->get_state(); + playlist->remove_region (clicked_regionview->region()); + XMLNode &after = playlist->get_state(); + session->add_command(new MementoCommand(*playlist, &before, &after)); + commit_reversible_command (); +} + + +/** Remove the selected regions */ +void +Editor::remove_selected_regions () +{ + RegionSelection rs; + get_regions_for_action (rs); + + if (!session) { + return; + } + + if (rs.empty()) { + return; + } + + begin_reversible_command (_("remove region")); + + list > regions_to_remove; + + for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) { + // we can't just remove the region(s) in this loop because + // this removes them from the RegionSelection, and they thus + // disappear from underneath the iterator, and the ++i above + // SEGVs in a puzzling fashion. + + // so, first iterate over the regions to be removed from rs and + // add them to the regions_to_remove list, and then + // iterate over the list to actually remove them. + + regions_to_remove.push_back ((*i)->region()); + } + + vector playlists; + + for (list >::iterator rl = regions_to_remove.begin(); rl != regions_to_remove.end(); ++rl) { + + boost::shared_ptr playlist = (*rl)->playlist(); + + if (!playlist) { + // is this check necessary? + continue; + } + + vector::iterator i; + + //only take state if this is a new playlist. + for (i = playlists.begin(); i != playlists.end(); ++i) { + if ((*i).playlist == playlist) { + break; + } + } + + if (i == playlists.end()) { + + PlaylistState before; + before.playlist = playlist; + before.before = &playlist->get_state(); + + playlist->freeze (); + playlists.push_back(before); + } + + playlist->remove_region (*rl); + } + + vector::iterator pl; + + for (pl = playlists.begin(); pl != playlists.end(); ++pl) { + (*pl).playlist->thaw (); + session->add_command(new MementoCommand(*(*pl).playlist, (*pl).before, &(*pl).playlist->get_state())); + } + + commit_reversible_command (); +} + +/** Cut, copy or clear selected regions. + * @param op Operation (Cut, Copy or Clear) + */ +void +Editor::cut_copy_regions (CutCopyOp op, RegionSelection& rs) +{ + /* we can't use a std::map here because the ordering is important, and we can't trivially sort + a map when we want ordered access to both elements. i think. + */ + + vector pmap; + + nframes64_t first_position = max_frames; + + set freezelist; + pair::iterator,bool> insert_result; + + /* get ordering correct before we cut/copy */ + + rs.sort_by_position_and_track (); + + for (RegionSelection::iterator x = rs.begin(); x != rs.end(); ++x) { + + first_position = min ((nframes64_t) (*x)->region()->position(), first_position); + + if (op == Cut || op == Clear) { + boost::shared_ptr pl = (*x)->region()->playlist(); + + if (pl) { + set::iterator fl; + + //only take state if this is a new playlist. + for (fl = freezelist.begin(); fl != freezelist.end(); ++fl) { + if ((*fl).playlist == pl) { + break; + } + } + + if (fl == freezelist.end()) { + PlaylistState before; + before.playlist = pl; + before.before = &pl->get_state(); + pl->freeze (); + insert_result = freezelist.insert (before); + } + } + } + + TimeAxisView* tv = &(*x)->get_trackview(); + vector::iterator z; + + for (z = pmap.begin(); z != pmap.end(); ++z) { + if ((*z).tv == tv) { + break; + } + } + + if (z == pmap.end()) { + pmap.push_back (PlaylistMapping (tv)); + } + } + + for (RegionSelection::iterator x = rs.begin(); x != rs.end(); ) { + + boost::shared_ptr pl = (*x)->region()->playlist(); + + if (!pl) { + /* impossible, but this handles it for the future */ + continue; + } + + TimeAxisView& tv = (*x)->get_trackview(); + boost::shared_ptr npl; + RegionSelection::iterator tmp; + + tmp = x; + ++tmp; + + vector::iterator z; + + for (z = pmap.begin(); z != pmap.end(); ++z) { + if ((*z).tv == &tv) { + break; + } + } + + assert (z != pmap.end()); + + if (!(*z).pl) { + npl = PlaylistFactory::create (pl->data_type(), *session, "cutlist", true); + npl->freeze(); + (*z).pl = npl; + } else { + npl = (*z).pl; + } + + boost::shared_ptr r = (*x)->region(); + boost::shared_ptr _xx; + + assert (r != 0); + + switch (op) { + case Cut: + _xx = RegionFactory::create (r); + npl->add_region (_xx, r->position() - first_position); + pl->remove_region (r); + break; + + case Copy: + /* copy region before adding, so we're not putting same object into two different playlists */ + npl->add_region (RegionFactory::create (r), r->position() - first_position); + break; + + case Clear: + pl->remove_region (r); + break; + } + + x = tmp; + } + + list > foo; + + /* the pmap is in the same order as the tracks in which selected regions occured */ + + for (vector::iterator i = pmap.begin(); i != pmap.end(); ++i) { + (*i).pl->thaw(); + foo.push_back ((*i).pl); + } + + + if (!foo.empty()) { + cut_buffer->set (foo); + } + + for (set::iterator pl = freezelist.begin(); pl != freezelist.end(); ++pl) { + (*pl).playlist->thaw (); + session->add_command (new MementoCommand(*(*pl).playlist, (*pl).before, &(*pl).playlist->get_state())); + } +} + +void +Editor::cut_copy_ranges (CutCopyOp op) +{ + TrackSelection* ts; + TrackSelection entered; + + if (selection->tracks.empty()) { + if (!entered_track) { + return; + } + entered.push_back (entered_track); + ts = &entered; + } else { + ts = &selection->tracks; + } + + for (TrackSelection::iterator i = ts->begin(); i != ts->end(); ++i) { + (*i)->cut_copy_clear (*selection, op); + } +} + +void +Editor::paste (float times) +{ + paste_internal (get_preferred_edit_position(), times); +} + +void +Editor::mouse_paste () +{ + nframes64_t where; + bool ignored; + + if (!mouse_frame (where, ignored)) { + return; + } + + snap_to (where); + paste_internal (where, 1); +} + +void +Editor::paste_internal (nframes64_t position, float times) +{ + bool commit = false; + + if (internal_editing()) { + if (cut_buffer->midi_notes.empty()) { + return; + } + } else { + if (cut_buffer->empty()) { + return; + } + } + + if (position == max_frames) { + position = get_preferred_edit_position(); + } + + begin_reversible_command (_("paste")); + + TrackSelection ts; + TrackSelection::iterator i; + size_t nth; + + /* get everything in the correct order */ + + if (!selection->tracks.empty()) { + sort_track_selection (); + ts = selection->tracks; + } else if (entered_track) { + ts.push_back (entered_track); + } + + for (nth = 0, i = ts.begin(); i != ts.end(); ++i, ++nth) { + + /* undo/redo is handled by individual tracks/regions */ + + if (internal_editing()) { + + RegionSelection rs; + RegionSelection::iterator r; + MidiNoteSelection::iterator cb; + + get_regions_at (rs, position, ts); + + for (cb = cut_buffer->midi_notes.begin(), r = rs.begin(); + cb != cut_buffer->midi_notes.end() && r != rs.end(); ++r) { + MidiRegionView* mrv = dynamic_cast (*r); + if (mrv) { + mrv->paste (position, times, **cb); + ++cb; + } + } + + } else { + + if ((*i)->paste (position, times, *cut_buffer, nth)) { + commit = true; + } + } + } + + if (commit) { + commit_reversible_command (); + } +} + +void +Editor::duplicate_some_regions (RegionSelection& regions, float times) +{ + boost::shared_ptr playlist; + RegionSelection sel = regions; // clear (below) may clear the argument list if its the current region selection + RegionSelection foo; + + begin_reversible_command (_("duplicate region")); + + selection->clear_regions (); + + for (RegionSelection::iterator i = sel.begin(); i != sel.end(); ++i) { + + boost::shared_ptr r ((*i)->region()); + + TimeAxisView& tv = (*i)->get_time_axis_view(); + RouteTimeAxisView* rtv = dynamic_cast (&tv); + latest_regionviews.clear (); + sigc::connection c = rtv->view()->RegionViewAdded.connect (mem_fun(*this, &Editor::collect_new_region_view)); + + playlist = (*i)->region()->playlist(); + XMLNode &before = playlist->get_state(); + playlist->duplicate (r, r->last_frame(), times); + session->add_command(new MementoCommand(*playlist, &before, &playlist->get_state())); + + c.disconnect (); + + foo.insert (foo.end(), latest_regionviews.begin(), latest_regionviews.end()); + } + + commit_reversible_command (); + + if (!foo.empty()) { + selection->set (foo); + } +} + +void +Editor::duplicate_selection (float times) +{ + if (selection->time.empty() || selection->tracks.empty()) { + return; + } + + boost::shared_ptr playlist; + vector > new_regions; + vector >::iterator ri; + + create_region_from_selection (new_regions); + + if (new_regions.empty()) { + return; + } + + begin_reversible_command (_("duplicate selection")); + + ri = new_regions.begin(); + + for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) { + if ((playlist = (*i)->playlist()) == 0) { + continue; + } + XMLNode &before = playlist->get_state(); + playlist->duplicate (*ri, selection->time[clicked_selection].end, times); + XMLNode &after = playlist->get_state(); + session->add_command (new MementoCommand(*playlist, &before, &after)); + + ++ri; + if (ri == new_regions.end()) { + --ri; + } + } + + commit_reversible_command (); +} + +void +Editor::reset_point_selection () +{ + /* reset all selected points to the relevant default value */ + + for (PointSelection::iterator i = selection->points.begin(); i != selection->points.end(); ++i) { + + AutomationTimeAxisView* atv = dynamic_cast(&(*i).track); + + if (atv) { + atv->reset_objects (selection->points); + } + } +} + +void +Editor::center_playhead () +{ + float page = _canvas_width * frames_per_unit; + center_screen_internal (playhead_cursor->current_frame, page); +} + +void +Editor::center_edit_point () +{ + float page = _canvas_width * frames_per_unit; + center_screen_internal (get_preferred_edit_position(), page); +} + +void +Editor::clear_playlist (boost::shared_ptr playlist) +{ + begin_reversible_command (_("clear playlist")); + XMLNode &before = playlist->get_state(); + playlist->clear (); + XMLNode &after = playlist->get_state(); + session->add_command (new MementoCommand(*playlist.get(), &before, &after)); + commit_reversible_command (); +} + +void +Editor::nudge_track (bool use_edit, bool forwards) { - double wx, wy; - double cx, cy; - TimeAxisView *tv; - nframes_t where; - AudioTimeAxisView *atv = 0; boost::shared_ptr playlist; - - track_canvas.window_to_world (x, y, wx, wy); - wx += horizontal_adjustment.get_value(); - wy += vertical_adjustment.get_value(); - - GdkEvent event; - event.type = GDK_BUTTON_RELEASE; - event.button.x = wx; - event.button.y = wy; - - where = event_frame (&event, &cx, &cy); + nframes64_t distance; + nframes64_t next_distance; + nframes64_t start; - if (where < leftmost_frame || where > leftmost_frame + current_page_frames()) { - /* clearly outside canvas area */ - return; + if (use_edit) { + start = get_preferred_edit_position(); + } else { + start = 0; } - - if ((tv = trackview_by_y_position (cy)) == 0) { + + if ((distance = get_nudge_distance (start, next_distance)) == 0) { return; } - - if ((atv = dynamic_cast(tv)) == 0) { + + if (selection->tracks.empty()) { return; } - if ((playlist = atv->playlist()) == 0) { - return; + begin_reversible_command (_("nudge track")); + + for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) { + + if ((playlist = (*i)->playlist()) == 0) { + continue; + } + + XMLNode &before = playlist->get_state(); + playlist->nudge_after (start, distance, forwards); + XMLNode &after = playlist->get_state(); + session->add_command (new MementoCommand(*playlist, &before, &after)); } - - cerr << "drop target playlist, UC = " << playlist.use_count() << endl; - snap_to (where); - - begin_reversible_command (_("insert dragged region")); - XMLNode &before = playlist->get_state(); - cerr << "pre add target playlist, UC = " << playlist.use_count() << endl; - playlist->add_region (RegionFactory::create (region), where, 1.0); - cerr << "post add target playlist, UC = " << playlist.use_count() << endl; - session->add_command(new MementoCommand(*playlist, &before, &playlist->get_state())); commit_reversible_command (); - - cerr << "post drop target playlist, UC = " << playlist.use_count() << endl; } void -Editor::insert_region_list_selection (float times) +Editor::remove_last_capture () { - RouteTimeAxisView *tv = 0; - boost::shared_ptr playlist; + vector choices; + string prompt; - if (clicked_audio_trackview != 0) { - tv = clicked_audio_trackview; - } else if (!selection->tracks.empty()) { - if ((tv = dynamic_cast(selection->tracks.front())) == 0) { - return; + if (!session) { + return; + } + + if (Config->get_verify_remove_last_capture()) { + prompt = _("Do you really want to destroy the last capture?" + "\n(This is destructive and cannot be undone)"); + + choices.push_back (_("No, do nothing.")); + choices.push_back (_("Yes, destroy it.")); + + Gtkmm2ext::Choice prompter (prompt, choices); + + if (prompter.run () == 1) { + session->remove_last_capture (); } + } else { + session->remove_last_capture(); + } +} + +void +Editor::normalize_region () +{ + if (!session) { return; } - if ((playlist = tv->playlist()) == 0) { + RegionSelection rs; + get_regions_for_action (rs); + + if (rs.empty()) { return; } - - Glib::RefPtr selected = region_list_display.get_selection(); - - if (selected->count_selected_rows() != 1) { + + Dialog dialog (rs.size() > 1 ? _("Normalize regions") : _("Normalize region")); + HBox hbox; + hbox.pack_start (*manage (new Label (_("Normalize to:")))); + SpinButton spin (0.2, 2); + spin.set_range (-112, 0); + spin.set_increments (0.1, 1); + spin.set_value (0); + hbox.pack_start (spin); + spin.set_value (_last_normalization_value); + hbox.pack_start (*manage (new Label (_("dbFS")))); + hbox.show_all (); + dialog.get_vbox()->pack_start (hbox); + dialog.add_button (Stock::CANCEL, RESPONSE_CANCEL); + dialog.add_button (_("Normalize"), RESPONSE_ACCEPT); + + if (dialog.run () == RESPONSE_CANCEL) { return; } - - TreeView::Selection::ListHandle_Path rows = selected->get_selected_rows (); - /* only one row selected, so rows.begin() is it */ + begin_reversible_command (_("normalize")); + + track_canvas->get_window()->set_cursor (*wait_cursor); + gdk_flush (); - TreeIter iter; + for (RegionSelection::iterator r = rs.begin(); r != rs.end(); ++r) { + AudioRegionView* const arv = dynamic_cast(*r); + if (!arv) + continue; + XMLNode &before = arv->region()->get_state(); + arv->audio_region()->normalize_to (spin.get_value()); + session->add_command (new MementoCommand(*(arv->region().get()), &before, &arv->region()->get_state())); + } - if ((iter = region_list_model->get_iter (*rows.begin()))) { + commit_reversible_command (); + track_canvas->get_window()->set_cursor (*current_canvas_cursor); - boost::shared_ptr region = (*iter)[region_list_columns.region]; - - begin_reversible_command (_("insert region")); - XMLNode &before = playlist->get_state(); - playlist->add_region ((RegionFactory::create (region)), edit_cursor->current_frame, times); - session->add_command(new MementoCommand(*playlist, &before, &playlist->get_state())); - commit_reversible_command (); - } + _last_normalization_value = spin.get_value (); } -/* BUILT-IN EFFECTS */ void -Editor::reverse_selection () +Editor::reset_region_scale_amplitude () { + if (!session) { + return; + } -} + RegionSelection rs; -/* GAIN ENVELOPE EDITING */ + get_regions_for_action (rs); -void -Editor::edit_envelope () -{ -} + if (rs.empty()) { + return; + } -/* PLAYBACK */ + begin_reversible_command ("reset gain"); + + for (RegionSelection::iterator r = rs.begin(); r != rs.end(); ++r) { + AudioRegionView* const arv = dynamic_cast(*r); + if (!arv) + continue; + XMLNode &before = arv->region()->get_state(); + arv->audio_region()->set_scale_amplitude (1.0f); + session->add_command (new MementoCommand(*(arv->region().get()), &before, &arv->region()->get_state())); + } + + commit_reversible_command (); +} void -Editor::toggle_playback (bool with_abort) +Editor::adjust_region_scale_amplitude (bool up) { if (!session) { return; } - switch (Config->get_slave_source()) { - case None: - case JACK: - break; - default: - /* transport controlled by the master */ - return; - } + RegionSelection rs; - if (session->is_auditioning()) { - session->cancel_audition (); + get_regions_for_action (rs); + + if (rs.empty()) { return; } - - if (session->transport_rolling()) { - session->request_stop (with_abort); - if (session->get_play_loop()) { - session->request_play_loop (false); + + begin_reversible_command ("denormalize"); + + for (RegionSelection::iterator r = rs.begin(); r != rs.end(); ++r) { + AudioRegionView* const arv = dynamic_cast(*r); + if (!arv) + continue; + XMLNode &before = arv->region()->get_state(); + + double fraction = gain_to_slider_position (arv->audio_region()->scale_amplitude ()); + + if (up) { + fraction += 0.05; + fraction = min (fraction, 1.0); + } else { + fraction -= 0.05; + fraction = max (fraction, 0.0); } - } else { - session->request_transport_speed (1.0f); + + if (!up && fraction <= 0) { + continue; + } + + fraction = slider_position_to_gain (fraction); + + if (up && fraction >= 2.0) { + continue; + } + + arv->audio_region()->set_scale_amplitude (fraction); + session->add_command (new MementoCommand(*(arv->region().get()), &before, &arv->region()->get_state())); } -} -void -Editor::play_from_start () -{ - session->request_locate (session->current_start_frame(), true); + commit_reversible_command (); } + void -Editor::play_from_edit_cursor () +Editor::reverse_region () { - session->request_locate (edit_cursor->current_frame, true); + if (!session) { + return; + } + + Reverse rev (*session); + apply_filter (rev, _("reverse regions")); } void -Editor::play_selection () +Editor::strip_region_silence () { - if (selection->time.empty()) { + if (!session) { + return; + } + + RegionSelection rs; + get_regions_for_action (rs); + + if (rs.empty()) { return; } - session->request_play_range (true); + std::list > ar; + + for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) { + AudioRegionView* const arv = dynamic_cast (*i); + if (arv) { + ar.push_back (arv->audio_region ()); + } + } + + StripSilenceDialog d (ar); + int const r = d.run (); + + if (r == Gtk::RESPONSE_OK) { + StripSilence s (*session, d.threshold (), d.minimum_length (), d.fade_length ()); + apply_filter (s, _("strip silence")); + } } -void -Editor::play_selected_region () +Command* +Editor::apply_midi_note_edit_op_to_region (MidiOperator& op, MidiRegionView& mrv) { - if (!selection->regions.empty()) { - RegionView *rv = *(selection->regions.begin()); + Evoral::Sequence::Notes selected; + mrv.selection_as_notelist (selected); - session->request_bounded_roll (rv->region()->position(), rv->region()->last_frame()); - } + vector::Notes> v; + v.push_back (selected); + + return op (mrv.midi_region()->model(), v); } void -Editor::loop_selected_region () +Editor::apply_midi_note_edit_op (MidiOperator& op) { - if (!selection->regions.empty()) { - RegionView *rv = *(selection->regions.begin()); - Location* tll; + RegionSelection rs; + Command* cmd; - if ((tll = transport_loop_location()) != 0) { + get_regions_for_action (rs); - tll->set (rv->region()->position(), rv->region()->last_frame()); - - // enable looping, reposition and start rolling + if (rs.empty()) { + return; + } - session->request_play_loop (true); - session->request_locate (tll->start(), false); - session->request_transport_speed (1.0f); + begin_reversible_command (op.name ()); + + for (RegionSelection::iterator r = rs.begin(); r != rs.end(); ) { + RegionSelection::iterator tmp = r; + ++tmp; + + MidiRegionView* const mrv = dynamic_cast (*r); + + if (mrv) { + cmd = apply_midi_note_edit_op_to_region (op, *mrv); + if (cmd) { + (*cmd)(); + session->add_command (cmd); + } } + + r = tmp; } + + commit_reversible_command (); + rs.clear (); } void -Editor::play_location (Location& location) +Editor::quantize_region () { - if (location.start() <= location.end()) { + if (!session) { return; } - session->request_bounded_roll (location.start(), location.end()); + QuantizeDialog* qd = new QuantizeDialog (*this); + + qd->present (); + const int r = qd->run (); + qd->hide (); + + if (r == Gtk::RESPONSE_OK) { + Quantize quant (*session, Plain, + qd->snap_start(), qd->snap_end(), + qd->start_grid_size(), qd->end_grid_size(), + qd->strength(), qd->swing(), qd->threshold()); + + apply_midi_note_edit_op (quant); + } } void -Editor::loop_location (Location& location) +Editor::apply_filter (Filter& filter, string command) { - if (location.start() <= location.end()) { + RegionSelection rs; + + get_regions_for_action (rs); + + if (rs.empty()) { return; } - Location* tll; + begin_reversible_command (command); - if ((tll = transport_loop_location()) != 0) { - tll->set (location.start(), location.end()); + track_canvas->get_window()->set_cursor (*wait_cursor); + gdk_flush (); - // enable looping, reposition and start rolling - session->request_play_loop (true); - session->request_locate (tll->start(), true); + for (RegionSelection::iterator r = rs.begin(); r != rs.end(); ) { + RegionSelection::iterator tmp = r; + ++tmp; + + AudioRegionView* const arv = dynamic_cast(*r); + if (arv) { + boost::shared_ptr playlist = arv->region()->playlist(); + + if (arv->audio_region()->apply (filter) == 0) { + + XMLNode &before = playlist->get_state(); + + if (filter.results.empty ()) { + + /* no regions returned; remove the old one */ + playlist->remove_region (arv->region ()); + + } else { + + std::vector >::iterator res = filter.results.begin (); + + /* first region replaces the old one */ + playlist->replace_region (arv->region(), *res, (*res)->position()); + ++res; + + /* add the rest */ + while (res != filter.results.end()) { + playlist->add_region (*res, (*res)->position()); + ++res; + } + + } + + XMLNode &after = playlist->get_state(); + session->add_command(new MementoCommand(*playlist, &before, &after)); + } else { + goto out; + } + } + + r = tmp; } + + commit_reversible_command (); + rs.clear (); + + out: + track_canvas->get_window()->set_cursor (*current_canvas_cursor); } void -Editor::raise_region () +Editor::region_selection_op (void (Region::*pmf)(void)) { - selection->foreach_region (&Region::raise); + for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) { + Region* region = (*i)->region().get(); + (region->*pmf)(); + } } + void -Editor::raise_region_to_top () +Editor::region_selection_op (void (Region::*pmf)(void*), void *arg) { - selection->foreach_region (&Region::raise_to_top); + for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) { + Region* region = (*i)->region().get(); + (region->*pmf)(arg); + } } void -Editor::lower_region () +Editor::region_selection_op (void (Region::*pmf)(bool), bool yn) { - selection->foreach_region (&Region::lower); + for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) { + Region* region = (*i)->region().get(); + (region->*pmf)(yn); + } } void -Editor::lower_region_to_bottom () +Editor::external_edit_region () { - selection->foreach_region (&Region::lower_to_bottom); + /* more to come */ } void -Editor::edit_region () +Editor::brush (nframes64_t pos) { - if (clicked_regionview == 0) { + RegionSelection sel; + RegionSelection rs; + + get_regions_for_action (rs); + + snap_to (pos); + + if (rs.empty()) { return; } - - clicked_regionview->show_region_editor (); + + for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) { + mouse_brush_insert_region ((*i), pos); + } } void -Editor::rename_region () +Editor::reset_region_gain_envelopes () { - Dialog dialog; - Entry entry; - Button ok_button (_("OK")); - Button cancel_button (_("Cancel")); + RegionSelection rs = get_equivalent_regions (selection->regions, RouteGroup::Edit); - if (selection->regions.empty()) { + if (!session || rs.empty()) { return; } - dialog.set_title (_("ardour: rename region")); - dialog.set_name ("RegionRenameWindow"); - dialog.set_size_request (300, -1); - dialog.set_position (Gtk::WIN_POS_MOUSE); - dialog.set_modal (true); + session->begin_reversible_command (_("reset region gain")); - dialog.get_vbox()->set_border_width (10); - dialog.get_vbox()->pack_start (entry); - dialog.get_action_area()->pack_start (ok_button); - dialog.get_action_area()->pack_start (cancel_button); + for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) { + AudioRegionView* const arv = dynamic_cast(*i); + if (arv) { + boost::shared_ptr alist (arv->audio_region()->envelope()); + XMLNode& before (alist->get_state()); - entry.set_name ("RegionNameDisplay"); - ok_button.set_name ("EditorGTKButton"); - cancel_button.set_name ("EditorGTKButton"); + arv->audio_region()->set_default_envelope (); + session->add_command (new MementoCommand(*arv->audio_region()->envelope().get(), &before, &alist->get_state())); + } + } - region_renamed = false; + session->commit_reversible_command (); +} - entry.signal_activate().connect (bind (mem_fun(*this, &Editor::rename_region_finished), true)); - ok_button.signal_clicked().connect (bind (mem_fun(*this, &Editor::rename_region_finished), true)); - cancel_button.signal_clicked().connect (bind (mem_fun(*this, &Editor::rename_region_finished), false)); +void +Editor::toggle_gain_envelope_visibility () +{ + RegionSelection rs = get_equivalent_regions (selection->regions, RouteGroup::Edit); - /* recurse */ + if (!session || rs.empty()) { + return; + } - dialog.show_all (); - Main::run (); + session->begin_reversible_command (_("region gain envelope visible")); - if (region_renamed) { - (*selection->regions.begin())->region()->set_name (entry.get_text()); - redisplay_regions (); + for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) { + AudioRegionView* const arv = dynamic_cast(*i); + if (arv) { + XMLNode &before = arv->region()->get_state (); + arv->set_envelope_visible (!arv->envelope_visible()); + XMLNode &after = arv->region()->get_state (); + session->add_command (new MementoCommand (*(arv->region().get()), &before, &after)); + } } -} - -void -Editor::rename_region_finished (bool status) -{ - region_renamed = status; - Main::quit (); + session->commit_reversible_command (); } void -Editor::audition_playlist_region_via_route (boost::shared_ptr region, Route& route) +Editor::toggle_gain_envelope_active () { - if (session->is_auditioning()) { - session->cancel_audition (); - } - - // note: some potential for creativity here, because region doesn't - // have to belong to the playlist that Route is handling + RegionSelection rs = get_equivalent_regions (selection->regions, RouteGroup::Edit); - // bool was_soloed = route.soloed(); + if (!session || rs.empty()) { + return; + } - route.set_solo (true, this); - - session->request_bounded_roll (region->position(), region->position() + region->length()); - - /* XXX how to unset the solo state ? */ -} + session->begin_reversible_command (_("region gain envelope active")); -void -Editor::audition_selected_region () -{ - if (!selection->regions.empty()) { - RegionView* rv = *(selection->regions.begin()); - session->audition_region (rv->region()); + for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) { + AudioRegionView* const arv = dynamic_cast(*i); + if (arv) { + XMLNode &before = arv->region()->get_state (); + arv->audio_region()->set_envelope_active (!arv->audio_region()->envelope_active()); + XMLNode &after = arv->region()->get_state (); + session->add_command (new MementoCommand (*(arv->region().get()), &before, &after)); + } } -} -void -Editor::audition_playlist_region_standalone (boost::shared_ptr region) -{ - session->audition_region (region); + session->commit_reversible_command (); } void -Editor::build_interthread_progress_window () +Editor::toggle_region_lock () { - interthread_progress_window = new ArdourDialog (X_("interthread progress"), true); - - interthread_progress_bar.set_orientation (Gtk::PROGRESS_LEFT_TO_RIGHT); - - interthread_progress_window->get_vbox()->pack_start (interthread_progress_label, false, false); - interthread_progress_window->get_vbox()->pack_start (interthread_progress_bar,false, false); + RegionSelection rs = get_equivalent_regions (selection->regions, RouteGroup::Edit); - // GTK2FIX: this button needs a modifiable label + if (!session || rs.empty()) { + return; + } - Button* b = interthread_progress_window->add_button (Stock::CANCEL, RESPONSE_CANCEL); - b->signal_clicked().connect (mem_fun(*this, &Editor::interthread_cancel_clicked)); + session->begin_reversible_command (_("region lock")); - interthread_cancel_button.add (interthread_cancel_label); + for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) { + XMLNode &before = (*i)->region()->get_state (); + (*i)->region()->set_locked (!(*i)->region()->locked()); + XMLNode &after = (*i)->region()->get_state (); + session->add_command (new MementoCommand (*((*i)->region().get()), &before, &after)); + } - interthread_progress_window->set_default_size (200, 100); + session->commit_reversible_command (); } void -Editor::interthread_cancel_clicked () +Editor::set_region_lock_style (Region::PositionLockStyle ps) { - if (current_interthread_info) { - current_interthread_info->cancel = true; - } -} + RegionSelection rs = get_equivalent_regions (selection->regions, RouteGroup::Edit); -void -Editor::region_from_selection () -{ - if (clicked_trackview == 0) { + if (!session || rs.empty()) { return; } - if (selection->time.empty()) { - return; + session->begin_reversible_command (_("region lock style")); + + for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) { + XMLNode &before = (*i)->region()->get_state (); + (*i)->region()->set_position_lock_style (ps); + XMLNode &after = (*i)->region()->get_state (); + session->add_command (new MementoCommand (*((*i)->region().get()), &before, &after)); } - nframes_t start = selection->time[clicked_selection].start; - nframes_t end = selection->time[clicked_selection].end; + session->commit_reversible_command (); +} - nframes_t selection_cnt = end - start + 1; - - for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) { - boost::shared_ptr current; - boost::shared_ptr current_r; - boost::shared_ptr pl; - nframes_t internal_start; - string new_name; +void +Editor::toggle_region_mute () +{ + RegionSelection rs = get_equivalent_regions (selection->regions, RouteGroup::Edit); - if ((pl = (*i)->playlist()) == 0) { - continue; - } + if (!session || rs.empty()) { + return; + } - if ((current_r = pl->top_region_at (start)) == 0) { - continue; - } + session->begin_reversible_command (_("region mute")); - current = boost::dynamic_pointer_cast (current_r); - // FIXME: audio only - if (current != 0) { - internal_start = start - current->position(); - session->region_name (new_name, current->name(), true); - boost::shared_ptr region (RegionFactory::create (current, internal_start, selection_cnt, new_name)); - } + for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) { + XMLNode &before = (*i)->region()->get_state (); + (*i)->region()->set_muted (!(*i)->region()->muted()); + XMLNode &after = (*i)->region()->get_state (); + session->add_command (new MementoCommand (*((*i)->region().get()), &before, &after)); } -} + + session->commit_reversible_command (); +} void -Editor::create_region_from_selection (vector >& new_regions) +Editor::toggle_region_opaque () { - if (selection->time.empty() || selection->tracks.empty()) { + RegionSelection rs = get_equivalent_regions (selection->regions, RouteGroup::Edit); + + if (!session || rs.empty()) { return; } - nframes_t start = selection->time[clicked_selection].start; - nframes_t end = selection->time[clicked_selection].end; - - for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) { + session->begin_reversible_command (_("region opacity")); - boost::shared_ptr current; - boost::shared_ptr current_r; - boost::shared_ptr playlist; - nframes_t internal_start; - string new_name; + for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) { + XMLNode &before = (*i)->region()->get_state (); + (*i)->region()->set_opaque (!(*i)->region()->opaque()); + XMLNode &after = (*i)->region()->get_state (); + session->add_command (new MementoCommand (*((*i)->region().get()), &before, &after)); + } - if ((playlist = (*i)->playlist()) == 0) { - continue; - } + session->commit_reversible_command (); +} - if ((current_r = playlist->top_region_at(start)) == 0) { +void +Editor::toggle_record_enable () +{ + bool new_state = false; + bool first = true; + for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) { + RouteTimeAxisView *rtav = dynamic_cast(*i); + if (!rtav) continue; - } - - if ((current = boost::dynamic_pointer_cast(current_r)) == 0) { + if (!rtav->is_track()) continue; + + if (first) { + new_state = !rtav->track()->record_enabled(); + first = false; } - - internal_start = start - current->position(); - session->region_name (new_name, current->name(), true); - - new_regions.push_back (boost::dynamic_pointer_cast (RegionFactory::create (current, internal_start, end - start + 1, new_name))); + + rtav->track()->set_record_enable(new_state, this); } } + void -Editor::split_multichannel_region () +Editor::set_fade_length (bool in) { - vector v; + RegionSelection rs; - AudioRegionView* clicked_arv = dynamic_cast(clicked_regionview); - - if (!clicked_arv || clicked_arv->audio_region()->n_channels() < 2) { + get_regions_for_action (rs, true); + + if (rs.empty()) { return; } - clicked_arv->audio_region()->separate_by_channel (*session, v); - - /* nothing else to do, really */ -} + /* we need a region to measure the offset from the start */ -void -Editor::new_region_from_selection () -{ - region_from_selection (); - cancel_selection (); -} + RegionView* rv = rs.front (); -void -Editor::separate_region_from_selection () -{ - bool doing_undo = false; + nframes64_t pos = get_preferred_edit_position(); + nframes64_t len; + char* cmd; - if (selection->time.empty()) { + if (pos > rv->region()->last_frame() || pos < rv->region()->first_frame()) { + /* edit point is outside the relevant region */ return; } - boost::shared_ptr playlist; - - for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) { + if (in) { + if (pos <= rv->region()->position()) { + /* can't do it */ + return; + } + len = pos - rv->region()->position(); + cmd = _("set fade in length"); + } else { + if (pos >= rv->region()->last_frame()) { + /* can't do it */ + return; + } + len = rv->region()->last_frame() - pos; + cmd = _("set fade out length"); + } - AudioTimeAxisView* atv; + begin_reversible_command (cmd); - if ((atv = dynamic_cast ((*i))) != 0) { + for (RegionSelection::iterator x = rs.begin(); x != rs.end(); ++x) { + AudioRegionView* tmp = dynamic_cast (*x); - if (atv->is_audio_track()) { - - if ((playlist = atv->playlist()) != 0) { - if (!doing_undo) { - begin_reversible_command (_("separate")); - doing_undo = true; - } - XMLNode *before; - if (doing_undo) - before = &(playlist->get_state()); - - /* XXX need to consider musical time selections here at some point */ + if (!tmp) { + return; + } - double speed = atv->get_diskstream()->speed(); + boost::shared_ptr alist; + if (in) { + alist = tmp->audio_region()->fade_in(); + } else { + alist = tmp->audio_region()->fade_out(); + } - for (list::iterator t = selection->time.begin(); t != selection->time.end(); ++t) { - playlist->partition ((nframes_t)((*t).start * speed), (nframes_t)((*t).end * speed), true); - } + XMLNode &before = alist->get_state(); - if (doing_undo) - session->add_command(new MementoCommand(*playlist, before, &playlist->get_state())); - } - } + if (in) { + tmp->audio_region()->set_fade_in_length (len); + tmp->audio_region()->set_fade_in_active (true); + } else { + tmp->audio_region()->set_fade_out_length (len); + tmp->audio_region()->set_fade_out_active (true); } + + XMLNode &after = alist->get_state(); + session->add_command(new MementoCommand(*alist, &before, &after)); } - if (doing_undo) commit_reversible_command (); + commit_reversible_command (); } void -Editor::separate_regions_using_location (Location& loc) +Editor::toggle_fade_active (bool in) { - bool doing_undo = false; + RegionSelection rs; - if (loc.is_mark()) { + get_regions_for_action (rs); + + if (rs.empty()) { return; } - boost::shared_ptr playlist; - - /* XXX i'm unsure as to whether this should operate on selected tracks only - or the entire enchillada. uncomment the below line to correct the behaviour - (currently set for all tracks) - */ - - for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) { - //for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) { + const char* cmd = (in ? _("toggle fade in active") : _("toggle fade out active")); + bool have_switch = false; + bool yn = false; - AudioTimeAxisView* atv; + begin_reversible_command (cmd); - if ((atv = dynamic_cast ((*i))) != 0) { + for (RegionSelection::iterator x = rs.begin(); x != rs.end(); ++x) { + AudioRegionView* tmp = dynamic_cast (*x); - if (atv->is_audio_track()) { - - if ((playlist = atv->playlist()) != 0) { - XMLNode *before; - if (!doing_undo) { - begin_reversible_command (_("separate")); - doing_undo = true; - } - if (doing_undo) - before = &(playlist->get_state()); - - - /* XXX need to consider musical time selections here at some point */ + if (!tmp) { + return; + } - double speed = atv->get_diskstream()->speed(); + boost::shared_ptr region (tmp->audio_region()); + /* make the behaviour consistent across all regions */ - playlist->partition ((nframes_t)(loc.start() * speed), (nframes_t)(loc.end() * speed), true); - if (doing_undo) - session->add_command(new MementoCommand(*playlist, before, &playlist->get_state())); - } + if (!have_switch) { + if (in) { + yn = region->fade_in_active(); + } else { + yn = region->fade_out_active(); } + have_switch = true; + } + + XMLNode &before = region->get_state(); + if (in) { + region->set_fade_in_active (!yn); + } else { + region->set_fade_out_active (!yn); } + XMLNode &after = region->get_state(); + session->add_command(new MementoCommand(*region.get(), &before, &after)); } - if (doing_undo) commit_reversible_command (); + commit_reversible_command (); } void -Editor::crop_region_to_selection () +Editor::set_fade_in_shape (AudioRegion::FadeShape shape) { - if (selection->time.empty()) { + RegionSelection rs; + + get_regions_for_action (rs); + + if (rs.empty()) { return; } - vector > playlists; - boost::shared_ptr playlist; + begin_reversible_command (_("set fade in shape")); - if (clicked_trackview != 0) { + for (RegionSelection::iterator x = rs.begin(); x != rs.end(); ++x) { + AudioRegionView* tmp = dynamic_cast (*x); - if ((playlist = clicked_trackview->playlist()) == 0) { + if (!tmp) { return; } - playlists.push_back (playlist); - - } else { - - for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) { - - AudioTimeAxisView* atv; + boost::shared_ptr alist = tmp->audio_region()->fade_in(); + XMLNode &before = alist->get_state(); - if ((atv = dynamic_cast ((*i))) != 0) { + tmp->audio_region()->set_fade_in_shape (shape); - if (atv->is_audio_track()) { - - if ((playlist = atv->playlist()) != 0) { - playlists.push_back (playlist); - } - } - } - } + XMLNode &after = alist->get_state(); + session->add_command(new MementoCommand(*alist.get(), &before, &after)); } - if (!playlists.empty()) { - - nframes_t start; - nframes_t end; - nframes_t cnt; - - begin_reversible_command (_("trim to selection")); - - for (vector >::iterator i = playlists.begin(); i != playlists.end(); ++i) { - - boost::shared_ptr region; - - start = selection->time.start(); - - if ((region = (*i)->top_region_at(start)) == 0) { - continue; - } - - /* now adjust lengths to that we do the right thing - if the selection extends beyond the region - */ - - start = max (start, region->position()); - end = min (selection->time.end_frame(), start + region->length() - 1); - cnt = end - start + 1; - - XMLNode &before = (*i)->get_state(); - region->trim_to (start, cnt, this); - XMLNode &after = (*i)->get_state(); - session->add_command (new MementoCommand(*(*i), &before, &after)); - } + commit_reversible_command (); - commit_reversible_command (); - } -} +} void -Editor::region_fill_track () +Editor::set_fade_out_shape (AudioRegion::FadeShape shape) { - nframes_t end; + RegionSelection rs; - if (!session || selection->regions.empty()) { + get_regions_for_action (rs); + + if (rs.empty()) { return; } - end = session->current_end_frame (); - - begin_reversible_command (_("region fill")); - - for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) { - - boost::shared_ptr region ((*i)->region()); - - // FIXME - boost::shared_ptr ar = boost::dynamic_pointer_cast(region); - if (!ar) - continue; + begin_reversible_command (_("set fade out shape")); - boost::shared_ptr pl = region->playlist(); + for (RegionSelection::iterator x = rs.begin(); x != rs.end(); ++x) { + AudioRegionView* tmp = dynamic_cast (*x); - if (end <= region->last_frame()) { + if (!tmp) { return; } - double times = (double) (end - region->last_frame()) / (double) region->length(); + boost::shared_ptr alist = tmp->audio_region()->fade_out(); + XMLNode &before = alist->get_state(); - if (times == 0) { - return; - } + tmp->audio_region()->set_fade_out_shape (shape); - XMLNode &before = pl->get_state(); - pl->add_region (RegionFactory::create (ar), ar->last_frame(), times); - session->add_command (new MementoCommand(*pl, &before, &pl->get_state())); + XMLNode &after = alist->get_state(); + session->add_command(new MementoCommand(*alist.get(), &before, &after)); } commit_reversible_command (); } void -Editor::region_fill_selection () +Editor::set_fade_in_active (bool yn) { - if (clicked_audio_trackview == 0 || !clicked_audio_trackview->is_audio_track()) { - return; - } - - if (selection->time.empty()) { - return; - } - + RegionSelection rs; - Glib::RefPtr selected = region_list_display.get_selection(); + get_regions_for_action (rs); - if (selected->count_selected_rows() != 1) { + if (rs.empty()) { return; } - TreeModel::iterator i = region_list_display.get_selection()->get_selected(); - boost::shared_ptr region = (*i)[region_list_columns.region]; + begin_reversible_command (_("set fade in active")); - nframes_t start = selection->time[clicked_selection].start; - nframes_t end = selection->time[clicked_selection].end; + for (RegionSelection::iterator x = rs.begin(); x != rs.end(); ++x) { + AudioRegionView* tmp = dynamic_cast (*x); - boost::shared_ptr playlist; - - if (selection->tracks.empty()) { - return; - } + if (!tmp) { + return; + } - nframes_t selection_length = end - start; - float times = (float)selection_length / region->length(); - - begin_reversible_command (_("fill selection")); - - for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) { - if ((playlist = (*i)->playlist()) == 0) { - continue; - } - - XMLNode &before = playlist->get_state(); - playlist->add_region (RegionFactory::create (region), start, times); - session->add_command (new MementoCommand(*playlist, &before, &playlist->get_state())); - } - - commit_reversible_command (); -} + boost::shared_ptr ar (tmp->audio_region()); -void -Editor::set_a_regions_sync_position (boost::shared_ptr region, nframes_t position) -{ + XMLNode &before = ar->get_state(); - if (!region->covers (position)) { - error << _("Programming error. that region doesn't cover that position") << __FILE__ << " +" << __LINE__ << endmsg; - return; + ar->set_fade_in_active (yn); + + XMLNode &after = ar->get_state(); + session->add_command(new MementoCommand(*ar, &before, &after)); } - begin_reversible_command (_("set region sync position")); - XMLNode &before = region->playlist()->get_state(); - region->set_sync_position (position); - XMLNode &after = region->playlist()->get_state(); - session->add_command(new MementoCommand(*(region->playlist()), &before, &after)); + commit_reversible_command (); } void -Editor::set_region_sync_from_edit_cursor () +Editor::set_fade_out_active (bool yn) { - if (clicked_regionview == 0) { + RegionSelection rs; + + get_regions_for_action (rs); + + if (rs.empty()) { return; } - if (!clicked_regionview->region()->covers (edit_cursor->current_frame)) { - error << _("Place the edit cursor at the desired sync point") << endmsg; - return; + begin_reversible_command (_("set fade out active")); + + for (RegionSelection::iterator x = rs.begin(); x != rs.end(); ++x) { + AudioRegionView* tmp = dynamic_cast (*x); + + if (!tmp) { + return; + } + + boost::shared_ptr ar (tmp->audio_region()); + + XMLNode &before = ar->get_state(); + + ar->set_fade_out_active (yn); + + XMLNode &after = ar->get_state(); + session->add_command(new MementoCommand(*ar, &before, &after)); } - boost::shared_ptr region (clicked_regionview->region()); - begin_reversible_command (_("set sync from edit cursor")); - XMLNode &before = region->playlist()->get_state(); - region->set_sync_position (edit_cursor->current_frame); - XMLNode &after = region->playlist()->get_state(); - session->add_command(new MementoCommand(*(region->playlist()), &before, &after)); commit_reversible_command (); } void -Editor::remove_region_sync () +Editor::toggle_selected_region_fades (int dir) { - if (clicked_regionview) { - boost::shared_ptr region (clicked_regionview->region()); - begin_reversible_command (_("remove sync")); - XMLNode &before = region->playlist()->get_state(); - region->clear_sync_position (); - XMLNode &after = region->playlist()->get_state(); - session->add_command(new MementoCommand(*(region->playlist()), &before, &after)); - commit_reversible_command (); + RegionSelection rs; + RegionSelection::iterator i; + boost::shared_ptr ar; + bool yn; + + get_regions_for_action (rs); + + if (rs.empty()) { + return; } -} -void -Editor::naturalize () -{ - if (selection->regions.empty()) { + for (i = rs.begin(); i != rs.end(); ++i) { + if ((ar = boost::dynamic_pointer_cast((*i)->region())) != 0) { + if (dir == -1) { + yn = ar->fade_out_active (); + } else { + yn = ar->fade_in_active (); + } + break; + } + } + + if (i == rs.end()) { return; } - begin_reversible_command (_("naturalize")); - for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) { - XMLNode &before = (*i)->region()->get_state(); - (*i)->region()->move_to_natural_position (this); - XMLNode &after = (*i)->region()->get_state(); - session->add_command (new MementoCommand(*((*i)->region().get()), &before, &after)); + + /* XXX should this undo-able? */ + + for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) { + if ((ar = boost::dynamic_pointer_cast((*i)->region())) == 0) { + continue; + } + if (dir == 1 || dir == 0) { + ar->set_fade_in_active (!yn); + } + + if (dir == -1 || dir == 0) { + ar->set_fade_out_active (!yn); + } } - commit_reversible_command (); } + +/** Update region fade visibility after its configuration has been changed */ void -Editor::align (RegionPoint what) +Editor::update_region_fade_visibility () { - align_selection (what, edit_cursor->current_frame); + bool _fade_visibility = session->config.get_show_region_fades (); + + for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) { + AudioTimeAxisView* v = dynamic_cast(*i); + if (v) { + if (_fade_visibility) { + v->audio_view()->show_all_fades (); + } else { + v->audio_view()->hide_all_fades (); + } + } + } } +/** Update crossfade visibility after its configuration has been changed */ void -Editor::align_relative (RegionPoint what) +Editor::update_xfade_visibility () { - align_selection_relative (what, edit_cursor->current_frame); -} + _xfade_visibility = session->config.get_xfades_visible (); -struct RegionSortByTime { - bool operator() (const AudioRegionView* a, const AudioRegionView* b) { - return a->region()->position() < b->region()->position(); - } -}; + for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) { + AudioTimeAxisView* v = dynamic_cast(*i); + if (v) { + if (_xfade_visibility) { + v->show_all_xfades (); + } else { + v->hide_all_xfades (); + } + } + } +} void -Editor::align_selection_relative (RegionPoint point, nframes_t position) +Editor::set_edit_point () { - if (selection->regions.empty()) { + nframes64_t where; + bool ignored; + + if (!mouse_frame (where, ignored)) { return; } - nframes_t distance; - nframes_t pos = 0; - int dir; - - list sorted; - selection->regions.by_position (sorted); - boost::shared_ptr r ((*sorted.begin())->region()); - - switch (point) { - case Start: - pos = r->first_frame (); - break; + snap_to (where); - case End: - pos = r->last_frame(); - break; + if (selection->markers.empty()) { - case SyncPoint: - pos = r->adjust_to_sync (r->first_frame()); - break; - } + mouse_add_new_marker (where); - if (pos > position) { - distance = pos - position; - dir = -1; } else { - distance = position - pos; - dir = 1; - } + bool ignored; - begin_reversible_command (_("align selection (relative)")); + Location* loc = find_location_from_marker (selection->markers.front(), ignored); - for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) { + if (loc) { + loc->move_to (where); + } + } +} - boost::shared_ptr region ((*i)->region()); +void +Editor::set_playhead_cursor () +{ + if (entered_marker) { + session->request_locate (entered_marker->position(), session->transport_rolling()); + } else { + nframes64_t where; + bool ignored; - XMLNode &before = region->playlist()->get_state(); - - if (dir > 0) { - region->set_position (region->position() + distance, this); - } else { - region->set_position (region->position() - distance, this); + if (!mouse_frame (where, ignored)) { + return; } - XMLNode &after = region->playlist()->get_state(); - session->add_command(new MementoCommand(*(region->playlist()), &before, &after)); + snap_to (where); + if (session) { + session->request_locate (where, session->transport_rolling()); + } } - - commit_reversible_command (); } void -Editor::align_selection (RegionPoint point, nframes_t position) +Editor::split () { - if (selection->regions.empty()) { - return; - } + RegionSelection rs; - begin_reversible_command (_("align selection")); + get_regions_for_action (rs, true); - for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) { - align_region_internal ((*i)->region(), point, position); + nframes64_t where = get_preferred_edit_position(); + + if (rs.empty()) { + return; } - commit_reversible_command (); + split_regions_at (where, rs); } void -Editor::align_region (boost::shared_ptr region, RegionPoint point, nframes_t position) +Editor::ensure_entered_track_selected (bool op_really_wants_one_track_if_none_are_selected) { - begin_reversible_command (_("align region")); - align_region_internal (region, point, position); - commit_reversible_command (); + if (entered_track && mouse_mode == MouseObject) { + if (!selection->tracks.empty()) { + if (!selection->selected (entered_track)) { + selection->add (entered_track); + } + } else { + /* there is no selection, but this operation requires/prefers selected objects */ + + if (op_really_wants_one_track_if_none_are_selected) { + selection->set (entered_track); + } + } + } } +struct EditorOrderRouteSorter { + bool operator() (boost::shared_ptr a, boost::shared_ptr b) { + /* use of ">" forces the correct sort order */ + return a->order_key ("editor") < b->order_key ("editor"); + } +}; + void -Editor::align_region_internal (boost::shared_ptr region, RegionPoint point, nframes_t position) +Editor::select_next_route() { - XMLNode &before = region->playlist()->get_state(); + if (selection->tracks.empty()) { + selection->set (track_views.front()); + return; + } - switch (point) { - case SyncPoint: - region->set_position (region->adjust_to_sync (position), this); - break; + TimeAxisView* current = selection->tracks.front(); - case End: - if (position > region->length()) { - region->set_position (position - region->length(), this); + RouteUI *rui; + 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; + } } - break; + rui = dynamic_cast(current); + } while ( current->hidden() || (rui != NULL && !rui->route()->active())); - case Start: - region->set_position (position, this); - break; - } + selection->set(current); - XMLNode &after = region->playlist()->get_state(); - session->add_command(new MementoCommand(*(region->playlist()), &before, &after)); -} + ensure_track_visible(current); +} void -Editor::trim_region_to_edit_cursor () +Editor::select_prev_route() { - if (clicked_regionview == 0) { + if (selection->tracks.empty()) { + selection->set (track_views.front()); return; } - boost::shared_ptr region (clicked_regionview->region()); - - float speed = 1.0f; - AudioTimeAxisView *atav; + TimeAxisView* current = selection->tracks.front(); - if ( clicked_trackview != 0 && (atav = dynamic_cast(clicked_trackview)) != 0 ) { - if (atav->get_diskstream() != 0) { - speed = atav->get_diskstream()->speed(); + RouteUI *rui; + 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; + } } - } + rui = dynamic_cast(current); + } while ( current->hidden() || (rui != NULL && !rui->route()->active())); - begin_reversible_command (_("trim to edit")); - XMLNode &before = region->playlist()->get_state(); - region->trim_end( session_frame_to_track_frame(edit_cursor->current_frame, speed), this); - XMLNode &after = region->playlist()->get_state(); - session->add_command(new MementoCommand(*(region->playlist()), &before, &after)); - commit_reversible_command (); + selection->set (current); + + ensure_track_visible(current); } void -Editor::trim_region_from_edit_cursor () +Editor::ensure_track_visible(TimeAxisView *track) { - if (clicked_regionview == 0) { + if (track->hidden()) return; - } - boost::shared_ptr region (clicked_regionview->region()); + double const current_view_min_y = vertical_adjustment.get_value(); + double const current_view_max_y = vertical_adjustment.get_value() + vertical_adjustment.get_page_size() - canvas_timebars_vsize; - float speed = 1.0f; - AudioTimeAxisView *atav; + double const track_min_y = track->y_position (); + double const track_max_y = track->y_position () + track->effective_height (); - if ( clicked_trackview != 0 && (atav = dynamic_cast(clicked_trackview)) != 0 ) { - if (atav->get_diskstream() != 0) { - speed = atav->get_diskstream()->speed(); - } + if (track_min_y >= current_view_min_y && + track_max_y <= current_view_max_y) { + return; } - begin_reversible_command (_("trim to edit")); - XMLNode &before = region->playlist()->get_state(); - region->trim_front ( session_frame_to_track_frame(edit_cursor->current_frame, speed), this); - XMLNode &after = region->playlist()->get_state(); - session->add_command(new MementoCommand(*(region->playlist()), &before, &after)); - commit_reversible_command (); + double new_value; + + if (track_min_y < current_view_min_y) { + // Track is above the current view + new_value = track_min_y; + } else { + // Track is below the current view + new_value = track->y_position () + track->effective_height() + canvas_timebars_vsize - vertical_adjustment.get_page_size(); + } + + vertical_adjustment.set_value(new_value); } void -Editor::unfreeze_route () +Editor::set_loop_from_selection (bool play) { - if (clicked_audio_trackview == 0 || !clicked_audio_trackview->is_audio_track()) { + if (session == 0 || selection->time.empty()) { return; } - - clicked_audio_trackview->audio_track()->unfreeze (); -} -void* -Editor::_freeze_thread (void* arg) -{ - PBD::ThreadCreated (pthread_self(), X_("Freeze")); - return static_cast(arg)->freeze_thread (); -} + nframes64_t start = selection->time[clicked_selection].start; + nframes64_t end = selection->time[clicked_selection].end; -void* -Editor::freeze_thread () -{ - clicked_audio_trackview->audio_track()->freeze (*current_interthread_info); - return 0; -} + set_loop_range (start, end, _("set loop range from selection")); -gint -Editor::freeze_progress_timeout (void *arg) -{ - interthread_progress_bar.set_fraction (current_interthread_info->progress/100); - return !(current_interthread_info->done || current_interthread_info->cancel); + if (play) { + session->request_play_loop (true); + session->request_locate (start, true); + } } void -Editor::freeze_route () +Editor::set_loop_from_edit_range (bool play) { - if (clicked_audio_trackview == 0 || !clicked_audio_trackview->is_audio_track()) { + if (session == 0) { return; } - - InterThreadInfo itt; - - if (interthread_progress_window == 0) { - build_interthread_progress_window (); - } - - interthread_progress_window->set_title (_("ardour: freeze")); - interthread_progress_window->set_position (Gtk::WIN_POS_MOUSE); - interthread_progress_window->show_all (); - interthread_progress_bar.set_fraction (0.0f); - interthread_progress_label.set_text (""); - interthread_cancel_label.set_text (_("Cancel Freeze")); - current_interthread_info = &itt; - - interthread_progress_connection = - Glib::signal_timeout().connect (bind (mem_fun(*this, &Editor::freeze_progress_timeout), (gpointer) 0), 100); - - itt.done = false; - itt.cancel = false; - itt.progress = 0.0f; - - pthread_attr_t attr; - pthread_attr_init(&attr); - pthread_attr_setstacksize(&attr, 500000); - pthread_create (&itt.thread, 0, _freeze_thread, this); + nframes64_t start; + nframes64_t end; - pthread_attr_destroy(&attr); + if (!get_edit_op_range (start, end)) { + return; + } - track_canvas.get_window()->set_cursor (Gdk::Cursor (Gdk::WATCH)); + set_loop_range (start, end, _("set loop range from edit range")); - while (!itt.done && !itt.cancel) { - gtk_main_iteration (); + if (play) { + session->request_play_loop (true); + session->request_locate (start, true); } - - interthread_progress_connection.disconnect (); - interthread_progress_window->hide_all (); - current_interthread_info = 0; - track_canvas.get_window()->set_cursor (*current_canvas_cursor); } void -Editor::bounce_range_selection () +Editor::set_loop_from_region (bool play) { - if (selection->time.empty()) { - return; - } + nframes64_t start = max_frames; + nframes64_t end = 0; - TrackViewList *views = get_valid_views (selection->time.track, selection->time.group); - - nframes_t start = selection->time[clicked_selection].start; - nframes_t end = selection->time[clicked_selection].end; - nframes_t cnt = end - start + 1; - - begin_reversible_command (_("bounce range")); + RegionSelection rs; - for (TrackViewList::iterator i = views->begin(); i != views->end(); ++i) { + get_regions_for_action (rs); - AudioTimeAxisView* atv; + if (rs.empty()) { + return; + } - if ((atv = dynamic_cast (*i)) == 0) { - continue; + for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) { + if ((*i)->region()->position() < start) { + start = (*i)->region()->position(); } - - boost::shared_ptr playlist; - - if ((playlist = atv->playlist()) == 0) { - return; + if ((*i)->region()->last_frame() + 1 > end) { + end = (*i)->region()->last_frame() + 1; } + } - InterThreadInfo itt; - - itt.done = false; - itt.cancel = false; - itt.progress = false; - - XMLNode &before = playlist->get_state(); - atv->audio_track()->bounce_range (start, cnt, itt); - XMLNode &after = playlist->get_state(); - session->add_command (new MementoCommand (*playlist, &before, &after)); + set_loop_range (start, end, _("set loop range from region")); + + if (play) { + session->request_play_loop (true); + session->request_locate (start, true); } - - commit_reversible_command (); - - delete views; } void -Editor::cut () +Editor::set_punch_from_selection () { - cut_copy (Cut); -} + if (session == 0 || selection->time.empty()) { + return; + } -void -Editor::copy () -{ - cut_copy (Copy); + nframes64_t start = selection->time[clicked_selection].start; + nframes64_t end = selection->time[clicked_selection].end; + + set_punch_range (start, end, _("set punch range from selection")); } -void -Editor::cut_copy (CutCopyOp op) +void +Editor::set_punch_from_edit_range () { - /* only cancel selection if cut/copy is successful.*/ + if (session == 0) { + return; + } - string opname; + nframes64_t start; + nframes64_t end; - switch (op) { - case Cut: - opname = _("cut"); - break; - case Copy: - opname = _("copy"); - break; - case Clear: - opname = _("clear"); - break; + if (!get_edit_op_range (start, end)) { + return; } - - cut_buffer->clear (); - switch (current_mouse_mode()) { - case MouseObject: - if (!selection->regions.empty() || !selection->points.empty()) { + set_punch_range (start, end, _("set punch range from edit range")); +} - begin_reversible_command (opname + _(" objects")); +void +Editor::set_punch_from_region () +{ + nframes64_t start = max_frames; + nframes64_t end = 0; - if (!selection->regions.empty()) { - - cut_copy_regions (op); - - if (op == Cut) { - selection->clear_regions (); - } - } + RegionSelection rs; - if (!selection->points.empty()) { - cut_copy_points (op); + get_regions_for_action (rs); - if (op == Cut) { - selection->clear_points (); - } - } + if (rs.empty()) { + return; + } - commit_reversible_command (); + for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ++i) { + if ((*i)->region()->position() < start) { + start = (*i)->region()->position(); } - break; - - case MouseRange: - if (!selection->time.empty()) { + if ((*i)->region()->last_frame() + 1 > end) { + end = (*i)->region()->last_frame() + 1; + } + } - begin_reversible_command (opname + _(" range")); - cut_copy_ranges (op); - commit_reversible_command (); + set_punch_range (start, end, _("set punch range from region")); +} - if (op == Cut) { - selection->clear_time (); - } - - } - break; - - default: - break; +void +Editor::pitch_shift_regions () +{ + RegionSelection rs; + + get_regions_for_action (rs); + + if (rs.empty()) { + return; } + + pitch_shift (rs, 1.2); } void -Editor::cut_copy_points (CutCopyOp op) +Editor::use_region_as_bar () { - for (PointSelection::iterator i = selection->points.begin(); i != selection->points.end(); ++i) { + if (!session) { + return; + } - AutomationTimeAxisView* atv = dynamic_cast(&(*i).track); + RegionSelection rs; - if (atv) { - atv->cut_copy_clear_objects (selection->points, op); - } + get_regions_for_action (rs); + + if (rs.empty()) { + return; } + + RegionView* rv = rs.front(); + + define_one_bar (rv->region()->position(), rv->region()->last_frame() + 1); } -struct PlaylistState { - boost::shared_ptr playlist; - XMLNode* before; -}; +void +Editor::use_range_as_bar () +{ + nframes64_t start, end; + if (get_edit_op_range (start, end)) { + define_one_bar (start, end); + } +} -struct lt_playlist { - bool operator () (const PlaylistState& a, const PlaylistState& b) { - return a.playlist < b.playlist; - } -}; - void -Editor::cut_copy_regions (CutCopyOp op) +Editor::define_one_bar (nframes64_t start, nframes64_t end) { - typedef std::map,boost::shared_ptr > PlaylistMapping; - PlaylistMapping pmap; - nframes_t first_position = max_frames; + nframes64_t length = end - start; - set freezelist; - pair::iterator,bool> insert_result; + const Meter& m (session->tempo_map().meter_at (start)); - for (RegionSelection::iterator x = selection->regions.begin(); x != selection->regions.end(); ++x) { - first_position = min ((*x)->region()->position(), first_position); + /* length = 1 bar */ - if (op == Cut || op == Clear) { - boost::shared_ptr pl = boost::dynamic_pointer_cast((*x)->region()->playlist()); + /* now we want frames per beat. + we have frames per bar, and beats per bar, so ... + */ - if (pl) { + double frames_per_beat = length / m.beats_per_bar(); - PlaylistState before; - before.playlist = pl; - before.before = &pl->get_state(); - - insert_result = freezelist.insert (before); + /* beats per minute = */ - if (insert_result.second) { - pl->freeze (); - } - } - } - } + double beats_per_minute = (session->frame_rate() * 60.0) / frames_per_beat; - for (RegionSelection::iterator x = selection->regions.begin(); x != selection->regions.end(); ) { + /* now decide whether to: - boost::shared_ptr pl = boost::dynamic_pointer_cast((*x)->region()->playlist()); - boost::shared_ptr npl; - RegionSelection::iterator tmp; - - tmp = x; - ++tmp; + (a) set global tempo + (b) add a new tempo marker - if (pl) { + */ - PlaylistMapping::iterator pi = pmap.find (pl); - - if (pi == pmap.end()) { - npl = boost::dynamic_pointer_cast (PlaylistFactory::create (*session, "cutlist", true)); - npl->freeze(); - pmap[pl] = npl; - } else { - npl = pi->second; - } + const TempoSection& t (session->tempo_map().tempo_section_at (start)); - // FIXME - boost::shared_ptr ar = boost::dynamic_pointer_cast((*x)->region()); - switch (op) { - case Cut: - if (!ar) break; + bool do_global = false; - npl->add_region (RegionFactory::create (ar), (*x)->region()->position() - first_position); - pl->remove_region (((*x)->region())); - break; + if ((session->tempo_map().n_tempos() == 1) && (session->tempo_map().n_meters() == 1)) { - case Copy: - if (!ar) break; + /* only 1 tempo & 1 meter: ask if the user wants to set the tempo + at the start, or create a new marker + */ - npl->add_region (RegionFactory::create (ar), (*x)->region()->position() - first_position); - break; + vector options; + options.push_back (_("Cancel")); + options.push_back (_("Add new marker")); + options.push_back (_("Set global tempo")); + Choice c (_("Do you want to set the global tempo or add new tempo marker?"), + options); + c.set_default_response (2); - case Clear: - pl->remove_region (((*x)->region())); - break; - } + switch (c.run()) { + case 0: + return; + + case 2: + do_global = true; + break; + + default: + do_global = false; } - x = tmp; + } else { + + /* more than 1 tempo and/or meter section already, go ahead do the "usual": + if the marker is at the region starter, change it, otherwise add + a new tempo marker + */ } - list > foo; + begin_reversible_command (_("set tempo from region")); + XMLNode& before (session->tempo_map().get_state()); - for (PlaylistMapping::iterator i = pmap.begin(); i != pmap.end(); ++i) { - foo.push_back (i->second); + if (do_global) { + session->tempo_map().change_initial_tempo (beats_per_minute, t.note_type()); + } else if (t.frame() == start) { + session->tempo_map().change_existing_tempo_at (start, beats_per_minute, t.note_type()); + } else { + session->tempo_map().add_tempo (Tempo (beats_per_minute, t.note_type()), start); } - if (!foo.empty()) { - cut_buffer->set (foo); - } - - for (set::iterator pl = freezelist.begin(); pl != freezelist.end(); ++pl) { - (*pl).playlist->thaw (); - session->add_command (new MementoCommand(*(*pl).playlist, (*pl).before, &(*pl).playlist->get_state())); - } + XMLNode& after (session->tempo_map().get_state()); + + session->add_command (new MementoCommand(session->tempo_map(), &before, &after)); + commit_reversible_command (); } void -Editor::cut_copy_ranges (CutCopyOp op) +Editor::split_region_at_transients () { - for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) { - (*i)->cut_copy_clear (*selection, op); + AnalysisFeatureList positions; + + if (!session) { + return; } -} -void -Editor::paste (float times) -{ - paste_internal (edit_cursor->current_frame, times); -} + RegionSelection rs; -void -Editor::mouse_paste () -{ - int x, y; - double wx, wy; + get_regions_for_action (rs); - track_canvas.get_pointer (x, y); - track_canvas.window_to_world (x, y, wx, wy); - wx += horizontal_adjustment.get_value(); - wy += vertical_adjustment.get_value(); + if (rs.empty()) { + return; + } + + session->begin_reversible_command (_("split regions")); + + for (RegionSelection::iterator i = rs.begin(); i != rs.end(); ) { + + RegionSelection::iterator tmp; + + tmp = i; + ++tmp; + + boost::shared_ptr ar = boost::dynamic_pointer_cast ((*i)->region()); + + if (ar && (ar->get_transients (positions) == 0)) { + split_region_at_points ((*i)->region(), positions, true); + positions.clear (); + } + + i = tmp; + } + + session->commit_reversible_command (); - GdkEvent event; - event.type = GDK_BUTTON_RELEASE; - event.button.x = wx; - event.button.y = wy; - - nframes_t where = event_frame (&event, 0, 0); - snap_to (where); - paste_internal (where, 1); } void -Editor::paste_internal (nframes_t position, float times) +Editor::split_region_at_points (boost::shared_ptr r, AnalysisFeatureList& positions, bool can_ferret) { - bool commit = false; + bool use_rhythmic_rodent = false; - if (cut_buffer->empty() || selection->tracks.empty()) { + boost::shared_ptr pl = r->playlist(); + + if (!pl) { return; } - if (position == max_frames) { - position = edit_cursor->current_frame; + if (positions.empty()) { + return; } - begin_reversible_command (_("paste")); - TrackSelection::iterator i; - size_t nth; + if (positions.size() > 20) { + Glib::ustring 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); - for (nth = 0, i = selection->tracks.begin(); i != selection->tracks.end(); ++i, ++nth) { - - /* undo/redo is handled by individual tracks */ + if (can_ferret) { + msg.add_button (_("Call for the Ferret!"), RESPONSE_APPLY); + msg.set_secondary_text (_("Press OK to continue with this split operation\nor ask the Ferret dialog to tune the analysis")); + } else { + msg.set_secondary_text (_("Press OK to continue with this split operation")); + } - if ((*i)->paste (position, times, *cut_buffer, nth)) { - commit = true; + msg.set_title (_("Excessive split?")); + msg.present (); + + int response = msg.run(); + msg.hide (); + switch (response) { + case RESPONSE_OK: + break; + case RESPONSE_APPLY: + use_rhythmic_rodent = true; + break; + default: + return; } } - if (commit) { - commit_reversible_command (); + if (use_rhythmic_rodent) { + show_rhythm_ferret (); + return; } -} -void -Editor::paste_named_selection (float times) -{ - TrackSelection::iterator t; + AnalysisFeatureList::const_iterator x; + + nframes64_t pos = r->position(); + + XMLNode& before (pl->get_state()); - Glib::RefPtr selected = named_selection_display.get_selection(); + x = positions.begin(); + + while (x != positions.end()) { + if ((*x) > pos) { + break; + } + ++x; + } - if (selected->count_selected_rows() != 1 || selection->tracks.empty()) { + if (x == positions.end()) { return; } - TreeModel::iterator i = selected->get_selected(); - NamedSelection* ns = (*i)[named_selection_columns.selection]; + pl->freeze (); + pl->remove_region (r); - list >::iterator chunk; - list >::iterator tmp; + while (x != positions.end()) { - chunk = ns->playlists.begin(); - - begin_reversible_command (_("paste chunk")); + /* file start = original start + how far we from the initial position ? + */ - for (t = selection->tracks.begin(); t != selection->tracks.end(); ++t) { - - AudioTimeAxisView* atv; - boost::shared_ptr pl; - boost::shared_ptr apl; + nframes64_t file_start = r->start() + (pos - r->position()); - if ((atv = dynamic_cast (*t)) == 0) { - continue; - } + /* length = next position - current position + */ - if ((pl = atv->playlist()) == 0) { - continue; - } - - if ((apl = boost::dynamic_pointer_cast (pl)) == 0) { - continue; - } + nframes64_t len = (*x) - pos; - tmp = chunk; - ++tmp; + /* XXX we do we really want to allow even single-sample regions? + shouldn't we have some kind of lower limit on region size? + */ - XMLNode &before = apl->get_state(); - apl->paste (*chunk, edit_cursor->current_frame, times); - session->add_command(new MementoCommand(*apl, &before, &apl->get_state())); + if (len <= 0) { + break; + } + + string new_name; - if (tmp != ns->playlists.end()) { - chunk = tmp; + if (session->region_name (new_name, r->name())) { + break; } - } - commit_reversible_command(); -} + /* do NOT announce new regions 1 by one, just wait till they are all done */ -void -Editor::duplicate_some_regions (RegionSelection& regions, float times) -{ - boost::shared_ptr playlist; - RegionSelection sel = regions; // clear (below) will clear the argument list - - begin_reversible_command (_("duplicate region")); + boost::shared_ptr nr = RegionFactory::create (r->sources(), file_start, len, new_name, 0, Region::DefaultFlags, false); + pl->add_region (nr, pos); - selection->clear_regions (); + pos += len; + ++x; - for (RegionSelection::iterator i = sel.begin(); i != sel.end(); ++i) { + if (*x > r->last_frame()) { - boost::shared_ptr r ((*i)->region()); + /* add final fragment */ - TimeAxisView& tv = (*i)->get_time_axis_view(); - AudioTimeAxisView* atv = dynamic_cast (&tv); - sigc::connection c = atv->view()->RegionViewAdded.connect (mem_fun(*this, &Editor::collect_new_region_view)); - - playlist = (*i)->region()->playlist(); - XMLNode &before = playlist->get_state(); - playlist->duplicate (r, r->last_frame(), times); - session->add_command(new MementoCommand(*playlist, &before, &playlist->get_state())); + file_start = r->start() + (pos - r->position()); + len = r->last_frame() - pos; - c.disconnect (); + nr = RegionFactory::create (r->sources(), file_start, len, new_name, 0, Region::DefaultFlags); + pl->add_region (nr, pos); - if (latest_regionview) { - selection->add (latest_regionview); + break; } } - - commit_reversible_command (); + pl->thaw (); + + XMLNode& after (pl->get_state()); + + session->add_command (new MementoCommand(*pl, &before, &after)); } void -Editor::duplicate_selection (float times) +Editor::tab_to_transient (bool forward) { - if (selection->time.empty() || selection->tracks.empty()) { + AnalysisFeatureList positions; + + if (!session) { return; } - boost::shared_ptr playlist; - vector > new_regions; - vector >::iterator ri; - - create_region_from_selection (new_regions); + nframes64_t pos = session->audible_frame (); - if (new_regions.empty()) { - return; + if (!selection->tracks.empty()) { + + for (TrackSelection::iterator t = selection->tracks.begin(); t != selection->tracks.end(); ++t) { + + RouteTimeAxisView* rtv = dynamic_cast (*t); + + if (rtv) { + boost::shared_ptr ds = rtv->get_diskstream(); + if (ds) { + boost::shared_ptr pl = rtv->get_diskstream()->playlist (); + if (pl) { + nframes64_t result = pl->find_next_transient (pos, forward ? 1 : -1); + + if (result >= 0) { + positions.push_back (result); + } + } + } + } + } + + } else { + + RegionSelection rs; + + get_regions_for_action (rs); + + if (rs.empty()) { + return; + } + + for (RegionSelection::iterator r = rs.begin(); r != rs.end(); ++r) { + (*r)->region()->get_transients (positions); + } } - - begin_reversible_command (_("duplicate selection")); - ri = new_regions.begin(); + TransientDetector::cleanup_transients (positions, session->frame_rate(), 3.0); - for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) { - if ((playlist = (*i)->playlist()) == 0) { - continue; + if (forward) { + AnalysisFeatureList::iterator x; + + for (x = positions.begin(); x != positions.end(); ++x) { + if ((*x) > pos) { + break; + } } - XMLNode &before = playlist->get_state(); - playlist->duplicate (*ri, selection->time[clicked_selection].end, times); - XMLNode &after = playlist->get_state(); - session->add_command (new MementoCommand(*playlist, &before, &after)); - ++ri; - if (ri == new_regions.end()) { - --ri; + if (x != positions.end ()) { + session->request_locate (*x); + } + + } else { + AnalysisFeatureList::reverse_iterator x; + + for (x = positions.rbegin(); x != positions.rend(); ++x) { + if ((*x) < pos) { + break; + } + } + + if (x != positions.rend ()) { + session->request_locate (*x); } } - - commit_reversible_command (); } - void -Editor::reset_point_selection () +Editor::playhead_forward_to_grid () { - /* reset all selected points to the relevant default value */ - - cerr << "point selection has " << selection->points.size() << " entries\n"; - - for (PointSelection::iterator i = selection->points.begin(); i != selection->points.end(); ++i) { - - AutomationTimeAxisView* atv = dynamic_cast(&(*i).track); - - if (atv) { - atv->reset_objects (selection->points); - } + if (!session) return; + nframes64_t pos = playhead_cursor->current_frame; + if (pos < max_frames - 1) { + pos += 2; + snap_to_internal (pos, 1, false); + session->request_locate (pos); } } -void -Editor::center_playhead () -{ - float page = canvas_width * frames_per_unit; - - center_screen_internal (playhead_cursor->current_frame, page); -} void -Editor::center_edit_cursor () +Editor::playhead_backward_to_grid () { - float page = canvas_width * frames_per_unit; - - center_screen_internal (edit_cursor->current_frame, page); + if (!session) return; + nframes64_t pos = playhead_cursor->current_frame; + if (pos > 2) { + pos -= 2; + snap_to_internal (pos, -1, false); + session->request_locate (pos); + } } void -Editor::clear_playlist (boost::shared_ptr playlist) +Editor::set_track_height (uint32_t h) { - begin_reversible_command (_("clear playlist")); - XMLNode &before = playlist->get_state(); - playlist->clear (); - XMLNode &after = playlist->get_state(); - session->add_command (new MementoCommand(*playlist.get(), &before, &after)); - commit_reversible_command (); + TrackSelection& ts (selection->tracks); + + for (TrackSelection::iterator x = ts.begin(); x != ts.end(); ++x) { + (*x)->set_height (h); + } } void -Editor::nudge_track (bool use_edit_cursor, bool forwards) +Editor::toggle_tracks_active () { - boost::shared_ptr playlist; - nframes_t distance; - nframes_t next_distance; - nframes_t start; - - if (use_edit_cursor) { - start = edit_cursor->current_frame; - } else { - start = 0; - } + TrackSelection& ts (selection->tracks); + bool first = true; + bool target = false; - if ((distance = get_nudge_distance (start, next_distance)) == 0) { + if (ts.empty()) { return; } - - if (selection->tracks.empty()) { - return; - } - - begin_reversible_command (_("nudge track")); - - for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) { - if ((playlist = (*i)->playlist()) == 0) { - continue; - } - - XMLNode &before = playlist->get_state(); - playlist->nudge_after (start, distance, forwards); - XMLNode &after = playlist->get_state(); - session->add_command (new MementoCommand(*playlist, &before, &after)); + for (TrackSelection::iterator x = ts.begin(); x != ts.end(); ++x) { + RouteTimeAxisView* rtv = dynamic_cast(*x); + + if (rtv) { + if (first) { + target = !rtv->_route->active(); + first = false; + } + rtv->_route->set_active (target); + } } - - commit_reversible_command (); } void -Editor::remove_last_capture () +Editor::remove_tracks () { - vector choices; - string prompt; - - if (!session) { + TrackSelection& ts (selection->tracks); + + if (ts.empty()) { return; } - if (Config->get_verify_remove_last_capture()) { - prompt = _("Do you really want to destroy the last capture?" - "\n(This is destructive and cannot be undone)"); - - choices.push_back (_("No, do nothing.")); - choices.push_back (_("Yes, destroy it.")); - - Gtkmm2ext::Choice prompter (prompt, choices); - - if (prompter.run () == 1) { - session->remove_last_capture (); + vector choices; + string prompt; + int ntracks = 0; + int nbusses = 0; + const char* trackstr; + const char* busstr; + vector > routes; + + for (TrackSelection::iterator x = ts.begin(); x != ts.end(); ++x) { + RouteTimeAxisView* rtv = dynamic_cast (*x); + if (rtv) { + if (rtv->is_track()) { + ntracks++; + } else { + nbusses++; + } } + routes.push_back (rtv->_route); + } + + if (ntracks + nbusses == 0) { + return; + } + if (ntracks > 1) { + trackstr = _("tracks"); } else { - session->remove_last_capture(); + trackstr = _("track"); } -} -void -Editor::normalize_region () -{ - if (!session) { - return; + if (nbusses > 1) { + busstr = _("busses"); + } else { + busstr = _("bus"); } - if (selection->regions.empty()) { - return; + if (ntracks) { + if (nbusses) { + prompt = string_compose (_("Do you really want to remove %1 %2 and %3 %4?\n" + "(You may also lose the playlists associated with the %2)\n\n" + "This action cannot be undone!"), + ntracks, trackstr, nbusses, busstr); + } else { + prompt = string_compose (_("Do you really want to remove %1 %2?\n" + "(You may also lose the playlists associated with the %2)\n\n" + "This action cannot be undone!"), + ntracks, trackstr); + } + } else if (nbusses) { + prompt = string_compose (_("Do you really want to remove %1 %2?"), + nbusses, busstr); } - begin_reversible_command (_("normalize")); + choices.push_back (_("No, do nothing.")); + if (ntracks + nbusses > 1) { + choices.push_back (_("Yes, remove them.")); + } else { + choices.push_back (_("Yes, remove it.")); + } - track_canvas.get_window()->set_cursor (*wait_cursor); - gdk_flush (); + Choice prompter (prompt, choices); - for (RegionSelection::iterator r = selection->regions.begin(); r != selection->regions.end(); ++r) { - AudioRegionView* const arv = dynamic_cast(*r); - if (!arv) - continue; - XMLNode &before = arv->region()->get_state(); - arv->audio_region()->normalize_to (0.0f); - session->add_command (new MementoCommand(*(arv->region().get()), &before, &arv->region()->get_state())); + if (prompter.run () != 1) { + return; } - commit_reversible_command (); - track_canvas.get_window()->set_cursor (*current_canvas_cursor); + for (vector >::iterator x = routes.begin(); x != routes.end(); ++x) { + session->remove_route (*x); + } } - void -Editor::denormalize_region () +Editor::do_insert_time () { - if (!session) { + if (selection->tracks.empty()) { return; } - if (selection->regions.empty()) { + ArdourDialog d (*this, _("Insert Time")); + + nframes64_t const pos = get_preferred_edit_position (); + + d.get_vbox()->set_border_width (12); + d.get_vbox()->set_spacing (4); + + Table table (2, 2); + table.set_spacings (4); + + Label time_label (_("Time to insert:")); + time_label.set_alignment (1, 0.5); + table.attach (time_label, 0, 1, 0, 1, FILL | EXPAND); + AudioClock clock ("insertTimeClock", true, X_("InsertTimeClock"), true, true, true); + clock.set (0); + clock.set_session (session); + clock.set_bbt_reference (pos); + table.attach (clock, 1, 2, 0, 1); + + Label intersected_label (_("Intersected regions should:")); + intersected_label.set_alignment (1, 0.5); + table.attach (intersected_label, 0, 1, 1, 2, FILL | EXPAND); + ComboBoxText intersected_combo; + intersected_combo.append_text (_("stay in position")); + intersected_combo.append_text (_("move")); + intersected_combo.append_text (_("be split")); + intersected_combo.set_active (0); + table.attach (intersected_combo, 1, 2, 1, 2); + + d.get_vbox()->pack_start (table); + + CheckButton move_glued (_("Move glued regions")); + d.get_vbox()->pack_start (move_glued); + CheckButton move_markers (_("Move markers")); + d.get_vbox()->pack_start (move_markers); + CheckButton move_tempos (_("Move tempo and meter changes")); + d.get_vbox()->pack_start (move_tempos); + + d.add_button (Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); + d.add_button (_("Insert time"), Gtk::RESPONSE_OK); + d.show_all (); + + int response = d.run (); + + if (response != RESPONSE_OK) { return; } - begin_reversible_command ("denormalize"); + nframes64_t distance = clock.current_duration (pos); - for (RegionSelection::iterator r = selection->regions.begin(); r != selection->regions.end(); ++r) { - AudioRegionView* const arv = dynamic_cast(*r); - if (!arv) - continue; - XMLNode &before = arv->region()->get_state(); - arv->audio_region()->set_scale_amplitude (1.0f); - session->add_command (new MementoCommand(*(arv->region().get()), &before, &arv->region()->get_state())); + if (distance == 0) { + return; } - commit_reversible_command (); -} + InsertTimeOption opt; - -void -Editor::reverse_region () -{ - if (!session) { - return; + switch (intersected_combo.get_active_row_number ()) { + case 0: + opt = LeaveIntersected; + break; + case 1: + opt = MoveIntersected; + break; + case 2: + opt = SplitIntersected; + break; } - Reverse rev (*session); - apply_filter (rev, _("reverse regions")); + insert_time (pos, distance, opt, move_glued.get_active(), move_markers.get_active(), move_tempos.get_active()); } void -Editor::apply_filter (AudioFilter& filter, string command) +Editor::insert_time (nframes64_t pos, nframes64_t frames, InsertTimeOption opt, + bool ignore_music_glue, bool markers_too, bool tempo_too) { - if (selection->regions.empty()) { + bool commit = false; + + if (Config->get_edit_mode() == Lock) { return; } - begin_reversible_command (command); + begin_reversible_command (_("insert time")); - track_canvas.get_window()->set_cursor (*wait_cursor); - gdk_flush (); + for (TrackSelection::iterator x = selection->tracks.begin(); x != selection->tracks.end(); ++x) { + /* regions */ + boost::shared_ptr pl = (*x)->playlist(); - for (RegionSelection::iterator r = selection->regions.begin(); r != selection->regions.end(); ) { - AudioRegionView* const arv = dynamic_cast(*r); - if (!arv) - continue; + if (pl) { - boost::shared_ptr playlist = arv->region()->playlist(); + XMLNode &before = pl->get_state(); - RegionSelection::iterator tmp; - - tmp = r; - ++tmp; + if (opt == SplitIntersected) { + pl->split (pos); + } - if (arv->audio_region()->apply (filter) == 0) { + pl->shift (pos, frames, (opt == MoveIntersected), ignore_music_glue); - XMLNode &before = playlist->get_state(); - playlist->replace_region (arv->region(), filter.results.front(), arv->region()->position()); - XMLNode &after = playlist->get_state(); - session->add_command(new MementoCommand(*playlist, &before, &after)); - } else { - goto out; + XMLNode &after = pl->get_state(); + + session->add_command (new MementoCommand (*pl, &before, &after)); + commit = true; } - r = tmp; + /* automation */ + RouteTimeAxisView* rtav = dynamic_cast (*x); + if (rtav) { + rtav->route ()->shift (pos, frames); + commit = true; + } } - commit_reversible_command (); - selection->regions.clear (); + /* markers */ + if (markers_too) { + bool moved = false; + XMLNode& before (session->locations()->get_state()); + Locations::LocationList copy (session->locations()->list()); - out: - track_canvas.get_window()->set_cursor (*current_canvas_cursor); -} + for (Locations::LocationList::iterator i = copy.begin(); i != copy.end(); ++i) { -void -Editor::region_selection_op (void (Region::*pmf)(void)) -{ - for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) { - Region* region = (*i)->region().get(); - (region->*pmf)(); + Locations::LocationList::const_iterator tmp; + + if ((*i)->start() >= pos) { + (*i)->set_start ((*i)->start() + frames); + if (!(*i)->is_mark()) { + (*i)->set_end ((*i)->end() + frames); + } + moved = true; + } + } + + if (moved) { + XMLNode& after (session->locations()->get_state()); + session->add_command (new MementoCommand(*session->locations(), &before, &after)); + } } -} + if (tempo_too) { + session->tempo_map().insert_time (pos, frames); + } -void -Editor::region_selection_op (void (Region::*pmf)(void*), void *arg) -{ - for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) { - Region* region = (*i)->region().get(); - (region->*pmf)(arg); + if (commit) { + commit_reversible_command (); } } void -Editor::region_selection_op (void (Region::*pmf)(bool), bool yn) +Editor::fit_selected_tracks () { - for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) { - Region* region = (*i)->region().get(); - (region->*pmf)(yn); - } + fit_tracks (selection->tracks); } void -Editor::external_edit_region () +Editor::fit_tracks (TrackSelection & tracks) { - if (!clicked_regionview) { + if (tracks.empty()) { return; } - /* more to come */ -} + uint32_t child_heights = 0; -void -Editor::brush (nframes_t pos) -{ - RegionSelection sel; - snap_to (pos); + for (TrackSelection::iterator t = tracks.begin(); t != tracks.end(); ++t) { - if (selection->regions.empty()) { - /* XXX get selection from region list */ - } else { - sel = selection->regions; - } + if (!(*t)->marked_for_display()) { + continue; + } - if (sel.empty()) { - return; + child_heights += (*t)->effective_height() - (*t)->current_height(); } - for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) { - mouse_brush_insert_region ((*i), pos); - } -} + uint32_t h = (uint32_t) floor ((_canvas_height - child_heights - canvas_timebars_vsize) / tracks.size()); + double first_y_pos = DBL_MAX; -void -Editor::reset_region_gain_envelopes () -{ - if (!session || selection->regions.empty()) { + if (h < TimeAxisView::hSmall) { + MessageDialog msg (*this, _("There are too many tracks to fit in the current window")); + /* too small to be displayed */ return; } - session->begin_reversible_command (_("reset region gain")); + undo_visual_stack.push_back (current_visual_state()); - for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) { - AudioRegionView* const arv = dynamic_cast(*i); - if (arv) { - AutomationList& alist (arv->audio_region()->envelope()); - XMLNode& before (alist.get_state()); + /* operate on all tracks, hide unselected ones that are in the middle of selected ones */ - arv->audio_region()->set_default_envelope (); - session->add_command (new MementoCommand(arv->audio_region()->envelope(), &before, &alist.get_state())); + bool prev_was_selected = false; + bool is_selected = tracks.contains (track_views.front()); + bool next_is_selected; + + for (TrackViewList::iterator t = track_views.begin(); t != track_views.end(); ++t) { + + TrackViewList::iterator next; + + next = t; + ++next; + + if (next != track_views.end()) { + next_is_selected = tracks.contains (*next); + } else { + next_is_selected = false; + } + + if (is_selected) { + (*t)->set_height (h); + first_y_pos = std::min ((*t)->y_position (), first_y_pos); + } else { + if (prev_was_selected && next_is_selected) { + hide_track_in_display (**t); + } } + + prev_was_selected = is_selected; + is_selected = next_is_selected; } - session->commit_reversible_command (); + /* + set the controls_layout height now, because waiting for its size + request signal handler will cause the vertical adjustment setting to fail + */ + + controls_layout.property_height () = full_canvas_height - canvas_timebars_vsize; + vertical_adjustment.set_value (first_y_pos); + + redo_visual_stack.push_back (current_visual_state()); } void -Editor::toggle_gain_envelope_visibility () +Editor::save_visual_state (uint32_t n) { - for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) { - AudioRegionView* const arv = dynamic_cast(*i); - if (arv) { - bool x = region_envelope_visible_item->get_active(); - if (x != arv->envelope_visible()) { - arv->set_envelope_visible (x); - } - } + while (visual_states.size() <= n) { + visual_states.push_back (0); } + + delete visual_states[n]; + + visual_states[n] = current_visual_state (true); + gdk_beep (); } void -Editor::toggle_gain_envelope_active () +Editor::goto_visual_state (uint32_t n) { - for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) { - AudioRegionView* const arv = dynamic_cast(*i); - if (arv) { - bool x = region_envelope_active_item->get_active(); - if (x != arv->audio_region()->envelope_active()) { - arv->audio_region()->set_envelope_active (x); - } - } + if (visual_states.size() <= n) { + return; } + + if (visual_states[n] == 0) { + return; + } + + use_visual_state (*visual_states[n]); } void -Editor::toggle_region_lock () +Editor::start_visual_state_op (uint32_t n) { - for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) { - AudioRegionView* const arv = dynamic_cast(*i); - if (arv) { - bool x = region_lock_item->get_active(); - if (x != arv->audio_region()->locked()) { - arv->audio_region()->set_locked (x); - } - } + if (visual_state_op_connection.empty()) { + visual_state_op_connection = Glib::signal_timeout().connect (bind (mem_fun (*this, &Editor::end_visual_state_op), n), 1000); } } void -Editor::toggle_region_mute () +Editor::cancel_visual_state_op (uint32_t n) { - for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) { - AudioRegionView* const arv = dynamic_cast(*i); - if (arv) { - bool x = region_mute_item->get_active(); - if (x != arv->audio_region()->muted()) { - arv->audio_region()->set_muted (x); - } - } + if (!visual_state_op_connection.empty()) { + visual_state_op_connection.disconnect(); + goto_visual_state (n); + } else { + //we land here if called from the menu OR if end_visual_state_op has been called + //so check if we are already in visual state n + // XXX not yet checking it at all, but redoing does not hurt + goto_visual_state (n); } } -void -Editor::toggle_region_opaque () +bool +Editor::end_visual_state_op (uint32_t n) { - for (RegionSelection::iterator i = selection->regions.begin(); i != selection->regions.end(); ++i) { - AudioRegionView* const arv = dynamic_cast(*i); - if (arv) { - bool x = region_opaque_item->get_active(); - if (x != arv->audio_region()->opaque()) { - arv->audio_region()->set_opaque (x); - } - } - } + visual_state_op_connection.disconnect(); + save_visual_state (n); + + PopUp* pup = new PopUp (WIN_POS_MOUSE, 1000, true); + char buf[32]; + snprintf (buf, sizeof (buf), _("Saved view %u"), n+1); + pup->set_text (buf); + pup->touch(); + + return false; // do not call again } +