X-Git-Url: https://git.carlh.net/gitweb/?a=blobdiff_plain;f=gtk2_ardour%2Feditor_drag.cc;h=2419169c7fb23f7d301ece441c762e7aaced59fc;hb=b604dd24139b4c84f20521af91ec4c0cd721bd13;hp=a075008d201ec7f9baad1ba995e3973791a0743d;hpb=bed955b40935cf7765829e260b70258b79803890;p=ardour.git diff --git a/gtk2_ardour/editor_drag.cc b/gtk2_ardour/editor_drag.cc index a075008d20..2419169c7f 100644 --- a/gtk2_ardour/editor_drag.cc +++ b/gtk2_ardour/editor_drag.cc @@ -22,6 +22,7 @@ #endif #include +#include #include "pbd/memento_command.h" #include "pbd/basename.h" @@ -29,6 +30,7 @@ #include "gtkmm2ext/utils.h" +#include "ardour/audioengine.h" #include "ardour/audioregion.h" #include "ardour/dB.h" #include "ardour/midi_region.h" @@ -49,13 +51,14 @@ #include "editor_drag.h" #include "audio_time_axis.h" #include "midi_time_axis.h" -#include "canvas-note.h" #include "selection.h" #include "midi_selection.h" #include "automation_time_axis.h" #include "debug.h" #include "editor_cursors.h" #include "mouse_cursors.h" +#include "note_base.h" +#include "patch_change.h" #include "verbose_cursor.h" using namespace std; @@ -124,7 +127,7 @@ DragManager::start_grab (GdkEvent* e, Gdk::Cursor* c) _old_follow_playhead = _editor->follow_playhead (); _editor->set_follow_playhead (false); - _current_pointer_frame = _editor->event_frame (e, &_current_pointer_x, &_current_pointer_y); + _current_pointer_frame = _editor->canvas_event_frame (e, &_current_pointer_x, &_current_pointer_y); for (list::const_iterator i = _drags.begin(); i != _drags.end(); ++i) { (*i)->start_grab (e, c); @@ -162,7 +165,25 @@ DragManager::motion_handler (GdkEvent* e, bool from_autoscroll) { bool r = false; - _current_pointer_frame = _editor->event_frame (e, &_current_pointer_x, &_current_pointer_y); + _current_pointer_frame = _editor->canvas_event_frame (e, &_current_pointer_x, &_current_pointer_y); + + for (list::iterator i = _drags.begin(); i != _drags.end(); ++i) { + bool const t = (*i)->motion_handler (e, from_autoscroll); + if (t) { + r = true; + } + + } + + return r; +} + +bool +DragManager::window_motion_handler (GdkEvent* e, bool from_autoscroll) +{ + bool r = false; + + _current_pointer_frame = _editor->window_event_frame (e, &_current_pointer_x, &_current_pointer_y); for (list::iterator i = _drags.begin(); i != _drags.end(); ++i) { bool const t = (*i)->motion_handler (e, from_autoscroll); @@ -199,15 +220,15 @@ Drag::Drag (Editor* e, ArdourCanvas::Item* i) } void -Drag::swap_grab (ArdourCanvas::Item* new_item, Gdk::Cursor* cursor, uint32_t time) +Drag::swap_grab (ArdourCanvas::Item* new_item, Gdk::Cursor* cursor, uint32_t /*time*/) { - _item->ungrab (0); + _item->ungrab (); _item = new_item; if (cursor == 0) { - _item->grab (Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK, time); + _item->grab (); } else { - _item->grab (Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK, *cursor, time); + _item->grab (); } } @@ -229,7 +250,7 @@ Drag::start_grab (GdkEvent* event, Gdk::Cursor *cursor) _y_constrained = false; } - _raw_grab_frame = _editor->event_frame (event, &_grab_x, &_grab_y); + _raw_grab_frame = _editor->canvas_event_frame (event, &_grab_x, &_grab_y); setup_pointer_frame_offset (); _grab_frame = adjusted_frame (_raw_grab_frame, event); _last_pointer_frame = _grab_frame; @@ -237,12 +258,12 @@ Drag::start_grab (GdkEvent* event, Gdk::Cursor *cursor) _last_pointer_y = _grab_y; if (cursor == 0) { - _item->grab (Gdk::POINTER_MOTION_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK, - event->button.time); + _item->grab (); + } else { - _item->grab (Gdk::POINTER_MOTION_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK, - *cursor, - event->button.time); + /* CAIROCANVAS need a variant here that passes *cursor */ + _item->grab (); + } if (_editor->session() && _editor->session()->transport_rolling()) { @@ -274,7 +295,7 @@ Drag::end_grab (GdkEvent* event) { _editor->stop_canvas_autoscroll (); - _item->ungrab (event ? event->button.time : 0); + _item->ungrab (); finished (event, _move_threshold_passed); @@ -353,7 +374,7 @@ void Drag::abort () { if (_item) { - _item->ungrab (0); + _item->ungrab (); } aborted (_move_threshold_passed); @@ -368,7 +389,7 @@ Drag::show_verbose_cursor_time (framepos_t frame) _editor->verbose_cursor()->set_time ( frame, _drags->current_pointer_x() + 10 - _editor->horizontal_position(), - _drags->current_pointer_y() + 10 - _editor->vertical_adjustment.get_value() + _editor->canvas_timebars_vsize + _drags->current_pointer_y() + 10 - _editor->vertical_adjustment.get_value() ); _editor->verbose_cursor()->show (); @@ -382,7 +403,7 @@ Drag::show_verbose_cursor_duration (framepos_t start, framepos_t end, double xof _editor->verbose_cursor()->set_duration ( start, end, _drags->current_pointer_x() + 10 - _editor->horizontal_position(), - _drags->current_pointer_y() + 10 - _editor->vertical_adjustment.get_value() + _editor->canvas_timebars_vsize + _drags->current_pointer_y() + 10 - _editor->vertical_adjustment.get_value() ); } @@ -394,7 +415,7 @@ Drag::show_verbose_cursor_text (string const & text) _editor->verbose_cursor()->set ( text, _drags->current_pointer_x() + 10 - _editor->horizontal_position(), - _drags->current_pointer_y() + 10 - _editor->vertical_adjustment.get_value() + _editor->canvas_timebars_vsize + _drags->current_pointer_y() + 10 - _editor->vertical_adjustment.get_value() ); } @@ -420,7 +441,7 @@ struct EditorOrderTimeAxisViewSorter { RouteTimeAxisView* ra = dynamic_cast (a); RouteTimeAxisView* rb = dynamic_cast (b); assert (ra && rb); - return ra->route()->order_key (N_ ("editor")) < rb->route()->order_key (N_ ("editor")); + return ra->route()->order_key (EditorSort) < rb->route()->order_key (EditorSort); } }; @@ -493,10 +514,9 @@ RegionMotionDrag::RegionMotionDrag (Editor* e, ArdourCanvas::Item* i, RegionView _brushing (b), _total_x_delta (0) { - + DEBUG_TRACE (DEBUG::Drags, "New RegionMotionDrag\n"); } - void RegionMotionDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor) { @@ -549,7 +569,7 @@ RegionMotionDrag::compute_x_delta (GdkEvent const * event, framepos_t* pending_r if ((*pending_region_position != _last_frame_position) && x_move_allowed) { /* x movement since last time (in pixels) */ - dx = (static_cast (*pending_region_position) - _last_frame_position) / _editor->frames_per_unit; + dx = (static_cast (*pending_region_position) - _last_frame_position) / _editor->samples_per_pixel; /* total x movement */ framecnt_t total_dx = *pending_region_position; @@ -635,6 +655,7 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move) double const x_delta = compute_x_delta (event, &pending_region_position); /* Work out the change in y */ + int delta_time_axis_view = current_pointer_time_axis_view - _last_pointer_time_axis_view; double delta_layer = current_pointer_layer - _last_pointer_layer; @@ -657,7 +678,7 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move) RegionView* rv = i->view; - if (rv->region()->locked()) { + if (rv->region()->locked() || rv->region()->video_locked()) { continue; } @@ -665,20 +686,19 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move) rv->drag_start (); - /* Absolutely no idea why this is necessary, but it is; without - it, the region view disappears after the reparent. - */ - _editor->update_canvas_now (); - /* Reparent to a non scrolling group so that we can keep the region selection above all time axis views. Reparenting means that we will have to move the region view - later, as the two parent groups have different coordinates. + within its new parent, as the two parent groups have different coordinates. */ - rv->get_canvas_group()->reparent (*(_editor->_region_motion_group)); - + ArdourCanvas::Group* rvg = rv->get_canvas_group(); + Duple rv_canvas_offset = rvg->item_to_canvas (Duple (0,0)); + + rv->get_canvas_group()->reparent (_editor->_region_motion_group); + rv->fake_set_opaque (true); + rvg->set_position (rv_canvas_offset); } /* If we have moved tracks, we'll fudge the layer delta so that the @@ -727,9 +747,8 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move) double y = 0; /* Get the y coordinate of the top of the track that this region is now on */ - tv->canvas_display()->i2w (x, y); - y += _editor->get_trackview_group_vertical_offset(); - + tv->canvas_display()->item_to_canvas (x, y); + /* And adjust for the layer that it should be on */ StreamView* cv = tv->view (); switch (cv->layer_display ()) { @@ -744,17 +763,13 @@ RegionMotionDrag::motion (GdkEvent* event, bool first_move) } /* Now move the region view */ - rv->move (x_delta, y - rv->get_canvas_group()->property_y()); + rv->move (x_delta, y - rv->get_canvas_group()->position().y); } } /* foreach region */ _total_x_delta += x_delta; - if (first_move) { - _editor->cursor_group->raise_to_top(); - } - if (x_delta != 0 && !_brushing) { show_verbose_cursor_time (_last_frame_position); } @@ -817,15 +832,6 @@ RegionMoveDrag::motion (GdkEvent* event, bool first_move) _views = new_regionviews; swap_grab (new_regionviews.front().view->get_canvas_group (), 0, event ? event->motion.time : 0); - - /* - sync the canvas to what we think is its current state - without it, the canvas seems to - "forget" to update properly after the upcoming reparent() - ..only if the mouse is in rapid motion at the time of the grab. - something to do with regionview creation taking so long? - */ - _editor->update_canvas_now(); } } @@ -878,8 +884,6 @@ RegionMoveDrag::finished (GdkEvent* ev, bool movement_occurred) bool const changed_tracks = (_time_axis_views[_views.front().time_axis_view] != &_views.front().view->get_time_axis_view()); framecnt_t const drag_delta = _primary->region()->position() - _last_frame_position; - _editor->update_canvas_now (); - if (_copy) { finished_copy ( @@ -898,9 +902,7 @@ RegionMoveDrag::finished (GdkEvent* ev, bool movement_occurred) } - if (_editor->session() && Config->get_always_play_range()) { - _editor->session()->request_locate (_editor->get_selection().regions.start()); - } + _editor->maybe_locate_with_edit_preroll (_editor->get_selection().regions.start()); } void @@ -930,7 +932,7 @@ RegionMoveDrag::finished_copy (bool const changed_position, bool const /*changed /* insert the regions into their new playlists */ for (list::const_iterator i = _views.begin(); i != _views.end(); ++i) { - if (i->view->region()->locked()) { + if (i->view->region()->locked() || i->view->region()->video_locked()) { continue; } @@ -1009,7 +1011,7 @@ RegionMoveDrag::finished_no_copy ( RouteTimeAxisView* const dest_rtv = dynamic_cast (_time_axis_views[i->time_axis_view]); double const dest_layer = i->layer; - if (rv->region()->locked()) { + if (rv->region()->locked() || rv->region()->video_locked()) { ++i; continue; } @@ -1061,8 +1063,8 @@ RegionMoveDrag::finished_no_copy ( No need to do anything for copies as they are fake regions which will be deleted. */ - rv->get_canvas_group()->reparent (*dest_rtv->view()->canvas_item()); - rv->get_canvas_group()->property_y() = i->initial_y; + rv->get_canvas_group()->reparent (dest_rtv->view()->canvas_item()); + rv->get_canvas_group()->set_y_position (i->initial_y); rv->drag_end (); /* just change the model */ @@ -1275,15 +1277,13 @@ RegionMotionDrag::aborted (bool) TimeAxisView* tv = &(rv->get_time_axis_view ()); RouteTimeAxisView* rtv = dynamic_cast (tv); assert (rtv); - rv->get_canvas_group()->reparent (*rtv->view()->canvas_item()); - rv->get_canvas_group()->property_y() = 0; + rv->get_canvas_group()->reparent (rtv->view()->canvas_item()); + rv->get_canvas_group()->set_y_position (0); rv->drag_end (); rv->fake_set_opaque (false); rv->move (-_total_x_delta, 0); rv->set_height (rtv->view()->child_height ()); } - - _editor->update_canvas_now (); } /** @param b true to brush, otherwise false. @@ -1332,12 +1332,10 @@ RegionInsertDrag::RegionInsertDrag (Editor* e, boost::shared_ptr r, Rout void RegionInsertDrag::finished (GdkEvent *, bool) { - _editor->update_canvas_now (); - RouteTimeAxisView* dest_rtv = dynamic_cast (_time_axis_views[_views.front().time_axis_view]); - _primary->get_canvas_group()->reparent (*dest_rtv->view()->canvas_item()); - _primary->get_canvas_group()->property_y() = 0; + _primary->get_canvas_group()->reparent (dest_rtv->view()->canvas_item()); + _primary->get_canvas_group()->set_y_position (0); boost::shared_ptr playlist = dest_rtv->playlist(); @@ -1481,7 +1479,7 @@ RegionCreateDrag::motion (GdkEvent* event, bool first_move) place snapped notes at the start of the region. */ - framecnt_t const len = abs (f - grab_frame () - 1); + framecnt_t const len = (framecnt_t) fabs (f - grab_frame () - 1); _region->set_length (len < 1 ? 1 : len); } } @@ -1495,10 +1493,6 @@ RegionCreateDrag::finished (GdkEvent*, bool movement_occurred) } else { _view->playlist()->thaw (); } - - if (_region) { - _editor->commit_reversible_command (); - } } void @@ -1522,7 +1516,8 @@ void NoteResizeDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*ignored*/) { Gdk::Cursor* cursor; - ArdourCanvas::CanvasNoteEvent* cnote = dynamic_cast(_item); + NoteBase* cnote = reinterpret_cast (_item->get_data ("notebase")); + assert (cnote); float x_fraction = cnote->mouse_x_fraction (); if (x_fraction > 0.0 && x_fraction < 0.25) { @@ -1536,7 +1531,7 @@ NoteResizeDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*ignored*/) region = &cnote->region_view(); double const region_start = region->get_position_pixels(); - double const middle_point = region_start + cnote->x1() + (cnote->x2() - cnote->x1()) / 2.0L; + double const middle_point = region_start + cnote->x0() + (cnote->x1() - cnote->x0()) / 2.0L; if (grab_x() <= middle_point) { cursor = _editor->cursors()->left_side_trim; @@ -1546,7 +1541,7 @@ NoteResizeDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*ignored*/) at_front = false; } - _item->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, *cursor, event->motion.time); + _item->grab (); if (event->motion.state & Keyboard::PrimaryModifier) { relative = false; @@ -1580,7 +1575,9 @@ NoteResizeDrag::motion (GdkEvent* /*event*/, bool /*first_move*/) { MidiRegionSelection& ms (_editor->get_selection().midi_regions); for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) { - (*r)->update_resizing (dynamic_cast(_item), at_front, _drags->current_pointer_x() - grab_x(), relative); + NoteBase* nb = reinterpret_cast (_item->get_data ("notebase")); + assert (nb); + (*r)->update_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative); } } @@ -1589,7 +1586,9 @@ NoteResizeDrag::finished (GdkEvent*, bool /*movement_occurred*/) { MidiRegionSelection& ms (_editor->get_selection().midi_regions); for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) { - (*r)->commit_resizing (dynamic_cast(_item), at_front, _drags->current_pointer_x() - grab_x(), relative); + NoteBase* nb = reinterpret_cast (_item->get_data ("notebase")); + assert (nb); + (*r)->commit_resizing (nb, at_front, _drags->current_pointer_x() - grab_x(), relative); } } @@ -1602,10 +1601,170 @@ NoteResizeDrag::aborted (bool) } } -TrimDrag::TrimDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list const & v) +AVDraggingView::AVDraggingView (RegionView* v) + : view (v) +{ + initial_position = v->region()->position (); +} + +VideoTimeLineDrag::VideoTimeLineDrag (Editor* e, ArdourCanvas::Item* i) + : Drag (e, i) +{ + DEBUG_TRACE (DEBUG::Drags, "New VideoTimeLineDrag\n"); + + RegionSelection rs; + TrackViewList empty; + empty.clear(); + _editor->get_regions_after(rs, (framepos_t) 0, empty); + std::list views = rs.by_layer(); + + for (list::iterator i = views.begin(); i != views.end(); ++i) { + RegionView* rv = (*i); + if (!rv->region()->video_locked()) { + continue; + } + _views.push_back (AVDraggingView (rv)); + } +} + +void +VideoTimeLineDrag::start_grab (GdkEvent* event, Gdk::Cursor*) +{ + Drag::start_grab (event); + if (_editor->session() == 0) { + return; + } + + _startdrag_video_offset=ARDOUR_UI::instance()->video_timeline->get_offset(); + _max_backwards_drag = ( + ARDOUR_UI::instance()->video_timeline->get_duration() + + ARDOUR_UI::instance()->video_timeline->get_offset() + - ceil(ARDOUR_UI::instance()->video_timeline->get_apv()) + ); + + for (list::iterator i = _views.begin(); i != _views.end(); ++i) { + if (i->initial_position < _max_backwards_drag || _max_backwards_drag < 0) { + _max_backwards_drag = ARDOUR_UI::instance()->video_timeline->quantify_frames_to_apv (i->initial_position); + } + } + DEBUG_TRACE (DEBUG::Drags, string_compose("VideoTimeLineDrag: max backwards-drag: %1\n", _max_backwards_drag)); + + char buf[128]; + Timecode::Time timecode; + _editor->session()->sample_to_timecode(abs(_startdrag_video_offset), timecode, true /* use_offset */, false /* use_subframes */ ); + snprintf (buf, sizeof (buf), "Video Start:\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32, (_startdrag_video_offset<0?'-':' '), timecode.hours, timecode.minutes, timecode.seconds, timecode.frames); + _editor->verbose_cursor()->set(buf, event->button.x + 10, event->button.y + 10); + _editor->verbose_cursor()->show (); +} + +void +VideoTimeLineDrag::motion (GdkEvent* event, bool first_move) +{ + if (_editor->session() == 0) { + return; + } + if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) { + return; + } + + framecnt_t dt = adjusted_current_frame (event) - raw_grab_frame() + _pointer_frame_offset; + dt = ARDOUR_UI::instance()->video_timeline->quantify_frames_to_apv(dt); + + if (_max_backwards_drag >= 0 && dt <= - _max_backwards_drag) { + dt = - _max_backwards_drag; + } + + ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset+dt); + ARDOUR_UI::instance()->flush_videotimeline_cache(true); + + for (list::iterator i = _views.begin(); i != _views.end(); ++i) { + RegionView* rv = i->view; + DEBUG_TRACE (DEBUG::Drags, string_compose("SHIFT REGION at %1 by %2\n", i->initial_position, dt)); + if (first_move) { + rv->drag_start (); + rv->fake_set_opaque (true); + rv->region()->clear_changes (); + rv->region()->suspend_property_changes(); + } + rv->region()->set_position(i->initial_position + dt); + rv->region_changed(ARDOUR::Properties::position); + } + + const framepos_t offset = ARDOUR_UI::instance()->video_timeline->get_offset(); + Timecode::Time timecode; + Timecode::Time timediff; + char buf[128]; + _editor->session()->sample_to_timecode(abs(offset), timecode, true /* use_offset */, false /* use_subframes */ ); + _editor->session()->sample_to_timecode(abs(dt), timediff, false /* use_offset */, false /* use_subframes */ ); + snprintf (buf, sizeof (buf), + "%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32 + "\n%s\n%c%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32 + , _("Video Start:"), + (offset<0?'-':' '), timecode.hours, timecode.minutes, timecode.seconds, timecode.frames + , _("Diff:"), + (dt<0?'-':' '), timediff.hours, timediff.minutes, timediff.seconds, timediff.frames + ); + _editor->verbose_cursor()->set(buf, event->button.x + 10, event->button.y + 10); + _editor->verbose_cursor()->show (); +} + +void +VideoTimeLineDrag::finished (GdkEvent * /*event*/, bool movement_occurred) +{ + if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) { + return; + } + + if (!movement_occurred || ! _editor->session()) { + return; + } + + ARDOUR_UI::instance()->flush_videotimeline_cache(true); + + _editor->begin_reversible_command (_("Move Video")); + + XMLNode &before = ARDOUR_UI::instance()->video_timeline->get_state(); + ARDOUR_UI::instance()->video_timeline->save_undo(); + XMLNode &after = ARDOUR_UI::instance()->video_timeline->get_state(); + _editor->session()->add_command(new MementoCommand(*(ARDOUR_UI::instance()->video_timeline), &before, &after)); + + for (list::iterator i = _views.begin(); i != _views.end(); ++i) { + i->view->drag_end(); + i->view->fake_set_opaque (false); + i->view->region()->resume_property_changes (); + + _editor->session()->add_command (new StatefulDiffCommand (i->view->region())); + } + + _editor->session()->maybe_update_session_range( + std::max(ARDOUR_UI::instance()->video_timeline->get_offset(), (ARDOUR::frameoffset_t) 0), + std::max(ARDOUR_UI::instance()->video_timeline->get_offset() + ARDOUR_UI::instance()->video_timeline->get_duration(), (ARDOUR::frameoffset_t) 0) + ); + + + _editor->commit_reversible_command (); +} + +void +VideoTimeLineDrag::aborted (bool) +{ + if (ARDOUR_UI::instance()->video_timeline->is_offset_locked()) { + return; + } + ARDOUR_UI::instance()->video_timeline->set_offset(_startdrag_video_offset); + ARDOUR_UI::instance()->flush_videotimeline_cache(true); + + for (list::iterator i = _views.begin(); i != _views.end(); ++i) { + i->view->region()->resume_property_changes (); + i->view->region()->set_position(i->initial_position); + } +} + +TrimDrag::TrimDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list const & v, bool preserve_fade_anchor) : RegionDrag (e, i, p, v) { DEBUG_TRACE (DEBUG::Drags, "New TrimDrag\n"); + _preserve_fade_anchor = preserve_fade_anchor; } void @@ -1671,6 +1830,7 @@ TrimDrag::motion (GdkEvent* event, bool first_move) TimeAxisView* tvp = &_primary->get_time_axis_view (); RouteTimeAxisView* tv = dynamic_cast(tvp); pair >::iterator,bool> insert_result; + frameoffset_t frame_delta = 0; if (tv && tv->is_track()) { speed = tv->track()->speed(); @@ -1727,39 +1887,51 @@ TrimDrag::motion (GdkEvent* event, bool first_move) switch (_operation) { case StartTrim: for (list::const_iterator i = _views.begin(); i != _views.end(); ++i) { - i->view->trim_front (i->initial_position + dt, non_overlap_trim); + bool changed = i->view->trim_front (i->initial_position + dt, non_overlap_trim); + if (changed && _preserve_fade_anchor) { + AudioRegionView* arv = dynamic_cast (i->view); + if (arv) { + double distance; + double new_length; + framecnt_t len; + boost::shared_ptr ar (arv->audio_region()); + distance = _drags->current_pointer_x() - grab_x(); + len = ar->fade_in()->back()->when; + new_length = len - _editor->pixel_to_sample (distance); + new_length = ar->verify_xfade_bounds (new_length, true /*START*/ ); + arv->reset_fade_in_shape_width (ar, new_length); //the grey shape + } + } } break; case EndTrim: for (list::const_iterator i = _views.begin(); i != _views.end(); ++i) { - i->view->trim_end (i->initial_end + dt, non_overlap_trim); + bool changed = i->view->trim_end (i->initial_end + dt, non_overlap_trim); + if (changed && _preserve_fade_anchor) { + AudioRegionView* arv = dynamic_cast (i->view); + if (arv) { + double distance; + double new_length; + framecnt_t len; + boost::shared_ptr ar (arv->audio_region()); + distance = grab_x() - _drags->current_pointer_x(); + len = ar->fade_out()->back()->when; + new_length = len - _editor->pixel_to_sample (distance); + new_length = ar->verify_xfade_bounds (new_length, false /*END*/ ); + arv->reset_fade_out_shape_width (ar, new_length); //the grey shape + } + } } break; case ContentsTrim: { - bool swap_direction = false; - - if (event && Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) { - swap_direction = true; - } - - framecnt_t frame_delta = 0; - - bool left_direction = false; - if (last_pointer_frame() > adjusted_current_frame(event)) { - left_direction = true; - } - - if (left_direction) { - frame_delta = (last_pointer_frame() - adjusted_current_frame(event)); - } else { - frame_delta = (adjusted_current_frame(event) - last_pointer_frame()); - } + frame_delta = (adjusted_current_frame(event) - last_pointer_frame()); + // frame_delta = (last_pointer_frame() - adjusted_current_frame(event)); for (list::const_iterator i = _views.begin(); i != _views.end(); ++i) { - i->view->trim_contents (frame_delta, left_direction, swap_direction); + i->view->move_contents (frame_delta); } } break; @@ -1773,7 +1945,7 @@ TrimDrag::motion (GdkEvent* event, bool first_move) show_verbose_cursor_time ((framepos_t) (rv->region()->last_frame() / speed)); break; case ContentsTrim: - show_verbose_cursor_time (adjusted_current_frame (event)); + // show_verbose_cursor_time (frame_delta); break; } } @@ -1785,16 +1957,61 @@ TrimDrag::finished (GdkEvent* event, bool movement_occurred) if (movement_occurred) { motion (event, false); - /* This must happen before the region's StatefulDiffCommand is created, as it may - `correct' (ahem) the region's _start from being negative to being zero. It - needs to be zero in the undo record. - */ if (_operation == StartTrim) { for (list::const_iterator i = _views.begin(); i != _views.end(); ++i) { - i->view->trim_front_ending (); + { + /* This must happen before the region's StatefulDiffCommand is created, as it may + `correct' (ahem) the region's _start from being negative to being zero. It + needs to be zero in the undo record. + */ + i->view->trim_front_ending (); + } + if (_preserve_fade_anchor) { + AudioRegionView* arv = dynamic_cast (i->view); + if (arv) { + double distance; + double new_length; + framecnt_t len; + boost::shared_ptr ar (arv->audio_region()); + distance = _drags->current_pointer_x() - grab_x(); + len = ar->fade_in()->back()->when; + new_length = len - _editor->pixel_to_sample (distance); + new_length = ar->verify_xfade_bounds (new_length, true /*START*/ ); + ar->set_fade_in_length(new_length); + } + } + } + } else if (_operation == EndTrim) { + for (list::const_iterator i = _views.begin(); i != _views.end(); ++i) { + if (_preserve_fade_anchor) { + AudioRegionView* arv = dynamic_cast (i->view); + if (arv) { + double distance; + double new_length; + framecnt_t len; + boost::shared_ptr ar (arv->audio_region()); + distance = _drags->current_pointer_x() - grab_x(); + len = ar->fade_out()->back()->when; + new_length = len - _editor->pixel_to_sample (distance); + new_length = ar->verify_xfade_bounds (new_length, false /*END*/ ); + ar->set_fade_out_length(new_length); + } + } } } + if (!_views.empty()) { + if (_operation == StartTrim) { + _editor->maybe_locate_with_edit_preroll( + _views.begin()->view->region()->position()); + } + if (_operation == EndTrim) { + _editor->maybe_locate_with_edit_preroll( + _views.begin()->view->region()->position() + + _views.begin()->view->region()->length()); + } + } + if (!_editor->selection->selected (_primary)) { _primary->thaw_after_trim (); } else { @@ -1820,6 +2037,7 @@ TrimDrag::finished (GdkEvent* event, bool movement_occurred) } } } + for (set >::iterator p = _editor->motion_frozen_playlists.begin(); p != _editor->motion_frozen_playlists.end(); ++p) { (*p)->thaw (); } @@ -1928,7 +2146,7 @@ MeterMarkerDrag::motion (GdkEvent* event, bool first_move) _marker = new MeterMarker ( *_editor, *_editor->meter_group, - ARDOUR_UI::config()->canvasvar_MeterMarker.get(), + ARDOUR_UI::config()->get_canvasvar_MeterMarker(), name, *new MeterSection (_marker->meter()) ); @@ -2047,7 +2265,7 @@ TempoMarkerDrag::motion (GdkEvent* event, bool first_move) _marker = new TempoMarker ( *_editor, *_editor->tempo_group, - ARDOUR_UI::config()->canvasvar_TempoMarker.get(), + ARDOUR_UI::config()->get_canvasvar_TempoMarker(), name, *new TempoSection (_marker->tempo()) ); @@ -2120,9 +2338,10 @@ TempoMarkerDrag::aborted (bool moved) } } -CursorDrag::CursorDrag (Editor* e, ArdourCanvas::Item* i, bool s) - : Drag (e, i), - _stop (s) +CursorDrag::CursorDrag (Editor* e, EditorCursor& c, bool s) + : Drag (e, &c.time_bar_canvas_item()) + , _cursor (c) + , _stop (s) { DEBUG_TRACE (DEBUG::Drags, "New CursorDrag\n"); } @@ -2138,7 +2357,7 @@ CursorDrag::fake_locate (framepos_t t) Session* s = _editor->session (); if (s->timecode_transmission_suspended ()) { - framepos_t const f = _editor->playhead_cursor->current_frame; + framepos_t const f = _editor->playhead_cursor->current_frame (); s->send_mmc_locate (f); s->send_full_time_code (f); } @@ -2152,15 +2371,20 @@ CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c) { Drag::start_grab (event, c); - _grab_zoom = _editor->frames_per_unit; + _grab_zoom = _editor->samples_per_pixel; + + framepos_t where = _editor->canvas_event_frame (event, 0, 0); - framepos_t where = _editor->event_frame (event, 0, 0); _editor->snap_to_with_modifier (where, event); _editor->_dragging_playhead = true; Session* s = _editor->session (); + /* grab the track canvas item as well */ + + _cursor.track_canvas_item().grab(); + if (s) { if (_was_rolling && _stop) { s->request_stop (); @@ -2170,9 +2394,20 @@ CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c) s->cancel_audition (); } - s->request_suspend_timecode_transmission (); - while (!s->timecode_transmission_suspended ()) { - /* twiddle our thumbs */ + + if (AudioEngine::instance()->connected()) { + + /* do this only if we're the engine is connected + * because otherwise this request will never be + * serviced and we'll busy wait forever. likewise, + * notice if we are disconnected while waiting for the + * request to be serviced. + */ + + s->request_suspend_timecode_transmission (); + while (AudioEngine::instance()->connected() && !s->timecode_transmission_suspended ()) { + /* twiddle our thumbs */ + } } } @@ -2185,9 +2420,6 @@ CursorDrag::motion (GdkEvent* event, bool) framepos_t const adjusted_frame = adjusted_current_frame (event); if (adjusted_frame != last_pointer_frame()) { fake_locate (adjusted_frame); -#ifdef GTKOSX - _editor->update_canvas_now (); -#endif } } @@ -2196,6 +2428,8 @@ CursorDrag::finished (GdkEvent* event, bool movement_occurred) { _editor->_dragging_playhead = false; + _cursor.track_canvas_item().ungrab(); + if (!movement_occurred && _stop) { return; } @@ -2204,7 +2438,7 @@ CursorDrag::finished (GdkEvent* event, bool movement_occurred) Session* s = _editor->session (); if (s) { - s->request_locate (_editor->playhead_cursor->current_frame, _was_rolling); + s->request_locate (_editor->playhead_cursor->current_frame (), _was_rolling); _editor->_pending_locate_request = true; s->request_resume_timecode_transmission (); } @@ -2213,6 +2447,8 @@ CursorDrag::finished (GdkEvent* event, bool movement_occurred) void CursorDrag::aborted (bool) { + _cursor.track_canvas_item().ungrab(); + if (_editor->_dragging_playhead) { _editor->session()->request_resume_timecode_transmission (); _editor->_dragging_playhead = false; @@ -2236,8 +2472,6 @@ FadeInDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor) boost::shared_ptr const r = arv->audio_region (); show_verbose_cursor_duration (r->position(), r->position() + r->fade_in()->back()->when, 32); - - arv->show_fade_line((framepos_t) r->fade_in()->back()->when); } void @@ -2273,8 +2507,7 @@ FadeInDrag::motion (GdkEvent* event, bool) continue; } - tmp->reset_fade_in_shape_width (fade_length); - tmp->show_fade_line((framecnt_t) fade_length); + tmp->reset_fade_in_shape_width (tmp->audio_region(), fade_length); } show_verbose_cursor_duration (region->position(), region->position() + fade_length, 32); @@ -2316,7 +2549,6 @@ FadeInDrag::finished (GdkEvent* event, bool movement_occurred) tmp->audio_region()->set_fade_in_length (fade_length); tmp->audio_region()->set_fade_in_active (true); - tmp->hide_fade_line(); XMLNode &after = alist->get_state(); _editor->session()->add_command(new MementoCommand(*alist.get(), &before, &after)); @@ -2335,8 +2567,7 @@ FadeInDrag::aborted (bool) continue; } - tmp->reset_fade_in_shape_width (tmp->audio_region()->fade_in()->back()->when); - tmp->hide_fade_line(); + tmp->reset_fade_in_shape_width (tmp->audio_region(), tmp->audio_region()->fade_in()->back()->when); } } @@ -2355,8 +2586,6 @@ FadeOutDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor) boost::shared_ptr r = arv->audio_region (); show_verbose_cursor_duration (r->last_frame() - r->fade_out()->back()->when, r->last_frame()); - - arv->show_fade_line(r->length() - r->fade_out()->back()->when); } void @@ -2394,8 +2623,7 @@ FadeOutDrag::motion (GdkEvent* event, bool) continue; } - tmp->reset_fade_out_shape_width (fade_length); - tmp->show_fade_line(region->length() - fade_length); + tmp->reset_fade_out_shape_width (tmp->audio_region(), fade_length); } show_verbose_cursor_duration (region->last_frame() - fade_length, region->last_frame()); @@ -2439,7 +2667,6 @@ FadeOutDrag::finished (GdkEvent* event, bool movement_occurred) tmp->audio_region()->set_fade_out_length (fade_length); tmp->audio_region()->set_fade_out_active (true); - tmp->hide_fade_line(); XMLNode &after = alist->get_state(); _editor->session()->add_command(new MementoCommand(*alist.get(), &before, &after)); @@ -2458,8 +2685,7 @@ FadeOutDrag::aborted (bool) continue; } - tmp->reset_fade_out_shape_width (tmp->audio_region()->fade_out()->back()->when); - tmp->hide_fade_line(); + tmp->reset_fade_out_shape_width (tmp->audio_region(), tmp->audio_region()->fade_out()->back()->when); } } @@ -2471,17 +2697,24 @@ MarkerDrag::MarkerDrag (Editor* e, ArdourCanvas::Item* i) _marker = reinterpret_cast (_item->get_data ("marker")); assert (_marker); - _points.push_back (Gnome::Art::Point (0, 0)); - _points.push_back (Gnome::Art::Point (0, physical_screen_height (_editor->get_window()))); + _points.push_back (ArdourCanvas::Duple (0, 0)); + _points.push_back (ArdourCanvas::Duple (0, physical_screen_height (_editor->get_window()))); } MarkerDrag::~MarkerDrag () { - for (list::iterator i = _copied_locations.begin(); i != _copied_locations.end(); ++i) { - delete *i; + for (CopiedLocationInfo::iterator i = _copied_locations.begin(); i != _copied_locations.end(); ++i) { + delete i->location; } } +MarkerDrag::CopiedLocationMarkerInfo::CopiedLocationMarkerInfo (Location* l, Marker* m) +{ + location = new Location (*l); + markers.push_back (m); + move_both = false; +} + void MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor) { @@ -2507,7 +2740,7 @@ MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor) switch (op) { case Selection::Toggle: - _editor->selection->toggle (_marker); + /* we toggle on the button release */ break; case Selection::Set: if (!_editor->selection->selected (_marker)) { @@ -2549,11 +2782,37 @@ MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor) break; } - /* Set up copies for us to manipulate during the drag */ + /* Set up copies for us to manipulate during the drag + */ for (MarkerSelection::iterator i = _editor->selection->markers.begin(); i != _editor->selection->markers.end(); ++i) { + Location* l = _editor->find_location_from_marker (*i, is_start); - _copied_locations.push_back (new Location (*l)); + + if (!l) { + continue; + } + + if (l->is_mark()) { + _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i)); + } else { + /* range: check that the other end of the range isn't + already there. + */ + CopiedLocationInfo::iterator x; + for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) { + if (*(*x).location == *l) { + break; + } + } + if (x == _copied_locations.end()) { + _copied_locations.push_back (CopiedLocationMarkerInfo (l, *i)); + } else { + (*x).markers.push_back (*i); + (*x).move_both = true; + } + } + } } @@ -2571,33 +2830,31 @@ MarkerDrag::motion (GdkEvent* event, bool) framecnt_t f_delta = 0; bool is_start; bool move_both = false; - Marker* marker; Location *real_location; Location *copy_location = 0; framepos_t const newframe = adjusted_current_frame (event); - framepos_t next = newframe; if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) { move_both = true; } - MarkerSelection::iterator i; - list::iterator x; + CopiedLocationInfo::iterator x; /* find the marker we're dragging, and compute the delta */ - for (i = _editor->selection->markers.begin(), x = _copied_locations.begin(); - x != _copied_locations.end() && i != _editor->selection->markers.end(); - ++i, ++x) { + for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) { - copy_location = *x; - marker = *i; + copy_location = (*x).location; - if (marker == _marker) { + if (find (x->markers.begin(), x->markers.end(), _marker) != x->markers.end()) { - if ((real_location = _editor->find_location_from_marker (marker, is_start)) == 0) { + /* this marker is represented by this + * CopiedLocationMarkerInfo + */ + + if ((real_location = _editor->find_location_from_marker (_marker, is_start)) == 0) { /* que pasa ?? */ return; } @@ -2607,7 +2864,7 @@ MarkerDrag::motion (GdkEvent* event, bool) } else { - switch (marker->type()) { + switch (_marker->type()) { case Marker::SessionStart: case Marker::RangeStart: case Marker::LoopStart: @@ -2626,27 +2883,25 @@ MarkerDrag::motion (GdkEvent* event, bool) return; } } + break; } } - if (i == _editor->selection->markers.end()) { + if (x == _copied_locations.end()) { /* hmm, impossible - we didn't find the dragged marker */ return; } /* now move them all */ - for (i = _editor->selection->markers.begin(), x = _copied_locations.begin(); - x != _copied_locations.end() && i != _editor->selection->markers.end(); - ++i, ++x) { + for (x = _copied_locations.begin(); x != _copied_locations.end(); ++x) { - copy_location = *x; - marker = *i; + copy_location = x->location; /* call this to find out if its the start or end */ - if ((real_location = _editor->find_location_from_marker (marker, is_start)) == 0) { + if ((real_location = _editor->find_location_from_marker (x->markers.front(), is_start)) == 0) { continue; } @@ -2661,13 +2916,13 @@ MarkerDrag::motion (GdkEvent* event, bool) copy_location->set_start (copy_location->start() + f_delta); } else { - + framepos_t new_start = copy_location->start() + f_delta; framepos_t new_end = copy_location->end() + f_delta; - + if (is_start) { // start-of-range marker - - if (move_both) { + + if (move_both || (*x).move_both) { copy_location->set_start (new_start); copy_location->set_end (new_end); } else if (new_start < copy_location->end()) { @@ -2680,7 +2935,7 @@ MarkerDrag::motion (GdkEvent* event, bool) } else { // end marker - if (move_both) { + if (move_both || (*x).move_both) { copy_location->set_end (new_end); copy_location->set_start (new_start); } else if (new_end > copy_location->start()) { @@ -2694,21 +2949,25 @@ MarkerDrag::motion (GdkEvent* event, bool) } update_item (copy_location); - + + /* now lookup the actual GUI items used to display this + * location and move them to wherever the copy of the location + * is now. This means that the logic in ARDOUR::Location is + * still enforced, even though we are not (yet) modifying + * the real Location itself. + */ + Editor::LocationMarkers* lm = _editor->find_location_markers (real_location); if (lm) { lm->set_position (copy_location->start(), copy_location->end()); } + } assert (!_copied_locations.empty()); show_verbose_cursor_time (newframe); - -#ifdef GTKOSX - _editor->update_canvas_now (); -#endif } void @@ -2730,6 +2989,10 @@ MarkerDrag::finished (GdkEvent* event, bool movement_occurred) break; case Selection::Toggle: + /* we toggle on the button release, click only */ + _editor->selection->toggle (_marker); + break; + case Selection::Extend: case Selection::Add: break; @@ -2744,7 +3007,7 @@ MarkerDrag::finished (GdkEvent* event, bool movement_occurred) XMLNode &before = _editor->session()->locations()->get_state(); MarkerSelection::iterator i; - list::iterator x; + CopiedLocationInfo::iterator x; bool is_start; for (i = _editor->selection->markers.begin(), x = _copied_locations.begin(); @@ -2760,9 +3023,9 @@ MarkerDrag::finished (GdkEvent* event, bool movement_occurred) } if (location->is_mark()) { - location->set_start ((*x)->start()); + location->set_start (((*x).location)->start()); } else { - location->set ((*x)->start(), (*x)->end()); + location->set (((*x).location)->start(), ((*x).location)->end()); } } } @@ -2818,6 +3081,12 @@ ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/) event->button.x + 10, event->button.y + 10); _editor->verbose_cursor()->show (); + + _pushing = Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier); + + if (!_point->can_slide ()) { + _x_constrained = true; + } } void @@ -2859,7 +3128,7 @@ ControlPointDrag::motion (GdkEvent* event, bool) cy = max (0.0, cy); cy = min ((double) _point->line().height(), cy); - framepos_t cx_frames = _editor->unit_to_frame (cx); + framepos_t cx_frames = _editor->pixel_to_sample (cx); if (!_x_constrained) { _editor->snap_to_with_modifier (cx_frames, event); @@ -2869,9 +3138,7 @@ ControlPointDrag::motion (GdkEvent* event, bool) float const fraction = 1.0 - (cy / _point->line().height()); - bool const push = Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier); - - _point->line().drag_motion (_editor->frame_to_unit (cx_frames), fraction, false, push); + _point->line().drag_motion (_editor->sample_to_pixel_unrounded (cx_frames), fraction, false, _pushing, _final_index); _editor->verbose_cursor()->set_text (_point->line().get_verbose_cursor_string (fraction)); } @@ -2891,7 +3158,7 @@ ControlPointDrag::finished (GdkEvent* event, bool movement_occurred) motion (event, false); } - _point->line().end_drag (); + _point->line().end_drag (_pushing, _final_index); _editor->session()->commit_reversible_command (); } @@ -2936,9 +3203,9 @@ LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/) double cx = event->button.x; double cy = event->button.y; - _line->parent_group().w2i (cx, cy); + _line->parent_group().canvas_to_item (cx, cy); - framecnt_t const frame_within_region = (framecnt_t) floor (cx * _editor->frames_per_unit); + framecnt_t const frame_within_region = (framecnt_t) floor (cx * _editor->samples_per_pixel); uint32_t before; uint32_t after; @@ -2982,10 +3249,10 @@ LineDrag::motion (GdkEvent* event, bool) cy = min ((double) _line->height(), cy); double const fraction = 1.0 - (cy / _line->height()); - bool const push = !Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier); + uint32_t ignored; /* we are ignoring x position for this drag, so we can just pass in anything */ - _line->drag_motion (0, fraction, true, push); + _line->drag_motion (0, fraction, true, false, ignored); _editor->verbose_cursor()->set_text (_line->get_verbose_cursor_string (fraction)); } @@ -2994,7 +3261,7 @@ void LineDrag::finished (GdkEvent* event, bool) { motion (event, false); - _line->end_drag (); + _line->end_drag (false, 0); _editor->session()->commit_reversible_command (); } @@ -3025,7 +3292,7 @@ FeatureLineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/) double cx = event->button.x; double cy = event->button.y; - _item->property_parent().get_value()->w2i(cx, cy); + _item->parent()->canvas_to_item (cx, cy); /* store grab start in parent frame */ _region_view_grab_x = cx; @@ -3034,7 +3301,7 @@ FeatureLineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/) _arv = reinterpret_cast (_item->get_data ("regionview")); - _max_x = _editor->frame_to_pixel(_arv->get_duration()); + _max_x = _editor->sample_to_pixel(_arv->get_duration()); } void @@ -3055,16 +3322,9 @@ FeatureLineDrag::motion (GdkEvent*, bool) cx = 0; } - ArdourCanvas::Points points; - - double x1 = 0, x2 = 0, y1 = 0, y2 = 0; - - _line->get_bounds(x1, y2, x2, y2); - - points.push_back(Gnome::Art::Point(cx, 2.0)); // first x-coord needs to be a non-normal value - points.push_back(Gnome::Art::Point(cx, y2 - y1)); - - _line->property_points() = points; + boost::optional bbox = _line->bounding_box (); + assert (bbox); + _line->set (ArdourCanvas::Duple (cx, 2.0), ArdourCanvas::Duple (cx, bbox.get().height ())); float *pos = new float; *pos = cx; @@ -3137,20 +3397,20 @@ RubberbandSelectDrag::motion (GdkEvent* event, bool) if (start != end || y1 != y2) { - double x1 = _editor->frame_to_pixel (start); - double x2 = _editor->frame_to_pixel (end); + double x1 = _editor->sample_to_pixel (start); + double x2 = _editor->sample_to_pixel (end); - _editor->rubberband_rect->property_x1() = x1; + _editor->rubberband_rect->set_x0 (x1); if (_vertical_only) { /* fixed 10 pixel width */ - _editor->rubberband_rect->property_x2() = x1 + 10; + _editor->rubberband_rect->set_x1 (x1 + 10); } else { - _editor->rubberband_rect->property_x2() = x2; + _editor->rubberband_rect->set_x1 (x2); } - _editor->rubberband_rect->property_y1() = y1; - _editor->rubberband_rect->property_y2() = y2; - + _editor->rubberband_rect->set_y0 (y1); + _editor->rubberband_rect->set_y1 (y2); + _editor->rubberband_rect->show(); _editor->rubberband_rect->raise_to_top(); @@ -3212,7 +3472,13 @@ RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred) } } - if (do_deselect) { + /* do not deselect if Primary or Tertiary (toggle-select or + * extend-select are pressed. + */ + + if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier) && + !Keyboard::modifier_state_contains (event->button.state, Keyboard::TertiaryModifier) && + do_deselect) { deselect_things (); } @@ -3245,11 +3511,16 @@ void TimeFXDrag::motion (GdkEvent* event, bool) { RegionView* rv = _primary; + StreamView* cv = rv->get_time_axis_view().view (); + + pair const tv = _editor->trackview_by_y_position (grab_y()); + int layer = tv.first->layer_display() == Overlaid ? 0 : tv.second; + int layers = tv.first->layer_display() == Overlaid ? 1 : cv->layers(); framepos_t const pf = adjusted_current_frame (event); if (pf > rv->region()->position()) { - rv->get_time_axis_view().show_timestretch (rv->region()->position(), pf); + rv->get_time_axis_view().show_timestretch (rv->region()->position(), pf, layers, layer); } show_verbose_cursor_time (pf); @@ -3335,12 +3606,18 @@ ScrubDrag::aborted (bool) SelectionDrag::SelectionDrag (Editor* e, ArdourCanvas::Item* i, Operation o) : Drag (e, i) , _operation (o) - , _copy (false) + , _add (false) + , _extend (false) , _original_pointer_time_axis (-1) , _last_pointer_time_axis (-1) , _time_selection_at_start (!_editor->get_selection().time.empty()) { DEBUG_TRACE (DEBUG::Drags, "New SelectionDrag\n"); + + if (_time_selection_at_start) { + start_at_start = _editor->get_selection().time.start(); + end_at_start = _editor->get_selection().time.end_frame(); + } } void @@ -3354,10 +3631,10 @@ SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*) switch (_operation) { case CreateSelection: - if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) { - _copy = true; + if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) { + _add = true; } else { - _copy = false; + _add = false; } cursor = _editor->cursors()->selector; Drag::start_grab (event, cursor); @@ -3380,6 +3657,10 @@ SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*) case SelectionMove: Drag::start_grab (event, cursor); break; + + case SelectionExtend: + Drag::start_grab (event, cursor); + break; } if (_operation == SelectionMove) { @@ -3407,6 +3688,9 @@ SelectionDrag::setup_pointer_frame_offset () case SelectionEndTrim: _pointer_frame_offset = raw_grab_frame() - _editor->selection->time[_editor->clicked_selection].end; break; + + case SelectionExtend: + break; } } @@ -3415,7 +3699,8 @@ SelectionDrag::motion (GdkEvent* event, bool first_move) { framepos_t start = 0; framepos_t end = 0; - framecnt_t length; + framecnt_t length = 0; + framecnt_t distance = 0; pair const pending_time_axis = _editor->trackview_by_y_position (_drags->current_pointer_y ()); if (pending_time_axis.first == 0) { @@ -3436,10 +3721,15 @@ SelectionDrag::motion (GdkEvent* event, bool first_move) framepos_t grab = grab_frame (); if (first_move) { - _editor->snap_to (grab); + grab = adjusted_current_frame (event, false); + if (grab < pending_position) { + _editor->snap_to (grab, -1); + } else { + _editor->snap_to (grab, 1); + } } - if (pending_position < grab_frame()) { + if (pending_position < grab) { start = pending_position; end = grab; } else { @@ -3453,12 +3743,12 @@ SelectionDrag::motion (GdkEvent* event, bool first_move) if (first_move) { - if (_copy) { + if (_add) { /* adding to the selection */ _editor->set_selected_track_as_side_effect (Selection::Add); //_editor->selection->add (_editor->clicked_axisview); _editor->clicked_selection = _editor->selection->add (start, end); - _copy = false; + _add = false; } else { /* new selection */ @@ -3526,28 +3816,39 @@ SelectionDrag::motion (GdkEvent* event, bool first_move) } break; - + case SelectionMove: start = _editor->selection->time[_editor->clicked_selection].start; end = _editor->selection->time[_editor->clicked_selection].end; length = end - start; - + distance = pending_position - start; start = pending_position; _editor->snap_to (start); end = start + length; break; + + case SelectionExtend: + break; } - if (event->button.x >= _editor->horizontal_position() + _editor->_canvas_width) { + if (event->button.x >= _editor->horizontal_position() + _editor->_visible_canvas_width) { _editor->start_canvas_autoscroll (1, 0); } if (start != end) { - _editor->selection->replace (_editor->clicked_selection, start, end); + switch (_operation) { + case SelectionMove: + if (_time_selection_at_start) { + _editor->selection->move_time (distance); + } + break; + default: + _editor->selection->replace (_editor->clicked_selection, start, end); + } } if (_operation == SelectionMove) { @@ -3571,13 +3872,11 @@ SelectionDrag::finished (GdkEvent* event, bool movement_occurred) /* XXX what if its a music time selection? */ if (s) { - if ((s->config.get_auto_play() || (s->get_play_range() && s->transport_rolling()))) { + if ( s->get_play_range() && s->transport_rolling() ) { s->request_play_range (&_editor->selection->time, true); } else { - if (Config->get_always_play_range()) { - if (_editor->doing_range_stuff()) { - s->request_locate (_editor->get_selection().time.start()); - } + if (Config->get_always_play_range() && !s->transport_rolling()) { + s->request_locate (_editor->get_selection().time.start()); } } } @@ -3586,44 +3885,37 @@ SelectionDrag::finished (GdkEvent* event, bool movement_occurred) /* just a click, no pointer movement. */ - if (Keyboard::no_modifier_keys_pressed (&event->button)) { - if (!_time_selection_at_start) { - if (_editor->clicked_regionview) { - if (_editor->get_selection().selected (_editor->clicked_regionview)) { - /* range select the entire current - region selection - */ - _editor->select_range (_editor->get_selection().regions.start(), - _editor->get_selection().regions.end_frame()); - } else { - /* range select this (unselected) - * region - */ - _editor->select_range (_editor->clicked_regionview->region()->position(), - _editor->clicked_regionview->region()->last_frame()); - } + if (_operation == SelectionExtend) { + if (_time_selection_at_start) { + framepos_t pos = adjusted_current_frame (event, false); + framepos_t start = min (pos, start_at_start); + framepos_t end = max (pos, end_at_start); + _editor->selection->set (start, end); + } + } else { + if (Keyboard::modifier_state_equals (event->button.state, Keyboard::CopyModifier)) { + if (_editor->clicked_selection) { + _editor->selection->remove (_editor->clicked_selection); } } else { - _editor->selection->clear_time(); + if (!_editor->clicked_selection) { + _editor->selection->clear_time(); + } } } if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) { _editor->selection->set (_editor->clicked_axisview); } - + if (s && s->get_play_range () && s->transport_rolling()) { s->request_stop (false, false); } - if (Config->get_always_play_range()) { - if (_editor->doing_range_stuff()) { - s->request_locate (_editor->get_selection().time.start()); - } - } } _editor->stop_canvas_autoscroll (); + _editor->clicked_selection = 0; } void @@ -3639,12 +3931,13 @@ RangeMarkerBarDrag::RangeMarkerBarDrag (Editor* e, ArdourCanvas::Item* i, Operat { DEBUG_TRACE (DEBUG::Drags, "New RangeMarkerBarDrag\n"); - _drag_rect = new ArdourCanvas::SimpleRect (*_editor->time_line_group, 0.0, 0.0, 0.0, - physical_screen_height (_editor->get_window())); + _drag_rect = new ArdourCanvas::Rectangle (_editor->time_line_group, + ArdourCanvas::Rect (0.0, 0.0, 0.0, + physical_screen_height (_editor->get_window()))); _drag_rect->hide (); - _drag_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_RangeDragRect.get(); - _drag_rect->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_RangeDragRect.get(); + _drag_rect->set_fill_color (ARDOUR_UI::config()->get_canvasvar_RangeDragRect()); + _drag_rect->set_outline_color (ARDOUR_UI::config()->get_canvasvar_RangeDragRect()); } void @@ -3684,7 +3977,7 @@ RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move) { framepos_t start = 0; framepos_t end = 0; - ArdourCanvas::SimpleRect *crect; + ArdourCanvas::Rectangle *crect; switch (_operation) { case CreateRangeMarker: @@ -3697,7 +3990,7 @@ RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move) crect = _editor->cd_marker_bar_drag_rect; break; default: - cerr << "Error: unknown range marker op passed to Editor::drag_range_markerbar_op ()" << endl; + error << string_compose (_("programming_error: %1"), "Error: unknown range marker op passed to Editor::drag_range_markerbar_op ()") << endmsg; return; break; } @@ -3733,17 +4026,17 @@ RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move) } } - if (event->button.x >= _editor->horizontal_position() + _editor->_canvas_width) { + if (event->button.x >= _editor->horizontal_position() + _editor->_visible_canvas_width) { _editor->start_canvas_autoscroll (1, 0); } if (start != end) { _editor->temp_location->set (start, end); - double x1 = _editor->frame_to_pixel (start); - double x2 = _editor->frame_to_pixel (end); - crect->property_x1() = x1; - crect->property_x2() = x2; + double x1 = _editor->sample_to_pixel (start); + double x2 = _editor->sample_to_pixel (end); + crect->set_x0 (x1); + crect->set_x1 (x2); update_item (_editor->temp_location); } @@ -3841,11 +4134,11 @@ RangeMarkerBarDrag::aborted (bool) void RangeMarkerBarDrag::update_item (Location* location) { - double const x1 = _editor->frame_to_pixel (location->start()); - double const x2 = _editor->frame_to_pixel (location->end()); + double const x1 = _editor->sample_to_pixel (location->start()); + double const x2 = _editor->sample_to_pixel (location->end()); - _drag_rect->property_x1() = x1; - _drag_rect->property_x2() = x2; + _drag_rect->set_x0 (x1); + _drag_rect->set_x1 (x2); } MouseZoomDrag::MouseZoomDrag (Editor* e, ArdourCanvas::Item* i) @@ -3937,7 +4230,8 @@ NoteDrag::NoteDrag (Editor* e, ArdourCanvas::Item* i) { DEBUG_TRACE (DEBUG::Drags, "New NoteDrag\n"); - _primary = dynamic_cast (_item); + _primary = reinterpret_cast (_item->get_data ("notebase")); + assert (_primary); _region = &_primary->region_view (); _note_height = _region->midi_stream_view()->note_height (); } @@ -3973,7 +4267,7 @@ frameoffset_t NoteDrag::total_dx () const { /* dx in frames */ - frameoffset_t const dx = _editor->unit_to_frame (_drags->current_pointer_x() - grab_x()); + frameoffset_t const dx = _editor->pixel_to_sample (_drags->current_pointer_x() - grab_x()); /* primary note time */ frameoffset_t const n = _region->source_beats_to_absolute_frames (_primary->note()->time ()); @@ -3994,7 +4288,15 @@ NoteDrag::total_dx () const int8_t NoteDrag::total_dy () const { - return ((int8_t) (grab_y() / _note_height)) - ((int8_t) (_drags->current_pointer_y() / _note_height)); + MidiStreamView* msv = _region->midi_stream_view (); + double const y = _region->midi_view()->y_position (); + /* new current note */ + uint8_t n = msv->y_to_note (_drags->current_pointer_y () - y); + /* clamp */ + n = max (msv->lowest_note(), n); + n = min (msv->highest_note(), n); + /* and work out delta */ + return n - msv->y_to_note (grab_y() - y); } void @@ -4005,7 +4307,7 @@ NoteDrag::motion (GdkEvent *, bool) int8_t const dy = total_dy (); /* Now work out what we have to do to the note canvas items to set this new drag delta */ - double const tdx = _editor->frame_to_unit (dx) - _cumulative_dx; + double const tdx = _editor->sample_to_pixel (dx) - _cumulative_dx; double const tdy = -dy * _note_height - _cumulative_dy; if (tdx || tdy) { @@ -4077,7 +4379,7 @@ AutomationRangeDrag::AutomationRangeDrag (Editor* editor, AutomationTimeAxisView , _nothing_to_drag (false) { DEBUG_TRACE (DEBUG::Drags, "New AutomationRangeDrag\n"); - + y_origin = atv->y_position(); setup (atv->lines ()); } @@ -4091,6 +4393,7 @@ AutomationRangeDrag::AutomationRangeDrag (Editor* editor, AudioRegionView* rv, l list > lines; lines.push_back (rv->get_gain_line ()); + y_origin = rv->get_time_axis_view().y_position(); setup (lines); } @@ -4132,6 +4435,12 @@ AutomationRangeDrag::setup (list > const & lin /* Now ::lines contains the AutomationLines that somehow overlap our drag */ } +double +AutomationRangeDrag::y_fraction (boost::shared_ptr line, double global_y) const +{ + return 1.0 - ((global_y - y_origin) / line->height()); +} + void AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor) { @@ -4140,6 +4449,7 @@ AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor) /* Get line states before we start changing things */ for (list::iterator i = _lines.begin(); i != _lines.end(); ++i) { i->state = &i->line->get_state (); + i->original_fraction = y_fraction (i->line, _drags->current_pointer_y()); } if (_ranges.empty()) { @@ -4245,7 +4555,7 @@ AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor) } for (list::iterator i = _lines.begin(); i != _lines.end(); ++i) { - i->line->start_drag_multiple (i->points, 1 - (_drags->current_pointer_y() / i->line->height ()), i->state); + i->line->start_drag_multiple (i->points, y_fraction (i->line, _drags->current_pointer_y()), i->state); } } @@ -4256,11 +4566,12 @@ AutomationRangeDrag::motion (GdkEvent*, bool /*first_move*/) return; } - for (list::iterator i = _lines.begin(); i != _lines.end(); ++i) { - float const f = 1 - (_drags->current_pointer_y() / i->line->height()); - + for (list::iterator l = _lines.begin(); l != _lines.end(); ++l) { + float const f = y_fraction (l->line, _drags->current_pointer_y()); /* we are ignoring x position for this drag, so we can just pass in anything */ - i->line->drag_motion (0, f, true, false); + uint32_t ignored; + l->line->drag_motion (0, f, true, false, ignored); + show_verbose_cursor_text (l->line->get_verbose_cursor_relative_string (l->original_fraction, f)); } } @@ -4273,7 +4584,7 @@ AutomationRangeDrag::finished (GdkEvent* event, bool) motion (event, false); for (list::iterator i = _lines.begin(); i != _lines.end(); ++i) { - i->line->end_drag (); + i->line->end_drag (false, 0); } _editor->session()->commit_reversible_command (); @@ -4292,19 +4603,21 @@ DraggingView::DraggingView (RegionView* v, RegionDrag* parent) { time_axis_view = parent->find_time_axis_view (&v->get_time_axis_view ()); layer = v->region()->layer (); - initial_y = v->get_canvas_group()->property_y (); + initial_y = v->get_canvas_group()->position().y; initial_playlist = v->region()->playlist (); initial_position = v->region()->position (); initial_end = v->region()->position () + v->region()->length (); } -PatchChangeDrag::PatchChangeDrag (Editor* e, CanvasPatchChange* i, MidiRegionView* r) - : Drag (e, i) +PatchChangeDrag::PatchChangeDrag (Editor* e, PatchChange* i, MidiRegionView* r) + : Drag (e, i->canvas_item ()) , _region_view (r) , _patch_change (i) , _cumulative_dx (0) { - DEBUG_TRACE (DEBUG::Drags, "New PatchChangeDrag\n"); + DEBUG_TRACE (DEBUG::Drags, string_compose ("New PatchChangeDrag, patch @ %1, grab @ %2\n", + _region_view->source_beats_to_absolute_frames (_patch_change->patch()->time()), + grab_frame())); } void @@ -4315,9 +4628,9 @@ PatchChangeDrag::motion (GdkEvent* ev, bool) f = max (f, r->position ()); f = min (f, r->last_frame ()); - framecnt_t const dxf = f - grab_frame(); - double const dxu = _editor->frame_to_unit (dxf); - _patch_change->move (dxu - _cumulative_dx, 0); + framecnt_t const dxf = f - grab_frame(); // permitted dx in frames + double const dxu = _editor->sample_to_pixel (dxf); // permitted fx in units + _patch_change->move (ArdourCanvas::Duple (dxu - _cumulative_dx, 0)); _cumulative_dx = dxu; } @@ -4329,21 +4642,20 @@ PatchChangeDrag::finished (GdkEvent* ev, bool movement_occurred) } boost::shared_ptr r (_region_view->region ()); - framepos_t f = adjusted_current_frame (ev); f = max (f, r->position ()); f = min (f, r->last_frame ()); _region_view->move_patch_change ( *_patch_change, - _region_view->region_frames_to_region_beats (f - r->position() - r->start()) + _region_view->region_frames_to_region_beats (f - (r->position() - r->start())) ); } void PatchChangeDrag::aborted (bool) { - _patch_change->move (-_cumulative_dx, 0); + _patch_change->move (ArdourCanvas::Duple (-_cumulative_dx, 0)); } void @@ -4354,7 +4666,7 @@ PatchChangeDrag::setup_pointer_frame_offset () } MidiRubberbandSelectDrag::MidiRubberbandSelectDrag (Editor* e, MidiRegionView* rv) - : RubberbandSelectDrag (e, rv->get_canvas_frame ()) + : RubberbandSelectDrag (e, rv->get_canvas_group ()) , _region_view (rv) { @@ -4372,8 +4684,8 @@ MidiRubberbandSelectDrag::select_things (int button_state, framepos_t x1, framep y2 = max (0.0, y2 - y); _region_view->update_drag_selection ( - _editor->frame_to_pixel (x1), - _editor->frame_to_pixel (x2), + _editor->sample_to_pixel (x1), + _editor->sample_to_pixel (x2), y1, y2, Keyboard::modifier_state_contains (button_state, Keyboard::TertiaryModifier) @@ -4387,7 +4699,7 @@ MidiRubberbandSelectDrag::deselect_things () } MidiVerticalSelectDrag::MidiVerticalSelectDrag (Editor* e, MidiRegionView* rv) - : RubberbandSelectDrag (e, rv->get_canvas_frame ()) + : RubberbandSelectDrag (e, rv->get_canvas_group ()) , _region_view (rv) { _vertical_only = true; @@ -4476,7 +4788,7 @@ NoteCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor) { Drag::start_grab (event, cursor); - _drag_rect = new ArdourCanvas::SimpleRect (*_region_view->get_canvas_group ()); + _drag_rect = new ArdourCanvas::Rectangle (_region_view->get_canvas_group ()); framepos_t pf = _drags->current_pointer_frame (); framecnt_t const g = grid_frames (pf); @@ -4491,28 +4803,24 @@ NoteCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor) _note[0] = adjusted_frame (pf, event) - _region_view->region()->position (); MidiStreamView* sv = _region_view->midi_stream_view (); - double const x = _editor->frame_to_pixel (_note[0]); + double const x = _editor->sample_to_pixel (_note[0]); double const y = sv->note_to_y (sv->y_to_note (y_to_region (event->button.y))); - _drag_rect->property_x1() = x; - _drag_rect->property_y1() = y; - _drag_rect->property_x2() = x; - _drag_rect->property_y2() = y + floor (_region_view->midi_stream_view()->note_height ()); - - _drag_rect->property_outline_what() = 0xff; - _drag_rect->property_outline_color_rgba() = 0xffffff99; - _drag_rect->property_fill_color_rgba() = 0xffffff66; + _drag_rect->set (ArdourCanvas::Rect (x, y, x, y + floor (_region_view->midi_stream_view()->note_height ()))); + _drag_rect->set_outline_what (0xff); + _drag_rect->set_outline_color (0xffffff99); + _drag_rect->set_fill_color (0xffffff66); } void NoteCreateDrag::motion (GdkEvent* event, bool) { _note[1] = max ((framepos_t)0, adjusted_current_frame (event) - _region_view->region()->position ()); - double const x = _editor->frame_to_pixel (_note[1]); + double const x = _editor->sample_to_pixel (_note[1]); if (_note[1] > _note[0]) { - _drag_rect->property_x2() = x; + _drag_rect->set_x1 (x); } else { - _drag_rect->property_x1() = x; + _drag_rect->set_x0 (x); } } @@ -4524,7 +4832,7 @@ NoteCreateDrag::finished (GdkEvent*, bool had_movement) } framepos_t const start = min (_note[0], _note[1]); - framecnt_t length = abs (_note[0] - _note[1]); + framecnt_t length = (framecnt_t) fabs (_note[0] - _note[1]); framecnt_t const g = grid_frames (start); double const one_tick = 1 / Timecode::BBT_Time::ticks_per_beat; @@ -4535,14 +4843,14 @@ NoteCreateDrag::finished (GdkEvent*, bool had_movement) double const length_beats = max (one_tick, _region_view->region_frames_to_region_beats (length)); - _region_view->create_note_at (start, _drag_rect->property_y1(), length_beats, false); + _region_view->create_note_at (start, _drag_rect->y0(), length_beats, false); } double NoteCreateDrag::y_to_region (double y) const { double x = 0; - _region_view->get_canvas_group()->w2i (x, y); + _region_view->get_canvas_group()->canvas_to_item (x, y); return y; } @@ -4552,19 +4860,12 @@ NoteCreateDrag::aborted (bool) } -/*------------*/ - CrossfadeEdgeDrag::CrossfadeEdgeDrag (Editor* e, AudioRegionView* rv, ArdourCanvas::Item* i, bool start_yn) : Drag (e, i) , arv (rv) , start (start_yn) { - cerr << "new xfade drag\n"; -} - -CrossfadeEdgeDrag::~CrossfadeEdgeDrag () -{ - cerr << "destory xfade drag\n"; + std::cout << ("CrossfadeEdgeDrag is DEPRECATED. See TrimDrag::preserve_fade_anchor") << endl; } void @@ -4590,12 +4891,18 @@ CrossfadeEdgeDrag::motion (GdkEvent*, bool) len = ar->fade_out()->back()->when; } - new_length = len + _editor->unit_to_frame (distance); - + /* how long should it be ? */ + + new_length = len + _editor->pixel_to_sample (distance); + + /* now check with the region that this is legal */ + + new_length = ar->verify_xfade_bounds (new_length, start); + if (start) { - arv->redraw_start_xfade_to (ar, new_length); + arv->reset_fade_in_shape_width (ar, new_length); } else { - arv->redraw_end_xfade_to (ar, new_length); + arv->reset_fade_out_shape_width (ar, new_length); } } @@ -4616,13 +4923,27 @@ CrossfadeEdgeDrag::finished (GdkEvent*, bool) len = ar->fade_out()->back()->when; } - new_length = len + _editor->unit_to_frame (distance); + new_length = ar->verify_xfade_bounds (len + _editor->pixel_to_sample (distance), start); + _editor->begin_reversible_command ("xfade trim"); + ar->playlist()->clear_owned_changes (); + if (start) { ar->set_fade_in_length (new_length); } else { ar->set_fade_out_length (new_length); } + + /* Adjusting the xfade may affect other regions in the playlist, so we need + to get undo Commands from the whole playlist rather than just the + region. + */ + + vector cmds; + ar->playlist()->rdiff (cmds); + _editor->session()->add_commands (cmds); + _editor->commit_reversible_command (); + } void