Add comment about parameters to coverage() including the end point.
[ardour.git] / gtk2_ardour / editor_ops.cc
index 9c3b40798aed039564688b55ca8d9b0baf58172c..e8ca0e1ee2f7754256fd5f579340cd402c62ed27 100644 (file)
@@ -24,6 +24,7 @@
 #include <cstdlib>
 #include <cmath>
 #include <string>
+#include <limits>
 #include <map>
 #include <set>
 
@@ -63,6 +64,7 @@
 #include "audio_region_view.h"
 #include "audio_streamview.h"
 #include "audio_time_axis.h"
+#include "automation_region_view.h"
 #include "automation_time_axis.h"
 #include "control_point.h"
 #include "debug.h"
@@ -75,6 +77,7 @@
 #include "gui_thread.h"
 #include "insert_time_dialog.h"
 #include "interthread_progress_window.h"
+#include "item_counts.h"
 #include "keyboard.h"
 #include "midi_region_view.h"
 #include "mixer_strip.h"
@@ -1530,7 +1533,7 @@ Editor::temporal_zoom (framecnt_t fpp)
        // would be 4147200000 samples, so 2592000 samples per pixel.
 
        nfpp = min (fpp, (framecnt_t) 2592000);
-       nfpp = max ((framecnt_t) 1, fpp);
+       nfpp = max ((framecnt_t) 1, nfpp);
 
        new_page_size = (framepos_t) floor (_visible_canvas_width * nfpp);
        half_page_size = new_page_size / 2;
