diff options
author | Taybin Rutkin <taybin@taybin.com> | 2005-09-25 18:42:24 +0000 |
---|---|---|
committer | Taybin Rutkin <taybin@taybin.com> | 2005-09-25 18:42:24 +0000 |
commit | 209d967b1bb80a9735d690d8f4f0455ecb9970ca (patch) | |
tree | 9d76ddcd7c1ac9d91bb2b1a33d31b66ce4ded5de /gtk2_ardour/editor_mouse.cc | |
parent | e4b9aed743fc765219ac775905a221c017c88fba (diff) |
Initial import of gtk2_ardour.
git-svn-id: svn://localhost/trunk/ardour2@24 d708f5d6-7413-0410-9779-e7cbd77b26cf
Diffstat (limited to 'gtk2_ardour/editor_mouse.cc')
-rw-r--r-- | gtk2_ardour/editor_mouse.cc | 4584 |
1 files changed, 4584 insertions, 0 deletions
diff --git a/gtk2_ardour/editor_mouse.cc b/gtk2_ardour/editor_mouse.cc new file mode 100644 index 0000000000..2c0d90f703 --- /dev/null +++ b/gtk2_ardour/editor_mouse.cc @@ -0,0 +1,4584 @@ +/* + Copyright (C) 2000-2001 Paul Davis + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + $Id$ +*/ + +#include <cstdlib> +#include <stdint.h> +#include <cmath> +#include <set> +#include <string> +#include <algorithm> + +#include <pbd/error.h> +#include <gtkmmext/utils.h> + +#include "ardour_ui.h" +#include "editor.h" +#include "time_axis_view.h" +#include "audio_time_axis.h" +#include "regionview.h" +#include "marker.h" +#include "streamview.h" +#include "region_gain_line.h" +#include "automation_time_axis.h" +#include "prompter.h" +#include "utils.h" +#include "selection.h" +#include "keyboard.h" +#include "editing.h" +#include "rgb_macros.h" +#include "extra_bind.h" + +#include <ardour/types.h> +#include <ardour/route.h> +#include <ardour/audio_track.h> +#include <ardour/diskstream.h> +#include <ardour/playlist.h> +#include <ardour/audioplaylist.h> +#include <ardour/audioregion.h> +#include <ardour/dB.h> +#include <ardour/utils.h> +#include <ardour/region_factory.h> + +#include <bitset> + +#include "i18n.h" + +using namespace std; +using namespace ARDOUR; +using namespace SigC; +using namespace Gtk; +using namespace Editing; + +jack_nframes_t +Editor::event_frame (GdkEvent* event, double* pcx, double* pcy) +{ + double cx, cy; + + if (pcx == 0) { + pcx = &cx; + } + if (pcy == 0) { + pcy = &cy; + } + + *pcx = 0; + *pcy = 0; + + switch (event->type) { + case GDK_BUTTON_RELEASE: + case GDK_BUTTON_PRESS: + case GDK_2BUTTON_PRESS: + case GDK_3BUTTON_PRESS: + gtk_canvas_w2c_d (GTK_CANVAS(track_gtk_canvas), event->button.x, event->button.y, pcx, pcy); + break; + case GDK_MOTION_NOTIFY: + gtk_canvas_w2c_d (GTK_CANVAS(track_gtk_canvas), event->motion.x, event->motion.y, pcx, pcy); + break; + case GDK_ENTER_NOTIFY: + case GDK_LEAVE_NOTIFY: + gtk_canvas_w2c_d (GTK_CANVAS(track_gtk_canvas), event->crossing.x, event->crossing.y, pcx, pcy); + break; + default: + warning << compose (_("Editor::event_frame() used on unhandled event type %1"), event->type) << endmsg; + break; + } + + /* note that pixel_to_frame() never returns less than zero, so even if the pixel + position is negative (as can be the case with motion events in particular), + the frame location is always positive. + */ + + return pixel_to_frame (*pcx); +} + +void +Editor::mouse_mode_toggled (MouseMode m) +{ + if (ignore_mouse_mode_toggle) { + return; + } + + switch (m) { + case MouseRange: + if (mouse_select_button.get_active()) { + set_mouse_mode (m); + } + break; + + case MouseObject: + if (mouse_move_button.get_active()) { + set_mouse_mode (m); + } + break; + + case MouseGain: + if (mouse_gain_button.get_active()) { + set_mouse_mode (m); + } + break; + + case MouseZoom: + if (mouse_zoom_button.get_active()) { + set_mouse_mode (m); + } + break; + + case MouseTimeFX: + if (mouse_timefx_button.get_active()) { + set_mouse_mode (m); + } + break; + + case MouseAudition: + if (mouse_audition_button.get_active()) { + set_mouse_mode (m); + } + break; + + default: + break; + } +} + +void +Editor::set_mouse_mode (MouseMode m, bool force) +{ + if (drag_info.item) { + return; + } + + if (m == mouse_mode && !force) { + return; + } + + mouse_mode = m; + + instant_save (); + + if (mouse_mode != MouseRange) { + + /* in all modes except range, hide the range selection, + show the object (region) selection. + */ + + for (AudioRegionSelection::iterator i = selection->audio_regions.begin(); i != selection->audio_regions.end(); ++i) { + (*i)->set_should_show_selection (true); + } + for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) { + (*i)->hide_selection (); + } + + } else { + + /* in range mode, hide object (region) selection, and show the + range selection. + */ + + for (AudioRegionSelection::iterator i = selection->audio_regions.begin(); i != selection->audio_regions.end(); ++i) { + (*i)->set_should_show_selection (false); + } + for (TrackSelection::iterator i = selection->tracks.begin(); i != selection->tracks.end(); ++i) { + if ((*i)->selected()) { + (*i)->show_selection (selection->time); + } + } + } + + /* XXX the hack of unsetting all other buttongs should go + away once GTK2 allows us to use regular radio buttons drawn like + normal buttons, rather than my silly GroupedButton hack. + */ + + ignore_mouse_mode_toggle = true; + + switch (mouse_mode) { + case MouseRange: + mouse_select_button.set_active (true); + current_canvas_cursor = selector_cursor; + break; + + case MouseObject: + mouse_move_button.set_active (true); + current_canvas_cursor = grabber_cursor; + break; + + case MouseGain: + mouse_gain_button.set_active (true); + current_canvas_cursor = cross_hair_cursor; + break; + + case MouseZoom: + mouse_zoom_button.set_active (true); + current_canvas_cursor = zoom_cursor; + break; + + case MouseTimeFX: + mouse_timefx_button.set_active (true); + current_canvas_cursor = time_fx_cursor; // just use playhead + break; + + case MouseAudition: + mouse_audition_button.set_active (true); + current_canvas_cursor = speaker_cursor; + break; + } + + ignore_mouse_mode_toggle = false; + + if (is_drawable()) { + gdk_window_set_cursor (track_canvas_scroller.get_window(), current_canvas_cursor); + } +} + +void +Editor::step_mouse_mode (bool next) +{ + switch (current_mouse_mode()) { + case MouseObject: + if (next) set_mouse_mode (MouseRange); + else set_mouse_mode (MouseTimeFX); + break; + + case MouseRange: + if (next) set_mouse_mode (MouseZoom); + else set_mouse_mode (MouseObject); + break; + + case MouseZoom: + if (next) set_mouse_mode (MouseGain); + else set_mouse_mode (MouseRange); + break; + + case MouseGain: + if (next) set_mouse_mode (MouseTimeFX); + else set_mouse_mode (MouseZoom); + break; + + case MouseTimeFX: + if (next) set_mouse_mode (MouseAudition); + else set_mouse_mode (MouseGain); + break; + + case MouseAudition: + if (next) set_mouse_mode (MouseObject); + else set_mouse_mode (MouseTimeFX); + break; + } +} + +gint +Editor::button_press_handler (GtkCanvasItem* item, GdkEvent* event, ItemType item_type) +{ + jack_nframes_t where = event_frame (event, 0, 0); + + if (session && session->actively_recording()) { + return TRUE; + } + + /* in object/audition/timefx mode, any button press sets + the selection if the object can be selected. this is a + bit of hack, because we want to avoid this if the + mouse operation is a region alignment. + */ + + if (((mouse_mode == MouseObject) || + (mouse_mode == MouseAudition && item_type == RegionItem) || + (mouse_mode == MouseTimeFX && item_type == RegionItem)) && + event->type == GDK_BUTTON_PRESS && + event->button.button <= 3) { + + AudioRegionView* rv; + ControlPoint* cp; + + /* not dbl-click or triple-click */ + + switch (item_type) { + case RegionItem: + set_selected_regionview_from_click (Keyboard::modifier_state_equals (event->button.state, Keyboard::Shift), true); + break; + + case AudioRegionViewNameHighlight: + case AudioRegionViewName: + if ((rv = reinterpret_cast<AudioRegionView *> (gtk_object_get_data(GTK_OBJECT(item), "regionview"))) != 0) { + set_selected_regionview_from_click (Keyboard::modifier_state_equals (event->button.state, Keyboard::Shift), true); + } + break; + + case GainAutomationControlPointItem: + case PanAutomationControlPointItem: + case RedirectAutomationControlPointItem: + if ((cp = reinterpret_cast<ControlPoint *> (gtk_object_get_data(GTK_OBJECT(item), "control_point"))) != 0) { + set_selected_control_point_from_click (Keyboard::modifier_state_equals (event->button.state, Keyboard::Shift), true); + } + break; + + case StreamItem: + break; + + case AutomationTrackItem: + break; + + default: + break; + } + } + +#define SELECT_TRACK_FROM_CANVAS_IN_RANGE_MODE +#ifdef SELECT_TRACK_FROM_CANVAS_IN_RANGE_MODE + /* in range mode, button 1/2/3 press potentially selects a track */ + + if (mouse_mode == MouseRange && + event->type == GDK_BUTTON_PRESS && + event->button.button <= 3) { + + AudioRegionView* rv; + + switch (item_type) { + case StreamItem: + case RegionItem: + case AutomationTrackItem: + set_selected_track_from_click (Keyboard::modifier_state_equals (event->button.state, Keyboard::Shift), true, true); + break; + + case AudioRegionViewNameHighlight: + case AudioRegionViewName: + rv = reinterpret_cast<AudioRegionView *> (gtk_object_get_data(GTK_OBJECT(item), "regionview")); + default: + break; + } + } +#endif + + if (drag_info.item == 0 && + (Keyboard::is_delete_event (&event->button) || + Keyboard::is_context_menu_event (&event->button) || + Keyboard::is_edit_event (&event->button))) { + + /* handled by button release */ + return TRUE; + } + + switch (event->button.button) { + case 1: + + if (event->type == GDK_BUTTON_PRESS) { + + if (drag_info.item) { + gtk_canvas_item_ungrab (drag_info.item, event->button.time); + } + + /* single mouse clicks on any of these item types operate + independent of mouse mode, mostly because they are + not on the main track canvas or because we want + them to be modeless. + */ + + switch (item_type) { + case EditCursorItem: + case PlayheadCursorItem: + start_cursor_grab (item, event); + return TRUE; + + case MarkerItem: + if (Keyboard::modifier_state_equals (event->button.state, + Keyboard::ModifierMask(Keyboard::Control|Keyboard::Shift))) { + hide_marker (item, event); + } else { + start_marker_grab (item, event); + } + return TRUE; + + case TempoMarkerItem: + start_tempo_marker_grab (item, event); + return TRUE; + + case MeterMarkerItem: + start_meter_marker_grab (item, event); + return TRUE; + + case TempoBarItem: + return TRUE; + + case MeterBarItem: + return TRUE; + + case RangeMarkerBarItem: + start_range_markerbar_op (item, event, CreateRangeMarker); + return TRUE; + break; + case TransportMarkerBarItem: + start_range_markerbar_op (item, event, CreateTransportMarker); + return TRUE; + break; + + default: + break; + } + } + + switch (mouse_mode) { + case MouseRange: + switch (item_type) { + case StartSelectionTrimItem: + start_selection_op (item, event, SelectionStartTrim); + break; + + case EndSelectionTrimItem: + start_selection_op (item, event, SelectionEndTrim); + break; + + case SelectionItem: + if (Keyboard::modifier_state_contains + (event->button.state, Keyboard::ModifierMask(Keyboard::Alt))) { + // contains and not equals because I can't use alt as a modifier alone. + start_selection_grab (item, event); + } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::Control)) { + /* grab selection for moving */ + start_selection_op (item, event, SelectionMove); + } + else { + /* this was debated, but decided the more common action was to + make a new selection */ + start_selection_op (item, event, CreateSelection); + } + break; + + default: + start_selection_op (item, event, CreateSelection); + } + return TRUE; + break; + + case MouseObject: + if (Keyboard::modifier_state_contains (event->button.state, + Keyboard::ModifierMask(Keyboard::Control|Keyboard::Alt)) + && event->type == GDK_BUTTON_PRESS) { + + start_rubberband_select (item, event); + + } else if (event->type == GDK_BUTTON_PRESS) { + + switch (item_type) { + case FadeInHandleItem: + start_fade_in_grab (item, event); + return TRUE; + + case FadeOutHandleItem: + start_fade_out_grab (item, event); + return TRUE; + + case RegionItem: + if (Keyboard::modifier_state_contains (event->button.state, Keyboard::Control)) { + start_region_copy_grab (item, event); + } else if (Keyboard::the_keyboard().key_is_down (GDK_b)) { + start_region_brush_grab (item, event); + } else { + start_region_grab (item, event); + } + break; + + case AudioRegionViewNameHighlight: + start_trim (item, event); + return TRUE; + break; + + case AudioRegionViewName: + /* rename happens on edit clicks */ + start_trim (clicked_regionview->get_name_highlight(), event); + return TRUE; + break; + + case GainAutomationControlPointItem: + case PanAutomationControlPointItem: + case RedirectAutomationControlPointItem: + start_control_point_grab (item, event); + return TRUE; + break; + + case GainAutomationLineItem: + case PanAutomationLineItem: + case RedirectAutomationLineItem: + start_line_grab_from_line (item, event); + return TRUE; + break; + + case StreamItem: + case AutomationTrackItem: + start_rubberband_select (item, event); + break; + + /* <CMT Additions> */ + case ImageFrameHandleStartItem: + imageframe_start_handle_op(item, event) ; + return(TRUE) ; + break ; + case ImageFrameHandleEndItem: + imageframe_end_handle_op(item, event) ; + return(TRUE) ; + break ; + case MarkerViewHandleStartItem: + markerview_item_start_handle_op(item, event) ; + return(TRUE) ; + break ; + case MarkerViewHandleEndItem: + markerview_item_end_handle_op(item, event) ; + return(TRUE) ; + break ; + /* </CMT Additions> */ + + /* <CMT Additions> */ + case MarkerViewItem: + start_markerview_grab(item, event) ; + break ; + case ImageFrameItem: + start_imageframe_grab(item, event) ; + break ; + /* </CMT Additions> */ + + default: + break; + } + } + return TRUE; + break; + + case MouseGain: + switch (item_type) { + case RegionItem: + // start_line_grab_from_regionview (item, event); + break; + + case GainControlPointItem: + start_control_point_grab (item, event); + return TRUE; + + case GainLineItem: + start_line_grab_from_line (item, event); + return TRUE; + + case GainAutomationControlPointItem: + case PanAutomationControlPointItem: + case RedirectAutomationControlPointItem: + start_control_point_grab (item, event); + return TRUE; + break; + + default: + break; + } + return TRUE; + break; + + switch (item_type) { + case GainAutomationControlPointItem: + case PanAutomationControlPointItem: + case RedirectAutomationControlPointItem: + start_control_point_grab (item, event); + break; + + case GainAutomationLineItem: + case PanAutomationLineItem: + case RedirectAutomationLineItem: + start_line_grab_from_line (item, event); + break; + + case RegionItem: + // XXX need automation mode to identify which + // line to use + // start_line_grab_from_regionview (item, event); + break; + + default: + break; + } + return TRUE; + break; + + case MouseZoom: + if (event->type == GDK_BUTTON_PRESS) { + start_mouse_zoom (item, event); + } + + return TRUE; + break; + + case MouseTimeFX: + if (item_type == RegionItem) { + start_time_fx (item, event); + } + break; + + case MouseAudition: + /* handled in release */ + break; + + default: + break; + } + break; + + case 2: + switch (mouse_mode) { + case MouseObject: + if (event->type == GDK_BUTTON_PRESS) { + switch (item_type) { + case RegionItem: + if (Keyboard::modifier_state_contains (event->button.state, Keyboard::Control)) { + start_region_copy_grab (item, event); + } else { + start_region_grab (item, event); + } + + break; + case GainAutomationControlPointItem: + case PanAutomationControlPointItem: + case RedirectAutomationControlPointItem: + start_control_point_grab (item, event); + return TRUE; + break; + + default: + break; + } + } + + + switch (item_type) { + case AudioRegionViewNameHighlight: + start_trim (item, event); + return TRUE; + break; + + case AudioRegionViewName: + start_trim (clicked_regionview->get_name_highlight(), event); + return TRUE; + break; + + default: + break; + } + + break; + + case MouseRange: + if (event->type == GDK_BUTTON_PRESS) { + /* relax till release */ + } + return TRUE; + break; + + + case MouseZoom: + if (Keyboard::modifier_state_equals (event->button.state, Keyboard::Control)) { + temporal_zoom_session(); + } else { + temporal_zoom_to_frame (true, event_frame(event)); + } + return TRUE; + break; + + default: + break; + } + + break; + + case 3: + break; + + case 4: + switch (mouse_mode) { + case MouseZoom: + //temporal_zoom_to_frame (true, where); + if (Keyboard::modifier_state_equals (event->button.state, Keyboard::Control)) { + temporal_zoom_to_frame (true, where); + } + else { + temporal_zoom_step (true); + } + break; + default: + + if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::Alt))) { + scroll_backward (0.6f); + } + else if (Keyboard::no_modifier_keys_pressed (&event->button)) { + scroll_tracks_up_line (); + } else { + if (Keyboard::modifier_state_equals (event->button.state, Keyboard::Shift)) { + if (clicked_trackview) { + if (!current_stepping_trackview) { + TimeoutSig t; + step_timeout = t.connect (slot (*this, &Editor::track_height_step_timeout), 500); + current_stepping_trackview = clicked_trackview; + } + gettimeofday (&last_track_height_step_timestamp, 0); + current_stepping_trackview->step_height (true); + } + } + else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::Control)) { + temporal_zoom_to_frame (true, where); + } + } + } + break; + + case 5: + switch (mouse_mode) { + case MouseZoom: + // temporal_zoom_to_frame (false, where); + if (Keyboard::modifier_state_equals (event->button.state, Keyboard::Control)) { + temporal_zoom_to_frame (false, where); + } + else { + temporal_zoom_step (false); + } + break; + default: + + if (Keyboard::modifier_state_contains (event->button.state, Keyboard::ModifierMask(Keyboard::Alt))) { + scroll_forward (0.6f); + } + else if (Keyboard::no_modifier_keys_pressed (&event->button)) { + scroll_tracks_down_line (); + } else { + if (Keyboard::modifier_state_equals (event->button.state, Keyboard::Shift)) { + if (clicked_trackview) { + if (!current_stepping_trackview) { + TimeoutSig t; + step_timeout = t.connect (slot (*this, &Editor::track_height_step_timeout), 500); + current_stepping_trackview = clicked_trackview; + } + gettimeofday (&last_track_height_step_timestamp, 0); + current_stepping_trackview->step_height (false); + } + } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::Control)) { + temporal_zoom_to_frame (false, where); + } + } + } + break; + + default: + break; + + } + return FALSE; +} + +gint +Editor::button_release_handler (GtkCanvasItem* item, GdkEvent* event, ItemType item_type) +{ + jack_nframes_t where = event_frame (event, 0, 0); + + /* no action if we're recording */ + + if (session && session->actively_recording()) { + return TRUE; + } + + /* first, see if we're finishing a drag ... */ + + if (drag_info.item) { + if (end_grab (item, event)) { + /* grab dragged, so do nothing else */ + return TRUE; + } + } + + /* edit events get handled here */ + + if (drag_info.item == 0 && Keyboard::is_edit_event (&event->button)) { + switch (item_type) { + case RegionItem: + edit_region (); + break; + + case TempoMarkerItem: + edit_tempo_marker (item); + break; + + case MeterMarkerItem: + edit_meter_marker (item); + break; + + case AudioRegionViewName: + if (clicked_regionview->name_active()) { + return mouse_rename_region (item, event); + } + break; + + default: + break; + } + return TRUE; + } + + /* context menu events get handled here */ + + if (Keyboard::is_context_menu_event (&event->button)) { + + if (drag_info.item == 0) { + + /* no matter which button pops up the context menu, tell the menu + widget to use button 1 to drive menu selection. + */ + + switch (item_type) { + case FadeInItem: + case FadeInHandleItem: + case FadeOutItem: + case FadeOutHandleItem: + popup_fade_context_menu (1, event->button.time, item, item_type); + break; + + case StreamItem: + popup_track_context_menu (1, event->button.time, item_type, false, where); + break; + + case RegionItem: + case AudioRegionViewNameHighlight: + case AudioRegionViewName: + popup_track_context_menu (1, event->button.time, item_type, false, where); + break; + + case SelectionItem: + popup_track_context_menu (1, event->button.time, item_type, true, where); + break; + + case AutomationTrackItem: + popup_track_context_menu (1, event->button.time, item_type, false, where); + break; + + case MarkerBarItem: + case RangeMarkerBarItem: + case TransportMarkerBarItem: + case TempoBarItem: + case MeterBarItem: + popup_ruler_menu (pixel_to_frame(event->button.x), item_type); + break; + + case MarkerItem: + marker_context_menu (&event->button, item); + break; + + case TempoMarkerItem: + tm_marker_context_menu (&event->button, item); + break; + + case MeterMarkerItem: + tm_marker_context_menu (&event->button, item); + break; + + case CrossfadeViewItem: + popup_track_context_menu (1, event->button.time, item_type, false, where); + break; + + /* <CMT Additions> */ + case ImageFrameItem: + popup_imageframe_edit_menu(1, event->button.time, item, true) ; + break ; + case ImageFrameTimeAxisItem: + popup_imageframe_edit_menu(1, event->button.time, item, false) ; + break ; + case MarkerViewItem: + popup_marker_time_axis_edit_menu(1, event->button.time, item, true) ; + break ; + case MarkerTimeAxisItem: + popup_marker_time_axis_edit_menu(1, event->button.time, item, false) ; + break ; + /* <CMT Additions> */ + + + default: + break; + } + + return TRUE; + } + } + + /* delete events get handled here */ + + if (drag_info.item == 0 && Keyboard::is_delete_event (&event->button)) { + + switch (item_type) { + case TempoMarkerItem: + remove_tempo_marker (item); + break; + + case MeterMarkerItem: + remove_meter_marker (item); + break; + + case MarkerItem: + remove_marker (item, event); + break; + + case RegionItem: + if (mouse_mode == MouseObject) { + remove_clicked_region (); + } + break; + + case GainControlPointItem: + if (mouse_mode == MouseGain) { + remove_gain_control_point (item, event); + } + break; + + case GainAutomationControlPointItem: + case PanAutomationControlPointItem: + case RedirectAutomationControlPointItem: + remove_control_point (item, event); + break; + + default: + break; + } + return TRUE; + } + + switch (event->button.button) { + case 1: + + switch (item_type) { + /* see comments in button_press_handler */ + case EditCursorItem: + case PlayheadCursorItem: + case MarkerItem: + case GainLineItem: + case GainAutomationLineItem: + case PanAutomationLineItem: + case RedirectAutomationLineItem: + case StartSelectionTrimItem: + case EndSelectionTrimItem: + return TRUE; + + case MarkerBarItem: + if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) { + snap_to (where, 0, true); + } + mouse_add_new_marker (where); + return TRUE; + + case TempoBarItem: + if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) { + snap_to (where); + } + mouse_add_new_tempo_event (where); + return TRUE; + + case MeterBarItem: + mouse_add_new_meter_event (pixel_to_frame (event->button.x)); + return TRUE; + break; + + default: + break; + } + + switch (mouse_mode) { + case MouseObject: + switch (item_type) { + case AutomationTrackItem: + dynamic_cast<AutomationTimeAxisView*>(clicked_trackview)->add_automation_event + (item, + event, + where, + event->button.y); + return TRUE; + break; + + default: + break; + } + break; + + case MouseGain: + switch (item_type) { + case RegionItem: + clicked_regionview->add_gain_point_event (item, event); + return TRUE; + break; + + case AutomationTrackItem: + dynamic_cast<AutomationTimeAxisView*>(clicked_trackview)-> + add_automation_event (item, event, where, event->button.y); + return TRUE; + break; + default: + break; + } + break; + + case MouseAudition: + switch (item_type) { + case RegionItem: + audition_selected_region (); + break; + default: + break; + } + break; + + default: + break; + + } + + return TRUE; + break; + + + case 2: + switch (mouse_mode) { + + case MouseObject: + switch (item_type) { + case RegionItem: + if (Keyboard::modifier_state_equals (event->button.state, Keyboard::Shift)) { + raise_region (); + } else if (Keyboard::modifier_state_equals (event->button.state, Keyboard::ModifierMask (Keyboard::Shift|Keyboard::Alt))) { + lower_region (); + } else { + // Button2 click is unused + } + return TRUE; + + break; + + default: + break; + } + break; + + case MouseRange: + + // x_style_paste (where, 1.0); + return TRUE; + break; + + default: + break; + } + + break; + + case 3: + break; + + default: + break; + } + return FALSE; +} + +void +Editor::maybe_autoscroll (GdkEvent* event) +{ + jack_nframes_t one_page = (jack_nframes_t) rint (canvas_width * frames_per_unit); + jack_nframes_t rightmost_frame = leftmost_frame + one_page; + + jack_nframes_t frame = drag_info.current_pointer_frame; + + if (autoscroll_timeout_tag < 0) { + if (frame > rightmost_frame) { + if (rightmost_frame < max_frames) { + start_canvas_autoscroll (1); + } + } else if (frame < leftmost_frame) { + if (leftmost_frame > 0) { + start_canvas_autoscroll (-1); + } + } + } else { + if (frame >= leftmost_frame && frame < rightmost_frame) { + stop_canvas_autoscroll (); + } + } +} + +gint +Editor::enter_handler (GtkCanvasItem* item, GdkEvent* event, ItemType item_type) +{ + ControlPoint* cp; + Marker * marker; + double fraction; + + switch (item_type) { + case GainControlPointItem: + if (mouse_mode == MouseGain) { + cp = reinterpret_cast<ControlPoint*>(gtk_object_get_data (GTK_OBJECT(item), "control_point")); + cp->set_visible (true); + + double at_x, at_y; + at_x = cp->get_x(); + at_y = cp->get_y (); + gtk_canvas_item_i2w (cp->item, &at_x, &at_y); + at_x += 20.0; + at_y += 20.0; + + fraction = 1.0 - (cp->get_y() / cp->line.height()); + + set_verbose_canvas_cursor (cp->line.get_verbose_cursor_string (fraction), at_x, at_y); + show_verbose_canvas_cursor (); + + if (is_drawable()) { + gdk_window_set_cursor (track_canvas_scroller.get_window(), fader_cursor); + } + } + break; + + case GainAutomationControlPointItem: + case PanAutomationControlPointItem: + case RedirectAutomationControlPointItem: + cp = reinterpret_cast<ControlPoint*>(gtk_object_get_data (GTK_OBJECT(item), "control_point")); + cp->set_visible (true); + + double at_x, at_y; + at_x = cp->get_x(); + at_y = cp->get_y (); + gtk_canvas_item_i2w (cp->item, &at_x, &at_y); + at_x += 20.0; + at_y += 20.0; + + fraction = 1.0 - (cp->get_y() / cp->line.height()); + + set_verbose_canvas_cursor (cp->line.get_verbose_cursor_string (fraction), at_x, at_y); + show_verbose_canvas_cursor (); + + if (is_drawable()) { + gdk_window_set_cursor (track_canvas_scroller.get_window(), fader_cursor); + } + break; + + case GainLineItem: + if (mouse_mode == MouseGain) { + gtk_canvas_item_set (item, "fill_color_rgba", color_map[cEnteredGainLine], NULL); + if (is_drawable()) { + gdk_window_set_cursor (track_canvas_scroller.get_window(), fader_cursor); + } + } + break; + + case GainAutomationLineItem: + case RedirectAutomationLineItem: + case PanAutomationLineItem: + gtk_canvas_item_set (item, "fill_color_rgba", color_map[cEnteredAutomationLine], NULL); + if (is_drawable()) { + gdk_window_set_cursor (track_canvas_scroller.get_window(), fader_cursor); + } + break; + + case AudioRegionViewNameHighlight: + if (is_drawable() && mouse_mode == MouseObject) { + gdk_window_set_cursor (track_canvas_scroller.get_window(), trimmer_cursor); + } + break; + + case StartSelectionTrimItem: + case EndSelectionTrimItem: + /* <CMT Additions> */ + case ImageFrameHandleStartItem: + case ImageFrameHandleEndItem: + case MarkerViewHandleStartItem: + case MarkerViewHandleEndItem: + /* </CMT Additions> */ + + if (is_drawable()) { + gdk_window_set_cursor (track_canvas_scroller.get_window(), trimmer_cursor); + } + break; + + case EditCursorItem: + case PlayheadCursorItem: + if (is_drawable()) { + gdk_window_set_cursor (track_canvas_scroller.get_window(), grabber_cursor); + } + break; + + case AudioRegionViewName: + + /* when the name is not an active item, the entire name highlight is for trimming */ + + if (!reinterpret_cast<AudioRegionView *> (gtk_object_get_data(GTK_OBJECT(item), "regionview"))->name_active()) { + if (mouse_mode == MouseObject && is_drawable()) { + gdk_window_set_cursor (track_canvas_scroller.get_window(), trimmer_cursor); + } + } + break; + + + case AutomationTrackItem: + if (is_drawable()) { + GdkCursor *cursor; + switch (mouse_mode) { + case MouseRange: + cursor = selector_cursor; + break; + case MouseZoom: + cursor = zoom_cursor; + break; + default: + cursor = cross_hair_cursor; + break; + } + + gdk_window_set_cursor (track_canvas_scroller.get_window(), cursor); + + AutomationTimeAxisView* atv; + if ((atv = static_cast<AutomationTimeAxisView*>(gtk_object_get_data(GTK_OBJECT(item), "trackview"))) != 0) { + clear_entered_track = false; + set_entered_track (atv); + } + } + break; + + case MarkerBarItem: + case RangeMarkerBarItem: + case TransportMarkerBarItem: + case MeterBarItem: + case TempoBarItem: + if (is_drawable()) { + gdk_window_set_cursor (time_canvas_scroller.get_window(), timebar_cursor); + } + break; + + case MarkerItem: + if ((marker = static_cast<Marker *> (gtk_object_get_data (GTK_OBJECT(item), "marker"))) == 0) { + break; + } + marker->set_color_rgba (color_map[cEnteredMarker]); + // fall through + case MeterMarkerItem: + case TempoMarkerItem: + if (is_drawable()) { + gdk_window_set_cursor (time_canvas_scroller.get_window(), timebar_cursor); + } + break; + case FadeInHandleItem: + case FadeOutHandleItem: + if (mouse_mode == MouseObject) { + gtk_canvas_item_set (item, "fill_color_rgba", 0, "outline_pixels", 1, NULL); + } + break; + + default: + break; + } + + /* second pass to handle entered track status in a comprehensible way. + */ + + switch (item_type) { + case GainLineItem: + case GainAutomationLineItem: + case RedirectAutomationLineItem: + case PanAutomationLineItem: + case GainControlPointItem: + case GainAutomationControlPointItem: + case PanAutomationControlPointItem: + case RedirectAutomationControlPointItem: + /* these do not affect the current entered track state */ + clear_entered_track = false; + break; + + case AutomationTrackItem: + /* handled above already */ + break; + + default: + set_entered_track (0); + break; + } + + return FALSE; +} + +gint +Editor::leave_handler (GtkCanvasItem* item, GdkEvent* event, ItemType item_type) +{ + AutomationLine* al; + ControlPoint* cp; + Marker *marker; + Location *loc; + AudioRegionView* rv; + bool is_start; + + switch (item_type) { + case GainControlPointItem: + case GainAutomationControlPointItem: + case PanAutomationControlPointItem: + case RedirectAutomationControlPointItem: + cp = reinterpret_cast<ControlPoint*>(gtk_object_get_data (GTK_OBJECT(item), "control_point")); + if (cp->line.npoints() > 1) { + if (!cp->selected) { + cp->set_visible (false); + } + } + + if (is_drawable()) { + gdk_window_set_cursor (track_canvas_scroller.get_window(), current_canvas_cursor); + } + + hide_verbose_canvas_cursor (); + break; + + case AudioRegionViewNameHighlight: + case StartSelectionTrimItem: + case EndSelectionTrimItem: + case EditCursorItem: + case PlayheadCursorItem: + /* <CMT Additions> */ + case ImageFrameHandleStartItem: + case ImageFrameHandleEndItem: + case MarkerViewHandleStartItem: + case MarkerViewHandleEndItem: + /* </CMT Additions> */ + if (is_drawable()) { + gdk_window_set_cursor (track_canvas_scroller.get_window(), current_canvas_cursor); + } + break; + + case GainLineItem: + case GainAutomationLineItem: + case RedirectAutomationLineItem: + case PanAutomationLineItem: + al = reinterpret_cast<AutomationLine*> (gtk_object_get_data (GTK_OBJECT(item),"line")); + gtk_canvas_item_set (item, "fill_color_rgba", al->get_line_color(), NULL); + if (is_drawable()) { + gdk_window_set_cursor (track_canvas_scroller.get_window(), current_canvas_cursor); + } + break; + + case AudioRegionViewName: + /* see enter_handler() for notes */ + if (!reinterpret_cast<AudioRegionView *> (gtk_object_get_data(GTK_OBJECT(item), "regionview"))->name_active()) { + if (is_drawable() && mouse_mode == MouseObject) { + gdk_window_set_cursor (track_canvas_scroller.get_window(), current_canvas_cursor); + } + } + break; + + case RangeMarkerBarItem: + case TransportMarkerBarItem: + case MeterBarItem: + case TempoBarItem: + case MarkerBarItem: + if (is_drawable()) { + gdk_window_set_cursor (time_canvas_scroller.get_window(), timebar_cursor); + } + break; + + case MarkerItem: + if ((marker = static_cast<Marker *> (gtk_object_get_data (GTK_OBJECT(item), "marker"))) == 0) { + break; + } + loc = find_location_from_marker (marker, is_start); + if (loc) location_flags_changed (loc, this); + // fall through + case MeterMarkerItem: + case TempoMarkerItem: + + if (is_drawable()) { + gdk_window_set_cursor (time_canvas_scroller.get_window(), timebar_cursor); + } + + break; + + case FadeInHandleItem: + case FadeOutHandleItem: + rv = static_cast<AudioRegionView*>(gtk_object_get_data (GTK_OBJECT(item), "regionview")); + gtk_canvas_item_set (item, "fill_color_rgba", rv->get_fill_color(), "outline_pixels", 0, NULL); + break; + + case AutomationTrackItem: + if (is_drawable()) { + gdk_window_set_cursor (track_canvas_scroller.get_window(), current_canvas_cursor); + + clear_entered_track = true; + Main::idle.connect (slot (*this, &Editor::left_automation_track)); + } + break; + + default: + break; + } + + return FALSE; +} + +gint +Editor::left_automation_track () +{ + if (clear_entered_track) { + set_entered_track (0); + clear_entered_track = false; + } + return FALSE; +} + +gint +Editor::motion_handler (GtkCanvasItem* item, GdkEvent* event, ItemType item_type) +{ + gint x, y; + + /* We call this so that MOTION_NOTIFY events continue to be + delivered to the canvas. We need to do this because we set + GDK_POINTER_MOTION_HINT_MASK on the canvas. This reduces + the density of the events, at the expense of a round-trip + to the server. Given that this will mostly occur on cases + where DISPLAY = :0.0, and given the cost of what the motion + event might do, its a good tradeoff. + */ + + track_canvas->get_pointer (x, y); + + if (current_stepping_trackview) { + /* don't keep the persistent stepped trackview if the mouse moves */ + current_stepping_trackview = 0; + step_timeout.disconnect (); + } + + if (session && session->actively_recording()) { + /* Sorry. no dragging stuff around while we record */ + return TRUE; + } + + drag_info.current_pointer_frame = event_frame (event, &drag_info.current_pointer_x, + &drag_info.current_pointer_y); + if (drag_info.item) { + /* item != 0 is the best test i can think of for + dragging. + */ + if (!drag_info.move_threshold_passsed) + { + drag_info.move_threshold_passsed = (abs ((int) (drag_info.current_pointer_x - drag_info.grab_x)) > 4); + + // and change the initial grab loc/frame if this drag info wants us to + if (drag_info.want_move_threshold && drag_info.move_threshold_passsed) { + drag_info.grab_frame = drag_info.current_pointer_frame; + drag_info.grab_x = drag_info.current_pointer_x; + drag_info.grab_y = drag_info.current_pointer_y; + drag_info.last_pointer_frame = drag_info.grab_frame; + drag_info.pointer_frame_offset = drag_info.grab_frame - drag_info.last_frame_position; + } + } + } + + switch (item_type) { + case PlayheadCursorItem: + case EditCursorItem: + case MarkerItem: + case GainControlPointItem: + case RedirectAutomationControlPointItem: + case GainAutomationControlPointItem: + case PanAutomationControlPointItem: + case TempoMarkerItem: + case MeterMarkerItem: + case AudioRegionViewNameHighlight: + case StartSelectionTrimItem: + case EndSelectionTrimItem: + case SelectionItem: + case GainLineItem: + case RedirectAutomationLineItem: + case GainAutomationLineItem: + case PanAutomationLineItem: + case FadeInHandleItem: + case FadeOutHandleItem: + /* <CMT Additions> */ + case ImageFrameHandleStartItem: + case ImageFrameHandleEndItem: + case MarkerViewHandleStartItem: + case MarkerViewHandleEndItem: + /* </CMT Additions> */ + if (drag_info.item && (event->motion.state & GDK_BUTTON1_MASK || + (event->motion.state & GDK_BUTTON2_MASK))) { + maybe_autoscroll (event); + (this->*(drag_info.motion_callback)) (item, event); + goto handled; + } + goto not_handled; + + default: + break; + } + + switch (mouse_mode) { + case MouseObject: + case MouseRange: + case MouseZoom: + case MouseTimeFX: + if (drag_info.item && (event->motion.state & GDK_BUTTON1_MASK || + (event->motion.state & GDK_BUTTON2_MASK))) { + maybe_autoscroll (event); + (this->*(drag_info.motion_callback)) (item, event); + goto handled; + } + goto not_handled; + break; + + default: + break; + } + + handled: + track_canvas_motion (item, event); + return TRUE; + + not_handled: + return FALSE; +} + +void +Editor::start_grab (GdkEvent* event, GdkCursor *cursor) +{ + if (drag_info.item == 0) { + fatal << _("programming error: start_grab called without drag item") << endmsg; + /*NOTREACHED*/ + return; + } + + if (cursor == 0) { + cursor = grabber_cursor; + } + + // if dragging with button2, the motion is x constrained, with Alt-button2 it is y constrained + + if (event->button.button == 2) { + drag_info.x_constrained = true; + } else { + drag_info.x_constrained = false; + } + + drag_info.grab_frame = event_frame(event, &drag_info.grab_x, &drag_info.grab_y); + drag_info.last_pointer_frame = drag_info.grab_frame; + drag_info.current_pointer_frame = drag_info.grab_frame; + drag_info.current_pointer_x = drag_info.grab_x; + drag_info.current_pointer_y = drag_info.grab_y; + drag_info.cumulative_x_drag = 0; + drag_info.cumulative_y_drag = 0; + drag_info.first_move = true; + drag_info.move_threshold_passsed = false; + drag_info.want_move_threshold = false; + drag_info.pointer_frame_offset = 0; + drag_info.brushing = false; + drag_info.copied_location = 0; + + gtk_canvas_item_grab (drag_info.item, + GDK_POINTER_MOTION_MASK|GDK_BUTTON_PRESS_MASK|GDK_BUTTON_RELEASE_MASK, + cursor, + event->button.time); + + if (session && session->transport_rolling()) { + drag_info.was_rolling = true; + } else { + drag_info.was_rolling = false; + } + + switch (snap_type) { + case SnapToRegionStart: + case SnapToRegionEnd: + case SnapToRegionSync: + case SnapToRegionBoundary: + build_region_boundary_cache (); + break; + default: + break; + } +} + +bool +Editor::end_grab (GtkCanvasItem* item, GdkEvent* event) +{ + bool did_drag = false; + + stop_canvas_autoscroll (); + + if (drag_info.item == 0) { + return false; + } + + gtk_canvas_item_ungrab (drag_info.item, event->button.time); + + if (drag_info.finished_callback) { + (this->*(drag_info.finished_callback)) (item, event); + } + + did_drag = !drag_info.first_move; + + hide_verbose_canvas_cursor(); + + drag_info.item = 0; + drag_info.copy = false; + drag_info.motion_callback = 0; + drag_info.finished_callback = 0; + drag_info.last_trackview = 0; + drag_info.last_frame_position = 0; + drag_info.grab_frame = 0; + drag_info.last_pointer_frame = 0; + drag_info.current_pointer_frame = 0; + drag_info.brushing = false; + + if (drag_info.copied_location) { + delete drag_info.copied_location; + drag_info.copied_location = 0; + } + + return did_drag; +} + +void +Editor::set_edit_cursor (GdkEvent* event) +{ + jack_nframes_t pointer_frame = event_frame (event); + + if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) { + if (snap_type != SnapToEditCursor) { + snap_to (pointer_frame); + } + } + + edit_cursor->set_position (pointer_frame); + edit_cursor_clock.set (pointer_frame); +} + +void +Editor::set_playhead_cursor (GdkEvent* event) +{ + jack_nframes_t pointer_frame = event_frame (event); + + if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) { + snap_to (pointer_frame); + } + + if (session) { + session->request_locate (pointer_frame, session->transport_rolling()); + } +} + +void +Editor::start_fade_in_grab (GtkCanvasItem* item, GdkEvent* event) +{ + drag_info.item = item; + drag_info.motion_callback = &Editor::fade_in_drag_motion_callback; + drag_info.finished_callback = &Editor::fade_in_drag_finished_callback; + + start_grab (event); + + if ((drag_info.data = (gtk_object_get_data (GTK_OBJECT(item), "regionview"))) == 0) { + fatal << _("programming error: fade in canvas item has no regionview data pointer!") << endmsg; + /*NOTREACHED*/ + } + + AudioRegionView* arv = static_cast<AudioRegionView*>(drag_info.data); + + drag_info.pointer_frame_offset = drag_info.grab_frame - ((jack_nframes_t) arv->region.fade_in().back()->when + arv->region.position()); +} + +void +Editor::fade_in_drag_motion_callback (GtkCanvasItem* item, GdkEvent* event) +{ + AudioRegionView* arv = static_cast<AudioRegionView*>(drag_info.data); + jack_nframes_t pos; + jack_nframes_t fade_length; + + if ((long)drag_info.current_pointer_frame > drag_info.pointer_frame_offset) { + pos = drag_info.current_pointer_frame - drag_info.pointer_frame_offset; + } + else { + pos = 0; + } + + if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) { + snap_to (pos); + } + + if (pos < (arv->region.position() + 64)) { + fade_length = 64; // this should be a minimum defined somewhere + } else if (pos > arv->region.last_frame()) { + fade_length = arv->region.length(); + } else { + fade_length = pos - arv->region.position(); + } + + arv->reset_fade_in_shape_width (fade_length); + + show_verbose_duration_cursor (arv->region.position(), arv->region.position() + fade_length, 10); + + drag_info.first_move = false; +} + +void +Editor::fade_in_drag_finished_callback (GtkCanvasItem* item, GdkEvent* event) +{ + if (drag_info.first_move) return; + + AudioRegionView* arv = static_cast<AudioRegionView*>(drag_info.data); + jack_nframes_t pos; + jack_nframes_t fade_length; + + if ((long)drag_info.current_pointer_frame > drag_info.pointer_frame_offset) { + pos = drag_info.current_pointer_frame - drag_info.pointer_frame_offset; + } + else { + pos = 0; + } + + if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) { + snap_to (pos); + } + + if (pos < (arv->region.position() + 64)) { + fade_length = 64; // this should be a minimum defined somewhere + } + else if (pos > arv->region.last_frame()) { + fade_length = arv->region.length(); + } + else { + fade_length = pos - arv->region.position(); + } + + begin_reversible_command (_("change fade in length")); + session->add_undo (arv->region.get_memento()); + arv->region.set_fade_in_length (fade_length); + session->add_redo_no_execute (arv->region.get_memento()); + commit_reversible_command (); + fade_in_drag_motion_callback (item, event); +} + +void +Editor::start_fade_out_grab (GtkCanvasItem* item, GdkEvent* event) +{ + drag_info.item = item; + drag_info.motion_callback = &Editor::fade_out_drag_motion_callback; + drag_info.finished_callback = &Editor::fade_out_drag_finished_callback; + + start_grab (event); + + if ((drag_info.data = (gtk_object_get_data (GTK_OBJECT(item), "regionview"))) == 0) { + fatal << _("programming error: fade out canvas item has no regionview data pointer!") << endmsg; + /*NOTREACHED*/ + } + + AudioRegionView* arv = static_cast<AudioRegionView*>(drag_info.data); + + drag_info.pointer_frame_offset = drag_info.grab_frame - (arv->region.length() - (jack_nframes_t) arv->region.fade_out().back()->when + arv->region.position()); +} + +void +Editor::fade_out_drag_motion_callback (GtkCanvasItem* item, GdkEvent* event) +{ + AudioRegionView* arv = static_cast<AudioRegionView*>(drag_info.data); + jack_nframes_t pos; + jack_nframes_t fade_length; + + if ((long)drag_info.current_pointer_frame > drag_info.pointer_frame_offset) { + pos = drag_info.current_pointer_frame - drag_info.pointer_frame_offset; + } + else { + pos = 0; + } + + if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) { + snap_to (pos); + } + + if (pos > (arv->region.last_frame() - 64)) { + fade_length = 64; // this should really be a minimum fade defined somewhere + } + else if (pos < arv->region.position()) { + fade_length = arv->region.length(); + } + else { + fade_length = arv->region.last_frame() - pos; + } + + arv->reset_fade_out_shape_width (fade_length); + + show_verbose_duration_cursor (arv->region.last_frame() - fade_length, arv->region.last_frame(), 10); + + drag_info.first_move = false; +} + +void +Editor::fade_out_drag_finished_callback (GtkCanvasItem* item, GdkEvent* event) +{ + if (drag_info.first_move) return; + + AudioRegionView* arv = static_cast<AudioRegionView*>(drag_info.data); + jack_nframes_t pos; + jack_nframes_t fade_length; + + if ((long)drag_info.current_pointer_frame > drag_info.pointer_frame_offset) { + pos = drag_info.current_pointer_frame - drag_info.pointer_frame_offset; + } + else { + pos = 0; + } + + if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) { + snap_to (pos); + } + + if (pos > (arv->region.last_frame() - 64)) { + fade_length = 64; // this should really be a minimum fade defined somewhere + } + else if (pos < arv->region.position()) { + fade_length = arv->region.length(); + } + else { + fade_length = arv->region.last_frame() - pos; + } + + begin_reversible_command (_("change fade out length")); + session->add_undo (arv->region.get_memento()); + arv->region.set_fade_out_length (fade_length); + session->add_redo_no_execute (arv->region.get_memento()); + commit_reversible_command (); + + fade_out_drag_motion_callback (item, event); +} + +void +Editor::start_cursor_grab (GtkCanvasItem* item, GdkEvent* event) +{ + drag_info.item = item; + drag_info.motion_callback = &Editor::cursor_drag_motion_callback; + drag_info.finished_callback = &Editor::cursor_drag_finished_callback; + + start_grab (event); + + if ((drag_info.data = (gtk_object_get_data (GTK_OBJECT(item), "cursor"))) == 0) { + fatal << _("programming error: cursor canvas item has no cursor data pointer!") << endmsg; + /*NOTREACHED*/ + } + + Cursor* cursor = (Cursor *) drag_info.data; + + if (session && cursor == playhead_cursor) { + if (drag_info.was_rolling) { + session->request_stop (); + } + } + + drag_info.pointer_frame_offset = drag_info.grab_frame - cursor->current_frame; + + show_verbose_time_cursor (cursor->current_frame, 10); +} + +void +Editor::cursor_drag_motion_callback (GtkCanvasItem* item, GdkEvent* event) +{ + Cursor* cursor = (Cursor *) drag_info.data; + jack_nframes_t adjusted_frame; + + if ((long)drag_info.current_pointer_frame > drag_info.pointer_frame_offset) { + adjusted_frame = drag_info.current_pointer_frame - drag_info.pointer_frame_offset; + } + else { + adjusted_frame = 0; + } + + if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) { + if (cursor != edit_cursor || snap_type != SnapToEditCursor) { + snap_to (adjusted_frame); + } + } + + if (adjusted_frame == drag_info.last_pointer_frame) return; + + cursor->set_position (adjusted_frame); + + if (cursor == edit_cursor) { + edit_cursor_clock.set (cursor->current_frame); + } + + show_verbose_time_cursor (cursor->current_frame, 10); + + drag_info.last_pointer_frame = adjusted_frame; + drag_info.first_move = false; +} + +void +Editor::cursor_drag_finished_callback (GtkCanvasItem* item, GdkEvent* event) +{ + if (drag_info.first_move) return; + + cursor_drag_motion_callback (item, event); + + if (item == playhead_cursor->canvas_item) { + if (session) { + session->request_locate (playhead_cursor->current_frame, drag_info.was_rolling); + } + } else if (item == edit_cursor->canvas_item) { + edit_cursor->set_position (edit_cursor->current_frame); + edit_cursor_clock.set (edit_cursor->current_frame); + } +} + +void +Editor::update_marker_drag_item (Location *location) +{ + double x1 = frame_to_pixel (location->start()); + double x2 = frame_to_pixel (location->end()); + + if (location->is_mark()) { + marker_drag_line_points->coords[0] = x1; + marker_drag_line_points->coords[2] = x1; + gtk_canvas_item_set (marker_drag_line, "points", marker_drag_line_points, NULL); + } + else { + gtk_canvas_item_set (range_marker_drag_rect, "x1", x1, "x2", x2, NULL); + } +} + +void +Editor::start_marker_grab (GtkCanvasItem* item, GdkEvent* event) +{ + Marker* marker; + + if ((marker = static_cast<Marker *> (gtk_object_get_data (GTK_OBJECT(item), "marker"))) == 0) { + fatal << _("programming error: marker canvas item has no marker object pointer!") << endmsg; + /*NOTREACHED*/ + } + + bool is_start; + + Location *location = find_location_from_marker (marker, is_start); + + drag_info.item = item; + drag_info.data = marker; + drag_info.motion_callback = &Editor::marker_drag_motion_callback; + drag_info.finished_callback = &Editor::marker_drag_finished_callback; + + start_grab (event); + + drag_info.copied_location = new Location (*location); + drag_info.pointer_frame_offset = drag_info.grab_frame - (is_start ? location->start() : location->end()); + + update_marker_drag_item (location); + + if (location->is_mark()) { + gtk_canvas_item_show (marker_drag_line); + gtk_canvas_item_raise_to_top (marker_drag_line); + } + else { + gtk_canvas_item_show (range_marker_drag_rect); + gtk_canvas_item_raise_to_top (range_marker_drag_rect); + } + + if (is_start) show_verbose_time_cursor (location->start(), 10); + else show_verbose_time_cursor (location->end(), 10); +} + +void +Editor::marker_drag_motion_callback (GtkCanvasItem* item, GdkEvent* event) +{ + jack_nframes_t f_delta; + Marker* marker = (Marker *) drag_info.data; + Location *real_location; + Location *copy_location; + bool is_start; + bool move_both = false; + + jack_nframes_t newframe; + if (drag_info.pointer_frame_offset <= (long) drag_info.current_pointer_frame) { + newframe = drag_info.current_pointer_frame - drag_info.pointer_frame_offset; + } + else { + newframe = 0; + } + + jack_nframes_t next = newframe; + + if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) { + snap_to (newframe, 0, true); + } + + if (drag_info.current_pointer_frame == drag_info.last_pointer_frame) return; + + /* call this to find out if its the start or end */ + + real_location = find_location_from_marker (marker, is_start); + + /* use the copy that we're "dragging" around */ + + copy_location = drag_info.copied_location; + + f_delta = copy_location->end() - copy_location->start(); + + if (Keyboard::modifier_state_equals (event->button.state, Keyboard::Control)) { + move_both = true; + } + + if (is_start) { // start marker + + if (move_both) { + copy_location->set_start (newframe); + copy_location->set_end (newframe + f_delta); + } else if (newframe < copy_location->end()) { + copy_location->set_start (newframe); + } else { + snap_to (next, 1, true); + copy_location->set_end (next); + copy_location->set_start (newframe); + } + + } else { // end marker + + if (move_both) { + copy_location->set_end (newframe); + copy_location->set_start (newframe - f_delta); + } else if (newframe > copy_location->start()) { + copy_location->set_end (newframe); + + } else if (newframe > 0) { + snap_to (next, -1, true); + copy_location->set_start (next); + copy_location->set_end (newframe); + } + } + + drag_info.last_pointer_frame = drag_info.current_pointer_frame; + drag_info.first_move = false; + + update_marker_drag_item (copy_location); + + LocationMarkers* lm = find_location_markers (real_location); + lm->set_position (copy_location->start(), copy_location->end()); + + show_verbose_time_cursor (newframe, 10); +} + +void +Editor::marker_drag_finished_callback (GtkCanvasItem* item, GdkEvent* event) +{ + if (drag_info.first_move) { + marker_drag_motion_callback (item, event); + + } + + Marker* marker = (Marker *) drag_info.data; + bool is_start; + Location * location = find_location_from_marker (marker, is_start); + if (location) { + location->set (drag_info.copied_location->start(), drag_info.copied_location->end()); + } + + gtk_canvas_item_hide (marker_drag_line); + gtk_canvas_item_hide (range_marker_drag_rect); +} + +void +Editor::start_meter_marker_grab (GtkCanvasItem* item, GdkEvent* event) +{ + Marker* marker; + MeterMarker* meter_marker; + + if ((marker = reinterpret_cast<Marker *> (gtk_object_get_data (GTK_OBJECT(item), "marker"))) == 0) { + fatal << _("programming error: meter marker canvas item has no marker object pointer!") << endmsg; + /*NOTREACHED*/ + } + + meter_marker = dynamic_cast<MeterMarker*> (marker); + + MetricSection& section (meter_marker->meter()); + + if (!section.movable()) { + return; + } + + drag_info.item = item; + drag_info.data = marker; + drag_info.motion_callback = &Editor::meter_marker_drag_motion_callback; + drag_info.finished_callback = &Editor::meter_marker_drag_finished_callback; + + start_grab (event); + + drag_info.pointer_frame_offset = drag_info.grab_frame - meter_marker->meter().frame(); + + show_verbose_time_cursor (drag_info.current_pointer_frame, 10); +} + +void +Editor::meter_marker_drag_motion_callback (GtkCanvasItem* item, GdkEvent* event) +{ + MeterMarker* marker = (MeterMarker *) drag_info.data; + jack_nframes_t adjusted_frame; + + if ((long)drag_info.current_pointer_frame > drag_info.pointer_frame_offset) { + adjusted_frame = drag_info.current_pointer_frame - drag_info.pointer_frame_offset; + } + else { + adjusted_frame = 0; + } + + if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) { + snap_to (adjusted_frame); + } + + if (adjusted_frame == drag_info.last_pointer_frame) return; + + marker->set_position (adjusted_frame); + + + drag_info.last_pointer_frame = adjusted_frame; + drag_info.first_move = false; + + show_verbose_time_cursor (adjusted_frame, 10); +} + +void +Editor::meter_marker_drag_finished_callback (GtkCanvasItem* item, GdkEvent* event) +{ + if (drag_info.first_move) return; + + meter_marker_drag_motion_callback (item, event); + + MeterMarker* marker = (MeterMarker *) drag_info.data; + BBT_Time when; + + TempoMap& map (session->tempo_map()); + map.bbt_time (drag_info.last_pointer_frame, when); + + begin_reversible_command (_("move meter mark")); + session->add_undo (map.get_memento()); + map.move_meter (marker->meter(), when); + session->add_redo_no_execute (map.get_memento()); + commit_reversible_command (); +} + +void +Editor::start_tempo_marker_grab (GtkCanvasItem* item, GdkEvent* event) +{ + Marker* marker; + TempoMarker* tempo_marker; + + if ((marker = reinterpret_cast<Marker *> (gtk_object_get_data (GTK_OBJECT(item), "tempo_marker"))) == 0) { + fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg; + /*NOTREACHED*/ + } + + if ((tempo_marker = dynamic_cast<TempoMarker *> (marker)) == 0) { + fatal << _("programming error: marker for tempo is not a tempo marker!") << endmsg; + /*NOTREACHED*/ + } + + MetricSection& section (tempo_marker->tempo()); + + if (!section.movable()) { + return; + } + + drag_info.item = item; + drag_info.data = marker; + drag_info.motion_callback = &Editor::tempo_marker_drag_motion_callback; + drag_info.finished_callback = &Editor::tempo_marker_drag_finished_callback; + + start_grab (event); + + drag_info.pointer_frame_offset = drag_info.grab_frame - tempo_marker->tempo().frame(); + show_verbose_time_cursor (drag_info.current_pointer_frame, 10); +} + +void +Editor::tempo_marker_drag_motion_callback (GtkCanvasItem* item, GdkEvent* event) +{ + TempoMarker* marker = (TempoMarker *) drag_info.data; + jack_nframes_t adjusted_frame; + + if ((long)drag_info.current_pointer_frame > drag_info.pointer_frame_offset) { + adjusted_frame = drag_info.current_pointer_frame - drag_info.pointer_frame_offset; + } + else { + adjusted_frame = 0; + } + + if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) { + snap_to (adjusted_frame); + } + + if (adjusted_frame == drag_info.last_pointer_frame) return; + + /* OK, we've moved far enough to make it worth actually move the thing. */ + + marker->set_position (adjusted_frame); + + show_verbose_time_cursor (adjusted_frame, 10); + + drag_info.last_pointer_frame = adjusted_frame; + drag_info.first_move = false; +} + +void +Editor::tempo_marker_drag_finished_callback (GtkCanvasItem* item, GdkEvent* event) +{ + if (drag_info.first_move) return; + + tempo_marker_drag_motion_callback (item, event); + + TempoMarker* marker = (TempoMarker *) drag_info.data; + BBT_Time when; + + TempoMap& map (session->tempo_map()); + map.bbt_time (drag_info.last_pointer_frame, when); + + begin_reversible_command (_("move tempo mark")); + session->add_undo (map.get_memento()); + map.move_tempo (marker->tempo(), when); + session->add_redo_no_execute (map.get_memento()); + commit_reversible_command (); +} + +void +Editor::remove_gain_control_point (GtkCanvasItem*item, GdkEvent* event) +{ + ControlPoint* control_point; + + if ((control_point = reinterpret_cast<ControlPoint *> (gtk_object_get_data (GTK_OBJECT(item), "control_point"))) == 0) { + fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg; + /*NOTREACHED*/ + } + + // We shouldn't remove the first or last gain point + if (control_point->line.is_last_point(*control_point) || + control_point->line.is_first_point(*control_point)) { + return; + } + + control_point->line.remove_point (*control_point); +} + +void +Editor::remove_control_point (GtkCanvasItem*item, GdkEvent* event) +{ + ControlPoint* control_point; + + if ((control_point = reinterpret_cast<ControlPoint *> (gtk_object_get_data (GTK_OBJECT(item), "control_point"))) == 0) { + fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg; + /*NOTREACHED*/ + } + + control_point->line.remove_point (*control_point); +} + +void +Editor::start_control_point_grab (GtkCanvasItem* item, GdkEvent* event) +{ + ControlPoint* control_point; + + if ((control_point = reinterpret_cast<ControlPoint *> (gtk_object_get_data (GTK_OBJECT(item), "control_point"))) == 0) { + fatal << _("programming error: control point canvas item has no control point object pointer!") << endmsg; + /*NOTREACHED*/ + } + + drag_info.item = item; + drag_info.data = control_point; + drag_info.motion_callback = &Editor::control_point_drag_motion_callback; + drag_info.finished_callback = &Editor::control_point_drag_finished_callback; + + start_grab (event, fader_cursor); + + control_point->line.start_drag (control_point, 0); + + float fraction = 1.0 - (control_point->get_y() / control_point->line.height()); + set_verbose_canvas_cursor (control_point->line.get_verbose_cursor_string (fraction), + drag_info.current_pointer_x + 20, drag_info.current_pointer_y + 20); + + show_verbose_canvas_cursor (); +} + +void +Editor::control_point_drag_motion_callback (GtkCanvasItem* item, GdkEvent* event) +{ + ControlPoint* cp = reinterpret_cast<ControlPoint *> (drag_info.data); + + double cx = drag_info.current_pointer_x; + double cy = drag_info.current_pointer_y; + + drag_info.cumulative_x_drag = cx - drag_info.grab_x ; + drag_info.cumulative_y_drag = cy - drag_info.grab_y ; + + bool x_constrained = false; + + if (drag_info.x_constrained) { + if (fabs(drag_info.cumulative_x_drag) < fabs(drag_info.cumulative_y_drag)) { + cx = drag_info.grab_x; + x_constrained = true; + + } else { + cy = drag_info.grab_y; + } + + } + + gtk_canvas_item_w2i (cp->line.parent_group(), &cx, &cy); + + cx = max (0.0, cx); + cy = max (0.0, cy); + cy = min ((double) cp->line.height(), cy); + + //translate cx to frames + jack_nframes_t cx_frames = (jack_nframes_t) floor (cx * frames_per_unit); + + if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier()) && !x_constrained) { + snap_to (cx_frames); + } + + float fraction = 1.0 - (cy / cp->line.height()); + + bool push; + + if (Keyboard::modifier_state_contains (event->button.state, Keyboard::Control)) { + push = true; + } else { + push = false; + } + + cp->line.point_drag (*cp, cx_frames , fraction, push); + + set_verbose_canvas_cursor_text (cp->line.get_verbose_cursor_string (fraction)); +} + +void +Editor::control_point_drag_finished_callback (GtkCanvasItem* item, GdkEvent* event) +{ + ControlPoint* cp = reinterpret_cast<ControlPoint *> (drag_info.data); + control_point_drag_motion_callback (item, event); + cp->line.end_drag (cp); +} + +void +Editor::start_line_grab_from_regionview (GtkCanvasItem* item, GdkEvent* event) +{ + switch (mouse_mode) { + case MouseGain: + start_line_grab (clicked_regionview->get_gain_line(), event); + break; + default: + break; + } +} + +void +Editor::start_line_grab_from_line (GtkCanvasItem* item, GdkEvent* event) +{ + AutomationLine* al; + + if ((al = reinterpret_cast<AutomationLine*> (gtk_object_get_data (GTK_OBJECT(item), "line"))) == 0) { + fatal << _("programming error: line canvas item has no line pointer!") << endmsg; + /*NOTREACHED*/ + } + + start_line_grab (al, event); +} + +void +Editor::start_line_grab (AutomationLine* line, GdkEvent* event) +{ + double cx; + double cy; + jack_nframes_t frame_within_region; + + /* need to get x coordinate in terms of parent (TimeAxisItemView) + origin. + */ + + cx = event->button.x; + cy = event->button.y; + gtk_canvas_item_w2i (line->parent_group(), &cx, &cy); + frame_within_region = (jack_nframes_t) floor (cx * frames_per_unit); + + if (!line->control_points_adjacent (frame_within_region, current_line_drag_info.before, + current_line_drag_info.after)) { + /* no adjacent points */ + return; + } + + drag_info.item = line->grab_item(); + drag_info.data = line; + drag_info.motion_callback = &Editor::line_drag_motion_callback; + drag_info.finished_callback = &Editor::line_drag_finished_callback; + + start_grab (event, fader_cursor); + + double fraction = 1.0 - (cy / line->height()); + + line->start_drag (0, fraction); + + set_verbose_canvas_cursor (line->get_verbose_cursor_string (fraction), + drag_info.current_pointer_x + 20, drag_info.current_pointer_y + 20); + show_verbose_canvas_cursor (); +} + +void +Editor::line_drag_motion_callback (GtkCanvasItem* item, GdkEvent* event) +{ + AutomationLine* line = reinterpret_cast<AutomationLine *> (drag_info.data); + double cx = drag_info.current_pointer_x; + double cy = drag_info.current_pointer_y; + + gtk_canvas_item_w2i (line->parent_group(), &cx, &cy); + + double fraction; + fraction = 1.0 - (cy / line->height()); + + bool push; + + if (Keyboard::modifier_state_contains (event->button.state, Keyboard::Control)) { + push = false; + } else { + push = true; + } + + line->line_drag (current_line_drag_info.before, current_line_drag_info.after, fraction, push); + + set_verbose_canvas_cursor_text (line->get_verbose_cursor_string (fraction)); +} + +void +Editor::line_drag_finished_callback (GtkCanvasItem* item, GdkEvent* event) +{ + AutomationLine* line = reinterpret_cast<AutomationLine *> (drag_info.data); + line_drag_motion_callback (item, event); + line->end_drag (0); +} + +void +Editor::start_region_grab (GtkCanvasItem* item, GdkEvent* event) +{ + if (selection->audio_regions.empty() || clicked_regionview == 0) { + return; + } + + drag_info.copy = false; + drag_info.item = item; + drag_info.data = clicked_regionview; + drag_info.motion_callback = &Editor::region_drag_motion_callback; + drag_info.finished_callback = &Editor::region_drag_finished_callback; + + start_grab (event); + + double speed = 1.0; + TimeAxisView* tvp = clicked_trackview; + AudioTimeAxisView* tv = dynamic_cast<AudioTimeAxisView*>(tvp); + + if (tv && tv->is_audio_track()) { + speed = tv->get_diskstream()->speed(); + } + + drag_info.last_frame_position = (jack_nframes_t) (clicked_regionview->region.position() / speed); + drag_info.pointer_frame_offset = drag_info.grab_frame - drag_info.last_frame_position; + drag_info.last_trackview = &clicked_regionview->get_time_axis_view(); + // we want a move threshold + drag_info.want_move_threshold = true; + + show_verbose_time_cursor (drag_info.last_frame_position, 10); + + begin_reversible_command (_("move region(s)")); +} + +void +Editor::start_region_copy_grab (GtkCanvasItem* item, GdkEvent* event) +{ + if (selection->audio_regions.empty() || clicked_regionview == 0) { + return; + } + + /* this is committed in the grab finished callback. */ + + begin_reversible_command (_("Drag region copy")); + + /* duplicate the region(s) */ + + vector<AudioRegionView*> new_regionviews; + + for (list<AudioRegionView*>::const_iterator i = selection->audio_regions.by_layer().begin(); i != selection->audio_regions.by_layer().end(); ++i) { + AudioRegionView* rv; + + rv = (*i); + + Playlist* to_playlist = rv->region.playlist(); + AudioTimeAxisView* atv = dynamic_cast<AudioTimeAxisView*>(&rv->get_time_axis_view()); + + session->add_undo (to_playlist->get_memento ()); + latest_regionview = 0; + + SigC::Connection c = atv->view->AudioRegionViewAdded.connect (slot (*this, &Editor::collect_new_region_view)); + + /* create a new region with the same name. + */ + + AudioRegion* newregion = new AudioRegion (rv->region); + + /* if the original region was locked, we don't care */ + + newregion->set_locked (false); + + to_playlist->add_region (*newregion, (jack_nframes_t) (rv->region.position() * atv->get_diskstream()->speed())); + + c.disconnect (); + + if (latest_regionview) { + new_regionviews.push_back (latest_regionview); + } + + } + + if (new_regionviews.empty()) { + return; + } + + /* reset selection to new regionviews */ + + selection->set (new_regionviews); + + drag_info.item = new_regionviews.front()->get_canvas_group (); + drag_info.copy = true; + drag_info.data = new_regionviews.front(); + drag_info.motion_callback = &Editor::region_drag_motion_callback; + drag_info.finished_callback = &Editor::region_drag_finished_callback; + + start_grab(event); + + TimeAxisView* tv = &clicked_regionview->get_time_axis_view(); + AudioTimeAxisView* atv = dynamic_cast<AudioTimeAxisView*>(tv); + double speed = 1.0; + + if (atv && atv->is_audio_track()) { + speed = atv->get_diskstream()->speed(); + } + + drag_info.last_trackview = &clicked_regionview->get_time_axis_view(); + drag_info.last_frame_position = (jack_nframes_t) (clicked_regionview->region.position() / speed); + drag_info.pointer_frame_offset = drag_info.grab_frame - drag_info.last_frame_position; + // we want a move threshold + drag_info.want_move_threshold = true; + + show_verbose_time_cursor (drag_info.last_frame_position, 10); + + begin_reversible_command (_("copy region(s)")); +} + +void +Editor::start_region_brush_grab (GtkCanvasItem* item, GdkEvent* event) +{ + if (selection->audio_regions.empty() || clicked_regionview == 0) { + return; + } + + drag_info.copy = false; + drag_info.item = item; + drag_info.data = clicked_regionview; + drag_info.motion_callback = &Editor::region_drag_motion_callback; + drag_info.finished_callback = &Editor::region_drag_finished_callback; + + start_grab (event); + + double speed = 1.0; + TimeAxisView* tvp = clicked_trackview; + AudioTimeAxisView* tv = dynamic_cast<AudioTimeAxisView*>(tvp); + + if (tv && tv->is_audio_track()) { + speed = tv->get_diskstream()->speed(); + } + + drag_info.last_frame_position = (jack_nframes_t) (clicked_regionview->region.position() / speed); + drag_info.pointer_frame_offset = drag_info.grab_frame - drag_info.last_frame_position; + drag_info.last_trackview = &clicked_regionview->get_time_axis_view(); + // we want a move threshold + drag_info.want_move_threshold = true; + drag_info.brushing = true; + + begin_reversible_command (_("Drag region brush")); +} + +void +Editor::region_drag_motion_callback (GtkCanvasItem* item, GdkEvent* event) +{ + double x_delta; + double y_delta = 0; + AudioRegionView *rv = reinterpret_cast<AudioRegionView*> (drag_info.data); + jack_nframes_t pending_region_position = 0; + int32_t pointer_y_span = 0, canvas_pointer_y_span = 0, original_pointer_order; + int32_t visible_y_high = 0, visible_y_low = 512; //high meaning higher numbered.. not the height on the screen + bool clamp_y_axis = false; + vector<int32_t> height_list(512) ; + vector<int32_t>::iterator j; + + /* Which trackview is this ? */ + + TimeAxisView* tvp = trackview_by_y_position (drag_info.current_pointer_y); + AudioTimeAxisView* tv = dynamic_cast<AudioTimeAxisView*>(tvp); + + /* The region motion is only processed if the pointer is over + an audio track. + */ + + if (!tv || !tv->is_audio_track()) { + /* To make sure we hide the verbose canvas cursor when the mouse is + not held over and audiotrack. + */ + hide_verbose_canvas_cursor (); + return; + } + + original_pointer_order = drag_info.last_trackview->order; + + /************************************************************ + Y-Delta Computation + ************************************************************/ + + if (drag_info.brushing) { + clamp_y_axis = true; + pointer_y_span = 0; + goto y_axis_done; + } + + if ((pointer_y_span = (drag_info.last_trackview->order - tv->order)) != 0) { + + int32_t children = 0, numtracks = 0; + bitset <512> tracks (0x00); + /* get a bitmask representing the visible tracks */ + + for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) { + TimeAxisView *tracklist_timeview; + tracklist_timeview = (*i); + AudioTimeAxisView* atv2 = dynamic_cast<AudioTimeAxisView*>(tracklist_timeview); + list<TimeAxisView*> children_list; + + /* zeroes are audio tracks. ones are other types. */ + + if (!atv2->hidden()) { + + if (visible_y_high < atv2->order) { + visible_y_high = atv2->order; + } + if (visible_y_low > atv2->order) { + visible_y_low = atv2->order; + } + + if (!atv2->is_audio_track()) { + tracks = tracks |= (0x01 << atv2->order); + } + + height_list[atv2->order] = (*i)->height; + children = 1; + if ((children_list = atv2->get_child_list()).size() > 0) { + for (list<TimeAxisView*>::iterator j = children_list.begin(); j != children_list.end(); ++j) { + tracks = tracks |= (0x01 << (atv2->order + children)); + height_list[atv2->order + children] = (*j)->height; + numtracks++; + children++; + } + } + numtracks++; + } + } + /* find the actual span according to the canvas */ + + canvas_pointer_y_span = pointer_y_span; + if (drag_info.last_trackview->order >= tv->order) { + int32_t y; + for (y = tv->order; y < drag_info.last_trackview->order; y++) { + if (height_list[y] == 0 ) { + canvas_pointer_y_span--; + } + } + } else { + int32_t y; + for (y = drag_info.last_trackview->order;y <= tv->order; y++) { + if ( height_list[y] == 0 ) { + canvas_pointer_y_span++; + } + } + } + + for (list<AudioRegionView*>::const_iterator i = selection->audio_regions.by_layer().begin(); i != selection->audio_regions.by_layer().end(); ++i) { + AudioRegionView* rv2; + rv2 = (*i); + double ix1, ix2, iy1, iy2; + int32_t n = 0; + + gtk_canvas_item_get_bounds (rv2->get_canvas_frame(), &ix1, &iy1, &ix2, &iy2); + gtk_canvas_item_i2w (rv2->get_canvas_group(), &ix1, &iy1); + TimeAxisView* tvp2 = trackview_by_y_position (iy1); + AudioTimeAxisView* atv2 = dynamic_cast<AudioTimeAxisView*>(tvp2); + + if (atv2->order != original_pointer_order) { + /* this isn't the pointer track */ + + if (canvas_pointer_y_span > 0) { + + /* moving up the canvas */ + if ((atv2->order - canvas_pointer_y_span) >= visible_y_low) { + + int32_t visible_tracks = 0; + while (visible_tracks < canvas_pointer_y_span ) { + visible_tracks++; + + while (height_list[atv2->order - (visible_tracks - n)] == 0) { + /* we're passing through a hidden track */ + n--; + } + } + + if (tracks[atv2->order - (canvas_pointer_y_span - n)] != 0x00) { + clamp_y_axis = true; + } + + } else { + clamp_y_axis = true; + } + + } else if (canvas_pointer_y_span < 0) { + + /*moving down the canvas*/ + + if ((atv2->order - (canvas_pointer_y_span - n)) <= visible_y_high) { // we will overflow + + + int32_t visible_tracks = 0; + + while (visible_tracks > canvas_pointer_y_span ) { + visible_tracks--; + + while (height_list[atv2->order - (visible_tracks - n)] == 0) { + n++; + } + } + if ( tracks[atv2->order - ( canvas_pointer_y_span - n)] != 0x00) { + clamp_y_axis = true; + + } + } else { + + clamp_y_axis = true; + } + } + + } else { + + /* this is the pointer's track */ + if ((atv2->order - pointer_y_span) > visible_y_high) { // we will overflow + clamp_y_axis = true; + } else if ((atv2->order - pointer_y_span) < visible_y_low) { // we will underflow + clamp_y_axis = true; + } + } + if (clamp_y_axis) { + break; + } + } + + } else if (drag_info.last_trackview == tv) { + clamp_y_axis = true; + } + + y_axis_done: + if (!clamp_y_axis) { + drag_info.last_trackview = tv; + } + + /************************************************************ + X DELTA COMPUTATION + ************************************************************/ + + /* compute the amount of pointer motion in frames, and where + the region would be if we moved it by that much. + */ + + if (drag_info.move_threshold_passsed) { + + if ((int32_t)drag_info.current_pointer_frame > drag_info.pointer_frame_offset) { + + jack_nframes_t sync_frame; + jack_nframes_t sync_offset; + int32_t sync_dir; + + pending_region_position = drag_info.current_pointer_frame - drag_info.pointer_frame_offset; + + sync_offset = rv->region.sync_offset (sync_dir); + sync_frame = rv->region.adjust_to_sync (pending_region_position); + + /* we snap if the snap modifier is not enabled. + */ + + if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) { + snap_to (sync_frame); + } + + if (sync_frame - sync_offset <= sync_frame) { + pending_region_position = sync_frame - (sync_dir*sync_offset); + } else { + pending_region_position = 0; + } + + } else { + pending_region_position = 0; + } + + if (pending_region_position > max_frames - rv->region.length()) { + pending_region_position = drag_info.last_frame_position; + } + + // printf ("3: pending_region_position= %lu %lu\n", pending_region_position, drag_info.last_frame_position ); + + if (pending_region_position != drag_info.last_frame_position && !drag_info.x_constrained) { + + /* now compute the canvas unit distance we need to move the regiondrag_info.last_trackview->order + to make it appear at the new location. + */ + + if (pending_region_position > drag_info.last_frame_position) { + x_delta = ((double) (pending_region_position - drag_info.last_frame_position) / frames_per_unit); + } else { + x_delta = -((double) (drag_info.last_frame_position - pending_region_position) / frames_per_unit); + } + + drag_info.last_frame_position = pending_region_position; + + } else { + x_delta = 0; + } + + } else { + /* threshold not passed */ + + x_delta = 0; + } + + /************************************************************* + PREPARE TO MOVE + ************************************************************/ + + if (x_delta == 0 && (pointer_y_span == 0)) { + /* haven't reached next snap point, and we're not switching + trackviews. nothing to do. + */ + return; + } + + if (x_delta < 0) { + for (list<AudioRegionView*>::const_iterator i = selection->audio_regions.by_layer().begin(); i != selection->audio_regions.by_layer().end(); ++i) { + + AudioRegionView* rv2; + rv2 = (*i); + + /* if any regionview is at zero, we need to know so we can + stop further leftward motion. + */ + + double ix1, ix2, iy1, iy2; + gtk_canvas_item_get_bounds (rv2->get_canvas_frame(), &ix1, &iy1, &ix2, &iy2); + gtk_canvas_item_i2w (rv2->get_canvas_group(), &ix1, &iy1); + + if (ix1 <= 1) { + x_delta = 0; + break; + } + } + } + + /************************************************************* + MOTION + ************************************************************/ + + pair<set<Playlist*>::iterator,bool> insert_result; + + for (list<AudioRegionView*>::const_iterator i = selection->audio_regions.by_layer().begin(); i != selection->audio_regions.by_layer().end(); ++i) { + + AudioRegionView* rv; + rv = (*i); + double ix1, ix2, iy1, iy2; + int32_t temp_pointer_y_span = pointer_y_span; + + /* get item BBox, which will be relative to parent. so we have + to query on a child, then convert to world coordinates using + the parent. + */ + + gtk_canvas_item_get_bounds (rv->get_canvas_frame(), &ix1, &iy1, &ix2, &iy2); + gtk_canvas_item_i2w (rv->get_canvas_group(), &ix1, &iy1); + TimeAxisView* tvp2 = trackview_by_y_position (iy1); + AudioTimeAxisView* canvas_atv = dynamic_cast<AudioTimeAxisView*>(tvp2); + AudioTimeAxisView* temp_atv; + + if ((pointer_y_span != 0) && !clamp_y_axis) { + y_delta = 0; + int32_t x = 0; + for (j = height_list.begin(); j!= height_list.end(); j++) { + if (x == canvas_atv->order) { + /* we found the track the region is on */ + if (x != original_pointer_order) { + /*this isn't from the same track we're dragging from */ + temp_pointer_y_span = canvas_pointer_y_span; + } + while (temp_pointer_y_span > 0) { + /* we're moving up canvas-wise, + so we need to find the next track height + */ + if (j != height_list.begin()) { + j--; + } + if (x != original_pointer_order) { + /* we're not from the dragged track, so ignore hidden tracks. */ + if ((*j) == 0) { + temp_pointer_y_span++; + } + } + y_delta -= (*j); + temp_pointer_y_span--; + } + while (temp_pointer_y_span < 0) { + y_delta += (*j); + if (x != original_pointer_order) { + if ((*j) == 0) { + temp_pointer_y_span--; + } + } + + if (j != height_list.end()) { + j++; + } + temp_pointer_y_span++; + } + /* find out where we'll be when we move and set height accordingly */ + + tvp2 = trackview_by_y_position (iy1 + y_delta); + temp_atv = dynamic_cast<AudioTimeAxisView*>(tvp2); + rv->set_height (temp_atv->height); + + /* if you un-comment the following, the region colours will follow the track colours whilst dragging, + personally, i think this can confuse things, but never mind. + */ + + //const GdkColor& col (temp_atv->view->get_region_color()); + //rv->set_color (const_cast<GdkColor&>(col)); + break; + } + x++; + } + } + + /* prevent the regionview from being moved to before + the zero position on the canvas. + */ + /* clamp */ + + if (x_delta < 0) { + if (-x_delta > ix1) { + x_delta = -ix1; + } + } else if ((x_delta > 0) &&(rv->region.last_frame() > max_frames - x_delta)) { + x_delta = max_frames - rv->region.last_frame(); + } + + if (drag_info.first_move) { + + /* hide any dependent views */ + +// rv->get_time_axis_view().hide_dependent_views (*rv); + + /* this is subtle. raising the regionview itself won't help, + because raise_to_top() just puts the item on the top of + its parent's stack. so, we need to put the trackview canvas_display group + on the top, since its parent is the whole canvas. + */ + + gtk_canvas_item_raise_to_top (rv->get_canvas_group()); + gtk_canvas_item_raise_to_top (rv->get_time_axis_view().canvas_display); + gtk_canvas_item_raise_to_top (cursor_group); + + /* freeze the playlists from notifying till + the motion is done. + */ + + AudioTimeAxisView* atv = dynamic_cast<AudioTimeAxisView*> (&rv->get_time_axis_view()); + if (atv && atv->is_audio_track()) { + AudioPlaylist* pl = atv->get_diskstream()->playlist(); + if (pl) { + /* only freeze and capture state once */ + + insert_result = motion_frozen_playlists.insert (pl); + if (insert_result.second) { + pl->freeze(); + session->add_undo(pl->get_memento()); + } + } + } + } + + if (drag_info.brushing) { + mouse_brush_insert_region (rv, pending_region_position); + } else { + rv->move (x_delta, y_delta); + } + } + + if (drag_info.first_move) { + gtk_canvas_item_raise_to_top (cursor_group); + } + + drag_info.first_move = false; + + if (x_delta != 0 && !drag_info.brushing) { + show_verbose_time_cursor (drag_info.last_frame_position, 10); + } + +} + +void +Editor::region_drag_finished_callback (GtkCanvasItem* item, GdkEvent* event) +{ + jack_nframes_t where; + AudioRegionView* rv = reinterpret_cast<AudioRegionView *> (drag_info.data); + pair<set<Playlist*>::iterator,bool> insert_result; + bool nocommit = true; + double speed; + AudioTimeAxisView* atv; + bool regionview_y_movement; + bool regionview_x_movement; + + /* first_move is set to false if the regionview has been moved in the + motion handler. + */ + + if (drag_info.first_move) { + /* just a click */ + goto out; + } + + nocommit = false; + + /* The regionview has been moved at some stage during the grab so we need + to account for any mouse movement between this event and the last one. + */ + + region_drag_motion_callback (item, event); + + if (drag_info.brushing) { + /* all changes were made during motion event handlers */ + goto out; + } + + /* adjust for track speed */ + speed = 1.0; + + atv = dynamic_cast<AudioTimeAxisView*> (drag_info.last_trackview); + if (atv && atv->get_diskstream()) { + speed = atv->get_diskstream()->speed(); + } + + regionview_x_movement = (drag_info.last_frame_position != (jack_nframes_t) (rv->region.position()/speed)); + regionview_y_movement = (drag_info.last_trackview != &rv->get_time_axis_view()); + + //printf ("last_frame: %s position is %lu %g\n", rv->get_time_axis_view().name().c_str(), drag_info.last_frame_position, speed); + //printf ("last_rackview: %s \n", drag_info.last_trackview->name().c_str()); + + if (regionview_y_movement) { + + /* motion between tracks */ + + list<AudioRegionView*> new_selection; + + /* moved to a different audio track. */ + + for (list<AudioRegionView*>::const_iterator i = selection->audio_regions.by_layer().begin(); i != selection->audio_regions.by_layer().end(); ) { + + AudioRegionView* rv2 = (*i); + + /* the region that used to be in the old playlist is not + moved to the new one - we make a copy of it. as a result, + any existing editor for the region should no longer be + visible. + */ + + if (!drag_info.copy) { + rv2->hide_region_editor(); + } + new_selection.push_back (rv2); + i++; + } + + /* first, freeze the target tracks */ + + for (list<AudioRegionView*>::const_iterator i = new_selection.begin(); i != new_selection.end();i++ ) { + + Playlist* from_playlist; + Playlist* to_playlist; + + double ix1, ix2, iy1, iy2; + + gtk_canvas_item_get_bounds ((*i)->get_canvas_frame(), &ix1, &iy1, &ix2, &iy2); + gtk_canvas_item_i2w ((*i)->get_canvas_group(), &ix1, &iy1); + TimeAxisView* tvp2 = trackview_by_y_position (iy1); + AudioTimeAxisView* atv2 = dynamic_cast<AudioTimeAxisView*>(tvp2); + + from_playlist = (*i)->region.playlist(); + to_playlist = atv2->playlist(); + + /* the from_playlist was frozen in the "first_move" case + of the motion handler. the insert can fail, + but that doesn't matter. it just means + we already have the playlist in the list. + */ + + motion_frozen_playlists.insert (from_playlist); + + /* only freeze the to_playlist once */ + + insert_result = motion_frozen_playlists.insert(to_playlist); + if (insert_result.second) { + to_playlist->freeze(); + session->add_undo(to_playlist->get_memento()); + } + + } + + /* now do it again with the actual operations */ + + for (list<AudioRegionView*>::const_iterator i = new_selection.begin(); i != new_selection.end();i++ ) { + + Playlist* from_playlist; + Playlist* to_playlist; + + double ix1, ix2, iy1, iy2; + + gtk_canvas_item_get_bounds ((*i)->get_canvas_frame(), &ix1, &iy1, &ix2, &iy2); + gtk_canvas_item_i2w ((*i)->get_canvas_group(), &ix1, &iy1); + TimeAxisView* tvp2 = trackview_by_y_position (iy1); + AudioTimeAxisView* atv2 = dynamic_cast<AudioTimeAxisView*>(tvp2); + + from_playlist = (*i)->region.playlist(); + to_playlist = atv2->playlist(); + + latest_regionview = 0; + + where = (jack_nframes_t) (unit_to_frame (ix1) * speed); + Region* new_region = createRegion ((*i)->region); + + from_playlist->remove_region (&((*i)->region)); + + SigC::Connection c = atv2->view->AudioRegionViewAdded.connect (slot (*this, &Editor::collect_new_region_view)); + to_playlist->add_region (*new_region, where); + c.disconnect (); + + if (latest_regionview) { + selection->add (latest_regionview); + } + } + + } else { + + /* motion within a single track */ + + for (list<AudioRegionView*>::const_iterator i = selection->audio_regions.by_layer().begin(); i != selection->audio_regions.by_layer().end(); ++i) { + + rv = (*i); + + if (rv->region.locked()) { + continue; + } + + if (regionview_x_movement) { + double ownspeed = 1.0; + AudioTimeAxisView* atv = dynamic_cast<AudioTimeAxisView*> (&(rv->get_time_axis_view())); + + if (atv && atv->get_diskstream()) { + ownspeed = atv->get_diskstream()->speed(); + } + + /* base the new region position on the current position of the regionview.*/ + + double ix1, ix2, iy1, iy2; + + gtk_canvas_item_get_bounds (rv->get_canvas_frame(), &ix1, &iy1, &ix2, &iy2); + gtk_canvas_item_i2w (rv->get_canvas_group(), &ix1, &iy1); + where = (jack_nframes_t) (unit_to_frame (ix1) * ownspeed); + + } else { + + where = rv->region.position(); + } + + rv->get_time_axis_view().reveal_dependent_views (*rv); + + session->add_undo (rv->region.playlist()->get_memento()); + rv->region.set_position (where, (void *) this); + } + } + + out: + for (set<Playlist*>::iterator p = motion_frozen_playlists.begin(); p != motion_frozen_playlists.end(); ++p) { + (*p)->thaw (); + session->add_redo_no_execute ((*p)->get_memento()); + } + + motion_frozen_playlists.clear (); + + if (!nocommit) { + commit_reversible_command (); + } +} + +void +Editor::region_view_item_click (AudioRegionView& rv, GdkEventButton* event) +{ + /* Either add to or set the set the region selection, unless + this is an alignment click (control used) + */ + + if (Keyboard::modifier_state_contains (event->state, Keyboard::Control)) { + TimeAxisView* tv = &rv.get_time_axis_view(); + AudioTimeAxisView* atv = dynamic_cast<AudioTimeAxisView*>(tv); + double speed = 1.0; + if (atv && atv->is_audio_track()) { + speed = atv->get_diskstream()->speed(); + } + + if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::Control|Keyboard::Alt))) { + + align_region (rv.region, SyncPoint, (jack_nframes_t) (edit_cursor->current_frame * speed)); + + } else if (Keyboard::modifier_state_equals (event->state, Keyboard::ModifierMask (Keyboard::Control|Keyboard::Shift))) { + + align_region (rv.region, End, (jack_nframes_t) (edit_cursor->current_frame * speed)); + + } else { + + align_region (rv.region, Start, (jack_nframes_t) (edit_cursor->current_frame * speed)); + } + } +} + +void +Editor::show_verbose_time_cursor (jack_nframes_t frame, double offset, double xpos, double ypos) +{ + char buf[128]; + SMPTE_Time smpte; + BBT_Time bbt; + float secs; + + if (session == 0) { + return; + } + + switch (ARDOUR_UI::instance()->secondary_clock.mode ()) { + case AudioClock::BBT: + session->bbt_time (frame, bbt); + snprintf (buf, sizeof (buf), "%02" PRIu32 "|%02" PRIu32 "|%02" PRIu32, bbt.bars, bbt.beats, bbt.ticks); + break; + + case AudioClock::SMPTE: + session->smpte_time (frame, smpte); + snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32, smpte.hours, smpte.minutes, smpte.seconds, smpte.frames); + break; + + case AudioClock::MinSec: + /* XXX fix this to compute min/sec properly */ + session->smpte_time (frame, smpte); + secs = smpte.seconds + ((float) smpte.frames / session->smpte_frames_per_second); + snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%.4f", smpte.hours, smpte.minutes, secs); + break; + + default: + snprintf (buf, sizeof(buf), "%u", frame); + break; + } + + if (xpos >= 0 && ypos >=0) { + set_verbose_canvas_cursor (buf, xpos + offset, ypos + offset); + } + else { + set_verbose_canvas_cursor (buf, drag_info.current_pointer_x + offset, drag_info.current_pointer_y + offset); + } + show_verbose_canvas_cursor (); +} + +void +Editor::show_verbose_duration_cursor (jack_nframes_t start, jack_nframes_t end, double offset, double xpos, double ypos) +{ + char buf[128]; + SMPTE_Time smpte; + BBT_Time sbbt; + BBT_Time ebbt; + float secs; + Meter meter_at_start(session->tempo_map().meter_at(start)); + + if (session == 0) { + return; + } + + switch (ARDOUR_UI::instance()->secondary_clock.mode ()) { + case AudioClock::BBT: + session->bbt_time (start, sbbt); + session->bbt_time (end, ebbt); + + /* subtract */ + /* XXX this computation won't work well if the + user makes a selection that spans any meter changes. + */ + + ebbt.bars -= sbbt.bars; + if (ebbt.beats >= sbbt.beats) { + ebbt.beats -= sbbt.beats; + } else { + ebbt.bars--; + ebbt.beats = int(meter_at_start.beats_per_bar()) + ebbt.beats - sbbt.beats; + } + if (ebbt.ticks >= sbbt.ticks) { + ebbt.ticks -= sbbt.ticks; + } else { + ebbt.beats--; + ebbt.ticks = int(Meter::ticks_per_beat) + ebbt.ticks - sbbt.ticks; + } + + snprintf (buf, sizeof (buf), "%02" PRIu32 "|%02" PRIu32 "|%02" PRIu32, ebbt.bars, ebbt.beats, ebbt.ticks); + break; + + case AudioClock::SMPTE: + session->smpte_duration (end - start, smpte); + snprintf (buf, sizeof (buf), "%02" PRId32 ":%02" PRId32 ":%02" PRId32 ":%02" PRId32, smpte.hours, smpte.minutes, smpte.seconds, smpte.frames); + break; + + case AudioClock::MinSec: + /* XXX fix this to compute min/sec properly */ + session->smpte_duration (end - start, smpte); + secs = smpte.seconds + ((float) smpte.frames / session->smpte_frames_per_second); + snprintf (buf, sizeof (buf), "%02ld:%02ld:%.4f", smpte.hours, smpte.minutes, secs); + break; + + default: + snprintf (buf, sizeof(buf), "%u", end - start); + break; + } + + if (xpos >= 0 && ypos >=0) { + set_verbose_canvas_cursor (buf, xpos + offset, ypos + offset); + } + else { + set_verbose_canvas_cursor (buf, drag_info.current_pointer_x + offset, drag_info.current_pointer_y + offset); + } + show_verbose_canvas_cursor (); +} + +void +Editor::collect_new_region_view (AudioRegionView* rv) +{ + latest_regionview = rv; +} + +void +Editor::start_selection_grab (GtkCanvasItem* item, GdkEvent* event) +{ + if (clicked_regionview == 0) { + return; + } + + /* lets try to create new Region for the selection */ + + vector<AudioRegion*> new_regions; + create_region_from_selection (new_regions); + + if (new_regions.empty()) { + return; + } + + /* XXX fix me one day to use all new regions */ + + Region* region = new_regions.front(); + + /* add it to the current stream/playlist. + + tricky: the streamview for the track will add a new regionview. we will + catch the signal it sends when it creates the regionview to + set the regionview we want to then drag. + */ + + latest_regionview = 0; + SigC::Connection c = clicked_audio_trackview->view->AudioRegionViewAdded.connect (slot (*this, &Editor::collect_new_region_view)); + + /* A selection grab currently creates two undo/redo operations, one for + creating the new region and another for moving it. + */ + + begin_reversible_command (_("selection grab")); + + Playlist* playlist = clicked_trackview->playlist(); + + session->add_undo (playlist->get_memento ()); + clicked_trackview->playlist()->add_region (*region, selection->time[clicked_selection].start); + session->add_redo_no_execute (playlist->get_memento ()); + + commit_reversible_command (); + + c.disconnect (); + + if (latest_regionview == 0) { + /* something went wrong */ + return; + } + + /* we need to deselect all other regionviews, and select this one + i'm ignoring undo stuff, because the region creation will take care of it */ + selection->set (latest_regionview); + + drag_info.item = latest_regionview->get_canvas_group(); + drag_info.data = latest_regionview; + drag_info.motion_callback = &Editor::region_drag_motion_callback; + drag_info.finished_callback = &Editor::region_drag_finished_callback; + + start_grab (event); + + drag_info.last_trackview = clicked_trackview; + drag_info.last_frame_position = latest_regionview->region.position(); + drag_info.pointer_frame_offset = drag_info.grab_frame - drag_info.last_frame_position; + + show_verbose_time_cursor (drag_info.last_frame_position, 10); +} + +void +Editor::cancel_selection () +{ + for (TrackViewList::iterator i = track_views.begin(); i != track_views.end(); ++i) { + (*i)->hide_selection (); + } + selection->clear (); + clicked_selection = 0; +} + +void +Editor::start_selection_op (GtkCanvasItem* item, GdkEvent* event, SelectionOp op) +{ + jack_nframes_t start = 0; + jack_nframes_t end = 0; + + if (session == 0) { + return; + } + + drag_info.item = item; + drag_info.motion_callback = &Editor::drag_selection; + drag_info.finished_callback = &Editor::end_selection_op; + + selection_op = op; + + switch (op) { + case CreateSelection: + + if (Keyboard::modifier_state_equals (event->button.state, Keyboard::Shift)) { + drag_info.copy = true; + } else { + drag_info.copy = false; + } + start_grab (event, selector_cursor); + break; + + case SelectionStartTrim: + clicked_trackview->order_selection_trims (item, true); + start_grab (event, trimmer_cursor); + start = selection->time[clicked_selection].start; + drag_info.pointer_frame_offset = drag_info.grab_frame - start; + break; + + case SelectionEndTrim: + clicked_trackview->order_selection_trims (item, false); + start_grab (event, trimmer_cursor); + end = selection->time[clicked_selection].end; + drag_info.pointer_frame_offset = drag_info.grab_frame - end; + break; + + case SelectionMove: + start = selection->time[clicked_selection].start; + start_grab (event); + drag_info.pointer_frame_offset = drag_info.grab_frame - start; + break; + } + + if (selection_op == SelectionMove) { + show_verbose_time_cursor(start, 10); + } else { + show_verbose_time_cursor(drag_info.current_pointer_frame, 10); + } +} + +void +Editor::drag_selection (GtkCanvasItem* item, GdkEvent* event) +{ + jack_nframes_t start = 0; + jack_nframes_t end = 0; + jack_nframes_t length; + jack_nframes_t pending_position; + + if ((int32_t) drag_info.current_pointer_frame > drag_info.pointer_frame_offset) { + pending_position = drag_info.current_pointer_frame - drag_info.pointer_frame_offset; + } + else { + pending_position = 0; + } + + if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) { + snap_to (pending_position); + } + + /* only alter selection if the current frame is + different from the last frame position (adjusted) + */ + + if (pending_position == drag_info.last_pointer_frame) return; + + switch (selection_op) { + case CreateSelection: + + if (drag_info.first_move) { + snap_to (drag_info.grab_frame); + } + + if (pending_position < drag_info.grab_frame) { + start = pending_position; + end = drag_info.grab_frame; + } else { + end = pending_position; + start = drag_info.grab_frame; + } + + /* first drag: Either add to the selection + or create a new selection-> + */ + + if (drag_info.first_move) { + + begin_reversible_command (_("range selection")); + + if (drag_info.copy) { + /* adding to the selection */ + clicked_selection = selection->add (start, end); + drag_info.copy = false; + } else { + /* new selection-> */ + clicked_selection = selection->set (clicked_trackview, start, end); + } + } + break; + + case SelectionStartTrim: + + if (drag_info.first_move) { + begin_reversible_command (_("trim selection start")); + } + + start = selection->time[clicked_selection].start; + end = selection->time[clicked_selection].end; + + if (pending_position > end) { + start = end; + } else { + start = pending_position; + } + break; + + case SelectionEndTrim: + + if (drag_info.first_move) { + begin_reversible_command (_("trim selection end")); + } + + start = selection->time[clicked_selection].start; + end = selection->time[clicked_selection].end; + + if (pending_position < start) { + end = start; + } else { + end = pending_position; + } + + break; + + case SelectionMove: + + if (drag_info.first_move) { + begin_reversible_command (_("move selection")); + } + + start = selection->time[clicked_selection].start; + end = selection->time[clicked_selection].end; + + length = end - start; + + start = pending_position; + snap_to (start); + + end = start + length; + + break; + } + + + if (event->button.x >= track_canvas_scroller.get_hadjustment()->get_value() + canvas_width) { + start_canvas_autoscroll (1); + } + + if (start != end) { + selection->replace (clicked_selection, start, end); + } + + drag_info.last_pointer_frame = pending_position; + drag_info.first_move = false; + + if (selection_op == SelectionMove) { + show_verbose_time_cursor(start, 10); + } else { + show_verbose_time_cursor(pending_position, 10); + } +} + +void +Editor::end_selection_op (GtkCanvasItem* item, GdkEvent* event) +{ + if (!drag_info.first_move) { + drag_selection (item, event); + /* XXX this is not object-oriented programming at all. ick */ + if (selection->time.consolidate()) { + selection->TimeChanged (); + } + commit_reversible_command (); + } else { + /* just a click, no pointer movement.*/ + + if (Keyboard::no_modifier_keys_pressed (&event->button)) { + + selection->clear_time(); + + } + } + + /* XXX what happens if its a music selection? */ + session->set_audio_range (selection->time); + stop_canvas_autoscroll (); +} + +void +Editor::start_trim (GtkCanvasItem* item, GdkEvent* event) +{ + double speed = 1.0; + TimeAxisView* tvp = clicked_trackview; + AudioTimeAxisView* tv = dynamic_cast<AudioTimeAxisView*>(tvp); + + if (tv && tv->is_audio_track()) { + speed = tv->get_diskstream()->speed(); + } + + jack_nframes_t region_start = (jack_nframes_t) (clicked_regionview->region.position() / speed); + jack_nframes_t region_end = (jack_nframes_t) (clicked_regionview->region.last_frame() / speed); + jack_nframes_t region_length = (jack_nframes_t) (clicked_regionview->region.length() / speed); + + //drag_info.item = clicked_regionview->get_name_highlight(); + drag_info.item = item; + drag_info.motion_callback = &Editor::trim_motion_callback; + drag_info.finished_callback = &Editor::trim_finished_callback; + + start_grab (event, trimmer_cursor); + + if (Keyboard::modifier_state_equals (event->button.state, Keyboard::Control)) { + trim_op = ContentsTrim; + } else { + /* These will get overridden for a point trim.*/ + if (drag_info.current_pointer_frame < (region_start + region_length/2)) { + /* closer to start */ + trim_op = StartTrim; + } else if (drag_info.current_pointer_frame > (region_end - region_length/2)) { + /* closer to end */ + trim_op = EndTrim; + } + } + + switch (trim_op) { + case StartTrim: + show_verbose_time_cursor(region_start, 10); + break; + case EndTrim: + show_verbose_time_cursor(region_end, 10); + break; + case ContentsTrim: + show_verbose_time_cursor(drag_info.current_pointer_frame, 10); + break; + } + + flush_track_canvas (); +} + +void +Editor::trim_motion_callback (GtkCanvasItem* item, GdkEvent* event) +{ + AudioRegionView* rv = clicked_regionview; + jack_nframes_t frame_delta = 0; + bool left_direction; + bool obey_snap = !Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier()); + + /* snap modifier works differently here.. + its' current state has to be passed to the + various trim functions in order to work properly + */ + + double speed = 1.0; + TimeAxisView* tvp = clicked_trackview; + AudioTimeAxisView* tv = dynamic_cast<AudioTimeAxisView*>(tvp); + + if (tv && tv->is_audio_track()) { + speed = tv->get_diskstream()->speed(); + } + + if (drag_info.last_pointer_frame > drag_info.current_pointer_frame) { + left_direction = true; + } else { + left_direction = false; + } + + if (obey_snap) { + snap_to (drag_info.current_pointer_frame); + } + + if (drag_info.current_pointer_frame == drag_info.last_pointer_frame) { + return; + } + + if (drag_info.first_move) { + + string trim_type; + + switch (trim_op) { + case StartTrim: + trim_type = "Region start trim"; + break; + case EndTrim: + trim_type = "Region end trim"; + break; + case ContentsTrim: + trim_type = "Region content trim"; + break; + } + + begin_reversible_command (trim_type); + + for (list<AudioRegionView*>::const_iterator i = selection->audio_regions.by_layer().begin(); i != selection->audio_regions.by_layer().end(); ++i) { + (*i)->region.freeze (); + (*i)->temporarily_hide_envelope (); + session->add_undo ((*i)->region.playlist()->get_memento()); + } + } + + if (left_direction) { + frame_delta = (drag_info.last_pointer_frame - drag_info.current_pointer_frame); + } else { + frame_delta = (drag_info.current_pointer_frame - drag_info.last_pointer_frame); + } + + switch (trim_op) { + case StartTrim: + if ((left_direction == false) && (drag_info.current_pointer_frame <= rv->region.first_frame()/speed)) { + break; + } else { + for (list<AudioRegionView*>::const_iterator i = selection->audio_regions.by_layer().begin(); i != selection->audio_regions.by_layer().end(); ++i) { + single_start_trim (**i, frame_delta, left_direction, obey_snap); + } + break; + } + + case EndTrim: + if ((left_direction == true) && (drag_info.current_pointer_frame > (jack_nframes_t) (rv->region.last_frame()/speed))) { + break; + } else { + for (list<AudioRegionView*>::const_iterator i = selection->audio_regions.by_layer().begin(); i != selection->audio_regions.by_layer().end(); ++i) { + single_end_trim (**i, frame_delta, left_direction, obey_snap); + } + break; + } + + case ContentsTrim: + { + bool swap_direction = false; + + if (Keyboard::modifier_state_equals (event->button.state, Keyboard::Control)) { + swap_direction = true; + } + + for (list<AudioRegionView*>::const_iterator i = selection->audio_regions.by_layer().begin(); + i != selection->audio_regions.by_layer().end(); ++i) + { + single_contents_trim (**i, frame_delta, left_direction, swap_direction, obey_snap); + } + } + break; + } + + switch (trim_op) { + case StartTrim: + show_verbose_time_cursor((jack_nframes_t) (rv->region.position()/speed), 10); + break; + case EndTrim: + show_verbose_time_cursor((jack_nframes_t) (rv->region.last_frame()/speed), 10); + break; + case ContentsTrim: + show_verbose_time_cursor(drag_info.current_pointer_frame, 10); + break; + } + + drag_info.last_pointer_frame = drag_info.current_pointer_frame; + drag_info.first_move = false; +} + +void +Editor::single_contents_trim (AudioRegionView& rv, jack_nframes_t frame_delta, bool left_direction, bool swap_direction, bool obey_snap) +{ + Region& region (rv.region); + + if (region.locked()) { + return; + } + + jack_nframes_t new_bound; + + double speed = 1.0; + TimeAxisView* tvp = clicked_trackview; + AudioTimeAxisView* tv = dynamic_cast<AudioTimeAxisView*>(tvp); + + if (tv && tv->is_audio_track()) { + speed = tv->get_diskstream()->speed(); + } + + if (left_direction) { + if (swap_direction) { + new_bound = (jack_nframes_t) (region.position()/speed) + frame_delta; + } else { + new_bound = (jack_nframes_t) (region.position()/speed) - frame_delta; + } + } else { + if (swap_direction) { + new_bound = (jack_nframes_t) (region.position()/speed) - frame_delta; + } else { + new_bound = (jack_nframes_t) (region.position()/speed) + frame_delta; + } + } + + if (obey_snap) { + snap_to (new_bound); + } + region.trim_start ((jack_nframes_t) (new_bound * speed), this); + rv.region_changed (StartChanged); +} + +void +Editor::single_start_trim (AudioRegionView& rv, jack_nframes_t frame_delta, bool left_direction, bool obey_snap) +{ + Region& region (rv.region); + + if (region.locked()) { + return; + } + + jack_nframes_t new_bound; + + double speed = 1.0; + TimeAxisView* tvp = clicked_trackview; + AudioTimeAxisView* tv = dynamic_cast<AudioTimeAxisView*>(tvp); + + if (tv && tv->is_audio_track()) { + speed = tv->get_diskstream()->speed(); + } + + if (left_direction) { + new_bound = (jack_nframes_t) (region.position()/speed) - frame_delta; + } else { + new_bound = (jack_nframes_t) (region.position()/speed) + frame_delta; + } + + if (obey_snap) { + snap_to (new_bound, (left_direction ? 0 : 1)); + } + + region.trim_front ((jack_nframes_t) (new_bound * speed), this); + + rv.region_changed (Change (LengthChanged|PositionChanged|StartChanged)); +} + +void +Editor::single_end_trim (AudioRegionView& rv, jack_nframes_t frame_delta, bool left_direction, bool obey_snap) +{ + Region& region (rv.region); + + if (region.locked()) { + return; + } + + jack_nframes_t new_bound; + + double speed = 1.0; + TimeAxisView* tvp = clicked_trackview; + AudioTimeAxisView* tv = dynamic_cast<AudioTimeAxisView*>(tvp); + + if (tv && tv->is_audio_track()) { + speed = tv->get_diskstream()->speed(); + } + + if (left_direction) { + new_bound = (jack_nframes_t) ((region.last_frame() + 1)/speed) - frame_delta; + } else { + new_bound = (jack_nframes_t) ((region.last_frame() + 1)/speed) + frame_delta; + } + + if (obey_snap) { + snap_to (new_bound); + } + region.trim_end ((jack_nframes_t) (new_bound * speed), this); + rv.region_changed (LengthChanged); +} + +void +Editor::trim_finished_callback (GtkCanvasItem* item, GdkEvent* event) +{ + if (!drag_info.first_move) { + trim_motion_callback (item, event); + + if (!clicked_regionview->get_selected()) { + thaw_region_after_trim (*clicked_regionview); + } else { + + for (list<AudioRegionView*>::const_iterator i = selection->audio_regions.by_layer().begin(); + i != selection->audio_regions.by_layer().end(); ++i) + { + thaw_region_after_trim (**i); + } + } + commit_reversible_command(); + } else { + /* no mouse movement */ + point_trim (event); + } + + flush_track_canvas (); +} + +void +Editor::point_trim (GdkEvent* event) +{ + AudioRegionView* rv = clicked_regionview; + jack_nframes_t new_bound = drag_info.current_pointer_frame; + + if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) { + snap_to (new_bound); + } + + /* Choose action dependant on which button was pressed */ + switch (event->button.button) { + case 1: + trim_op = StartTrim; + begin_reversible_command (_("Start point trim")); + + if (rv->get_selected()) { + + for (list<AudioRegionView*>::const_iterator i = selection->audio_regions.by_layer().begin(); + i != selection->audio_regions.by_layer().end(); ++i) + { + if (!(*i)->region.locked()) { + session->add_undo ((*i)->region.playlist()->get_memento()); + (*i)->region.trim_front (new_bound, this); + session->add_redo_no_execute ((*i)->region.playlist()->get_memento()); + } + } + + } else { + + if (!rv->region.locked()) { + session->add_undo (rv->region.playlist()->get_memento()); + rv->region.trim_front (new_bound, this); + session->add_redo_no_execute (rv->region.playlist()->get_memento()); + } + } + + commit_reversible_command(); + + break; + case 2: + trim_op = EndTrim; + begin_reversible_command (_("End point trim")); + + if (rv->get_selected()) { + + for (list<AudioRegionView*>::const_iterator i = selection->audio_regions.by_layer().begin(); i != selection->audio_regions.by_layer().end(); ++i) + { + if (!(*i)->region.locked()) { + session->add_undo ((*i)->region.playlist()->get_memento()); + (*i)->region.trim_end (new_bound, this); + session->add_redo_no_execute ((*i)->region.playlist()->get_memento()); + } + } + + } else { + + if (!rv->region.locked()) { + session->add_undo (rv->region.playlist()->get_memento()); + rv->region.trim_end (new_bound, this); + session->add_redo_no_execute (rv->region.playlist()->get_memento()); + } + } + + commit_reversible_command(); + + break; + default: + break; + } +} + +void +Editor::thaw_region_after_trim (AudioRegionView& rv) +{ + Region& region (rv.region); + + if (region.locked()) { + return; + } + + region.thaw (_("trimmed region")); + session->add_redo_no_execute (region.playlist()->get_memento()); + + rv.unhide_envelope (); +} + +void +Editor::hide_marker (GtkCanvasItem* item, GdkEvent* event) +{ + Marker* marker; + bool is_start; + + if ((marker = static_cast<Marker *> (gtk_object_get_data (GTK_OBJECT(item), "marker"))) == 0) { + fatal << _("programming error: marker canvas item has no marker object pointer!") << endmsg; + /*NOTREACHED*/ + } + + Location* location = find_location_from_marker (marker, is_start); + location->set_hidden (true, this); +} + + +void +Editor::start_range_markerbar_op (GtkCanvasItem* item, GdkEvent* event, RangeMarkerOp op) +{ + + if (session == 0) { + return; + } + + drag_info.item = item; + drag_info.motion_callback = &Editor::drag_range_markerbar_op; + drag_info.finished_callback = &Editor::end_range_markerbar_op; + + range_marker_op = op; + + if (!temp_location) { + temp_location = new Location; + } + + switch (op) { + case CreateRangeMarker: + case CreateTransportMarker: + + if (Keyboard::modifier_state_equals (event->button.state, Keyboard::Shift)) { + drag_info.copy = true; + } else { + drag_info.copy = false; + } + start_grab (event, selector_cursor); + break; + } + + show_verbose_time_cursor(drag_info.current_pointer_frame, 10); + +} + +void +Editor::drag_range_markerbar_op (GtkCanvasItem* item, GdkEvent* event) +{ + jack_nframes_t start = 0; + jack_nframes_t end = 0; + + GtkCanvasItem * crect = (range_marker_op == CreateRangeMarker) ? range_bar_drag_rect: transport_bar_drag_rect; + + if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) { + snap_to (drag_info.current_pointer_frame); + } + + /* only alter selection if the current frame is + different from the last frame position. + */ + + if (drag_info.current_pointer_frame == drag_info.last_pointer_frame) return; + + switch (range_marker_op) { + case CreateRangeMarker: + case CreateTransportMarker: + if (drag_info.first_move) { + snap_to (drag_info.grab_frame); + } + + if (drag_info.current_pointer_frame < drag_info.grab_frame) { + start = drag_info.current_pointer_frame; + end = drag_info.grab_frame; + } else { + end = drag_info.current_pointer_frame; + start = drag_info.grab_frame; + } + + /* first drag: Either add to the selection + or create a new selection-> + */ + + if (drag_info.first_move) { + + temp_location->set (start, end); + + gtk_canvas_item_show (crect); + + update_marker_drag_item (temp_location); + gtk_canvas_item_show (range_marker_drag_rect); + gtk_canvas_item_raise_to_top (range_marker_drag_rect); + + } + break; + } + + + if (event->button.x >= track_canvas_scroller.get_hadjustment()->get_value() + canvas_width) { + start_canvas_autoscroll (1); + } + + if (start != end) { + temp_location->set (start, end); + + double x1 = frame_to_pixel (start); + double x2 = frame_to_pixel (end); + gtk_canvas_item_set (crect, "x1", x1, "x2", x2, NULL); + + update_marker_drag_item (temp_location); + } + + drag_info.last_pointer_frame = drag_info.current_pointer_frame; + drag_info.first_move = false; + + show_verbose_time_cursor(drag_info.current_pointer_frame, 10); + +} + +void +Editor::end_range_markerbar_op (GtkCanvasItem* item, GdkEvent* event) +{ + Location * newloc = 0; + + if (!drag_info.first_move) { + drag_range_markerbar_op (item, event); + + switch (range_marker_op) + { + case CreateRangeMarker: + begin_reversible_command (_("new range marker")); + session->add_undo (session->locations()->get_memento()); + newloc = new Location(temp_location->start(), temp_location->end(), "unnamed"); + session->locations()->add (newloc, true); + session->add_redo_no_execute (session->locations()->get_memento()); + commit_reversible_command (); + + gtk_canvas_item_hide (range_bar_drag_rect); + gtk_canvas_item_hide (range_marker_drag_rect); + break; + + case CreateTransportMarker: + // popup menu to pick loop or punch + new_transport_marker_context_menu (&event->button, item); + + break; + } + } else { + /* just a click, no pointer movement.*/ + + if (Keyboard::no_modifier_keys_pressed (&event->button)) { + + // nothing yet + + } + } + + stop_canvas_autoscroll (); +} + + + +void +Editor::start_mouse_zoom (GtkCanvasItem* item, GdkEvent* event) +{ + drag_info.item = item; + drag_info.motion_callback = &Editor::drag_mouse_zoom; + drag_info.finished_callback = &Editor::end_mouse_zoom; + + start_grab (event, zoom_cursor); + + show_verbose_time_cursor (drag_info.current_pointer_frame, 10); +} + +void +Editor::drag_mouse_zoom (GtkCanvasItem* item, GdkEvent* event) +{ + jack_nframes_t start; + jack_nframes_t end; + + if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) { + snap_to (drag_info.current_pointer_frame); + + if (drag_info.first_move) { + snap_to (drag_info.grab_frame); + } + } + + if (drag_info.current_pointer_frame == drag_info.last_pointer_frame) return; + + /* base start and end on initial click position */ + if (drag_info.current_pointer_frame < drag_info.grab_frame) { + start = drag_info.current_pointer_frame; + end = drag_info.grab_frame; + } else { + end = drag_info.current_pointer_frame; + start = drag_info.grab_frame; + } + + if (start != end) { + + if (drag_info.first_move) { + gtk_canvas_item_show (zoom_rect); + gtk_canvas_item_raise_to_top (zoom_rect); + } + + reposition_zoom_rect(start, end); + + drag_info.last_pointer_frame = drag_info.current_pointer_frame; + drag_info.first_move = false; + + show_verbose_time_cursor (drag_info.current_pointer_frame, 10); + } +} + +void +Editor::end_mouse_zoom (GtkCanvasItem* item, GdkEvent* event) +{ + if (!drag_info.first_move) { + drag_mouse_zoom (item, event); + + if (drag_info.grab_frame < drag_info.last_pointer_frame) { + temporal_zoom_by_frame (drag_info.grab_frame, drag_info.last_pointer_frame, "mouse zoom"); + } else { + temporal_zoom_by_frame (drag_info.last_pointer_frame, drag_info.grab_frame, "mouse zoom"); + } + } else { + temporal_zoom_to_frame (false, drag_info.grab_frame); + /* + temporal_zoom_step (false); + center_screen (drag_info.grab_frame); + */ + } + + gtk_canvas_item_hide (zoom_rect); +} + +void +Editor::reposition_zoom_rect (jack_nframes_t start, jack_nframes_t end) +{ + double x1 = frame_to_pixel (start); + double x2 = frame_to_pixel (end); + double y2 = canvas_height - 2; + + gtk_object_set (GTK_OBJECT(zoom_rect), + "x1", x1, + "y1", 1.0, + "x2", x2, + "y2", y2, + NULL); +} + +void +Editor::start_rubberband_select (GtkCanvasItem* item, GdkEvent* event) +{ + drag_info.item = item; + drag_info.motion_callback = &Editor::drag_rubberband_select; + drag_info.finished_callback = &Editor::end_rubberband_select; + + start_grab (event, cross_hair_cursor); + + show_verbose_time_cursor (drag_info.current_pointer_frame, 10); +} + +void +Editor::drag_rubberband_select (GtkCanvasItem* item, GdkEvent* event) +{ + jack_nframes_t start; + jack_nframes_t end; + double y1; + double y2; + + /* use a bigger drag threshold than the default */ + + if (abs ((int) (drag_info.current_pointer_frame - drag_info.grab_frame)) < 8) { + return; + } + +// if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) { +// snap_to (drag_info.current_pointer_frame); + +// if (drag_info.first_move) { +// snap_to (drag_info.grab_frame); +// } +// } + + + /* base start and end on initial click position */ + if (drag_info.current_pointer_frame < drag_info.grab_frame) { + start = drag_info.current_pointer_frame; + end = drag_info.grab_frame; + } else { + end = drag_info.current_pointer_frame; + start = drag_info.grab_frame; + } + + if (drag_info.current_pointer_y < drag_info.grab_y) { + y1 = drag_info.current_pointer_y; + y2 = drag_info.grab_y; + } + else { + y2 = drag_info.current_pointer_y; + y1 = drag_info.grab_y; + } + + + if (start != end || y1 != y2) { + + double x1 = frame_to_pixel (start); + double x2 = frame_to_pixel (end); + + gtk_object_set (GTK_OBJECT(rubberband_rect), + "x1", x1, + "y1", y1, + "x2", x2, + "y2", y2, + NULL); + + gtk_canvas_item_show (rubberband_rect); + gtk_canvas_item_raise_to_top (rubberband_rect); + + drag_info.last_pointer_frame = drag_info.current_pointer_frame; + drag_info.first_move = false; + + show_verbose_time_cursor (drag_info.current_pointer_frame, 10); + } +} + +void +Editor::end_rubberband_select (GtkCanvasItem* item, GdkEvent* event) +{ + if (!drag_info.first_move) { + + drag_rubberband_select (item, event); + + double y1,y2; + if (drag_info.current_pointer_y < drag_info.grab_y) { + y1 = drag_info.current_pointer_y; + y2 = drag_info.grab_y; + } + else { + y2 = drag_info.current_pointer_y; + y1 = drag_info.grab_y; + } + + + bool add = Keyboard::modifier_state_contains (event->button.state, Keyboard::Shift); + bool commit; + + begin_reversible_command (_("select regions")); + + if (drag_info.grab_frame < drag_info.last_pointer_frame) { + commit = select_all_within (drag_info.grab_frame, drag_info.last_pointer_frame, y1, y2, add); + } else { + commit = select_all_within (drag_info.last_pointer_frame, drag_info.grab_frame, y1, y2, add); + } + + if (commit) { + commit_reversible_command (); + } + + } else { + selection->clear_audio_regions(); + selection->clear_points (); + selection->clear_lines (); + } + + gtk_canvas_item_hide (rubberband_rect); +} + + +gint +Editor::mouse_rename_region (GtkCanvasItem* item, GdkEvent* event) +{ + using namespace Gtkmmext; + + ArdourPrompter prompter (false); + + prompter.set_prompt (_("Name for region:")); + prompter.set_initial_text (clicked_regionview->region.name()); + prompter.show_all (); + prompter.done.connect (Main::quit.slot()); + + Main::run (); + + if (prompter.status == Prompter::cancelled) { + return TRUE; + } + + string str; + + prompter.get_result(str); + clicked_regionview->region.set_name (str); + + return TRUE; +} + +void +Editor::start_time_fx (GtkCanvasItem* item, GdkEvent* event) +{ + drag_info.item = item; + drag_info.motion_callback = &Editor::time_fx_motion; + drag_info.finished_callback = &Editor::end_time_fx; + + start_grab (event); + + show_verbose_time_cursor (drag_info.current_pointer_frame, 10); +} + +void +Editor::time_fx_motion (GtkCanvasItem *item, GdkEvent* event) +{ + AudioRegionView* rv = clicked_regionview; + + if (!Keyboard::modifier_state_contains (event->button.state, Keyboard::snap_modifier())) { + snap_to (drag_info.current_pointer_frame); + } + + if (drag_info.current_pointer_frame == drag_info.last_pointer_frame) { + return; + } + + if (drag_info.current_pointer_frame > rv->region.position()) { + rv->get_time_axis_view().show_timestretch (rv->region.position(), drag_info.current_pointer_frame); + } + + drag_info.last_pointer_frame = drag_info.current_pointer_frame; + drag_info.first_move = false; + + show_verbose_time_cursor (drag_info.current_pointer_frame, 10); +} + +void +Editor::end_time_fx (GtkCanvasItem* item, GdkEvent* event) +{ + clicked_regionview->get_time_axis_view().hide_timestretch (); + + if (drag_info.first_move) { + return; + } + + jack_nframes_t newlen = drag_info.last_pointer_frame - clicked_regionview->region.position(); + float percentage = (float) ((double) newlen - (double) clicked_regionview->region.length()) / ((double) newlen) * 100.0f; + + begin_reversible_command (_("timestretch")); + + if (run_timestretch (selection->audio_regions, percentage) == 0) { + session->commit_reversible_command (); + } +} + +void +Editor::mouse_brush_insert_region (AudioRegionView* rv, jack_nframes_t pos) +{ + /* no brushing without a useful snap setting */ + + switch (snap_mode) { + case SnapMagnetic: + return; /* can't work because it allows region to be placed anywhere */ + default: + break; /* OK */ + } + + switch (snap_type) { + case SnapToFrame: + case SnapToMark: + case SnapToEditCursor: + return; + + default: + break; + } + + /* don't brush a copy over the original */ + + if (pos == rv->region.position()) { + return; + } + + AudioTimeAxisView* atv = dynamic_cast<AudioTimeAxisView*>(&rv->get_time_axis_view()); + + if (atv == 0 || !atv->is_audio_track()) { + return; + } + + Playlist* playlist = atv->playlist(); + double speed = atv->get_diskstream()->speed(); + + session->add_undo (playlist->get_memento()); + playlist->add_region (*(new AudioRegion (rv->region)), (jack_nframes_t) (pos * speed)); + session->add_redo_no_execute (playlist->get_memento()); + + // playlist is frozen, so we have to update manually + + playlist->StateChanged (Change (~0)); /* EMIT SIGNAL */ +} + +gint +Editor::track_height_step_timeout () +{ + struct timeval now; + struct timeval delta; + + gettimeofday (&now, 0); + timersub (&now, &last_track_height_step_timestamp, &delta); + + if (delta.tv_sec * 1000000 + delta.tv_usec > 250000) { /* milliseconds */ + current_stepping_trackview = 0; + return FALSE; + } + return TRUE; +} |