Fix quantization and other time-related ops.
[ardour.git] / gtk2_ardour / editor.cc
index eeb09c5aa845ef600b8684458eb9f4061e7bbad9..ce6e6b15ae5ca0a32b5617fa46a983b4b69a8a6b 100644 (file)
@@ -312,6 +312,8 @@ Editor::Editor ()
        clicked_control_point = 0;
        last_update_frame = 0;
         pre_press_cursor = 0;
+       last_paste_pos = 0;
+       paste_count = 0;
        _drags = new DragManager (this);
        lock_dialog = 0;
        ruler_dialog = 0;
@@ -388,11 +390,11 @@ Editor::Editor ()
 
        sfbrowser = 0;
 
-       location_marker_color = ARDOUR_UI::config()->get_canvasvar_LocationMarker();
-       location_range_color = ARDOUR_UI::config()->get_canvasvar_LocationRange();
-       location_cd_marker_color = ARDOUR_UI::config()->get_canvasvar_LocationCDMarker();
-       location_loop_color = ARDOUR_UI::config()->get_canvasvar_LocationLoop();
-       location_punch_color = ARDOUR_UI::config()->get_canvasvar_LocationPunch();
+       location_marker_color = ARDOUR_UI::config()->get_LocationMarker();
+       location_range_color = ARDOUR_UI::config()->get_LocationRange();
+       location_cd_marker_color = ARDOUR_UI::config()->get_LocationCDMarker();
+       location_loop_color = ARDOUR_UI::config()->get_LocationLoop();
+       location_punch_color = ARDOUR_UI::config()->get_LocationPunch();
 
        zoom_focus = ZoomFocusLeft;
        _edit_point = EditAtMouse;
@@ -1063,12 +1065,12 @@ Editor::control_scroll (float fraction)
                _dragging_playhead = true;
        }
 