@@ -2700,6 +2703,7 @@ static void
 add_if_covered (RegionView* rv, const AudioRange* ar, RegionSelection* rs)
 {
        switch (rv->region()->coverage (ar->start, ar->end - 1)) {
+       // n.b. -1 because AudioRange::end is one past the end, but coverage expects inclusive ranges
        case Evoral::OverlapNone:
                break;
        default:
@@ -3843,26 +3847,8 @@ Editor::cut_copy (CutCopyOp op)
 
        bool did_edit = false;
 
-       if (!selection->points.empty()) {
-               begin_reversible_command (opname + _(" points"));
-               did_edit = true;
-               cut_copy_points (op);
-               if (op == Cut || op == Delete) {
-                       selection->clear_points ();
-               }
-       } else if (!selection->regions.empty() || !selection->points.empty()) {
-
-               string thing_name;
-
-               if (selection->regions.empty()) {
-                       thing_name = _("points");
-               } else if (selection->points.empty()) {
-                       thing_name = _("regions");
-               } else {
-                       thing_name = _("objects");
-               }
-       
-               begin_reversible_command (opname + ' ' + thing_name);
+       if (!selection->regions.empty() || !selection->points.empty()) {
+               begin_reversible_command (opname + ' ' + _("objects"));
                did_edit = true;
 
                if (!selection->regions.empty()) {
@@ -3889,7 +3875,7 @@ Editor::cut_copy (CutCopyOp op)
                        selection->set (start, end);
                }
        } else if (!selection->time.empty()) {
-               begin_reversible_command (opname + _(" range"));
+               begin_reversible_command (opname + ' ' + _("range"));
 
                did_edit = true;
                cut_copy_ranges (op);
@@ -3912,10 +3898,11 @@ Editor::cut_copy (CutCopyOp op)
 }
 
 struct AutomationRecord {
-       AutomationRecord () : state (0) {}
-       AutomationRecord (XMLNode* s) : state (s) {}
+       AutomationRecord () : state (0) , line(NULL) {}
+       AutomationRecord (XMLNode* s, const AutomationLine* l) : state (s) , line (l) {}
        
        XMLNode* state; ///< state before any operation
+       const AutomationLine* line; ///< line this came from
        boost::shared_ptr<Evoral::ControlList> copy; ///< copied events for the cut buffer
 };
 
@@ -3938,12 +3925,13 @@ Editor::cut_copy_points (CutCopyOp op)
 
        /* Go through all selected points, making an AutomationRecord for each distinct AutomationList */
        for (PointSelection::iterator i = selection->points.begin(); i != selection->points.end(); ++i) {
-               boost::shared_ptr<AutomationList> al = (*i)->line().the_list();
+               const AutomationLine&                   line = (*i)->line();
+               const boost::shared_ptr<AutomationList> al   = line.the_list();
                if (lists.find (al) == lists.end ()) {
                        /* We haven't seen this list yet, so make a record for it.  This includes
                           taking a copy of its current state, in case this is needed for undo later.
                        */
-                       lists[al] = AutomationRecord (&al->get_state ());
+                       lists[al] = AutomationRecord (&al->get_state (), &line);
                }
        }
 
@@ -3951,22 +3939,33 @@ Editor::cut_copy_points (CutCopyOp op)
                /* This operation will involve putting things in the cut buffer, so create an empty
                   ControlList for each of our source lists to put the cut buffer data in.
                */
+               framepos_t start = std::numeric_limits<framepos_t>::max();
                for (Lists::iterator i = lists.begin(); i != lists.end(); ++i) {
                        i->second.copy = i->first->create (i->first->parameter ());
+
+                       /* Calculate earliest start position of any point in selection. */
+                       start = std::min(start, i->second.line->session_position(i->first->begin()));
                }
 
                /* Add all selected points to the relevant copy ControlLists */
                for (PointSelection::iterator i = selection->points.begin(); i != selection->points.end(); ++i) {
                        boost::shared_ptr<AutomationList> al = (*i)->line().the_list();
                        AutomationList::const_iterator j = (*i)->model ();
-                       lists[al].copy->add ((*j)->when, (*j)->value);
+                       lists[al].copy->fast_simple_add ((*j)->when, (*j)->value);
                }
 
+               /* Snap start time backwards, so copy/paste is snap aligned. */
+               snap_to(start, RoundDownMaybe);
+
                for (Lists::iterator i = lists.begin(); i != lists.end(); ++i) {
-                       /* Correct this copy list so that it starts at time 0 */
-                       double const start = i->second.copy->front()->when;
+                       /* Correct this copy list so that it is relative to the earliest
+                          start time, so relative ordering between points is preserved
+                          when copying from several lists. */
+                       const AutomationLine* line        = i->second.line;
+                       const double          line_offset = line->time_converter().from(start);
+
                        for (AutomationList::iterator j = i->second.copy->begin(); j != i->second.copy->end(); ++j) {
-                               (*j)->when -= start;
+                               (*j)->when -= line_offset;
                        }
 
                        /* And add it to the cut buffer */
@@ -4011,6 +4010,13 @@ Editor::cut_copy_midi (CutCopyOp op)
                        _last_cut_copy_source_track = &mrv->get_time_axis_view();
                }
        }
+
+       if (!selection->points.empty()) {
+               cut_copy_points (op);
+               if (op == Cut || op == Delete) {
+                       selection->clear_points ();
+               }
+       }
 }
 
 struct lt_playlist {
@@ -4352,14 +4358,8 @@ Editor::paste_internal (framepos_t position, float times)
 {
         DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("apparent paste position is %1\n", position));
 
-       if (internal_editing()) {
-               if (cut_buffer->midi_notes.empty()) {
-                       return;
-               }
-       } else {
-               if (cut_buffer->empty()) {
-                       return;
-               }
+       if (cut_buffer->empty(internal_editing())) {
+               return;
        }
 
        if (position == max_framepos) {
@@ -4376,27 +4376,67 @@ Editor::paste_internal (framepos_t position, float times)
                last_paste_pos = position;
        }
 
-       TrackViewList ts;
-       TrackViewList::iterator i;
-       size_t nth;
-
        /* get everything in the correct order */
 
-       if (_edit_point == Editing::EditAtMouse && entered_track) {
-               /* With the mouse edit point, paste onto the track under the mouse */
-               ts.push_back (entered_track);
-       } else if (_edit_point == Editing::EditAtMouse && entered_regionview) {
-               /* With the mouse edit point, paste onto the track of the region under the mouse */
-               ts.push_back (&entered_regionview->get_time_axis_view());
-       } else if (!selection->tracks.empty()) {
-               /* Otherwise, if there are some selected tracks, paste to them */
+       TrackViewList ts;
+       if (!selection->tracks.empty()) {
+               /* If there is a track selection, paste into exactly those tracks and
+                  only those tracks.  This allows the user to be explicit and override
+                  the below "do the reasonable thing" logic. */
                ts = selection->tracks.filter_to_unique_playlists ();
                sort_track_selection (ts);
-       } else if (_last_cut_copy_source_track) {
-               /* Otherwise paste to the track that the cut/copy came from;
-                  see discussion in mantis #3333.
-               */
-               ts.push_back (_last_cut_copy_source_track);
+       } else {
+               /* Figure out which track to base the paste at. */
+               TimeAxisView* base_track = NULL;
+               if (_edit_point == Editing::EditAtMouse && entered_track) {
+                       /* With the mouse edit point, paste onto the track under the mouse. */
+                       base_track = entered_track;
+               } else if (_edit_point == Editing::EditAtMouse && entered_regionview) {
+                       /* With the mouse edit point, paste onto the track of the region under the mouse. */
+                       base_track = &entered_regionview->get_time_axis_view();
+               } else if (_last_cut_copy_source_track) {
+                       /* Paste to the track that the cut/copy came from (see mantis #333). */
+                       base_track = _last_cut_copy_source_track;
+               } else {
+                       /* This is "impossible" since we've copied... well, do nothing. */
+                       return;
+               }
+
+               /* Walk up to parent if necessary, so base track is a route. */
+               while (base_track->get_parent()) {
+                       base_track = base_track->get_parent();
+               }
+
+               /* Add base track and all tracks below it.  The paste logic will select
+                  the appropriate object types from the cut buffer in relative order. */
+               for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) {
+                       if ((*i)->order() >= base_track->order()) {
+                               ts.push_back(*i);
+                       }
+               }
+
+               /* Sort tracks so the nth track of type T will pick the nth object of type T. */
+               sort_track_selection (ts);
+
+               /* Add automation children of each track in order, for pasting several lines. */
+               for (TrackViewList::iterator i = ts.begin(); i != ts.end();) {
+                       /* Add any automation children for pasting several lines */
+                       RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(*i++);
+                       if (!rtv) {
+                               continue;
+                       }
+
+                       typedef RouteTimeAxisView::AutomationTracks ATracks;
+                       const ATracks& atracks = rtv->automation_tracks();
+                       for (ATracks::const_iterator a = atracks.begin(); a != atracks.end(); ++a) {
+                               i = ts.insert(i, a->second.get());
+                               ++i;
+                       }
+               }
+
+               /* We now have a list of trackviews starting at base_track, including
+                  automation children, in the order shown in the editor, e.g. R1,
+                  R1.A1, R1.A2, R2, R2.A1, ... */
        }
 
        if (internal_editing ()) {
@@ -4404,17 +4444,13 @@ Editor::paste_internal (framepos_t position, float times)
                /* undo/redo is handled by individual tracks/regions */
 
                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) {
+               ItemCounts counts;
+               for (RegionSelection::iterator r = rs.begin(); r != rs.end(); ++r) {
                        MidiRegionView* mrv = dynamic_cast<MidiRegionView*> (*r);
                        if (mrv) {
-                               mrv->paste (position, paste_count, times, **cb);
-                               ++cb;
+                               mrv->paste (position, paste_count, times, *cut_buffer, counts);
                        }
                }
 
@@ -4424,8 +4460,9 @@ Editor::paste_internal (framepos_t position, float times)
 
                begin_reversible_command (Operations::paste);
 
-               for (nth = 0, i = ts.begin(); i != ts.end(); ++i, ++nth) {
-                       (*i)->paste (position, paste_count, times, *cut_buffer, nth);
+               ItemCounts counts;
+               for (TrackViewList::iterator i = ts.begin(); i != ts.end(); ++i) {
+                       (*i)->paste (position, paste_count, times, *cut_buffer, counts);
                }
 
                commit_reversible_command ();
@@ -4813,8 +4850,8 @@ Editor::apply_midi_note_edit_op_to_region (MidiOperator& op, MidiRegionView& mrv
        vector<Evoral::Sequence<Evoral::MusicalTime>::Notes> v;
        v.push_back (selected);
 
-       framepos_t pos_frames = mrv.midi_region()->position() - mrv.midi_region()->start();
-       double     pos_beats  = _session->tempo_map().framewalk_to_beats(0, pos_frames);
+       framepos_t          pos_frames = mrv.midi_region()->position() - mrv.midi_region()->start();
+       Evoral::MusicalTime pos_beats  = _session->tempo_map().framewalk_to_beats(0, pos_frames);
 
        return op (mrv.midi_region()->model(), pos_beats, v);
 }
@@ -4927,7 +4964,7 @@ Editor::quantize_region ()
        qd->hide ();
 
        if (r == Gtk::RESPONSE_OK) {
-               Quantize quant (*_session, qd->snap_start(), qd->snap_end(),
+               Quantize quant (qd->snap_start(), qd->snap_end(),
                                qd->start_grid_size(), qd->end_grid_size(),
                                qd->strength(), qd->swing(), qd->threshold());
 
@@ -4952,7 +4989,7 @@ Editor::insert_patch_change (bool from_context)
        */
        MidiRegionView* first = dynamic_cast<MidiRegionView*> (rs.front ());
 
-       Evoral::PatchChange<Evoral::MusicalTime> empty (0, 0, 0, 0);
+       Evoral::PatchChange<Evoral::MusicalTime> empty (Evoral::MusicalTime(), 0, 0, 0);
         PatchChangeDialog d (0, _session, empty, first->instrument_info(), Gtk::Stock::ADD);
 
        if (d.run() == RESPONSE_CANCEL) {