summaryrefslogtreecommitdiff
path: root/gtk2_ardour/editor_mouse.cc
diff options
context:
space:
mode:
authorTaybin Rutkin <taybin@taybin.com>2005-09-25 18:42:24 +0000
committerTaybin Rutkin <taybin@taybin.com>2005-09-25 18:42:24 +0000
commit209d967b1bb80a9735d690d8f4f0455ecb9970ca (patch)
tree9d76ddcd7c1ac9d91bb2b1a33d31b66ce4ded5de /gtk2_ardour/editor_mouse.cc
parente4b9aed743fc765219ac775905a221c017c88fba (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.cc4584
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;
+}