-       if ((fraction < 0.0f) && (*_control_scroll_target < (framepos_t) fabs(step))) {
+       if ((fraction < 0.0f) && (*_control_scroll_target <= (framepos_t) fabs(step))) {
                *_control_scroll_target = 0;
        } else if ((fraction > 0.0f) && (max_framepos - *_control_scroll_target < step)) {
                *_control_scroll_target = max_framepos - (current_page_samples()*2); // allow room for slop in where the PH is on the screen
        } else {
-               *_control_scroll_target += (framepos_t) floor (step);
+               *_control_scroll_target += (framepos_t) trunc (step);
        }
 
        /* move visuals, we'll catch up with it later */
@@ -1356,7 +1358,6 @@ Editor::set_session (Session *t)
        _session->locations()->added.connect (_session_connections, invalidator (*this), boost::bind (&Editor::add_new_location, this, _1), gui_context());
        _session->locations()->removed.connect (_session_connections, invalidator (*this), boost::bind (&Editor::location_gone, this, _1), gui_context());
        _session->locations()->changed.connect (_session_connections, invalidator (*this), boost::bind (&Editor::refresh_location_display, this), gui_context());
-       _session->locations()->StateChanged.connect (_session_connections, invalidator (*this), boost::bind (&Editor::refresh_location_display, this), gui_context());
        _session->history().Changed.connect (_session_connections, invalidator (*this), boost::bind (&Editor::history_changed, this), gui_context());
 
        playhead_cursor->show ();
@@ -2103,12 +2104,11 @@ Editor::set_edit_point_preference (EditPoint ep, bool force)
        bool changed = (_edit_point != ep);
 
        _edit_point = ep;
-       string str = edit_point_strings[(int)ep];
-
        if (Profile->get_mixbus())
                if (ep == EditAtSelectedMarker)
                        ep = EditAtPlayhead;
-       
+
+       string str = edit_point_strings[(int)ep];
        if (str != edit_point_selector.get_text ()) {
                edit_point_selector.set_text (str);
        }
@@ -2203,7 +2203,12 @@ Editor::set_state (const XMLNode& node, int /*version*/)
        if (_session && (prop = node.property ("playhead"))) {
                framepos_t pos;
                sscanf (prop->value().c_str(), "%" PRIi64, &pos);
-               playhead_cursor->set_position (pos);
+               if (pos >= 0) {
+                       playhead_cursor->set_position (pos);
+               } else {
+                       warning << _("Playhead position stored with a negative value - ignored (use zero instead)") << endmsg;
+                       playhead_cursor->set_position (0);
+               }
        } else {
                playhead_cursor->set_position (0);
        }
@@ -2547,7 +2552,7 @@ Editor::trackview_by_y_position (double y, bool trackview_relative_offset) const
  *  @param event Event to get current key modifier information from, or 0.
  */
 void
-Editor::snap_to_with_modifier (framepos_t& start, GdkEvent const * event, int32_t direction, bool for_mark)
+Editor::snap_to_with_modifier (framepos_t& start, GdkEvent const * event, RoundMode direction, bool for_mark)
 {
        if (!_session || !event) {
                return;
@@ -2565,7 +2570,7 @@ Editor::snap_to_with_modifier (framepos_t& start, GdkEvent const * event, int32_
 }
 
 void
-Editor::snap_to (framepos_t& start, int32_t direction, bool for_mark)
+Editor::snap_to (framepos_t& start, RoundMode direction, bool for_mark)
 {
        if (!_session || _snap_mode == SnapOff) {
                return;
@@ -2575,14 +2580,17 @@ Editor::snap_to (framepos_t& start, int32_t direction, bool for_mark)
 }
 
 void
-Editor::timecode_snap_to_internal (framepos_t& start, int32_t direction, bool /*for_mark*/)
+Editor::timecode_snap_to_internal (framepos_t& start, RoundMode direction, bool /*for_mark*/)
 {
        const framepos_t one_timecode_second = (framepos_t)(rint(_session->timecode_frames_per_second()) * _session->frames_per_timecode_frame());
        framepos_t one_timecode_minute = (framepos_t)(rint(_session->timecode_frames_per_second()) * _session->frames_per_timecode_frame() * 60);
 
        switch (_snap_type) {
        case SnapToTimecodeFrame:
-               if (((direction == 0) && (fmod((double)start, (double)_session->frames_per_timecode_frame()) > (_session->frames_per_timecode_frame() / 2))) || (direction > 0)) {
+               if ((direction == RoundUpMaybe || direction == RoundDownMaybe) &&
+                   fmod((double)start, (double)_session->frames_per_timecode_frame()) == 0) {
+                       /* start is already on a whole timecode frame, do nothing */
+               } else if (((direction == 0) && (fmod((double)start, (double)_session->frames_per_timecode_frame()) > (_session->frames_per_timecode_frame() / 2))) || (direction > 0)) {
                        start = (framepos_t) (ceil ((double) start / _session->frames_per_timecode_frame()) * _session->frames_per_timecode_frame());
                } else {
                        start = (framepos_t) (floor ((double) start / _session->frames_per_timecode_frame()) *  _session->frames_per_timecode_frame());
@@ -2595,7 +2603,10 @@ Editor::timecode_snap_to_internal (framepos_t& start, int32_t direction, bool /*
                } else {
                        start -= _session->config.get_timecode_offset ();
                }
-               if (((direction == 0) && (start % one_timecode_second > one_timecode_second / 2)) || direction > 0) {
+               if ((direction == RoundUpMaybe || direction == RoundDownMaybe) &&
+                   (start % one_timecode_second == 0)) {
+                       /* start is already on a whole second, do nothing */
+               } else if (((direction == 0) && (start % one_timecode_second > one_timecode_second / 2)) || direction > 0) {
                        start = (framepos_t) ceil ((double) start / one_timecode_second) * one_timecode_second;
                } else {
                        start = (framepos_t) floor ((double) start / one_timecode_second) * one_timecode_second;
@@ -2614,7 +2625,10 @@ Editor::timecode_snap_to_internal (framepos_t& start, int32_t direction, bool /*
                } else {
                        start -= _session->config.get_timecode_offset ();
                }
-               if (((direction == 0) && (start % one_timecode_minute > one_timecode_minute / 2)) || direction > 0) {
+               if ((direction == RoundUpMaybe || direction == RoundDownMaybe) &&
+                   (start % one_timecode_minute == 0)) {
+                       /* start is already on a whole minute, do nothing */
+               } else if (((direction == 0) && (start % one_timecode_minute > one_timecode_minute / 2)) || direction > 0) {
                        start = (framepos_t) ceil ((double) start / one_timecode_minute) * one_timecode_minute;
                } else {
                        start = (framepos_t) floor ((double) start / one_timecode_minute) * one_timecode_minute;
@@ -2627,12 +2641,12 @@ Editor::timecode_snap_to_internal (framepos_t& start, int32_t direction, bool /*
                break;
        default:
                fatal << "Editor::smpte_snap_to_internal() called with non-timecode snap type!" << endmsg;
-               /*NOTREACHED*/
+               abort(); /*NOTREACHED*/
        }
 }
 
 void
-Editor::snap_to_internal (framepos_t& start, int32_t direction, bool for_mark)
+Editor::snap_to_internal (framepos_t& start, RoundMode direction, bool for_mark)
 {
        const framepos_t one_second = _session->frame_rate();
        const framepos_t one_minute = _session->frame_rate() * 60;
@@ -2647,7 +2661,10 @@ Editor::snap_to_internal (framepos_t& start, int32_t direction, bool for_mark)
                return timecode_snap_to_internal (start, direction, for_mark);
 
        case SnapToCDFrame:
-               if (((direction == 0) && (start % (one_second/75) > (one_second/75) / 2)) || (direction > 0)) {
+               if ((direction == RoundUpMaybe || direction == RoundDownMaybe) &&
+                   start % (one_second/75) == 0) {
+                       /* start is already on a whole CD frame, do nothing */
+               } else if (((direction == 0) && (start % (one_second/75) > (one_second/75) / 2)) || (direction > 0)) {
                        start = (framepos_t) ceil ((double) start / (one_second / 75)) * (one_second / 75);
                } else {
                        start = (framepos_t) floor ((double) start / (one_second / 75)) * (one_second / 75);
@@ -2655,7 +2672,10 @@ Editor::snap_to_internal (framepos_t& start, int32_t direction, bool for_mark)
                break;
 
        case SnapToSeconds:
-               if (((direction == 0) && (start % one_second > one_second / 2)) || (direction > 0)) {
+               if ((direction == RoundUpMaybe || direction == RoundDownMaybe) &&
+                   start % one_second == 0) {
+                       /* start is already on a whole second, do nothing */
+               } else if (((direction == 0) && (start % one_second > one_second / 2)) || (direction > 0)) {
                        start = (framepos_t) ceil ((double) start / one_second) * one_second;
                } else {
                        start = (framepos_t) floor ((double) start / one_second) * one_second;
@@ -2663,7 +2683,10 @@ Editor::snap_to_internal (framepos_t& start, int32_t direction, bool for_mark)
                break;
 
        case SnapToMinutes:
-               if (((direction == 0) && (start % one_minute > one_minute / 2)) || (direction > 0)) {
+               if ((direction == RoundUpMaybe || direction == RoundDownMaybe) &&
+                   start % one_minute == 0) {
+                       /* start is already on a whole minute, do nothing */
+               } else if (((direction == 0) && (start % one_minute > one_minute / 2)) || (direction > 0)) {
                        start = (framepos_t) ceil ((double) start / one_minute) * one_minute;
                } else {
                        start = (framepos_t) floor ((double) start / one_minute) * one_minute;
@@ -2829,7 +2852,6 @@ Editor::setup_toolbar ()
        mouse_mode_size_group->add_widget (mouse_move_button);
        mouse_mode_size_group->add_widget (mouse_cut_button);
        mouse_mode_size_group->add_widget (mouse_select_button);
-       mouse_mode_size_group->add_widget (mouse_zoom_button);
        mouse_mode_size_group->add_widget (mouse_gain_button);
        mouse_mode_size_group->add_widget (mouse_timefx_button);
        mouse_mode_size_group->add_widget (mouse_audition_button);
@@ -2867,7 +2889,6 @@ Editor::setup_toolbar ()
 
        if (!ARDOUR::Profile->get_mixbus()) {
                mouse_mode_hbox->pack_start (mouse_cut_button, false, false);
-               mouse_mode_hbox->pack_start (mouse_zoom_button, false, false);
        }
        
        if (!ARDOUR::Profile->get_trx()) {
@@ -3165,7 +3186,6 @@ Editor::setup_tooltips ()
        ARDOUR_UI::instance()->set_tip (mouse_select_button, _("Range Mode (select/move Ranges)"));
        ARDOUR_UI::instance()->set_tip (mouse_draw_button, _("Draw/Edit MIDI Notes"));
        ARDOUR_UI::instance()->set_tip (mouse_gain_button, _("Draw Region Gain"));
-       ARDOUR_UI::instance()->set_tip (mouse_zoom_button, _("Select Zoom Range"));
        ARDOUR_UI::instance()->set_tip (mouse_timefx_button, _("Stretch/Shrink Regions and MIDI Notes"));
        ARDOUR_UI::instance()->set_tip (mouse_audition_button, _("Listen to Specific Regions"));
        ARDOUR_UI::instance()->set_tip (internal_edit_button, _("Note Level Editing"));
@@ -3855,6 +3875,25 @@ Editor::playlist_selector () const
        return *_playlist_selector;
 }
 
+framecnt_t
+Editor::get_paste_offset (framepos_t pos, unsigned paste_count, framecnt_t duration)
+{
+       if (paste_count == 0) {
+               /* don't bother calculating an offset that will be zero anyway */
+               return 0;
+       }
+
+       /* calculate basic unsnapped multi-paste offset */
+       framecnt_t offset = paste_count * duration;
+
+       /* snap offset so pos + offset is aligned to the grid */
+       framepos_t offset_pos = pos + offset;
+       snap_to(offset_pos, RoundUpMaybe);
+       offset = offset_pos - pos;
+
+       return offset;
+}
+
 Evoral::MusicalTime
 Editor::get_grid_type_as_beats (bool& success, framepos_t position)
 {
@@ -3862,64 +3901,64 @@ Editor::get_grid_type_as_beats (bool& success, framepos_t position)
 
        switch (_snap_type) {
        case SnapToBeat:
-               return 1.0;
+               return Evoral::MusicalTime(1.0);
                break;
 
        case SnapToBeatDiv128:
-               return 1.0/128.0;
+               return Evoral::MusicalTime(1.0/128.0);
                break;
        case SnapToBeatDiv64:
-               return 1.0/64.0;
+               return Evoral::MusicalTime(1.0/64.0);
                break;
        case SnapToBeatDiv32:
-               return 1.0/32.0;
+               return Evoral::MusicalTime(1.0/32.0);
                break;
        case SnapToBeatDiv28:
-               return 1.0/28.0;
+               return Evoral::MusicalTime(1.0/28.0);
                break;
        case SnapToBeatDiv24:
-               return 1.0/24.0;
+               return Evoral::MusicalTime(1.0/24.0);
                break;
        case SnapToBeatDiv20:
-               return 1.0/20.0;
+               return Evoral::MusicalTime(1.0/20.0);
                break;
        case SnapToBeatDiv16:
-               return 1.0/16.0;
+               return Evoral::MusicalTime(1.0/16.0);
                break;
        case SnapToBeatDiv14:
-               return 1.0/14.0;
+               return Evoral::MusicalTime(1.0/14.0);
                break;
        case SnapToBeatDiv12:
-               return 1.0/12.0;
+               return Evoral::MusicalTime(1.0/12.0);
                break;
        case SnapToBeatDiv10:
-               return 1.0/10.0;
+               return Evoral::MusicalTime(1.0/10.0);
                break;
        case SnapToBeatDiv8:
-               return 1.0/8.0;
+               return Evoral::MusicalTime(1.0/8.0);
                break;
        case SnapToBeatDiv7:
-               return 1.0/7.0;
+               return Evoral::MusicalTime(1.0/7.0);
                break;
        case SnapToBeatDiv6:
-               return 1.0/6.0;
+               return Evoral::MusicalTime(1.0/6.0);
                break;
        case SnapToBeatDiv5:
-               return 1.0/5.0;
+               return Evoral::MusicalTime(1.0/5.0);
                break;
        case SnapToBeatDiv4:
-               return 1.0/4.0;
+               return Evoral::MusicalTime(1.0/4.0);
                break;
        case SnapToBeatDiv3:
-               return 1.0/3.0;
+               return Evoral::MusicalTime(1.0/3.0);
                break;
        case SnapToBeatDiv2:
-               return 1.0/2.0;
+               return Evoral::MusicalTime(1.0/2.0);
                break;
 
        case SnapToBar:
                if (_session) {
-                       return _session->tempo_map().meter_at (position).divisions_per_bar();
+                       return Evoral::MusicalTime(_session->tempo_map().meter_at (position).divisions_per_bar());
                }
                break;
 
@@ -3938,7 +3977,7 @@ Editor::get_grid_type_as_beats (bool& success, framepos_t position)
                break;
        }
 
-       return 0.0;
+       return Evoral::MusicalTime();
 }
 
 framecnt_t
@@ -4260,7 +4299,9 @@ Editor::undo_visual_state ()
 
        redo_visual_stack.push_back (current_visual_state (vs ? vs->gui_state != 0 : false));
 
-       use_visual_state (*vs);
+       if (vs) {
+               use_visual_state (*vs);
+       }
 }
 
 void
@@ -4273,9 +4314,13 @@ Editor::redo_visual_state ()
        VisualState* vs = redo_visual_stack.back();
        redo_visual_stack.pop_back();
 
-       undo_visual_stack.push_back (current_visual_state (vs ? vs->gui_state != 0 : false));
+       // can 'vs' really be 0? Is there a place that puts NULL pointers onto the stack?
+       // why do we check here?
+       undo_visual_stack.push_back (current_visual_state (vs ? (vs->gui_state != 0) : false));
 
-       use_visual_state (*vs);
+       if (vs) {
+               use_visual_state (*vs);
+       }
 }
 
 void
@@ -4385,7 +4430,7 @@ Editor::ensure_visual_change_idle_handler ()
 {
        if (pending_visual_change.idle_handler_id < 0) {
                // see comment in add_to_idle_resize above.
-               pending_visual_change.idle_handler_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE + 20, _idle_visual_changer, this, NULL);
+               pending_visual_change.idle_handler_id = g_idle_add_full (G_PRIORITY_HIGH_IDLE + 10, _idle_visual_changer, this, NULL);
                pending_visual_change.being_handled = false;
        }
 }