diff options
Diffstat (limited to 'gtk2_ardour/editor_ops.cc')
-rw-r--r-- | gtk2_ardour/editor_ops.cc | 3627 |
1 files changed, 3627 insertions, 0 deletions
diff --git a/gtk2_ardour/editor_ops.cc b/gtk2_ardour/editor_ops.cc new file mode 100644 index 0000000000..756e2ea8ba --- /dev/null +++ b/gtk2_ardour/editor_ops.cc @@ -0,0 +1,3627 @@ +/* + Copyright (C) 2000-2004 Paul Davis + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#include <unistd.h> + +#include <cstdlib> +#include <cmath> +#include <string> +#include <map> + +#include <sndfile.h> + +#include <pbd/error.h> +#include <pbd/basename.h> +#include <pbd/pthread_utils.h> + +#include <gtkmmext/utils.h> +#include <gtkmmext/choice.h> + +#include <ardour/audioengine.h> +#include <ardour/session.h> +#include <ardour/audioplaylist.h> +#include <ardour/audioregion.h> +#include <ardour/diskstream.h> +#include <ardour/filesource.h> +#include <ardour/sndfilesource.h> +#include <ardour/utils.h> +#include <ardour/location.h> +#include <ardour/named_selection.h> +#include <ardour/audio_track.h> +#include <ardour/audioplaylist.h> +#include <ardour/region_factory.h> +#include <ardour/reverse.h> + +#include "ardour_ui.h" +#include "editor.h" +#include "time_axis_view.h" +#include "audio_time_axis.h" +#include "automation_time_axis.h" +#include "streamview.h" +#include "regionview.h" +#include "rgb_macros.h" +#include "extra_bind.h" +#include "selection_templates.h" +#include "selection.h" +#include "library_ui.h" +#include "editing.h" +#include "gtk-custom-hruler.h" + +#include "i18n.h" + +using namespace std; +using namespace ARDOUR; +using namespace SigC; +using namespace Gtk; +using namespace Editing; + +/*********************************************************************** + Editor operations + ***********************************************************************/ + +void +Editor::undo (uint32_t n) +{ + if (session) { + session->undo (n); + } +} + +void +Editor::redo (uint32_t n) +{ + if (session) { + session->redo (n); + } +} + +int +Editor::ensure_cursor (jack_nframes_t *pos) +{ + *pos = edit_cursor->current_frame; + return 0; +} + +void +Editor::split_region () +{ + split_region_at (edit_cursor->current_frame); +} + +void +Editor::split_region_at (jack_nframes_t where) +{ + split_regions_at (where, selection->audio_regions); +} + +void +Editor::split_regions_at (jack_nframes_t where, AudioRegionSelection& regions) +{ + begin_reversible_command (_("split")); + + snap_to (where); + for (AudioRegionSelection::iterator a = regions.begin(); a != regions.end(); ) { + + AudioRegionSelection::iterator tmp; + + tmp = a; + ++tmp; + + Playlist* pl = (*a)->region.playlist(); + + _new_regionviews_show_envelope = (*a)->envelope_visible(); + + if (pl) { + session->add_undo (pl->get_memento()); + pl->split_region ((*a)->region, where); + session->add_redo_no_execute (pl->get_memento()); + } + + a = tmp; + } + + commit_reversible_command (); + _new_regionviews_show_envelope = false; +} + +void +Editor::remove_clicked_region () +{ + if (clicked_audio_trackview == 0 || clicked_regionview == 0) { + return; + } + + Playlist* playlist = clicked_audio_trackview->playlist(); + + begin_reversible_command (_("remove region")); + session->add_undo (playlist->get_memento()); + playlist->remove_region (&clicked_regionview->region); + session->add_redo_no_execute (playlist->get_memento()); + commit_reversible_command (); +} + +void +Editor::destroy_clicked_region () +{ + int32_t selected = selection->audio_regions.size(); + + if (!session || clicked_regionview == 0 && selected == 0) { + return; + } + + vector<string> choices; + string prompt; + + prompt = 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"))); + + if (selected > 1) { + choices.push_back (_("Yes, destroy them.")); + } else { + choices.push_back (_("Yes, destroy it.")); + } + + choices.push_back (_("No, do nothing.")); + + Gtkmmext::Choice prompter (prompt, choices); + + prompter.chosen.connect (Gtk::Main::quit.slot()); + prompter.show_all (); + + Gtk::Main::run (); + + if (prompter.get_choice() != 0) { + return; + } + + if (selected > 0) { + list<Region*> r; + + for (AudioRegionSelection::iterator i = selection->audio_regions.begin(); i != selection->audio_regions.end(); ++i) { + r.push_back (&(*i)->region); + } + + session->destroy_regions (r); + + } else if (clicked_regionview) { + session->destroy_region (&clicked_regionview->region); + } +} + +AudioRegion * +Editor::select_region_for_operation (int dir, TimeAxisView **tv) +{ + AudioRegionView* rv; + AudioRegion *region; + jack_nframes_t start = 0; + + if (selection->time.start () == selection->time.end_frame ()) { + + /* no current selection-> is there a selected regionview? */ + + if (selection->audio_regions.empty()) { + return 0; + } + + } + + region = 0; + + if (!selection->audio_regions.empty()) { + + rv = *(selection->audio_regions.begin()); + (*tv) = &rv->get_time_axis_view(); + region = &rv->region; + + } else if (!selection->tracks.empty()) { + + (*tv) = selection->tracks.front(); + + AudioTimeAxisView* atv; + + if ((atv = dynamic_cast<AudioTimeAxisView*> (*tv)) != 0) { + Playlist *pl; + + if ((pl = atv->playlist()) == 0) { + return 0; + } + + region = dynamic_cast<AudioRegion*> (pl->top_region_at (start)); + } + } + + return region; +} + +void +Editor::extend_selection_to_end_of_region (bool next) +{ + TimeAxisView *tv; + Region *region; + jack_nframes_t start; + + if ((region = select_region_for_operation (next ? 1 : 0, &tv)) == 0) { + return; + } + + if (region && selection->time.start () == selection->time.end_frame ()) { + start = region->position(); + } else { + start = selection->time.start (); + } + + /* Try to leave the selection with the same route if possible */ + + if ((tv = selection->time.track) == 0) { + return; + } + + begin_reversible_command (_("extend selection")); + selection->set (tv, start, region->position() + region->length()); + commit_reversible_command (); +} + +void +Editor::extend_selection_to_start_of_region (bool previous) +{ + TimeAxisView *tv; + Region *region; + jack_nframes_t end; + + if ((region = select_region_for_operation (previous ? -1 : 0, &tv)) == 0) { + return; + } + + if (region && selection->time.start () == selection->time.end_frame ()) { + end = region->position() + region->length(); + } else { + end = selection->time.end_frame (); + } + + /* Try to leave the selection with the same route if possible */ + + if ((tv = selection->time.track) == 0) { + return; + } + + begin_reversible_command (_("extend selection")); + selection->set (tv, region->position(), end); + commit_reversible_command (); +} + + +void +Editor::nudge_forward (bool next) +{ + jack_nframes_t distance; + jack_nframes_t next_distance; + + if (!session) return; + + if (!selection->audio_regions.empty()) { + + begin_reversible_command (_("nudge forward")); + + for (AudioRegionSelection::iterator i = selection->audio_regions.begin(); i != selection->audio_regions.end(); ++i) { + AudioRegion& r ((*i)->region); + + distance = get_nudge_distance (r.position(), next_distance); + + if (next) { + distance = next_distance; + } + + session->add_undo (r.playlist()->get_memento()); + r.set_position (r.position() + distance, this); + session->add_redo_no_execute (r.playlist()->get_memento()); + } + + 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) +{ + jack_nframes_t distance; + jack_nframes_t next_distance; + + if (!session) return; + + if (!selection->audio_regions.empty()) { + + begin_reversible_command (_("nudge forward")); + + for (AudioRegionSelection::iterator i = selection->audio_regions.begin(); i != selection->audio_regions.end(); ++i) { + AudioRegion& r ((*i)->region); + + distance = get_nudge_distance (r.position(), next_distance); + + if (next) { + distance = next_distance; + } + + session->add_undo (r.playlist()->get_memento()); + + if (r.position() > distance) { + r.set_position (r.position() - distance, this); + } else { + r.set_position (0, this); + } + session->add_redo_no_execute (r.playlist()->get_memento()); + } + + commit_reversible_command (); + + } else { + + distance = get_nudge_distance (playhead_cursor->current_frame, next_distance); + + if (playhead_cursor->current_frame > distance) { + session->request_locate (playhead_cursor->current_frame - distance); + } else { + session->request_locate (0); + } + } +} + +void +Editor::nudge_forward_capture_offset () +{ + jack_nframes_t distance; + + if (!session) return; + + if (!selection->audio_regions.empty()) { + + begin_reversible_command (_("nudge forward")); + + distance = session->worst_output_latency(); + + for (AudioRegionSelection::iterator i = selection->audio_regions.begin(); i != selection->audio_regions.end(); ++i) { + AudioRegion& r ((*i)->region); + + session->add_undo (r.playlist()->get_memento()); + r.set_position (r.position() + distance, this); + session->add_redo_no_execute (r.playlist()->get_memento()); + } + + commit_reversible_command (); + + } +} + +void +Editor::nudge_backward_capture_offset () +{ + jack_nframes_t distance; + + if (!session) return; + + if (!selection->audio_regions.empty()) { + + begin_reversible_command (_("nudge forward")); + + distance = session->worst_output_latency(); + + for (AudioRegionSelection::iterator i = selection->audio_regions.begin(); i != selection->audio_regions.end(); ++i) { + AudioRegion& r ((*i)->region); + + session->add_undo (r.playlist()->get_memento()); + + if (r.position() > distance) { + r.set_position (r.position() - distance, this); + } else { + r.set_position (0, this); + } + session->add_redo_no_execute (r.playlist()->get_memento()); + } + + commit_reversible_command (); + } +} + +/* DISPLAY MOTION */ + +void +Editor::move_to_start () +{ + session->request_locate (0); +} + +void +Editor::move_to_end () +{ + + session->request_locate (session->current_end_frame()); +} + +void +Editor::build_region_boundary_cache () +{ + jack_nframes_t pos = 0; + RegionPoint point; + Region *r; + TrackViewList tracks; + + region_boundary_cache.clear (); + + if (session == 0) { + return; + } + + switch (snap_type) { + case SnapToRegionStart: + point = Start; + break; + case SnapToRegionEnd: + point = End; + break; + case SnapToRegionSync: + point = SyncPoint; + break; + case SnapToRegionBoundary: + point = Start; + break; + default: + fatal << compose (_("build_region_boundary_cache called with snap_type = %1"), snap_type) << endmsg; + /*NOTREACHED*/ + return; + } + + TimeAxisView *ontrack = 0; + + while (pos < session->current_end_frame()) { + + if (!selection->tracks.empty()) { + + if ((r = find_next_region (pos, point, 1, selection->tracks, &ontrack)) == 0) { + break; + } + + } else if (clicked_trackview) { + + TrackViewList t; + t.push_back (clicked_trackview); + + if ((r = find_next_region (pos, point, 1, t, &ontrack)) == 0) { + break; + } + + } else { + + if ((r = find_next_region (pos, point, 1, track_views, &ontrack)) == 0) { + break; + } + } + + jack_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; + + case SnapToRegionBoundary: + rpos = r->last_frame(); + break; + default: + break; + } + + float speed = 1.0f; + AudioTimeAxisView *atav; + + if ( ontrack != 0 && (atav = dynamic_cast<AudioTimeAxisView*>(ontrack)) != 0 ) { + if (atav->get_diskstream() != 0) { + speed = atav->get_diskstream()->speed(); + } + } + + rpos = (jack_nframes_t) floor ( (float) rpos / speed ); + + if (region_boundary_cache.empty() || rpos != region_boundary_cache.back()) { + if (snap_type == SnapToRegionBoundary) { + region_boundary_cache.push_back (r->first_frame()); + } + region_boundary_cache.push_back (rpos); + } + + pos = rpos + 1; + } +} + +Region* +Editor::find_next_region (jack_nframes_t frame, RegionPoint point, int32_t dir, TrackViewList& tracks, TimeAxisView **ontrack) +{ + TrackViewList::iterator i; + jack_nframes_t closest = max_frames; + Region* ret = 0; + jack_nframes_t rpos = 0; + + float track_speed; + jack_nframes_t track_frame; + AudioTimeAxisView *atav; + + for (i = tracks.begin(); i != tracks.end(); ++i) { + + jack_nframes_t distance; + Region* r; + + track_speed = 1.0f; + if ( (atav = dynamic_cast<AudioTimeAxisView*>(*i)) != 0 ) { + if (atav->get_diskstream()!=0) + track_speed = atav->get_diskstream()->speed(); + } + + track_frame = (jack_nframes_t) floor ( (float) frame * track_speed ); + + if ((r = (*i)->find_next_region (track_frame, point, dir)) == 0) { + continue; + } + + switch (point) { + case Start: + rpos = r->first_frame (); + break; + + case End: + rpos = r->last_frame (); + break; + + case SyncPoint: + rpos = r->adjust_to_sync (r->first_frame()); + break; + } + // rpos is a "track frame", converting it to "time frame" + rpos = (jack_nframes_t) floor ( (float) rpos / track_speed ); + + if (rpos > frame) { + distance = rpos - frame; + } else { + distance = frame - rpos; + } + + if (distance < closest) { + closest = distance; + if (ontrack != 0) + *ontrack = (*i); + ret = r; + } + } + + return ret; +} + +void +Editor::cursor_to_region_point (Cursor* cursor, RegionPoint point, int32_t dir) +{ + Region* r; + jack_nframes_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_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 (r == 0) { + return; + } + + 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; + AudioTimeAxisView *atav; + + if ( ontrack != 0 && (atav = dynamic_cast<AudioTimeAxisView*>(ontrack)) != 0 ) { + if (atav->get_diskstream() != 0) { + speed = atav->get_diskstream()->speed(); + } + } + + pos = (jack_nframes_t) floor ( (float) pos / speed ); + + + if (cursor == playhead_cursor) { + session->request_locate (pos); + } else { + cursor->set_position (pos); + } +} + +void +Editor::cursor_to_next_region_point (Cursor* cursor, RegionPoint point) +{ + cursor_to_region_point (cursor, point, 1); +} + +void +Editor::cursor_to_previous_region_point (Cursor* cursor, RegionPoint point) +{ + cursor_to_region_point (cursor, point, -1); +} + +void +Editor::cursor_to_selection_start (Cursor *cursor) +{ + jack_nframes_t pos = 0; + switch (mouse_mode) { + case MouseObject: + if (!selection->audio_regions.empty()) { + pos = selection->audio_regions.start(); + } + break; + + case MouseRange: + if (!selection->time.empty()) { + pos = selection->time.start (); + } + break; + + default: + return; + } + + if (cursor == playhead_cursor) { + session->request_locate (pos); + } else { + cursor->set_position (pos); + } +} + +void +Editor::cursor_to_selection_end (Cursor *cursor) +{ + jack_nframes_t pos = 0; + + switch (mouse_mode) { + case MouseObject: + if (!selection->audio_regions.empty()) { + pos = selection->audio_regions.end_frame(); + } + break; + + case MouseRange: + if (!selection->time.empty()) { + pos = selection->time.end_frame (); + } + break; + + default: + return; + } + + if (cursor == playhead_cursor) { + session->request_locate (pos); + } else { + cursor->set_position (pos); + } +} + +void +Editor::playhead_backward () +{ + jack_nframes_t pos; + jack_nframes_t cnt; + float prefix; + bool was_floating; + + if (get_prefix (prefix, was_floating)) { + cnt = 1; + } else { + if (was_floating) { + cnt = (jack_nframes_t) floor (prefix * session->frame_rate ()); + } else { + cnt = (jack_nframes_t) prefix; + } + } + + pos = playhead_cursor->current_frame; + + if ((jack_nframes_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 () +{ + jack_nframes_t pos; + jack_nframes_t cnt; + bool was_floating; + float prefix; + + if (get_prefix (prefix, was_floating)) { + cnt = 1; + } else { + if (was_floating) { + cnt = (jack_nframes_t) floor (prefix * session->frame_rate ()); + } else { + cnt = (jack_nframes_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 (playhead_to_edit) { + if (session) { + session->request_locate (edit_cursor->current_frame); + } + } else { + edit_cursor->set_position (playhead_cursor->current_frame); + } +} + +void +Editor::edit_cursor_backward () +{ + jack_nframes_t pos; + jack_nframes_t cnt; + float prefix; + bool was_floating; + + if (get_prefix (prefix, was_floating)) { + cnt = 1; + } else { + if (was_floating) { + cnt = (jack_nframes_t) floor (prefix * session->frame_rate ()); + } else { + cnt = (jack_nframes_t) prefix; + } + } + + pos = edit_cursor->current_frame; + + if ((jack_nframes_t) pos < cnt) { + pos = 0; + } else { + pos -= cnt; + } + + edit_cursor->set_position (pos); +} + +void +Editor::edit_cursor_forward () +{ + jack_nframes_t pos; + jack_nframes_t cnt; + bool was_floating; + float prefix; + + if (get_prefix (prefix, was_floating)) { + cnt = 1; + } else { + if (was_floating) { + cnt = (jack_nframes_t) floor (prefix * session->frame_rate ()); + } else { + cnt = (jack_nframes_t) floor (prefix); + } + } + + pos = edit_cursor->current_frame; + edit_cursor->set_position (pos+cnt); +} + +void +Editor::goto_frame () +{ + float prefix; + bool was_floating; + jack_nframes_t frame; + + if (get_prefix (prefix, was_floating)) { + return; + } + + if (was_floating) { + frame = (jack_nframes_t) floor (prefix * session->frame_rate()); + } else { + frame = (jack_nframes_t) floor (prefix); + } + + session->request_locate (frame); +} + +void +Editor::scroll_backward (float pages) +{ + jack_nframes_t frame; + jack_nframes_t one_page = (jack_nframes_t) rint (canvas_width * frames_per_unit); + bool was_floating; + float prefix; + jack_nframes_t cnt; + + if (get_prefix (prefix, was_floating)) { + cnt = (jack_nframes_t) floor (pages * one_page); + } else { + if (was_floating) { + cnt = (jack_nframes_t) floor (prefix * session->frame_rate()); + } else { + cnt = (jack_nframes_t) floor (prefix * one_page); + } + } + + if (leftmost_frame < cnt) { + frame = 0; + } else { + frame = leftmost_frame - cnt; + } + + reposition_x_origin (frame); +} + +void +Editor::scroll_forward (float pages) +{ + jack_nframes_t frame; + jack_nframes_t one_page = (jack_nframes_t) rint (canvas_width * frames_per_unit); + bool was_floating; + float prefix; + jack_nframes_t cnt; + + if (get_prefix (prefix, was_floating)) { + cnt = (jack_nframes_t) floor (pages * one_page); + } else { + if (was_floating) { + cnt = (jack_nframes_t) floor (prefix * session->frame_rate()); + } else { + cnt = (jack_nframes_t) floor (prefix * one_page); + } + } + + if (ULONG_MAX - cnt < leftmost_frame) { + frame = ULONG_MAX - cnt; + } else { + frame = leftmost_frame + cnt; + } + + reposition_x_origin (frame); +} + +void +Editor::scroll_tracks_down () +{ + float prefix; + bool was_floating; + int cnt; + + if (get_prefix (prefix, was_floating)) { + cnt = 1; + } else { + cnt = (int) floor (prefix); + } + + Gtk::Adjustment *adj = track_canvas_scroller.get_vadjustment(); + adj->set_value (adj->get_value() + (cnt * adj->get_page_size())); +} + +void +Editor::scroll_tracks_up () +{ + float prefix; + bool was_floating; + int cnt; + + if (get_prefix (prefix, was_floating)) { + cnt = 1; + } else { + cnt = (int) floor (prefix); + } + + Gtk::Adjustment *adj = track_canvas_scroller.get_vadjustment(); + adj->set_value (adj->get_value() - (cnt * adj->get_page_size())); +} + +void +Editor::scroll_tracks_down_line () +{ + edit_vscrollbar.default_vmotion (0, 10); +} + +void +Editor::scroll_tracks_up_line () +{ + edit_vscrollbar.default_vmotion (0, -10); +} + +/* ZOOM */ + +void +Editor::temporal_zoom_step (bool coarser) +{ + double nfpu; + + nfpu = frames_per_unit; + + if (coarser) { + nfpu *= 2.0; + } else { + nfpu = max(1.0,(nfpu/2.0)); + } + + temporal_zoom (nfpu); +} + +void +Editor::temporal_zoom (gdouble fpu) +{ + if (!session) return; + + jack_nframes_t current_page = current_page_frames(); + jack_nframes_t current_leftmost = leftmost_frame; + jack_nframes_t current_rightmost; + jack_nframes_t current_center; + jack_nframes_t new_page; + jack_nframes_t leftmost_after_zoom = 0; + double nfpu; + + nfpu = fpu; + + new_page = (jack_nframes_t) floor (canvas_width * nfpu); + + 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 { + leftmost_after_zoom = 0; + } + 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 { + leftmost_after_zoom = 0; + } + 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 { + leftmost_after_zoom = 0; + } + break; + + case ZoomFocusEdit: + /* try to keep the edit cursor in the center */ + if (edit_cursor->current_frame > leftmost_frame + (new_page/2)) { + leftmost_after_zoom = edit_cursor->current_frame - (new_page/2); + } else { + leftmost_after_zoom = 0; + } + break; + + } + + // leftmost_after_zoom = min (leftmost_after_zoom, session->current_end_frame()); + +// begin_reversible_command (_("zoom")); +// session->add_undo (bind (slot (*this, &Editor::reposition_and_zoom), current_leftmost, frames_per_unit)); +// session->add_redo (bind (slot (*this, &Editor::reposition_and_zoom), leftmost_after_zoom, nfpu)); +// commit_reversible_command (); + + reposition_and_zoom (leftmost_after_zoom, nfpu); +} + +void +Editor::temporal_zoom_selection () +{ + if (!selection) return; + + if (selection->time.empty()) { + return; + } + + jack_nframes_t start = selection->time[clicked_selection].start; + jack_nframes_t end = selection->time[clicked_selection].end; + + temporal_zoom_by_frame (start, end, "zoom to selection"); +} + +void +Editor::temporal_zoom_session () +{ + if (session) { + temporal_zoom_by_frame (0, session->current_end_frame(), "zoom to session"); + } +} + +void +Editor::temporal_zoom_by_frame (jack_nframes_t start, jack_nframes_t end, string op) +{ + if (!session) return; + + if ((start == 0 && end == 0) || end < start) { + return; + } + + jack_nframes_t range = end - start; + + double new_fpu = (double)range / (double)canvas_width; +// double p2 = 1.0; + +// while (p2 < new_fpu) { +// p2 *= 2.0; +// } +// new_fpu = p2; + + jack_nframes_t new_page = (jack_nframes_t) floor (canvas_width * new_fpu); + jack_nframes_t middle = (jack_nframes_t) floor( (double)start + ((double)range / 2.0f )); + jack_nframes_t new_leftmost = (jack_nframes_t) floor( (double)middle - ((double)new_page/2.0f)); + + if (new_leftmost > middle) new_leftmost = 0; + +// begin_reversible_command (op); +// session->add_undo (bind (slot (*this, &Editor::reposition_and_zoom), leftmost_frame, frames_per_unit)); +// session->add_redo (bind (slot (*this, &Editor::reposition_and_zoom), new_leftmost, new_fpu)); +// commit_reversible_command (); + + reposition_and_zoom (new_leftmost, new_fpu); +} + +void +Editor::temporal_zoom_to_frame (bool coarser, jack_nframes_t frame) +{ + if (!session) return; + + jack_nframes_t range_before = frame - leftmost_frame; + double new_fpu; + + new_fpu = frames_per_unit; + + if (coarser) { + new_fpu *= 2.0; + range_before *= 2; + } else { + new_fpu = max(1.0,(new_fpu/2.0)); + range_before /= 2; + } + + if (new_fpu == frames_per_unit) return; + + jack_nframes_t new_leftmost = frame - range_before; + + if (new_leftmost > frame) new_leftmost = 0; + +// begin_reversible_command (_("zoom to frame")); +// session->add_undo (bind (slot (*this, &Editor::reposition_and_zoom), leftmost_frame, frames_per_unit)); +// session->add_redo (bind (slot (*this, &Editor::reposition_and_zoom), new_leftmost, new_fpu)); +// commit_reversible_command (); + + reposition_and_zoom (new_leftmost, new_fpu); +} + +void +Editor::select_all_in_track (bool add) +{ + list<Selectable *> touched; + + if (!clicked_trackview) { + return; + } + + clicked_trackview->get_selectables (0, max_frames, 0, DBL_MAX, touched); + + if (add) { + selection->add (touched); + } else { + selection->set (touched); + } +} + +void +Editor::select_all (bool add) +{ + list<Selectable *> 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); + } + + if (add) { + selection->add (touched); + } else { + selection->set (touched); + } + +} + +void +Editor::invert_selection_in_track () +{ + list<Selectable *> touched; + + if (!clicked_trackview) { + return; + } + + clicked_trackview->get_inverted_selectables (*selection, touched); + selection->set (touched); +} + +void +Editor::invert_selection () +{ + list<Selectable *> 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 (jack_nframes_t start, jack_nframes_t end, double top, double bot, bool add) +{ + list<Selectable *> 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); + } + + if (add) { + selection->add (touched); + } else { + selection->set (touched); + } + + return !touched.empty(); +} + +void +Editor::set_selection_from_punch() +{ + Location* location; + + if ((location = session->locations()->auto_punch_location()) == 0) { + return; + } + + set_selection_from_range (*location); +} + +void +Editor::set_selection_from_loop() +{ + Location* location; + + if ((location = session->locations()->auto_loop_location()) == 0) { + return; + } + + set_selection_from_range (*location); +} + +void +Editor::set_selection_from_range (Location& range) +{ + if (clicked_trackview == 0) { + return; + } + + begin_reversible_command (_("set selection from range")); + selection->set (0, range.start(), range.end()); + commit_reversible_command (); +} + +void +Editor::amplitude_zoom_step (bool in) +{ + gdouble zoom = 1.0; + + if (in) { + zoom *= 2.0; + } else { + if (zoom > 2.0) { + zoom /= 2.0; + } else { + zoom = 1.0; + } + } + +#ifdef FIX_FOR_CANVAS + /* XXX DO SOMETHING */ +#endif +} + + +/* DELETION */ + + +void +Editor::delete_sample_forward () +{ +} + +void +Editor::delete_sample_backward () +{ +} + +void +Editor::delete_screen () +{ +} + +/* SEARCH */ + +void +Editor::search_backwards () +{ + /* what ? */ +} + +void +Editor::search_forwards () +{ + /* what ? */ +} + +/* MARKS */ + +void +Editor::jump_forward_to_mark () +{ + if (!session) { + return; + } + + Location *location = session->locations()->first_location_after (playhead_cursor->current_frame); + + if (location) { + session->request_locate (location->start(), session->transport_rolling()); + } else { + session->request_locate (session->current_end_frame()); + } +} + +void +Editor::jump_backward_to_mark () +{ + if (!session) { + return; + } + + Location *location = session->locations()->first_location_before (playhead_cursor->current_frame); + + if (location) { + session->request_locate (location->start(), session->transport_rolling()); + } else { + session->request_locate (0); + } +} + +void +Editor::set_mark () +{ + jack_nframes_t pos; + float prefix; + bool was_floating; + + if (get_prefix (prefix, was_floating)) { + pos = session->audible_frame (); + } else { + if (was_floating) { + pos = (jack_nframes_t) floor (prefix * session->frame_rate ()); + } else { + pos = (jack_nframes_t) floor (prefix); + } + } + + session->locations()->add (new Location (pos, 0, "mark", Location::IsMark), true); +} + +void +Editor::clear_markers () +{ + if (session) { + session->begin_reversible_command (_("clear markers")); + session->add_undo (session->locations()->get_memento()); + session->locations()->clear_markers (); + session->add_redo_no_execute (session->locations()->get_memento()); + session->commit_reversible_command (); + } +} + +void +Editor::clear_ranges () +{ + if (session) { + session->begin_reversible_command (_("clear ranges")); + session->add_undo (session->locations()->get_memento()); + + 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); + + session->add_redo_no_execute (session->locations()->get_memento()); + session->commit_reversible_command (); + } +} + +void +Editor::clear_locations () +{ + session->begin_reversible_command (_("clear locations")); + session->add_undo (session->locations()->get_memento()); + session->locations()->clear (); + session->add_redo_no_execute (session->locations()->get_memento()); + session->commit_reversible_command (); + session->locations()->clear (); +} + +/* INSERT/REPLACE */ + +void +Editor::insert_region_list_drag (AudioRegion& region) +{ + gint x, y; + double wx, wy; + double cx, cy; + TimeAxisView *tv; + jack_nframes_t where; + AudioTimeAxisView *atv = 0; + Playlist *playlist; + + track_canvas->get_pointer (x, y); + + gtk_canvas_window_to_world (GTK_CANVAS(track_gtk_canvas), x, y, &wx, &wy); + + 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; + } + + if ((tv = trackview_by_y_position (cy)) == 0) { + return; + } + + if ((atv = dynamic_cast<AudioTimeAxisView*>(tv)) == 0) { + return; + } + + if ((playlist = atv->playlist()) == 0) { + return; + } + + snap_to (where); + + begin_reversible_command (_("insert dragged region")); + session->add_undo (playlist->get_memento()); + playlist->add_region (*(new AudioRegion (region)), where, 1.0); + session->add_redo_no_execute (playlist->get_memento()); + commit_reversible_command (); +} + +void +Editor::insert_region_list_selection (float times) +{ + AudioTimeAxisView *tv = 0; + Playlist *playlist; + + if (clicked_audio_trackview != 0) { + tv = clicked_audio_trackview; + } else if (!selection->tracks.empty()) { + if ((tv = dynamic_cast<AudioTimeAxisView*>(selection->tracks.front())) == 0) { + return; + } + } else { + return; + } + + if ((playlist = tv->playlist()) == 0) { + return; + } + + Gtk::CTree_Helpers::SelectionList& selected = region_list_display.selection(); + + if (selected.empty()) { + return; + } + + Region* region = reinterpret_cast<Region *> (selected.front().get_data ()); + + begin_reversible_command (_("insert region")); + session->add_undo (playlist->get_memento()); + playlist->add_region (*(createRegion (*region)), edit_cursor->current_frame, times); + session->add_redo_no_execute (playlist->get_memento()); + commit_reversible_command (); +} + + +/* BUILT-IN EFFECTS */ + +void +Editor::reverse_selection () +{ + +} + +/* GAIN ENVELOPE EDITING */ + +void +Editor::edit_envelope () +{ +} + +/* PLAYBACK */ + +void +Editor::toggle_playback (bool with_abort) +{ + if (!session) { + return; + } + + switch (session->slave_source()) { + case Session::None: + case Session::JACK: + break; + default: + /* transport controlled by the master */ + return; + } + + if (session->is_auditioning()) { + session->cancel_audition (); + return; + } + + if (session->transport_rolling()) { + session->request_stop (with_abort); + if (session->get_auto_loop()) { + session->request_auto_loop (false); + } + } else { + session->request_transport_speed (1.0f); + } +} + +void +Editor::play_from_start () +{ + session->request_locate (0, true); +} + +void +Editor::play_selection () +{ + if (selection->time.empty()) { + return; + } + + session->request_play_range (true); +} + +void +Editor::play_selected_region () +{ + if (!selection->audio_regions.empty()) { + AudioRegionView *rv = *(selection->audio_regions.begin()); + + session->request_bounded_roll (rv->region.position(), rv->region.last_frame()); + } +} + +void +Editor::toggle_loop_playback () +{ + if (session) { + session->request_auto_loop (true); + } +} + +void +Editor::loop_selected_region () +{ + if (!selection->audio_regions.empty()) { + AudioRegionView *rv = *(selection->audio_regions.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_auto_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_auto_loop (true); + session->request_locate (tll->start(), true); + } +} + +void +Editor::toggle_region_mute () +{ + if (clicked_regionview) { + clicked_regionview->region.set_muted (!clicked_regionview->region.muted()); + } else if (!selection->audio_regions.empty()) { + bool yn = ! (*selection->audio_regions.begin())->region.muted(); + selection->foreach_audio_region (&AudioRegion::set_muted, yn); + } +} + +void +Editor::toggle_region_opaque () +{ + if (clicked_regionview) { + clicked_regionview->region.set_opaque (!clicked_regionview->region.opaque()); + } else if (!selection->audio_regions.empty()) { + bool yn = ! (*selection->audio_regions.begin())->region.opaque(); + selection->foreach_audio_region (&Region::set_opaque, yn); + } +} + +void +Editor::raise_region () +{ + selection->foreach_audio_region (&Region::raise); +} + +void +Editor::raise_region_to_top () +{ + selection->foreach_audio_region (&Region::raise_to_top); +} + +void +Editor::lower_region () +{ + selection->foreach_audio_region (&Region::lower); +} + +void +Editor::lower_region_to_bottom () +{ + selection->foreach_audio_region (&Region::lower_to_bottom); +} + +void +Editor::edit_region () +{ + if (clicked_regionview == 0) { + return; + } + + clicked_regionview->show_region_editor (); +} + +void +Editor::rename_region () +{ + Dialog dialog; + Entry entry; + Button ok_button (_("OK")); + Button cancel_button (_("Cancel")); + + if (selection->audio_regions.empty()) { + return; + } + + dialog.set_title (_("ardour: rename region")); + dialog.set_name ("RegionRenameWindow"); + dialog.set_usize (300, -1); + dialog.set_position (GTK_WIN_POS_MOUSE); + dialog.set_modal (true); + + 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); + + entry.set_name ("RegionNameDisplay"); + ok_button.set_name ("EditorGTKButton"); + cancel_button.set_name ("EditorGTKButton"); + + region_renamed = false; + + entry.activate.connect (bind (slot (*this, &Editor::rename_region_finished), true)); + ok_button.clicked.connect (bind (slot (*this, &Editor::rename_region_finished), true)); + cancel_button.clicked.connect (bind (slot (*this, &Editor::rename_region_finished), false)); + + /* recurse */ + + dialog.show_all (); + ARDOUR_UI::instance()->allow_focus (true); + Main::run (); + ARDOUR_UI::instance()->allow_focus (false); + + if (region_renamed) { + (*selection->audio_regions.begin())->region.set_name (entry.get_text()); + redisplay_regions (); + } +} + +void +Editor::rename_region_finished (bool status) + +{ + region_renamed = status; + Main::quit (); +} + +void +Editor::audition_playlist_region_via_route (AudioRegion& 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 ? */ +} + +void +Editor::audition_selected_region () +{ + if (!selection->audio_regions.empty()) { + AudioRegionView* rv = *(selection->audio_regions.begin()); + session->audition_region (rv->region); + } +} + +void +Editor::audition_playlist_region_standalone (AudioRegion& region) +{ + session->audition_region (region); +} + +void +Editor::build_interthread_progress_window () +{ + interthread_progress_window = new ArdourDialog (X_("interthread progress")); + + interthread_progress_bar.set_orientation (GTK_PROGRESS_LEFT_TO_RIGHT); + + interthread_progress_vbox.set_border_width (10); + interthread_progress_vbox.set_spacing (5); + interthread_progress_vbox.pack_start (interthread_progress_label, false, false); + interthread_progress_vbox.pack_start (interthread_progress_bar,false, false); + interthread_progress_vbox.pack_start (interthread_cancel_button,false, false); + + interthread_cancel_button.add (interthread_cancel_label); + + interthread_cancel_button.clicked.connect (slot (*this, &Editor::interthread_cancel_clicked)); + + interthread_progress_window->set_modal (true); + interthread_progress_window->set_default_size (200, 100); + interthread_progress_window->add (interthread_progress_vbox); +} + +void +Editor::interthread_cancel_clicked () +{ + if (current_interthread_info) { + current_interthread_info->cancel = true; + } +} + +void * +Editor::_import_thread (void *arg) +{ + PBD::ThreadCreated (pthread_self(), X_("Import")); + + Editor *ed = (Editor *) arg; + return ed->import_thread (); +} + +void * +Editor::import_thread () +{ + session->import_audiofile (import_status); + return 0; +} + +gint +Editor::import_progress_timeout (void *arg) +{ + interthread_progress_label.set_text (import_status.doing_what); + + if (import_status.freeze) { + interthread_cancel_button.set_sensitive(false); + } else { + interthread_cancel_button.set_sensitive(true); + } + + if (import_status.doing_what == "building peak files") { + interthread_progress_bar.set_activity_mode (true); + return FALSE; + } else { + interthread_progress_bar.set_percentage (import_status.progress); + } + + return !(import_status.done || import_status.cancel); +} + +void +Editor::import_audio (bool as_tracks) +{ + if (session == 0) { + warning << _("You can't import an audiofile until you have a session loaded.") << endmsg; + return; + } + + SoundFileSelector& sfdb (ARDOUR_UI::instance()->get_sfdb_window()); + SigC::Connection c; + string str; + + if (as_tracks) { + c = sfdb.Action.connect (bind (slot (*this, &Editor::do_import), true)); + str =_("Import selected as tracks"); + } else { + c = sfdb.Action.connect (bind (slot (*this, &Editor::do_import), false)); + str = _("Import selected to region list"); + } + + sfdb.run (str, true); + c.disconnect (); +} + +void +Editor::catch_new_audio_region (AudioRegion* ar) +{ + last_audio_region = ar; +} + +void +Editor::do_import (vector<string> paths, bool split, bool as_tracks) +{ + SigC::Connection c; + + /* SFDB sets "multichan" to true to indicate "split channels" + so reverse the setting to match the way libardour + interprets it. + */ + + import_status.multichan = !split; + + if (interthread_progress_window == 0) { + build_interthread_progress_window (); + } + + interthread_progress_window->set_title (_("ardour: audio import in progress")); + interthread_progress_window->set_position (GTK_WIN_POS_MOUSE); + interthread_progress_window->show_all (); + interthread_progress_bar.set_percentage (0.0f); + interthread_cancel_label.set_text (_("Cancel Import")); + current_interthread_info = &import_status; + + c = session->AudioRegionAdded.connect (slot (*this, &Editor::catch_new_audio_region)); + + for (vector<string>::iterator i = paths.begin(); i != paths.end(); ++i ) { + + interthread_progress_window->set_title (compose (_("ardour: importing %1"), (*i))); + + import_status.pathname = (*i); + import_status.done = false; + import_status.cancel = false; + import_status.freeze = false; + import_status.done = 0.0; + + interthread_progress_connection = + Gtk::Main::timeout.connect (bind (slot (*this, &Editor::import_progress_timeout), (gpointer) 0), 100); + + last_audio_region = 0; + + pthread_create_and_store ("import", &import_status.thread, 0, _import_thread, this); + pthread_detach (import_status.thread); + + while (!(import_status.done || import_status.cancel)) { + gtk_main_iteration (); + } + + import_status.done = true; + interthread_progress_connection.disconnect (); + + if (as_tracks && last_audio_region != 0) { + uint32_t channels = last_audio_region->n_channels(); + + AudioTrack* at = session->new_audio_track (channels, channels); + AudioRegion* copy = new AudioRegion (*last_audio_region); + at->disk_stream().playlist()->add_region (*copy, 0); + } + } + + c.disconnect (); + interthread_progress_window->hide_all (); +} + +int +Editor::reject_because_rate_differs (string path, SF_INFO& finfo, string action, bool multiple_pending) +{ + if (!session) { + return 1; + } + + if (finfo.samplerate != (int) session->frame_rate()) { + vector<string> choices; + + choices.push_back (compose (_("%1 it anyway"), action)); + + if (multiple_pending) { + /* XXX assumptions about sentence structure + here for translators. Sorry. + */ + choices.push_back (compose (_("Don't %1 it"), action)); + choices.push_back (compose (_("%1 all without questions"), action)); + choices.push_back (_("Cancel entire import")); + } else { + choices.push_back (_("Cancel")); + } + + Gtkmmext::Choice rate_choice ( + compose (_("%1\nThis audiofile's sample rate doesn't match the session sample rate!"), path), + choices); + + rate_choice.chosen.connect (Main::quit.slot()); + rate_choice.show_all (); + + Main::run (); + + switch (rate_choice.get_choice()) { + case 0: /* do it anyway */ + return 0; + case 1: /* don't import this one */ + return 1; + case 2: /* do the rest without asking */ + return -1; + case 3: /* stop a multi-file import */ + default: + return -2; + } + } + + return 0; +} + +void +Editor::embed_audio () +{ + if (session == 0) { + warning << _("You can't embed an audiofile until you have a session loaded.") << endmsg; + return; + } + + SoundFileSelector& sfdb (ARDOUR_UI::instance()->get_sfdb_window()); + SigC::Connection c = sfdb.Action.connect (slot (*this, &Editor::do_embed_sndfiles)); + + sfdb.run (_("Add to External Region list"), true); + + c.disconnect (); +} + +void +Editor::do_embed_sndfiles (vector<string> paths, bool split) +{ + bool multiple_files = paths.size() > 1; + bool check_sample_rate = true; + + for (vector<string>::iterator i = paths.begin(); i != paths.end(); ++i) { + embed_sndfile (*i, split, multiple_files, check_sample_rate); + } + + session->save_state (""); +} + +void +Editor::embed_sndfile (string path, bool split, bool multiple_files, bool& check_sample_rate) +{ + SndFileSource *source = 0; /* keep g++ quiet */ + AudioRegion::SourceList sources; + string idspec; + string linked_path; + SNDFILE *sf; + SF_INFO finfo; + + /* lets see if we can link it into the session */ + + linked_path = session->sound_dir(); + linked_path += PBD::basename (path); + + if (link (path.c_str(), linked_path.c_str()) == 0) { + + /* there are many reasons why link(2) might have failed. + but if it succeeds, we now have a link in the + session sound dir that will protect against + unlinking of the original path. nice. + */ + + path = linked_path; + } + + memset (&finfo, 0, sizeof(finfo)); + + /* note that we temporarily truncated _id at the colon */ + + if ((sf = sf_open (path.c_str(), SFM_READ, &finfo)) == 0) { + char errbuf[256]; + sf_error_str (0, errbuf, sizeof (errbuf) - 1); + error << compose(_("Editor: cannot open file \"%1\" (%2)"), selection, errbuf) << endmsg; + return; + } + sf_close (sf); + sf = 0; + + if (check_sample_rate) { + switch (reject_because_rate_differs (path, finfo, "Embed", multiple_files)) { + case 0: + break; + case 1: + return; + case -1: + check_sample_rate = false; + break; + + case -2: + default: + return; + } + } + + track_canvas_scroller.get_window().set_cursor (GDK_WATCH); + ARDOUR_UI::instance()->flush_pending (); + + /* make the proper number of channels in the region */ + + for (int n=0; n < finfo.channels; ++n) + { + idspec = path; + idspec += compose(":%1", n); + + try { + source = new SndFileSource (idspec.c_str()); + sources.push_back(source); + } + + catch (failed_constructor& err) { + error << compose(_("could not open %1"), path) << endmsg; + goto out; + } + + ARDOUR_UI::instance()->flush_pending (); + } + + if (sources.size() > 0) { + + string region_name = PBD::basename_nosuffix (path); + region_name += "-0"; + + /* The created region isn't dropped. It emits a signal + that is picked up by the session. + */ + + new AudioRegion (sources, 0, sources[0]->length(), region_name, 0, + Region::Flag (Region::DefaultFlags|Region::WholeFile|Region::External)); + + /* make sure we can see it in the list */ + + Gtk::CTree_Helpers::RowList::iterator external_node; + external_node = region_list_display.rows().begin(); + ++external_node; /* its the second node, always */ + external_node->expand_recursive (); + + ARDOUR_UI::instance()->flush_pending (); + } + + out: + track_canvas_scroller.get_window().set_cursor (current_canvas_cursor); +} + +void +Editor::insert_sndfile (bool as_tracks) +{ + SoundFileSelector& sfdb (ARDOUR_UI::instance()->get_sfdb_window()); + SigC::Connection c; + string str; + + if (as_tracks) { + + c = sfdb.Action.connect (slot (*this, &Editor::insert_paths_as_new_tracks)); + str = _("Insert selected as new tracks"); + + } else { + + jack_nframes_t pos; + + if (clicked_audio_trackview == 0) { + return; + } + + if (ensure_cursor (&pos)) { + return; + } + + c = sfdb.Action.connect (bind (slot (*this, &Editor::do_insert_sndfile), pos)); + str = _("Insert selected"); + } + + sfdb.run (str, false); + c.disconnect (); +} + +void +Editor::insert_paths_as_new_tracks (vector<string> paths, bool split) +{ + SNDFILE *sf; + SF_INFO finfo; + bool multiple_files; + bool check_sample_rate = true; + + multiple_files = paths.size() > 1; + + for (vector<string>::iterator p = paths.begin(); p != paths.end(); ++p) { + + memset (&finfo, 0, sizeof(finfo)); + + if ((sf = sf_open ((*p).c_str(), SFM_READ, &finfo)) == 0) { + char errbuf[256]; + sf_error_str (0, errbuf, sizeof (errbuf) - 1); + error << compose(_("Editor: cannot open file \"%1\" (%2)"), (*p), errbuf) << endmsg; + continue; + } + + sf_close (sf); + sf = 0; + + /* add a new track */ + + if (check_sample_rate) { + switch (reject_because_rate_differs (*p, finfo, "Insert", multiple_files)) { + case 0: + break; + case 1: + continue; + case -1: + check_sample_rate = false; + break; + + case -2: + return; + } + } + + uint32_t input_chan = finfo.channels; + uint32_t output_chan; + + if (session->get_output_auto_connect() & Session::AutoConnectMaster) { + output_chan = (session->master_out() ? session->master_out()->n_inputs() : input_chan); + } else { + output_chan = input_chan; + } + + (void) session->new_audio_track (input_chan, output_chan); + + + /* get the last (most recently added) track view */ + + AudioTimeAxisView* tv; + + if ((tv = dynamic_cast<AudioTimeAxisView*>(track_views.back())) == 0) { + fatal << _("programming error: ") + << X_("last trackview after new_audio_track is not an audio track!") + << endmsg; + /*NOTREACHED*/ + } + + jack_nframes_t pos = 0; + insert_sndfile_into (*p, true, tv, pos, false); + } +} + +void +Editor::do_insert_sndfile (vector<string> paths, bool split, jack_nframes_t pos) +{ + for (vector<string>::iterator x = paths.begin(); x != paths.end(); ++x) { + insert_sndfile_into (*x, !split, clicked_audio_trackview, pos); + } +} + +void +Editor::insert_sndfile_into (string path, bool multi, AudioTimeAxisView* tv, jack_nframes_t& pos, bool prompt) +{ + SndFileSource *source = 0; /* keep g++ quiet */ + AudioRegion::SourceList sources; + string idspec; + SNDFILE *sf; + SF_INFO finfo; + + memset (&finfo, 0, sizeof(finfo)); + + /* note that we temporarily truncated _id at the colon */ + + if ((sf = sf_open (path.c_str(), SFM_READ, &finfo)) == 0) { + char errbuf[256]; + sf_error_str (0, errbuf, sizeof (errbuf) - 1); + error << compose(_("Editor: cannot open file \"%1\" (%2)"), path, errbuf) << endmsg; + return; + } + sf_close (sf); + sf = 0; + + if (prompt && (reject_because_rate_differs (path, finfo, "Insert", false) != 0)) { + return; + } + + track_canvas_scroller.get_window().set_cursor (GDK_WATCH); + ARDOUR_UI::instance()->flush_pending (); + + /* make the proper number of channels in the region */ + + for (int n=0; n < finfo.channels; ++n) + { + idspec = path; + idspec += compose(":%1", n); + + try { + source = new SndFileSource (idspec.c_str()); + sources.push_back(source); + } + + catch (failed_constructor& err) { + error << compose(_("could not open %1"), path) << endmsg; + goto out; + } + + ARDOUR_UI::instance()->flush_pending (); + } + + if (sources.size() > 0) { + + string region_name = region_name_from_path (PBD::basename (path)); + + AudioRegion *region = new AudioRegion (sources, 0, sources[0]->length(), region_name, + 0, /* irrelevant these days */ + Region::Flag (Region::DefaultFlags|Region::WholeFile|Region::External)); + + begin_reversible_command (_("insert sndfile")); + session->add_undo (tv->playlist()->get_memento()); + tv->playlist()->add_region (*region, pos); + session->add_redo_no_execute (tv->playlist()->get_memento()); + commit_reversible_command (); + + pos += sources[0]->length(); + + ARDOUR_UI::instance()->flush_pending (); + } + + out: + track_canvas_scroller.get_window().set_cursor (current_canvas_cursor); + return; +} + +void +Editor::region_from_selection () +{ + if (clicked_trackview == 0) { + return; + } + + if (selection->time.empty()) { + return; + } + + jack_nframes_t start = selection->time[clicked_selection].start; + jack_nframes_t end = selection->time[clicked_selection].end; + + jack_nframes_t selection_cnt = end - start + 1; + + for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) { + + AudioRegion *region; + AudioRegion *current; + Region* current_r; + Playlist *pl; + + jack_nframes_t internal_start; + string new_name; + + if ((pl = (*i)->playlist()) == 0) { + continue; + } + + if ((current_r = pl->top_region_at (start)) == 0) { + continue; + } + + if ((current = dynamic_cast<AudioRegion*> (current_r)) != 0) { + internal_start = start - current->position(); + session->region_name (new_name, current->name(), true); + region = new AudioRegion (*current, internal_start, selection_cnt, new_name); + } + } +} + +void +Editor::create_region_from_selection (vector<AudioRegion *>& new_regions) +{ + if (selection->time.empty() || selection->tracks.empty()) { + return; + } + + jack_nframes_t start = selection->time[clicked_selection].start; + jack_nframes_t end = selection->time[clicked_selection].end; + + for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) { + + AudioRegion* current; + Region* current_r; + Playlist* playlist; + jack_nframes_t internal_start; + string new_name; + + if ((playlist = (*i)->playlist()) == 0) { + continue; + } + + if ((current_r = playlist->top_region_at(start)) == 0) { + continue; + } + + if ((current = dynamic_cast<AudioRegion*>(current_r)) == 0) { + continue; + } + + internal_start = start - current->position(); + session->region_name (new_name, current->name(), true); + + new_regions.push_back (new AudioRegion (*current, internal_start, end - start + 1, new_name)); + } +} + +void +Editor::split_multichannel_region () +{ + vector<AudioRegion*> v; + + if (!clicked_regionview || clicked_regionview->region.n_channels() < 2) { + return; + } + + clicked_regionview->region.separate_by_channel (*session, v); + + /* nothing else to do, really */ +} + +void +Editor::new_region_from_selection () +{ + region_from_selection (); + cancel_selection (); +} + +void +Editor::separate_region_from_selection () +{ + bool doing_undo = false; + + if (selection->time.empty()) { + return; + } + + Playlist *playlist; + + for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) { + + AudioTimeAxisView* atv; + + if ((atv = dynamic_cast<AudioTimeAxisView*> ((*i))) != 0) { + + if (atv->is_audio_track()) { + + if ((playlist = atv->playlist()) != 0) { + if (!doing_undo) { + begin_reversible_command (_("separate")); + doing_undo = true; + } + if (doing_undo) session->add_undo ((playlist)->get_memento()); + + /* XXX need to consider musical time selections here at some point */ + + double speed = atv->get_diskstream()->speed(); + + for (list<AudioRange>::iterator t = selection->time.begin(); t != selection->time.end(); ++t) { + playlist->partition ((jack_nframes_t)((*t).start * speed), (jack_nframes_t)((*t).end * speed), true); + } + + if (doing_undo) session->add_redo_no_execute (playlist->get_memento()); + } + } + } + } + + if (doing_undo) commit_reversible_command (); +} + +void +Editor::crop_region_to_selection () +{ + if (selection->time.empty()) { + return; + } + + vector<Playlist*> playlists; + Playlist *playlist; + + if (clicked_trackview != 0) { + + if ((playlist = clicked_trackview->playlist()) == 0) { + return; + } + + playlists.push_back (playlist); + + } else { + + for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) { + + AudioTimeAxisView* atv; + + if ((atv = dynamic_cast<AudioTimeAxisView*> ((*i))) != 0) { + + if (atv->is_audio_track()) { + + if ((playlist = atv->playlist()) != 0) { + playlists.push_back (playlist); + } + } + } + } + } + + if (!playlists.empty()) { + + jack_nframes_t start; + jack_nframes_t end; + jack_nframes_t cnt; + + begin_reversible_command (_("trim to selection")); + + for (vector<Playlist*>::iterator i = playlists.begin(); i != playlists.end(); ++i) { + + Region *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; + + session->add_undo ((*i)->get_memento()); + region->trim_to (start, cnt, this); + session->add_redo_no_execute ((*i)->get_memento()); + } + + commit_reversible_command (); + } +} + +void +Editor::region_fill_track () +{ + jack_nframes_t end; + + if (!session || selection->audio_regions.empty()) { + return; + } + + end = session->current_end_frame (); + + begin_reversible_command (_("region fill")); + + for (AudioRegionSelection::iterator i = selection->audio_regions.begin(); i != selection->audio_regions.end(); ++i) { + + AudioRegion& region ((*i)->region); + Playlist* pl = region.playlist(); + + if (end <= region.last_frame()) { + return; + } + + double times = (double) (end - region.last_frame()) / (double) region.length(); + + if (times == 0) { + return; + } + + session->add_undo (pl->get_memento()); + pl->add_region (*(new AudioRegion (region)), region.last_frame(), times); + session->add_redo_no_execute (pl->get_memento()); + } + + commit_reversible_command (); +} + +void +Editor::region_fill_selection () +{ + if (clicked_audio_trackview == 0 || !clicked_audio_trackview->is_audio_track()) { + return; + } + + if (selection->time.empty()) { + return; + } + + Region *region; + + Gtk::CTree_Helpers::SelectionList& selected = region_list_display.selection(); + + if (selected.empty()) { + return; + } + + region = reinterpret_cast<Region *> (selected.front().get_data()); + + jack_nframes_t start = selection->time[clicked_selection].start; + jack_nframes_t end = selection->time[clicked_selection].end; + + Playlist *playlist; + + if (selection->tracks.empty()) { + return; + } + + jack_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; + } + + session->add_undo (playlist->get_memento()); + playlist->add_region (*(createRegion (*region)), start, times); + session->add_redo_no_execute (playlist->get_memento()); + } + + commit_reversible_command (); +} + +void +Editor::set_region_sync_from_edit_cursor () +{ + if (clicked_regionview == 0) { + return; + } + + if (!clicked_regionview->region.covers (edit_cursor->current_frame)) { + error << _("Place the edit cursor at the desired sync point") << endmsg; + return; + } + + Region& region (clicked_regionview->region); + + begin_reversible_command (_("set sync from edit cursor")); + session->add_undo (region.playlist()->get_memento()); + region.set_sync_position (edit_cursor->current_frame); + session->add_redo_no_execute (region.playlist()->get_memento()); + commit_reversible_command (); +} + +void +Editor::remove_region_sync () +{ + if (clicked_regionview) { + Region& region (clicked_regionview->region); + begin_reversible_command (_("remove sync")); + session->add_undo (region.playlist()->get_memento()); + region.clear_sync_position (); + session->add_redo_no_execute (region.playlist()->get_memento()); + commit_reversible_command (); + } +} + +void +Editor::naturalize () +{ + if (selection->audio_regions.empty()) { + return; + } + begin_reversible_command (_("naturalize")); + for (AudioRegionSelection::iterator i = selection->audio_regions.begin(); i != selection->audio_regions.end(); ++i) { + session->add_undo ((*i)->region.get_memento()); + (*i)->region.move_to_natural_position (this); + session->add_redo_no_execute ((*i)->region.get_memento()); + } + commit_reversible_command (); +} + +void +Editor::align (RegionPoint what) +{ + align_selection (what, edit_cursor->current_frame); +} + +void +Editor::align_relative (RegionPoint what) +{ + align_selection_relative (what, edit_cursor->current_frame); +} + +struct RegionSortByTime { + bool operator() (const AudioRegionView* a, const AudioRegionView* b) { + return a->region.position() < b->region.position(); + } +}; + +void +Editor::align_selection_relative (RegionPoint point, jack_nframes_t position) +{ + if (selection->audio_regions.empty()) { + return; + } + + jack_nframes_t distance; + jack_nframes_t pos = 0; + int dir; + + list<AudioRegionView*> sorted; + selection->audio_regions.by_position (sorted); + Region& r ((*sorted.begin())->region); + + 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; + } + + if (pos > position) { + distance = pos - position; + dir = -1; + } else { + distance = position - pos; + dir = 1; + } + + begin_reversible_command (_("align selection (relative)")); + + for (AudioRegionSelection::iterator i = selection->audio_regions.begin(); i != selection->audio_regions.end(); ++i) { + + Region& region ((*i)->region); + + session->add_undo (region.playlist()->get_memento()); + + if (dir > 0) { + region.set_position (region.position() + distance, this); + } else { + region.set_position (region.position() - distance, this); + } + + session->add_redo_no_execute (region.playlist()->get_memento()); + + } + + commit_reversible_command (); +} + +void +Editor::align_selection (RegionPoint point, jack_nframes_t position) +{ + if (selection->audio_regions.empty()) { + return; + } + + begin_reversible_command (_("align selection")); + + for (AudioRegionSelection::iterator i = selection->audio_regions.begin(); i != selection->audio_regions.end(); ++i) { + align_region_internal ((*i)->region, point, position); + } + + commit_reversible_command (); +} + +void +Editor::align_region (Region& region, RegionPoint point, jack_nframes_t position) +{ + begin_reversible_command (_("align region")); + align_region_internal (region, point, position); + commit_reversible_command (); +} + +void +Editor::align_region_internal (Region& region, RegionPoint point, jack_nframes_t position) +{ + session->add_undo (region.playlist()->get_memento()); + + 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; + } + + session->add_redo_no_execute (region.playlist()->get_memento()); +} + +void +Editor::trim_region_to_edit_cursor () +{ + if (clicked_regionview == 0) { + return; + } + + Region& region (clicked_regionview->region); + + float speed = 1.0f; + AudioTimeAxisView *atav; + + if ( clicked_trackview != 0 && (atav = dynamic_cast<AudioTimeAxisView*>(clicked_trackview)) != 0 ) { + if (atav->get_diskstream() != 0) { + speed = atav->get_diskstream()->speed(); + } + } + + begin_reversible_command (_("trim to edit")); + session->add_undo (region.playlist()->get_memento()); + region.trim_end ( (jack_nframes_t) floor( (float)edit_cursor->current_frame * speed), this); + session->add_redo_no_execute (region.playlist()->get_memento()); + commit_reversible_command (); +} + +void +Editor::trim_region_from_edit_cursor () +{ + if (clicked_regionview == 0) { + return; + } + + Region& region (clicked_regionview->region); + + float speed = 1.0f; + AudioTimeAxisView *atav; + + if ( clicked_trackview != 0 && (atav = dynamic_cast<AudioTimeAxisView*>(clicked_trackview)) != 0 ) { + if (atav->get_diskstream() != 0) { + speed = atav->get_diskstream()->speed(); + } + } + + begin_reversible_command (_("trim to edit")); + session->add_undo (region.playlist()->get_memento()); + region.trim_front ( (jack_nframes_t) floor( (float)edit_cursor->current_frame * speed), this); + session->add_redo_no_execute (region.playlist()->get_memento()); + commit_reversible_command (); +} + +void +Editor::unfreeze_route () +{ + if (clicked_audio_trackview == 0 || !clicked_audio_trackview->is_audio_track()) { + return; + } + + clicked_audio_trackview->audio_track()->unfreeze (); +} + +void* +Editor::_freeze_thread (void* arg) +{ + PBD::ThreadCreated (pthread_self(), X_("Freeze")); + return static_cast<Editor*>(arg)->freeze_thread (); +} + +void* +Editor::freeze_thread () +{ + clicked_audio_trackview->audio_track()->freeze (*current_interthread_info); + return 0; +} + +gint +Editor::freeze_progress_timeout (void *arg) +{ + interthread_progress_bar.set_percentage (current_interthread_info->progress); + return !(current_interthread_info->done || current_interthread_info->cancel); +} + +void +Editor::freeze_route () +{ + if (clicked_audio_trackview == 0 || !clicked_audio_trackview->is_audio_track()) { + 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_percentage (0.0f); + interthread_progress_label.set_text (""); + interthread_cancel_label.set_text (_("Cancel Freeze")); + current_interthread_info = &itt; + + interthread_progress_connection = + Gtk::Main::timeout.connect (bind (slot (*this, &Editor::freeze_progress_timeout), (gpointer) 0), 100); + + itt.done = false; + itt.cancel = false; + itt.progress = 0.0f; + + pthread_create (&itt.thread, 0, _freeze_thread, this); + + track_canvas_scroller.get_window().set_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_scroller.get_window().set_cursor (current_canvas_cursor); +} + +void +Editor::bounce_range_selection () +{ + if (selection->time.empty()) { + return; + } + + TrackViewList *views = get_valid_views (selection->time.track, selection->time.group); + + jack_nframes_t start = selection->time[clicked_selection].start; + jack_nframes_t end = selection->time[clicked_selection].end; + jack_nframes_t cnt = end - start + 1; + + begin_reversible_command (_("bounce range")); + + for (TrackViewList::iterator i = views->begin(); i != views->end(); ++i) { + + AudioTimeAxisView* atv; + + if ((atv = dynamic_cast<AudioTimeAxisView*> (*i)) == 0) { + continue; + } + + Playlist* playlist; + + if ((playlist = atv->playlist()) == 0) { + return; + } + + InterThreadInfo itt; + + itt.done = false; + itt.cancel = false; + itt.progress = false; + + session->add_undo (playlist->get_memento()); + atv->audio_track()->bounce_range (start, cnt, itt); + session->add_redo_no_execute (playlist->get_memento()); + } + + commit_reversible_command (); + + delete views; +} + +void +Editor::cut () +{ + cut_copy (Cut); +} + +void +Editor::copy () +{ + cut_copy (Copy); +} + +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; + } + + cut_buffer->clear (); + + switch (current_mouse_mode()) { + case MouseObject: + if (!selection->audio_regions.empty() || !selection->points.empty()) { + + begin_reversible_command (opname + _(" objects")); + + if (!selection->audio_regions.empty()) { + + cut_copy_regions (op); + + if (op == Cut) { + selection->clear_audio_regions (); + } + } + + if (!selection->points.empty()) { + cut_copy_points (op); + + if (op == Cut) { + selection->clear_points (); + } + } + + commit_reversible_command (); + } + break; + + case MouseRange: + if (!selection->time.empty()) { + + begin_reversible_command (opname + _(" range")); + cut_copy_ranges (op); + commit_reversible_command (); + + if (op == Cut) { + selection->clear_time (); + } + + } + break; + + default: + break; + } +} + +void +Editor::cut_copy_points (CutCopyOp op) +{ + for (PointSelection::iterator i = selection->points.begin(); i != selection->points.end(); ++i) { + + AutomationTimeAxisView* atv = dynamic_cast<AutomationTimeAxisView*>(&(*i).track); + + if (atv) { + atv->cut_copy_clear_objects (selection->points, op); + } + } +} + +void +Editor::cut_copy_regions (CutCopyOp op) +{ + typedef map<AudioPlaylist*,AudioPlaylist*> PlaylistMapping; + PlaylistMapping pmap; + jack_nframes_t first_position = max_frames; + set<Playlist*> freezelist; + pair<set<Playlist*>::iterator,bool> insert_result; + + for (AudioRegionSelection::iterator x = selection->audio_regions.begin(); x != selection->audio_regions.end(); ++x) { + first_position = min ((*x)->region.position(), first_position); + + if (op == Cut || op == Clear) { + AudioPlaylist *pl = dynamic_cast<AudioPlaylist*>((*x)->region.playlist()); + if (pl) { + insert_result = freezelist.insert (pl); + if (insert_result.second) { + pl->freeze (); + session->add_undo (pl->get_memento()); + } + } + } + } + + for (AudioRegionSelection::iterator x = selection->audio_regions.begin(); x != selection->audio_regions.end(); ) { + + AudioPlaylist *pl = dynamic_cast<AudioPlaylist*>((*x)->region.playlist()); + AudioPlaylist* npl; + AudioRegionSelection::iterator tmp; + + tmp = x; + ++tmp; + + if (pl) { + + PlaylistMapping::iterator pi = pmap.find (pl); + + if (pi == pmap.end()) { + npl = new AudioPlaylist (*session, "cutlist", true); + npl->freeze(); + pmap[pl] = npl; + } else { + npl = pi->second; + } + + switch (op) { + case Cut: + npl->add_region (*(new AudioRegion ((*x)->region)), (*x)->region.position() - first_position); + pl->remove_region (&((*x)->region)); + break; + + case Copy: + npl->add_region (*(new AudioRegion ((*x)->region)), (*x)->region.position() - first_position); + break; + + case Clear: + pl->remove_region (&((*x)->region)); + break; + } + } + + x = tmp; + } + + list<Playlist*> foo; + + for (PlaylistMapping::iterator i = pmap.begin(); i != pmap.end(); ++i) { + foo.push_back (i->second); + } + + if (!foo.empty()) { + cut_buffer->set (foo); + } + + for (set<Playlist*>::iterator pl = freezelist.begin(); pl != freezelist.end(); ++pl) { + (*pl)->thaw (); + session->add_redo_no_execute ((*pl)->get_memento()); + } +} + +void +Editor::cut_copy_ranges (CutCopyOp op) +{ + for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) { + (*i)->cut_copy_clear (*selection, op); + } +} + +void +Editor::paste (float times) +{ + paste_internal (edit_cursor->current_frame, times); +} + +void +Editor::mouse_paste () +{ + gint x, y; + double wx, wy; + track_canvas->get_pointer (x, y); + + gtk_canvas_window_to_world (GTK_CANVAS(track_gtk_canvas), x, y, &wx, &wy); + + GdkEvent event; + event.type = GDK_BUTTON_RELEASE; + event.button.x = wx; + event.button.y = wy; + + jack_nframes_t where = event_frame (&event, 0, 0); + snap_to (where); + paste_internal (where, 1); +} + +void +Editor::paste_internal (jack_nframes_t position, float times) +{ + bool commit = false; + + if (cut_buffer->empty() || selection->tracks.empty()) { + return; + } + + if (position == max_frames) { + position = edit_cursor->current_frame; + } + + begin_reversible_command (_("paste")); + + TrackSelection::iterator i; + size_t nth; + + for (nth = 0, i = selection->tracks.begin(); i != selection->tracks.end(); ++i, ++nth) { + + /* undo/redo is handled by individual tracks */ + + if ((*i)->paste (position, times, *cut_buffer, nth)) { + commit = true; + } + } + + if (commit) { + commit_reversible_command (); + } +} + +void +Editor::paste_named_selection (float times) +{ + Gtk::CList_Helpers::SelectionList& selected = named_selection_display.selection(); + TrackSelection::iterator i; + + if (selected.empty() || selection->tracks.empty()) { + return; + } + + NamedSelection* ns = static_cast<NamedSelection*> (selected.front()->get_data ()); + list<Playlist*>::iterator chunk; + list<Playlist*>::iterator tmp; + + chunk = ns->playlists.begin(); + + begin_reversible_command (_("paste chunk")); + + for (i = selection->tracks.begin(); i != selection->tracks.end(); ++i) { + + AudioTimeAxisView* atv; + Playlist* pl; + AudioPlaylist* apl; + + if ((atv = dynamic_cast<AudioTimeAxisView*> (*i)) == 0) { + continue; + } + + if ((pl = atv->playlist()) == 0) { + continue; + } + + if ((apl = dynamic_cast<AudioPlaylist*> (pl)) == 0) { + continue; + } + + tmp = chunk; + ++tmp; + + session->add_undo (apl->get_memento()); + apl->paste (**chunk, edit_cursor->current_frame, times); + session->add_redo_no_execute (apl->get_memento()); + + if (tmp != ns->playlists.end()) { + chunk = tmp; + } + } + + commit_reversible_command(); +} + +void +Editor::duplicate_some_regions (AudioRegionSelection& regions, float times) +{ + Playlist *playlist; + + begin_reversible_command (_("duplicate region")); + + for (AudioRegionSelection::iterator i = regions.begin(); i != regions.end(); ++i) { + + Region& r ((*i)->region); + + playlist = (*i)->region.playlist(); + session->add_undo (playlist->get_memento()); + playlist->duplicate (r, r.last_frame(), times); + session->add_redo_no_execute (playlist->get_memento()); + + } + + commit_reversible_command (); +} + +void +Editor::duplicate_selection (float times) +{ + if (selection->time.empty() || selection->tracks.empty()) { + return; + } + + Playlist *playlist; + vector<AudioRegion*> new_regions; + vector<AudioRegion*>::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; + } + session->add_undo (playlist->get_memento()); + playlist->duplicate (**ri, selection->time[clicked_selection].end, times); + session->add_redo_no_execute (playlist->get_memento()); + + ++ri; + if (ri == new_regions.end()) { + --ri; + } + } + + commit_reversible_command (); +} + +void +Editor::center_playhead () +{ + float page = canvas_width * frames_per_unit; + + center_screen_internal (playhead_cursor->current_frame, page); +} + +void +Editor::center_edit_cursor () +{ + float page = canvas_width * frames_per_unit; + + center_screen_internal (edit_cursor->current_frame, page); +} + +void +Editor::clear_playlist (Playlist& playlist) +{ + begin_reversible_command (_("clear playlist")); + session->add_undo (playlist.get_memento()); + playlist.clear (); + session->add_redo_no_execute (playlist.get_memento()); + commit_reversible_command (); +} + +void +Editor::nudge_track (bool use_edit_cursor, bool forwards) +{ + Playlist *playlist; + jack_nframes_t distance; + jack_nframes_t next_distance; + jack_nframes_t start; + + if (use_edit_cursor) { + start = edit_cursor->current_frame; + } else { + start = 0; + } + + if ((distance = get_nudge_distance (start, next_distance)) == 0) { + 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; + } + + session->add_undo (playlist->get_memento()); + playlist->nudge_after (start, distance, forwards); + session->add_redo_no_execute (playlist->get_memento()); + } + + commit_reversible_command (); +} + +void +Editor::toggle_xfades_active () +{ + if (session) { + session->set_crossfades_active (!session->get_crossfades_active()); + } +} + +void +Editor::toggle_follow_playhead () +{ + set_follow_playhead (!_follow_playhead); +} + +void +Editor::set_xfade_visibility (bool yn) +{ + +} + +void +Editor::toggle_xfade_visibility () +{ + set_xfade_visibility (!xfade_visibility()); +} + +void +Editor::remove_last_capture () +{ + vector<string> choices; + string prompt; + + 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 (_("Yes, destroy it.")); + choices.push_back (_("No, do nothing.")); + + Gtkmmext::Choice prompter (prompt, choices); + + prompter.chosen.connect (Gtk::Main::quit.slot()); + prompter.show_all (); + + Gtk::Main::run (); + + if (prompter.get_choice() == 0) { + session->remove_last_capture (); + } + } else { + session->remove_last_capture(); + } +} + +void +Editor::normalize_region () +{ + if (!session) { + return; + } + + if (selection->audio_regions.empty()) { + return; + } + + begin_reversible_command (_("normalize")); + + track_canvas_scroller.get_window().set_cursor (wait_cursor); + gdk_flush (); + + for (AudioRegionSelection::iterator r = selection->audio_regions.begin(); r != selection->audio_regions.end(); ++r) { + session->add_undo ((*r)->region.get_memento()); + (*r)->region.normalize_to (0.0f); + session->add_redo_no_execute ((*r)->region.get_memento()); + } + + commit_reversible_command (); + gdk_window_set_cursor (track_canvas_scroller.get_window(), current_canvas_cursor); +} + + +void +Editor::denormalize_region () +{ + if (!session) { + return; + } + + if (selection->audio_regions.empty()) { + return; + } + + begin_reversible_command ("denormalize"); + + for (AudioRegionSelection::iterator r = selection->audio_regions.begin(); r != selection->audio_regions.end(); ++r) { + session->add_undo ((*r)->region.get_memento()); + (*r)->region.set_scale_amplitude (1.0f); + session->add_redo_no_execute ((*r)->region.get_memento()); + } + + commit_reversible_command (); +} + + +void +Editor::reverse_region () +{ + if (!session) { + return; + } + + Reverse rev (*session); + apply_filter (rev, _("reverse regions")); +} + +void +Editor::apply_filter (AudioFilter& filter, string command) +{ + if (selection->audio_regions.empty()) { + return; + } + + begin_reversible_command (command); + + track_canvas_scroller.get_window().set_cursor (wait_cursor); + gdk_flush (); + + for (AudioRegionSelection::iterator r = selection->audio_regions.begin(); r != selection->audio_regions.end(); ) { + + AudioRegion& region ((*r)->region); + Playlist* playlist = region.playlist(); + + AudioRegionSelection::iterator tmp; + + tmp = r; + ++tmp; + + if (region.apply (filter) == 0) { + + session->add_undo (playlist->get_memento()); + playlist->replace_region (region, *(filter.results.front()), region.position()); + session->add_redo_no_execute (playlist->get_memento()); + } else { + goto out; + } + + r = tmp; + } + + commit_reversible_command (); + selection->audio_regions.clear (); + + out: + gdk_window_set_cursor (track_canvas_scroller.get_window(), current_canvas_cursor); +} + +void +Editor::region_selection_op (void (Region::*pmf)(void)) +{ + for (AudioRegionSelection::iterator i = selection->audio_regions.begin(); i != selection->audio_regions.end(); ++i) { + ((*i)->region.*pmf)(); + } +} + + +void +Editor::region_selection_op (void (Region::*pmf)(void*), void *arg) +{ + for (AudioRegionSelection::iterator i = selection->audio_regions.begin(); i != selection->audio_regions.end(); ++i) { + ((*i)->region.*pmf)(arg); + } +} + +void +Editor::region_selection_op (void (Region::*pmf)(bool), bool yn) +{ + for (AudioRegionSelection::iterator i = selection->audio_regions.begin(); i != selection->audio_regions.end(); ++i) { + ((*i)->region.*pmf)(yn); + } +} + +void +Editor::external_edit_region () +{ + if (!clicked_regionview) { + return; + } + + /* more to come */ +} + +void +Editor::brush (jack_nframes_t pos) +{ + AudioRegionSelection sel; + snap_to (pos); + + if (selection->audio_regions.empty()) { + /* XXX get selection from region list */ + } else { + sel = selection->audio_regions; + } + + if (sel.empty()) { + return; + } + + for (AudioRegionSelection::iterator i = selection->audio_regions.begin(); i != selection->audio_regions.end(); ++i) { + mouse_brush_insert_region ((*i), pos); + } +} + +void +Editor::toggle_gain_envelope_visibility () +{ + for (AudioRegionSelection::iterator i = selection->audio_regions.begin(); i != selection->audio_regions.end(); ++i) { + (*i)->set_envelope_visible (!(*i)->envelope_visible()); + } +} + +void +Editor::toggle_gain_envelope_active () +{ + for (AudioRegionSelection::iterator i = selection->audio_regions.begin(); i != selection->audio_regions.end(); ++i) { + AudioRegion* ar = dynamic_cast<AudioRegion*>(&(*i)->region); + if (ar) { + ar->set_envelope_active (true); + } + } +} |