/*
    Copyright (C) 2009 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.

*/

#define __STDC_LIMIT_MACROS 1
#include <stdint.h>
#include "pbd/memento_command.h"
#include "pbd/basename.h"
#include "pbd/stateful_diff_command.h"
#include "ardour/session.h"
#include "ardour/dB.h"
#include "ardour/region_factory.h"
#include "editor.h"
#include "i18n.h"
#include "keyboard.h"
#include "audio_region_view.h"
#include "midi_region_view.h"
#include "ardour_ui.h"
#include "gui_thread.h"
#include "control_point.h"
#include "utils.h"
#include "region_gain_line.h"
#include "editor_drag.h"
#include "audio_time_axis.h"
#include "midi_time_axis.h"
#include "canvas-note.h"
#include "selection.h"
#include "midi_selection.h"
#include "automation_time_axis.h"

using namespace std;
using namespace ARDOUR;
using namespace PBD;
using namespace Gtk;
using namespace Editing;
using namespace ArdourCanvas;

using Gtkmm2ext::Keyboard;

double const ControlPointDrag::_zero_gain_fraction = gain_to_slider_position (dB_to_coefficient (0.0));

DragManager::DragManager (Editor* e)
	: _editor (e)
	, _ending (false)
	, _current_pointer_frame (0)
{

}

DragManager::~DragManager ()
{
	abort ();
}

/** Call abort for each active drag */
void
DragManager::abort ()
{
	_ending = true;
	
	for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
		(*i)->abort ();
		delete *i;
	}

	_drags.clear ();

	_ending = false;
}

void
DragManager::add (Drag* d)
{
	d->set_manager (this);
	_drags.push_back (d);
}

void
DragManager::set (Drag* d, GdkEvent* e, Gdk::Cursor* c)
{
	assert (_drags.empty ());
	d->set_manager (this);
	_drags.push_back (d);
	start_grab (e);
}

void
DragManager::start_grab (GdkEvent* e)
{
	_current_pointer_frame = _editor->event_frame (e, &_current_pointer_x, &_current_pointer_y);
	
	for (list<Drag*>::const_iterator i = _drags.begin(); i != _drags.end(); ++i) {
		(*i)->start_grab (e);
	}
}

/** Call end_grab for each active drag.
 *  @return true if any drag reported movement having occurred.
 */
bool
DragManager::end_grab (GdkEvent* e)
{
	_ending = true;
	
	bool r = false;
	for (list<Drag*>::iterator i = _drags.begin(); i != _drags.end(); ++i) {
		bool const t = (*i)->end_grab (e);
		if (t) {
			r = true;
		}
		delete *i;
	}

	_drags.clear ();

	_ending = false;
	
	return r;
}

bool
DragManager::motion_handler (GdkEvent* e, bool from_autoscroll)
{
	bool r = false;

	_current_pointer_frame = _editor->event_frame (e, &_current_pointer_x, &_current_pointer_y);
	
	for (list<Drag*>::iterator i = _drags.begin(); i != _drags.end(); ++i) {
		bool const t = (*i)->motion_handler (e, from_autoscroll);
		if (t) {
			r = true;
		}
		
	}

	return r;
}

bool
DragManager::have_item (ArdourCanvas::Item* i) const
{
	list<Drag*>::const_iterator j = _drags.begin ();
	while (j != _drags.end() && (*j)->item () != i) {
		++j;
	}

	return j != _drags.end ();
}

Drag::Drag (Editor* e, ArdourCanvas::Item* i) 
	: _editor (e)
	, _item (i)
	, _pointer_frame_offset (0)
	, _move_threshold_passed (false)
	, _grab_frame (0)
	, _last_pointer_frame (0)
{

}

void
Drag::swap_grab (ArdourCanvas::Item* new_item, Gdk::Cursor* cursor, uint32_t time)
{
	_item->ungrab (0);
	_item = new_item;

	if (cursor == 0) {
		cursor = _editor->which_grabber_cursor ();
	}

	_item->grab (Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK, *cursor, time);
}

void
Drag::start_grab (GdkEvent* event, Gdk::Cursor *cursor)
{
	if (cursor == 0) {
		cursor = _editor->which_grabber_cursor ();
	}

	// if dragging with button2, the motion is x constrained, with Alt-button2 it is y constrained

	if (Keyboard::is_button2_event (&event->button)) {
		if (Keyboard::modifier_state_equals (event->button.state, Keyboard::SecondaryModifier)) {
			_y_constrained = true;
			_x_constrained = false;
		} else {
			_y_constrained = false;
			_x_constrained = true;
		}
	} else {
		_x_constrained = false;
		_y_constrained = false;
	}

	_grab_frame = _editor->event_frame (event, &_grab_x, &_grab_y);
	_grab_frame = adjusted_frame (_grab_frame, event);
	_last_pointer_frame = _grab_frame;
	_last_pointer_x = _grab_x;
	_last_pointer_y = _grab_y;

	_item->grab (Gdk::POINTER_MOTION_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK,
                     *cursor,
                     event->button.time);

	if (_editor->session() && _editor->session()->transport_rolling()) {
		_was_rolling = true;
	} else {
		_was_rolling = false;
	}

	switch (_editor->snap_type()) {
	case SnapToRegionStart:
	case SnapToRegionEnd:
	case SnapToRegionSync:
	case SnapToRegionBoundary:
		_editor->build_region_boundary_cache ();
		break;
	default:
		break;
	}
}

/** Call to end a drag `successfully'.  Ungrabs item and calls
 *  subclass' finished() method.
 *
 *  @param event GDK event, or 0.
 *  @return true if some movement occurred, otherwise false.
 */
bool
Drag::end_grab (GdkEvent* event)
{
	_editor->stop_canvas_autoscroll ();

	_item->ungrab (event ? event->button.time : 0);

	finished (event, _move_threshold_passed);

	_editor->hide_verbose_canvas_cursor();

	return _move_threshold_passed;
}

nframes64_t
Drag::adjusted_frame (nframes64_t f, GdkEvent const * event, bool snap) const
{
	nframes64_t pos = 0;

	if (f > _pointer_frame_offset) {
		pos = f - _pointer_frame_offset;
	}

	if (snap) {
		_editor->snap_to_with_modifier (pos, event);
	}

	return pos;
}

nframes64_t
Drag::adjusted_current_frame (GdkEvent const * event, bool snap) const
{
	return adjusted_frame (_drags->current_pointer_frame (), event, snap);
}

bool
Drag::motion_handler (GdkEvent* event, bool from_autoscroll)
{
	/* check to see if we have moved in any way that matters since the last motion event */
	if ( (!x_movement_matters() || _last_pointer_frame == adjusted_current_frame (event)) &&
	     (!y_movement_matters() || _last_pointer_y == _drags->current_pointer_y ()) ) {
		return false;
	}

	pair<nframes64_t, int> const threshold = move_threshold ();

	bool const old_move_threshold_passed = _move_threshold_passed;

	if (!from_autoscroll && !_move_threshold_passed) {

		bool const xp = (::llabs (adjusted_current_frame (event) - _grab_frame) >= threshold.first);
		bool const yp = (::fabs ((_drags->current_pointer_y () - _grab_y)) >= threshold.second);

		_move_threshold_passed = ((xp && x_movement_matters()) || (yp && y_movement_matters()));
	}

	if (active (_editor->mouse_mode) && _move_threshold_passed) {

		if (event->motion.state & Gdk::BUTTON1_MASK || event->motion.state & Gdk::BUTTON2_MASK) {
			if (!from_autoscroll) {
				_editor->maybe_autoscroll (true, allow_vertical_autoscroll ());
			}

			motion (event, _move_threshold_passed != old_move_threshold_passed);

			_last_pointer_x = _drags->current_pointer_x ();
			_last_pointer_y = _drags->current_pointer_y ();
			_last_pointer_frame = adjusted_current_frame (event);
			
			return true;
		}
	}
	return false;
}

/** Call to abort a drag.  Ungrabs item and calls subclass's aborted () */
void
Drag::abort ()
{
	if (_item) {
		_item->ungrab (0);
	}

	aborted ();

	_editor->stop_canvas_autoscroll ();
	_editor->hide_verbose_canvas_cursor ();
}

RegionDrag::RegionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
	: Drag (e, i),
	  _primary (p)
{
	for (list<RegionView*>::const_iterator i = v.begin(); i != v.end(); ++i) {
		_views.push_back (DraggingView (*i));
	}
	
	RegionView::RegionViewGoingAway.connect (death_connection, invalidator (*this), ui_bind (&RegionDrag::region_going_away, this, _1), gui_context());
}

void
RegionDrag::region_going_away (RegionView* v)
{
	list<DraggingView>::iterator i = _views.begin ();
	while (i != _views.end() && i->view != v) {
		++i;
	}

	if (i != _views.end()) {
		_views.erase (i);
	}
}

RegionMotionDrag::RegionMotionDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b)
	: RegionDrag (e, i, p, v),
	  _dest_trackview (0),
	  _dest_layer (0),
	  _brushing (b),
	  _total_x_delta (0)
{

}


void
RegionMotionDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
{
	Drag::start_grab (event);

	_editor->show_verbose_time_cursor (_last_frame_position, 10);
}

RegionMotionDrag::TimeAxisViewSummary
RegionMotionDrag::get_time_axis_view_summary ()
{
	int32_t children = 0;
	TimeAxisViewSummary sum;

	_editor->visible_order_range (&sum.visible_y_low, &sum.visible_y_high);

	/* get a bitmask representing the visible tracks */

	for (TrackViewList::iterator i = _editor->track_views.begin(); i != _editor->track_views.end(); ++i) {
		RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
		TimeAxisView::Children children_list;

		/* zeroes are audio/MIDI tracks. ones are other types. */

		if (!rtv->hidden()) {

			if (!rtv->is_track()) {
				/* not an audio nor MIDI track */
				sum.tracks = sum.tracks |= (0x01 << rtv->order());
			}

			sum.height_list[rtv->order()] = (*i)->current_height();
			children = 1;

			if ((children_list = rtv->get_child_list()).size() > 0) {
				for (TimeAxisView::Children::iterator j = children_list.begin(); j != children_list.end(); ++j) {
					sum.tracks = sum.tracks |= (0x01 << (rtv->order() + children));
					sum.height_list[rtv->order() + children] = (*j)->current_height();
					children++;
				}
			}
		}
	}

	return sum;
}

bool
RegionMotionDrag::compute_y_delta (
	TimeAxisView const * last_pointer_view, TimeAxisView* current_pointer_view,
	int32_t last_pointer_layer, int32_t current_pointer_layer,
	TimeAxisViewSummary const & tavs,
	int32_t* pointer_order_span, int32_t* pointer_layer_span,
	int32_t* canvas_pointer_order_span
	)
{
	if (_brushing) {
		*pointer_order_span = 0;
		*pointer_layer_span = 0;
		return true;
	}

	bool clamp_y_axis = false;

	/* the change in track order between this callback and the last */
	*pointer_order_span = last_pointer_view->order() - current_pointer_view->order();
	/* the change in layer between this callback and the last;
	   only meaningful if pointer_order_span == 0 (ie we've not moved tracks) */
	*pointer_layer_span = last_pointer_layer - current_pointer_layer;

	if (*pointer_order_span != 0) {

		/* find the actual pointer span, in terms of the number of visible tracks;
		   to do this, we reduce |pointer_order_span| by the number of hidden tracks
		   over the span */

		*canvas_pointer_order_span = *pointer_order_span;
		if (last_pointer_view->order() >= current_pointer_view->order()) {
			for (int32_t y = current_pointer_view->order(); y < last_pointer_view->order(); y++) {
				if (tavs.height_list[y] == 0) {
					*canvas_pointer_order_span--;
				}
			}
		} else {
			for (int32_t y = last_pointer_view->order(); y <= current_pointer_view->order(); y++) {
				if (tavs.height_list[y] == 0) {
					*canvas_pointer_order_span++;
				}
			}
		}

		for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {

			RegionView* rv = i->view;

			if (rv->region()->locked()) {
				continue;
			}

			double ix1, ix2, iy1, iy2;
			rv->get_canvas_frame()->get_bounds (ix1, iy1, ix2, iy2);
			rv->get_canvas_frame()->i2w (ix1, iy1);
			iy1 += _editor->vertical_adjustment.get_value() - _editor->canvas_timebars_vsize;

			/* get the new trackview for this particular region */
			pair<TimeAxisView*, int> const tvp = _editor->trackview_by_y_position (iy1);
			assert (tvp.first);
			RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tvp.first);

			/* XXX: not sure that we should be passing canvas_pointer_order_span in here,
			   as surely this is a per-region thing... */

			clamp_y_axis = y_movement_disallowed (
				rtv->order(), last_pointer_view->order(), *canvas_pointer_order_span, tavs
				);

			if (clamp_y_axis) {
				break;
			}
		}

	} else if (_dest_trackview == current_pointer_view) {

		if (current_pointer_layer == last_pointer_layer) {
			/* No movement; clamp */
			clamp_y_axis = true;
		}
	}

	if (!clamp_y_axis) {
		_dest_trackview = current_pointer_view;
		_dest_layer = current_pointer_layer;
	}

	return clamp_y_axis;
}


double
RegionMotionDrag::compute_x_delta (GdkEvent const * event, nframes64_t* pending_region_position)
{
	/* compute the amount of pointer motion in frames, and where
	   the region would be if we moved it by that much.
	*/
	*pending_region_position = adjusted_current_frame (event);
	
	nframes64_t sync_frame;
	nframes64_t sync_offset;
	int32_t sync_dir;
	
	sync_offset = _primary->region()->sync_offset (sync_dir);
	
	/* we don't handle a sync point that lies before zero.
	 */
	if (sync_dir >= 0 || (sync_dir < 0 && *pending_region_position >= sync_offset)) {
		
		sync_frame = *pending_region_position + (sync_dir*sync_offset);
		
		_editor->snap_to_with_modifier (sync_frame, event);
		
		*pending_region_position = _primary->region()->adjust_to_sync (sync_frame);
		
	} else {
		*pending_region_position = _last_frame_position;
	}

	if (*pending_region_position > max_frames - _primary->region()->length()) {
		*pending_region_position = _last_frame_position;
	}

	double x_delta = 0;

	if ((*pending_region_position != _last_frame_position) && x_move_allowed ()) {

		/* now compute the canvas unit distance we need to move the regionview
		   to make it appear at the new location.
		*/

		x_delta = (static_cast<double> (*pending_region_position) - _last_frame_position) / _editor->frames_per_unit;

		if (*pending_region_position <= _last_frame_position) {

			for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {

				RegionView* rv = i->view;

				// If any regionview is at zero, we need to know so we can stop further leftward motion.

				double ix1, ix2, iy1, iy2;
				rv->get_canvas_frame()->get_bounds (ix1, iy1, ix2, iy2);
				rv->get_canvas_frame()->i2w (ix1, iy1);

				if (-x_delta > ix1 + _editor->horizontal_position()) {
					x_delta = 0;
					*pending_region_position = _last_frame_position;
					break;
				}
			}

		}

		_last_frame_position = *pending_region_position;
	}

	return x_delta;
}

void
RegionMotionDrag::motion (GdkEvent* event, bool first_move)
{
	double y_delta = 0;

	TimeAxisViewSummary tavs = get_time_axis_view_summary ();

	vector<int32_t>::iterator j;

	/* *pointer* variables reflect things about the pointer; as we may be moving
	   multiple regions, much detail must be computed per-region */

	/* current_pointer_view will become the TimeAxisView that we're currently pointing at, and
	   current_pointer_layer the current layer on that TimeAxisView; in this code layer numbers
	   are with respect to how the view's layers are displayed; if we are in Overlaid mode, layer
	   is always 0 regardless of what the region's "real" layer is */
	RouteTimeAxisView* current_pointer_view;
	layer_t current_pointer_layer;
	if (!check_possible (&current_pointer_view, &current_pointer_layer)) {
		return;
	}

	/* TimeAxisView that we were pointing at last time we entered this method */
	TimeAxisView const * const last_pointer_view = _dest_trackview;
	/* the order of the track that we were pointing at last time we entered this method */
	int32_t const last_pointer_order = last_pointer_view->order ();
	/* the layer that we were pointing at last time we entered this method */
	layer_t const last_pointer_layer = _dest_layer;

	int32_t pointer_order_span;
	int32_t pointer_layer_span;
	int32_t canvas_pointer_order_span;

	bool const clamp_y_axis = compute_y_delta (
		last_pointer_view, current_pointer_view,
		last_pointer_layer, current_pointer_layer, tavs,
		&pointer_order_span, &pointer_layer_span,
		&canvas_pointer_order_span
		);

	nframes64_t pending_region_position;
	double const x_delta = compute_x_delta (event, &pending_region_position);

	/*************************************************************
	    PREPARE TO MOVE
	************************************************************/

	if (x_delta == 0 && pointer_order_span == 0 && pointer_layer_span == 0 && !first_move) {
		/* haven't reached next snap point, and we're not switching
		   trackviews nor layers. nothing to do.
		*/
		return;
	}

	/*************************************************************
	    MOTION
	************************************************************/

	pair<set<boost::shared_ptr<Playlist> >::iterator,bool> insert_result;

	for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {

		RegionView* rv = i->view;

		if (rv->region()->locked()) {
			continue;
		}

		/* here we are calculating the y distance from the
		   top of the first track view to the top of the region
		   area of the track view that we're working on */

		/* this x value is just a dummy value so that we have something
		   to pass to i2w () */

		double ix1 = 0;

		/* distance from the top of this track view to the region area
		   of our track view is always 1 */

		double iy1 = 1;

		/* convert to world coordinates, ie distance from the top of
		   the ruler section */

		rv->get_canvas_frame()->i2w (ix1, iy1);

		/* compensate for the ruler section and the vertical scrollbar position */
		iy1 += _editor->get_trackview_group_vertical_offset ();

		if (first_move) {

			// hide any dependent views

			rv->get_time_axis_view().hide_dependent_views (*rv);

			/*
			   reparent to a non scrolling group so that we can keep the
			   region selection above all time axis views.
			   reparenting means we have to move the rv as the two
			   parent groups have different coordinates.
			*/

			rv->get_canvas_group()->property_y() = iy1 - 1;
			rv->get_canvas_group()->reparent(*(_editor->_region_motion_group));

			rv->fake_set_opaque (true);
		}

		/* current view for this particular region */
		pair<TimeAxisView*, int> pos = _editor->trackview_by_y_position (iy1);
		RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (pos.first);

		if (pointer_order_span != 0 && !clamp_y_axis) {

			/* INTER-TRACK MOVEMENT */

			/* move through the height list to the track that the region is currently on */
			vector<int32_t>::iterator j = tavs.height_list.begin ();
			int32_t x = 0;
			while (j != tavs.height_list.end () && x != rtv->order ()) {
				++x;
				++j;
			}

			y_delta = 0;
			int32_t temp_pointer_order_span = canvas_pointer_order_span;

			if (j != tavs.height_list.end ()) {

				/* Account for layers in the original and
				   destination tracks.  If we're moving around in layers we assume
				   that only one track is involved, so it's ok to use *pointer*
				   variables here. */

				StreamView* lv = last_pointer_view->view ();
				assert (lv);

				/* move to the top of the last trackview */
				if (lv->layer_display () == Stacked) {
					y_delta -= (lv->layers() - last_pointer_layer - 1) * lv->child_height ();
				}

				StreamView* cv = current_pointer_view->view ();
				assert (cv);

				/* move to the right layer on the current trackview */
				if (cv->layer_display () == Stacked) {
					y_delta += (cv->layers() - current_pointer_layer - 1) * cv->child_height ();
				}

				/* And for being on a non-topmost layer on the new
				   track */

				while (temp_pointer_order_span > 0) {
					/* we're moving up canvas-wise,
					   so we need to find the next track height
					*/
					if (j != tavs.height_list.begin()) {
						j--;
					}

					if (x != last_pointer_order) {
						if ((*j) == 0) {
							++temp_pointer_order_span;
						}
					}

					y_delta -= (*j);
					temp_pointer_order_span--;
				}

				while (temp_pointer_order_span < 0) {

					y_delta += (*j);

					if (x != last_pointer_order) {
						if ((*j) == 0) {
							--temp_pointer_order_span;
						}
					}

					if (j != tavs.height_list.end()) {
						j++;
					}

					temp_pointer_order_span++;
				}


				/* find out where we'll be when we move and set height accordingly */

				pair<TimeAxisView*, int> const pos = _editor->trackview_by_y_position (iy1 + y_delta);
				RouteTimeAxisView const * temp_rtv = dynamic_cast<RouteTimeAxisView*> (pos.first);
				rv->set_height (temp_rtv->view()->child_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_rtv->view->get_region_color());
				//rv->set_color (const_cast<GdkColor&>(col));
			}
		}

		if (pointer_order_span == 0 && pointer_layer_span != 0 && !clamp_y_axis) {

			/* INTER-LAYER MOVEMENT in the same track */
			y_delta = rtv->view()->child_height () * pointer_layer_span;
		}


		if (_brushing) {
			_editor->mouse_brush_insert_region (rv, pending_region_position);
		} else {
			rv->move (x_delta, y_delta);
		}

	} /* foreach region */

	_total_x_delta += x_delta;
	
	if (first_move) {
		_editor->cursor_group->raise_to_top();
	}

	if (x_delta != 0 && !_brushing) {
		_editor->show_verbose_time_cursor (_last_frame_position, 10);
	}
}

void
RegionMoveDrag::motion (GdkEvent* event, bool first_move)
{
	if (_copy && first_move) {
		copy_regions (event);
	}

	RegionMotionDrag::motion (event, first_move);
}

void
RegionMoveDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
{
	vector<RegionView*> copies;
	boost::shared_ptr<Track> tr;
	boost::shared_ptr<Playlist> from_playlist;
	boost::shared_ptr<Playlist> to_playlist;
	RegionSelection new_views;
	typedef set<boost::shared_ptr<Playlist> > PlaylistSet;
	PlaylistSet modified_playlists;
	PlaylistSet frozen_playlists;
	list <sigc::connection> modified_playlist_connections;
	pair<PlaylistSet::iterator,bool> insert_result, frozen_insert_result;
	nframes64_t drag_delta;
	bool changed_tracks, changed_position;
	map<RegionView*, pair<RouteTimeAxisView*, int> > final;
	RouteTimeAxisView* source_tv;
        vector<StatefulDiffCommand*> sdc;

	if (!movement_occurred) {
		/* just a click */
		return;
	}

	if (_brushing) {
		/* all changes were made during motion event handlers */

		if (_copy) {
			for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
				copies.push_back (i->view);
			}
		}

		goto out;
	}

	/* reverse this here so that we have the correct logic to finalize
	   the drag.
	*/

	if (Config->get_edit_mode() == Lock) {
		_x_constrained = !_x_constrained;
	}

	if (_copy) {
		if (_x_constrained) {
			_editor->begin_reversible_command (_("fixed time region copy"));
		} else {
			_editor->begin_reversible_command (_("region copy"));
		}
	} else {
		if (_x_constrained) {
			_editor->begin_reversible_command (_("fixed time region drag"));
		} else {
			_editor->begin_reversible_command (_("region drag"));
		}
	}

	changed_position = (_last_frame_position != (nframes64_t) (_primary->region()->position()));
	changed_tracks = (_dest_trackview != &_primary->get_time_axis_view());

	drag_delta = _primary->region()->position() - _last_frame_position;

	_editor->update_canvas_now ();

	/* make a list of where each region ended up */
	final = find_time_axis_views_and_layers ();

        cerr << "Iterate over " << _views.size() << " views\n";

	for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ) {

		RegionView* rv = i->view;
		RouteTimeAxisView* dest_rtv = final[rv].first;
		layer_t dest_layer = final[rv].second;

		nframes64_t where;

                from_playlist.reset ();
                to_playlist.reset ();

		if (rv->region()->locked()) {
			++i;
			continue;
		}

		if (changed_position && !_x_constrained) {
			where = rv->region()->position() - drag_delta;
		} else {
			where = rv->region()->position();
		}

		boost::shared_ptr<Region> new_region;

		if (_copy) {
			/* we already made a copy */
			new_region = rv->region();

			/* undo the previous hide_dependent_views so that xfades don't
			   disappear on copying regions
			*/

			//rv->get_time_axis_view().reveal_dependent_views (*rv);

		} else if (changed_tracks && dest_rtv->playlist()) {
			new_region = RegionFactory::create (rv->region());
		}

		if (changed_tracks || _copy) {

			to_playlist = dest_rtv->playlist();

			if (!to_playlist) {
				++i;
				continue;
			}

			_editor->latest_regionviews.clear ();

			sigc::connection c = dest_rtv->view()->RegionViewAdded.connect (sigc::mem_fun(*_editor, &Editor::collect_new_region_view));

			insert_result = modified_playlists.insert (to_playlist);

			if (insert_result.second) {
                                to_playlist->clear_history ();
			}

                        cerr << "To playlist " << to_playlist->name() << " region history contains "
                             << to_playlist->region_list().change().added.size() << " adds and " 
                             << to_playlist->region_list().change().removed.size() << " removes\n";

                        cerr << "Adding new region " << new_region->id() << " based on "
                             << rv->region()->id() << endl;
                        
			to_playlist->add_region (new_region, where);

			if (dest_rtv->view()->layer_display() == Stacked) {
				new_region->set_layer (dest_layer);
				new_region->set_pending_explicit_relayer (true);
			}

			c.disconnect ();

			if (!_editor->latest_regionviews.empty()) {
				// XXX why just the first one ? we only expect one
				// commented out in nick_m's canvas reworking. is that intended?
				//dest_atv->reveal_dependent_views (*latest_regionviews.front());
				new_views.push_back (_editor->latest_regionviews.front());
			}

		} else {
			rv->region()->clear_history ();

			/*
			   motion on the same track. plonk the previously reparented region
			   back to its original canvas group (its streamview).
			   No need to do anything for copies as they are fake regions which will be deleted.
			*/

			rv->get_canvas_group()->reparent (*dest_rtv->view()->canvas_item());
			rv->get_canvas_group()->property_y() = i->initial_y;
			rv->get_time_axis_view().reveal_dependent_views (*rv);

			/* just change the model */

			boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();
			
			if (dest_rtv->view()->layer_display() == Stacked) {
				rv->region()->set_layer (dest_layer);
				rv->region()->set_pending_explicit_relayer (true);
			}
			
			/* freeze playlist to avoid lots of relayering in the case of a multi-region drag */

			frozen_insert_result = frozen_playlists.insert(playlist);

			if (frozen_insert_result.second) {
				playlist->freeze();
			}

                        cerr << "Moving region " << rv->region()->id() << endl;

			rv->region()->set_position (where, (void*) this);

			sdc.push_back (new StatefulDiffCommand (rv->region()));
		}

		if (changed_tracks && !_copy) {

			/* get the playlist where this drag started. we can't use rv->region()->playlist()
			   because we may have copied the region and it has not been attached to a playlist.
			*/

			source_tv = dynamic_cast<RouteTimeAxisView*> (&rv->get_time_axis_view());
			tr = source_tv->track();
			from_playlist = tr->playlist();

			assert (source_tv);
			assert (tr);
			assert (from_playlist);

			/* moved to a different audio track, without copying */

			/* the region that used to be in the old playlist is not
			   moved to the new one - we use a copy of it. as a result,
			   any existing editor for the region should no longer be
			   visible.
			*/

			rv->hide_region_editor();
			rv->fake_set_opaque (false);

			/* remove the region from the old playlist */

			insert_result = modified_playlists.insert (from_playlist);

			if (insert_result.second) {
				from_playlist->clear_history ();
			}
                        
                        cerr << "From playlist " << from_playlist->name() << " region history contains "
                             << from_playlist->region_list().change().added.size() << " adds and " 
                             << from_playlist->region_list().change().removed.size() << " removes\n";

                        cerr << "removing region " << rv->region() << endl;
                        
			from_playlist->remove_region (rv->region());

			/* OK, this is where it gets tricky. If the playlist was being used by >1 tracks, and the region
			   was selected in all of them, then removing it from a playlist will have removed all
			   trace of it from the selection (i.e. there were N regions selected, we removed 1,
			   but since its the same playlist for N tracks, all N tracks updated themselves, removed the
			   corresponding regionview, and the selection is now empty).

			   this could have invalidated any and all iterators into the region selection.

			   the heuristic we use here is: if the region selection is empty, break out of the loop
			   here. if the region selection is not empty, then restart the loop because we know that
			   we must have removed at least the region(view) we've just been working on as well as any
			   that we processed on previous iterations.

			   EXCEPT .... if we are doing a copy drag, then the selection hasn't been modified and
			   we can just iterate.
			*/

			if (_views.empty()) {
                                if (to_playlist) {
                                        sdc.push_back (new StatefulDiffCommand (to_playlist));
                                        cerr << "Saved diff for to:" << to_playlist->name() << endl;
                                }
                                
                                if (from_playlist && (from_playlist != to_playlist)) {
                                        sdc.push_back (new StatefulDiffCommand (from_playlist));
                                        cerr << "Saved diff for from:" << from_playlist->name() << endl;
                                }				
                                break;
			} else {
				i = _views.begin();
			}

		} else {
			++i;
		}

		if (_copy) {
			copies.push_back (rv);
		}

                cerr << "Done with TV, top = " << to_playlist << " from = " << from_playlist << endl;

                if (to_playlist) {
                        sdc.push_back (new StatefulDiffCommand (to_playlist));
                        cerr << "Saved diff for to:" << to_playlist->name() << endl;
                }

                if (from_playlist && (from_playlist != to_playlist)) {
                        sdc.push_back (new StatefulDiffCommand (from_playlist));
                        cerr << "Saved diff for from:" << from_playlist->name() << endl;
                }
	}

	/*
	   if we've created new regions either by copying or moving 
	   to a new track, we want to replace the old selection with the new ones 
	*/

	if (new_views.size() > 0) {
		_editor->selection->set (new_views);
	}

	for (set<boost::shared_ptr<Playlist> >::iterator p = frozen_playlists.begin(); p != frozen_playlists.end(); ++p) {
		(*p)->thaw();
	}

  out:
        for (vector<StatefulDiffCommand*>::iterator i = sdc.begin(); i != sdc.end(); ++i) {
		_editor->session()->add_command (*i);
        }

	_editor->commit_reversible_command ();

	for (vector<RegionView*>::iterator x = copies.begin(); x != copies.end(); ++x) {
		delete *x;
	}
}

void
RegionMoveDrag::aborted ()
{
	if (_copy) {

		for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
			delete i->view;
		}

		_views.clear ();

	} else {
		RegionMotionDrag::aborted ();
	}
}

void
RegionMotionDrag::aborted ()
{
	for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
		RegionView* rv = i->view;
		TimeAxisView* tv = &(rv->get_time_axis_view ());
		RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
		assert (rtv);
		rv->get_canvas_group()->reparent (*rtv->view()->canvas_item());
		rv->get_canvas_group()->property_y() = 0;
		rv->get_time_axis_view().reveal_dependent_views (*rv);
		rv->fake_set_opaque (false);
		rv->move (-_total_x_delta, 0);
		rv->set_height (rtv->view()->child_height ());
	}

	_editor->update_canvas_now ();
}
				      

bool
RegionMotionDrag::x_move_allowed () const
{
	if (Config->get_edit_mode() == Lock) {
		/* in locked edit mode, reverse the usual meaning of _x_constrained */
		return _x_constrained;
	}

	return !_x_constrained;
}

void
RegionMotionDrag::copy_regions (GdkEvent* event)
{
	/* duplicate the regionview(s) and region(s) */

	list<DraggingView> new_regionviews;

	for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {

		RegionView* rv = i->view;
		AudioRegionView* arv = dynamic_cast<AudioRegionView*>(rv);
		MidiRegionView* mrv = dynamic_cast<MidiRegionView*>(rv);

		const boost::shared_ptr<const Region> original = rv->region();
		boost::shared_ptr<Region> region_copy = RegionFactory::create (original);
                region_copy->set_position (original->position(), this);

		RegionView* nrv;
		if (arv) {
			boost::shared_ptr<AudioRegion> audioregion_copy
				= boost::dynamic_pointer_cast<AudioRegion>(region_copy);

			nrv = new AudioRegionView (*arv, audioregion_copy);
		} else if (mrv) {
			boost::shared_ptr<MidiRegion> midiregion_copy
				= boost::dynamic_pointer_cast<MidiRegion>(region_copy);
			nrv = new MidiRegionView (*mrv, midiregion_copy);
		} else {
			continue;
		}

		nrv->get_canvas_group()->show ();
		new_regionviews.push_back (DraggingView (nrv));

		/* swap _primary to the copy */

		if (rv == _primary) {
			_primary = nrv;
		}

		/* ..and deselect the one we copied */

		rv->set_selected (false);
	}

	if (new_regionviews.empty()) {
		return;
	}

	/* reflect the fact that we are dragging the copies */

	_views = new_regionviews;

	swap_grab (new_regionviews.front().view->get_canvas_group (), 0, event ? event->motion.time : 0);

	/*
	   sync the canvas to what we think is its current state
	   without it, the canvas seems to
	   "forget" to update properly after the upcoming reparent()
	   ..only if the mouse is in rapid motion at the time of the grab.
	   something to do with regionview creation taking so long?
	*/
	_editor->update_canvas_now();
}

bool
RegionMotionDrag::check_possible (RouteTimeAxisView** tv, layer_t* layer)
{
	/* Which trackview is this ? */

	pair<TimeAxisView*, int> const tvp = _editor->trackview_by_y_position (_drags->current_pointer_y ());
	(*tv) = dynamic_cast<RouteTimeAxisView*> (tvp.first);
	(*layer) = tvp.second;

	if (*tv && (*tv)->layer_display() == Overlaid) {
		*layer = 0;
	}

	/* The region motion is only processed if the pointer is over
	   an audio track.
	*/

	if (!(*tv) || !(*tv)->is_track()) {
		/* To make sure we hide the verbose canvas cursor when the mouse is
		   not held over and audiotrack.
		*/
		_editor->hide_verbose_canvas_cursor ();
		return false;
	}

	return true;
}

/** @param new_order New track order.
 *  @param old_order Old track order.
 *  @param visible_y_low Lowest visible order.
 *  @return true if y movement should not happen, otherwise false.
 */
bool
RegionMotionDrag::y_movement_disallowed (int new_order, int old_order, int y_span, TimeAxisViewSummary const & tavs) const
{
	if (new_order != old_order) {

		/* this isn't the pointer track */

		if (y_span > 0) {

			/* moving up the canvas */
			if ( (new_order - y_span) >= tavs.visible_y_low) {

				int32_t n = 0;

				/* work out where we'll end up with this y span, taking hidden TimeAxisViews into account */
				int32_t visible_tracks = 0;
				while (visible_tracks < y_span ) {
					visible_tracks++;
					while (tavs.height_list[new_order - (visible_tracks - n)] == 0) {
						/* passing through a hidden track */
						n--;
					}
				}

				if (tavs.tracks[new_order - (y_span - n)] != 0x00) {
					/* moving to a non-track; disallow */
					return true;
				}


			} else {
				/* moving beyond the lowest visible track; disallow */
				return true;
			}

		} else if (y_span < 0) {

			/* moving down the canvas */
			if ((new_order - y_span) <= tavs.visible_y_high) {

				int32_t visible_tracks = 0;
				int32_t n = 0;
				while (visible_tracks > y_span ) {
					visible_tracks--;

					while (tavs.height_list[new_order - (visible_tracks - n)] == 0) {
						/* passing through a hidden track */
						n++;
					}
				}

				if (tavs.tracks[new_order - (y_span - n)] != 0x00) {
					/* moving to a non-track; disallow */
					return true;
				}


			} else {

				/* moving beyond the highest visible track; disallow */
				return true;
			}
		}

	} else {

		/* this is the pointer's track */

		if ((new_order - y_span) > tavs.visible_y_high) {
			/* we will overflow */
			return true;
		} else if ((new_order - y_span) < tavs.visible_y_low) {
			/* we will overflow */
			return true;
		}
	}

	return false;
}


RegionMoveDrag::RegionMoveDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v, bool b, bool c)
	: RegionMotionDrag (e, i, p, v, b),
	  _copy (c)
{
	TimeAxisView* const tv = &_primary->get_time_axis_view ();

	_dest_trackview = tv;
	if (tv->layer_display() == Overlaid) {
		_dest_layer = 0;
	} else {
		_dest_layer = _primary->region()->layer ();
	}

	double speed = 1;
	RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
	if (rtv && rtv->is_track()) {
		speed = rtv->track()->speed ();
	}

	_last_frame_position = static_cast<nframes64_t> (_primary->region()->position() / speed);
}

void
RegionMoveDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
{
	RegionMotionDrag::start_grab (event, c);

	_pointer_frame_offset = grab_frame() - _last_frame_position;
}

RegionInsertDrag::RegionInsertDrag (Editor* e, boost::shared_ptr<Region> r, RouteTimeAxisView* v, nframes64_t pos)
	: RegionMotionDrag (e, 0, 0, list<RegionView*> (), false)
{
	assert ((boost::dynamic_pointer_cast<AudioRegion> (r) && dynamic_cast<AudioTimeAxisView*> (v)) ||
		(boost::dynamic_pointer_cast<MidiRegion> (r) && dynamic_cast<MidiTimeAxisView*> (v)));

	_primary = v->view()->create_region_view (r, false, false);

	_primary->get_canvas_group()->show ();
	_primary->set_position (pos, 0);
	_views.push_back (DraggingView (_primary));

	_last_frame_position = pos;

	_item = _primary->get_canvas_group ();
	_dest_trackview = v;
	_dest_layer = _primary->region()->layer ();
}

map<RegionView*, pair<RouteTimeAxisView*, int> >
RegionMotionDrag::find_time_axis_views_and_layers ()
{
	map<RegionView*, pair<RouteTimeAxisView*, int> > tav;

	for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {

		double ix1, ix2, iy1, iy2;
		RegionView* rv = i->view;
		rv->get_canvas_frame()->get_bounds (ix1, iy1, ix2, iy2);
		rv->get_canvas_frame()->i2w (ix1, iy1);
		iy1 += _editor->vertical_adjustment.get_value() - _editor->canvas_timebars_vsize;

		pair<TimeAxisView*, int> tv = _editor->trackview_by_y_position (iy1);
		tav[rv] = make_pair (dynamic_cast<RouteTimeAxisView*> (tv.first), tv.second);
	}

	return tav;
}


void
RegionInsertDrag::finished (GdkEvent* /*event*/, bool /*movement_occurred*/)
{
	_editor->update_canvas_now ();

	map<RegionView*, pair<RouteTimeAxisView*, int> > final = find_time_axis_views_and_layers ();

	RouteTimeAxisView* dest_rtv = final[_primary].first;

	_primary->get_canvas_group()->reparent (*dest_rtv->view()->canvas_item());
	_primary->get_canvas_group()->property_y() = 0;

	boost::shared_ptr<Playlist> playlist = dest_rtv->playlist();

	_editor->begin_reversible_command (_("insert region"));
        playlist->clear_history ();
	playlist->add_region (_primary->region (), _last_frame_position);
	_editor->session()->add_command (new StatefulDiffCommand (playlist));
	_editor->commit_reversible_command ();

	delete _primary;
	_primary = 0;
	_views.clear ();
}

void
RegionInsertDrag::aborted ()
{
	delete _primary;
	_primary = 0;
	_views.clear ();
}

RegionSpliceDrag::RegionSpliceDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
	: RegionMoveDrag (e, i, p, v, false, false)
{

}

struct RegionSelectionByPosition {
    bool operator() (RegionView*a, RegionView* b) {
	    return a->region()->position () < b->region()->position();
    }
};

void
RegionSpliceDrag::motion (GdkEvent* event, bool)
{
	RouteTimeAxisView* tv;
	layer_t layer;

	if (!check_possible (&tv, &layer)) {
		return;
	}

	int dir;

	if ((_drags->current_pointer_x() - last_pointer_x()) > 0) {
		dir = 1;
	} else {
		dir = -1;
	}

	RegionSelection copy (_editor->selection->regions);

	RegionSelectionByPosition cmp;
	copy.sort (cmp);

	nframes64_t const pf = adjusted_current_frame (event);

	for (RegionSelection::iterator i = copy.begin(); i != copy.end(); ++i) {

		RouteTimeAxisView* atv = dynamic_cast<RouteTimeAxisView*> (&(*i)->get_time_axis_view());

		if (!atv) {
			continue;
		}

		boost::shared_ptr<Playlist> playlist;

		if ((playlist = atv->playlist()) == 0) {
			continue;
		}

		if (!playlist->region_is_shuffle_constrained ((*i)->region())) {
			continue;
		}

		if (dir > 0) {
			if (pf < (*i)->region()->last_frame() + 1) {
				continue;
			}
		} else {
			if (pf > (*i)->region()->first_frame()) {
				continue;
			}
		}


		playlist->shuffle ((*i)->region(), dir);
	}
}

void
RegionSpliceDrag::finished (GdkEvent* /*event*/, bool)
{

}

void
RegionSpliceDrag::aborted ()
{
	/* XXX: TODO */
}

RegionCreateDrag::RegionCreateDrag (Editor* e, ArdourCanvas::Item* i, TimeAxisView* v)
	: Drag (e, i),
	  _view (v)
{

}

void
RegionCreateDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
{
	_dest_trackview = _view;

	Drag::start_grab (event);
}


void
RegionCreateDrag::motion (GdkEvent* /*event*/, bool first_move)
{
	if (first_move) {
		// TODO: create region-create-drag region view here
	}

	// TODO: resize region-create-drag region view here
}

void
RegionCreateDrag::finished (GdkEvent* event, bool movement_occurred)
{
	MidiTimeAxisView* mtv = dynamic_cast<MidiTimeAxisView*> (_dest_trackview);

	if (!mtv) {
		return;
	}

	if (!movement_occurred) {
		mtv->add_region (grab_frame ());
	} else {
		motion (event, false);
		// TODO: create region-create-drag region here
	}
}

void
RegionCreateDrag::aborted ()
{
	/* XXX: TODO */
}

NoteResizeDrag::NoteResizeDrag (Editor* e, ArdourCanvas::Item* i)
	: Drag (e, i)
	, region (0)
{

}

void
NoteResizeDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*ignored*/)
{
	Gdk::Cursor* cursor;
	ArdourCanvas::CanvasNote* cnote = dynamic_cast<ArdourCanvas::CanvasNote*>(_item);

	Drag::start_grab (event);

	region = &cnote->region_view();

	double const region_start = region->get_position_pixels();
	double const middle_point = region_start + cnote->x1() + (cnote->x2() - cnote->x1()) / 2.0L;

	if (grab_x() <= middle_point) {
		cursor = _editor->left_side_trim_cursor;
		at_front = true;
	} else {
		cursor = _editor->right_side_trim_cursor;
		at_front = false;
	}

	_item->grab(GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, *cursor, event->motion.time);

	if (event->motion.state & Keyboard::PrimaryModifier) {
		relative = false;
	} else {
		relative = true;
	}

	MidiRegionSelection& ms (_editor->get_selection().midi_regions);

	if (ms.size() > 1) {
		/* has to be relative, may make no sense otherwise */
		relative = true;
	}

	/* select this note; if it is already selected, preserve the existing selection,
	   otherwise make this note the only one selected.
	*/
	region->note_selected (cnote, cnote->selected ());

	for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ) {
		MidiRegionSelection::iterator next;
		next = r;
		++next;
		(*r)->begin_resizing (at_front);
		r = next;
	}
}

void
NoteResizeDrag::motion (GdkEvent* /*event*/, bool /*first_move*/)
{
	MidiRegionSelection& ms (_editor->get_selection().midi_regions);
	for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
		(*r)->update_resizing (dynamic_cast<ArdourCanvas::CanvasNote*>(_item), at_front, _drags->current_pointer_x() - grab_x(), relative);
	}
}

void
NoteResizeDrag::finished (GdkEvent*, bool /*movement_occurred*/)
{
	MidiRegionSelection& ms (_editor->get_selection().midi_regions);
	for (MidiRegionSelection::iterator r = ms.begin(); r != ms.end(); ++r) {
		(*r)->commit_resizing (dynamic_cast<ArdourCanvas::CanvasNote*>(_item), at_front, _drags->current_pointer_x() - grab_x(), relative);
	}
}

void
NoteResizeDrag::aborted ()
{
	/* XXX: TODO */
}

void
RegionGainDrag::motion (GdkEvent* /*event*/, bool)
{

}

void
RegionGainDrag::finished (GdkEvent *, bool)
{

}

void
RegionGainDrag::aborted ()
{
	/* XXX: TODO */
}

TrimDrag::TrimDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
	: RegionDrag (e, i, p, v)
	, _have_transaction (false)
{

}

void
TrimDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
{
	double speed = 1.0;
	TimeAxisView* tvp = &_primary->get_time_axis_view ();
	RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);

	if (tv && tv->is_track()) {
		speed = tv->track()->speed();
	}

	nframes64_t const region_start = (nframes64_t) (_primary->region()->position() / speed);
	nframes64_t const region_end = (nframes64_t) (_primary->region()->last_frame() / speed);
	nframes64_t const region_length = (nframes64_t) (_primary->region()->length() / speed);

	nframes64_t const pf = adjusted_current_frame (event);

	if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
		_operation = ContentsTrim;
                Drag::start_grab (event, _editor->trimmer_cursor);
	} else {
		/* These will get overridden for a point trim.*/
		if (pf < (region_start + region_length/2)) {
			/* closer to start */
			_operation = StartTrim;
                        Drag::start_grab (event, _editor->left_side_trim_cursor);
		} else {
			/* closer to end */
			_operation = EndTrim;
                        Drag::start_grab (event, _editor->right_side_trim_cursor);
                }
	}

	switch (_operation) {
	case StartTrim:
		_editor->show_verbose_time_cursor (region_start, 10);
		break;
	case EndTrim:
		_editor->show_verbose_time_cursor (region_end, 10);
		break;
	case ContentsTrim:
		_editor->show_verbose_time_cursor (pf, 10);
		break;
	}
}

void
TrimDrag::motion (GdkEvent* event, bool first_move)
{
	RegionView* rv = _primary;

	/* 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 = &_primary->get_time_axis_view ();
	RouteTimeAxisView* tv = dynamic_cast<RouteTimeAxisView*>(tvp);
	pair<set<boost::shared_ptr<Playlist> >::iterator,bool> insert_result;

	if (tv && tv->is_track()) {
		speed = tv->track()->speed();
	}

	nframes64_t const pf = adjusted_current_frame (event);

	if (first_move) {

		string trim_type;

		switch (_operation) {
		case StartTrim:
			trim_type = "Region start trim";
			break;
		case EndTrim:
			trim_type = "Region end trim";
			break;
		case ContentsTrim:
			trim_type = "Region content trim";
			break;
		}

		_editor->begin_reversible_command (trim_type);
		_have_transaction = true;

		for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
			RegionView* rv = i->view;
			rv->fake_set_opaque(false);
			rv->enable_display (false);
                        rv->region()->clear_history ();
			rv->region()->suspend_property_changes ();

			AudioRegionView* const arv = dynamic_cast<AudioRegionView*> (rv);

			if (arv){
				arv->temporarily_hide_envelope ();
			}

			boost::shared_ptr<Playlist> pl = rv->region()->playlist();
			insert_result = _editor->motion_frozen_playlists.insert (pl);

			if (insert_result.second) {
				pl->freeze();
			}
		}
	}

	bool non_overlap_trim = false;

	if (event && Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
		non_overlap_trim = true;
	}

	switch (_operation) {
	case StartTrim:
		for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
			_editor->single_start_trim (*i->view, pf, non_overlap_trim);
		}
		break;

	case EndTrim:
		for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
			_editor->single_end_trim (*i->view, pf, non_overlap_trim);
		}
		break;

	case ContentsTrim:
		{
			bool swap_direction = false;

			if (event && Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
				swap_direction = true;
			}

			nframes64_t frame_delta = 0;
			
			bool left_direction = false;
			if (last_pointer_frame() > pf) {
				left_direction = true;
			}

			if (left_direction) {
				frame_delta = (last_pointer_frame() - pf);
			} else {
				frame_delta = (pf - last_pointer_frame());
			}

			for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
				_editor->single_contents_trim (*i->view, frame_delta, left_direction, swap_direction);
			}
		}
		break;
	}

	switch (_operation) {
	case StartTrim:
		_editor->show_verbose_time_cursor((nframes64_t) (rv->region()->position()/speed), 10);
		break;
	case EndTrim:
		_editor->show_verbose_time_cursor((nframes64_t) (rv->region()->last_frame()/speed), 10);
		break;
	case ContentsTrim:
		_editor->show_verbose_time_cursor (pf, 10);
		break;
	}
}


void
TrimDrag::finished (GdkEvent* event, bool movement_occurred)
{
	if (movement_occurred) {
		motion (event, false);

		if (!_editor->selection->selected (_primary)) {
			_editor->thaw_region_after_trim (*_primary);
		} else {

			for (list<DraggingView>::const_iterator i = _views.begin(); i != _views.end(); ++i) {
				_editor->thaw_region_after_trim (*i->view);
				i->view->enable_display (true);
				i->view->fake_set_opaque (true);
                                if (_have_transaction) {
                                        _editor->session()->add_command (new StatefulDiffCommand (i->view->region()));
                                }
			}
		}
		for (set<boost::shared_ptr<Playlist> >::iterator p = _editor->motion_frozen_playlists.begin(); p != _editor->motion_frozen_playlists.end(); ++p) {
			(*p)->thaw ();
		}

		_editor->motion_frozen_playlists.clear ();

		if (_have_transaction) {
			_editor->commit_reversible_command();
		}

	} else {
		/* no mouse movement */
		_editor->point_trim (event, adjusted_current_frame (event));
	}
}

void
TrimDrag::aborted ()
{
	/* Our motion method is changing model state, so use the Undo system
	   to cancel.  Perhaps not ideal, as this will leave an Undo point
	   behind which may be slightly odd from the user's point of view.
	*/

	finished (0, true);
	
	if (_have_transaction) {
		_editor->undo ();
	}
}

MeterMarkerDrag::MeterMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
	: Drag (e, i),
	  _copy (c)
{
	_marker = reinterpret_cast<MeterMarker*> (_item->get_data ("marker"));
	assert (_marker);
}

void
MeterMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
{
	if (_copy) {
		// create a dummy marker for visual representation of moving the copy.
		// The actual copying is not done before we reach the finish callback.
		char name[64];
		snprintf (name, sizeof(name), "%g/%g", _marker->meter().beats_per_bar(), _marker->meter().note_divisor ());
		MeterMarker* new_marker = new MeterMarker(*_editor, *_editor->meter_group, ARDOUR_UI::config()->canvasvar_MeterMarker.get(), name,
							  *new MeterSection (_marker->meter()));

		_item = &new_marker->the_item ();
		_marker = new_marker;

	} else {

		MetricSection& section (_marker->meter());

		if (!section.movable()) {
			return;
		}

	}

	Drag::start_grab (event, cursor);

	_pointer_frame_offset = grab_frame() - _marker->meter().frame();

	_editor->show_verbose_time_cursor (adjusted_current_frame(event), 10);
}

void
MeterMarkerDrag::motion (GdkEvent* event, bool)
{
	nframes64_t const pf = adjusted_current_frame (event);

	_marker->set_position (pf);
	
	_editor->show_verbose_time_cursor (pf, 10);
}

void
MeterMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
{
	if (!movement_occurred) {
		return;
	}

	motion (event, false);

	BBT_Time when;

	TempoMap& map (_editor->session()->tempo_map());
	map.bbt_time (last_pointer_frame(), when);

	if (_copy == true) {
		_editor->begin_reversible_command (_("copy meter mark"));
		XMLNode &before = map.get_state();
		map.add_meter (_marker->meter(), when);
		XMLNode &after = map.get_state();
		_editor->session()->add_command(new MementoCommand<TempoMap>(map, &before, &after));
		_editor->commit_reversible_command ();

		// delete the dummy marker we used for visual representation of copying.
		// a new visual marker will show up automatically.
		delete _marker;
	} else {
		_editor->begin_reversible_command (_("move meter mark"));
		XMLNode &before = map.get_state();
		map.move_meter (_marker->meter(), when);
		XMLNode &after = map.get_state();
		_editor->session()->add_command(new MementoCommand<TempoMap>(map, &before, &after));
		_editor->commit_reversible_command ();
	}
}

void
MeterMarkerDrag::aborted ()
{
	_marker->set_position (_marker->meter().frame ());
}

TempoMarkerDrag::TempoMarkerDrag (Editor* e, ArdourCanvas::Item* i, bool c)
	: Drag (e, i),
	  _copy (c)
{
	_marker = reinterpret_cast<TempoMarker*> (_item->get_data ("marker"));
	assert (_marker);
}

void
TempoMarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
{

	if (_copy) {

		// create a dummy marker for visual representation of moving the copy.
		// The actual copying is not done before we reach the finish callback.
		char name[64];
		snprintf (name, sizeof (name), "%.2f", _marker->tempo().beats_per_minute());
		TempoMarker* new_marker = new TempoMarker(*_editor, *_editor->tempo_group, ARDOUR_UI::config()->canvasvar_TempoMarker.get(), name,
							  *new TempoSection (_marker->tempo()));

		_item = &new_marker->the_item ();
		_marker = new_marker;

	} else {

		MetricSection& section (_marker->tempo());

		if (!section.movable()) {
			return;
		}
	}

	Drag::start_grab (event, cursor);

	_pointer_frame_offset = grab_frame() - _marker->tempo().frame();
	_editor->show_verbose_time_cursor (adjusted_current_frame (event), 10);
}

void
TempoMarkerDrag::motion (GdkEvent* event, bool)
{
	nframes64_t const pf = adjusted_current_frame (event);
	_marker->set_position (pf);
	_editor->show_verbose_time_cursor (pf, 10);
}

void
TempoMarkerDrag::finished (GdkEvent* event, bool movement_occurred)
{
	if (!movement_occurred) {
		return;
	}

	motion (event, false);

	BBT_Time when;

	TempoMap& map (_editor->session()->tempo_map());
	map.bbt_time (last_pointer_frame(), when);

	if (_copy == true) {
		_editor->begin_reversible_command (_("copy tempo mark"));
		XMLNode &before = map.get_state();
		map.add_tempo (_marker->tempo(), when);
		XMLNode &after = map.get_state();
		_editor->session()->add_command (new MementoCommand<TempoMap>(map, &before, &after));
		_editor->commit_reversible_command ();

		// delete the dummy marker we used for visual representation of copying.
		// a new visual marker will show up automatically.
		delete _marker;
	} else {
		_editor->begin_reversible_command (_("move tempo mark"));
		XMLNode &before = map.get_state();
		map.move_tempo (_marker->tempo(), when);
		XMLNode &after = map.get_state();
		_editor->session()->add_command (new MementoCommand<TempoMap>(map, &before, &after));
		_editor->commit_reversible_command ();
	}
}

void
TempoMarkerDrag::aborted ()
{
	_marker->set_position (_marker->tempo().frame());
}

CursorDrag::CursorDrag (Editor* e, ArdourCanvas::Item* i, bool s)
	: Drag (e, i),
	  _stop (s)
{
	_cursor = reinterpret_cast<EditorCursor*> (_item->get_data ("cursor"));
	assert (_cursor);
}

void
CursorDrag::start_grab (GdkEvent* event, Gdk::Cursor* c)
{
	Drag::start_grab (event, c);

	if (!_stop) {

		nframes64_t where = _editor->event_frame (event, 0, 0);

		_editor->snap_to_with_modifier (where, event);
		_editor->playhead_cursor->set_position (where);

	}

	if (_cursor == _editor->playhead_cursor) {
		_editor->_dragging_playhead = true;

		if (_editor->session()) {
			if (_was_rolling && _stop) {
				_editor->session()->request_stop ();
			}

			if (_editor->session()->is_auditioning()) {
				_editor->session()->cancel_audition ();
			}

			nframes64_t const f = _editor->playhead_cursor->current_frame;
			_editor->session()->send_mmc_locate (f);
			_editor->session()->send_full_time_code (f);
		}
	}

	_pointer_frame_offset = grab_frame() - _cursor->current_frame;

	_editor->show_verbose_time_cursor (_cursor->current_frame, 10);
}

void
CursorDrag::motion (GdkEvent* event, bool)
{
	nframes64_t const adjusted_frame = adjusted_current_frame (event);

	if (adjusted_frame == last_pointer_frame()) {
		return;
	}

	_cursor->set_position (adjusted_frame);

	_editor->show_verbose_time_cursor (_cursor->current_frame, 10);

	if (_editor->session() && _item == &_editor->playhead_cursor->canvas_item) {
		nframes64_t const f = _editor->playhead_cursor->current_frame;
		_editor->session()->send_mmc_locate (f);
		_editor->session()->send_full_time_code (f);
	}
	

#ifdef GTKOSX
	_editor->update_canvas_now ();
#endif
	_editor->UpdateAllTransportClocks (_cursor->current_frame);
}

void
CursorDrag::finished (GdkEvent* event, bool movement_occurred)
{
	_editor->_dragging_playhead = false;

	if (!movement_occurred && _stop) {
		return;
	}

	motion (event, false);

	if (_item == &_editor->playhead_cursor->canvas_item) {
		if (_editor->session()) {
			_editor->session()->request_locate (_editor->playhead_cursor->current_frame, _was_rolling);
			_editor->_pending_locate_request = true;
		}
	}
}

void
CursorDrag::aborted ()
{
	_editor->_dragging_playhead = false;
	_cursor->set_position (adjusted_frame (grab_frame (), 0, false));
}

FadeInDrag::FadeInDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
	: RegionDrag (e, i, p, v)
{

}

void
FadeInDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
{
	Drag::start_grab (event, cursor);

	AudioRegionView* a = dynamic_cast<AudioRegionView*> (_primary);
	boost::shared_ptr<AudioRegion> const r = a->audio_region ();

	_pointer_frame_offset = grab_frame() - ((nframes64_t) r->fade_in()->back()->when + r->position());
	_editor->show_verbose_duration_cursor (r->position(), r->position() + r->fade_in()->back()->when, 10);
	
}

void
FadeInDrag::motion (GdkEvent* event, bool)
{
	nframes64_t fade_length;

	nframes64_t const pos = adjusted_current_frame (event);

	boost::shared_ptr<Region> region = _primary->region ();

	if (pos < (region->position() + 64)) {
		fade_length = 64; // this should be a minimum defined somewhere
	} else if (pos > region->last_frame()) {
		fade_length = region->length();
	} else {
		fade_length = pos - region->position();
	}

	for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {

		AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);

		if (!tmp) {
			continue;
		}

		tmp->reset_fade_in_shape_width (fade_length);
	}

	_editor->show_verbose_duration_cursor (region->position(), region->position() + fade_length, 10);
}

void
FadeInDrag::finished (GdkEvent* event, bool movement_occurred)
{
	if (!movement_occurred) {
		return;
	}

	nframes64_t fade_length;

	nframes64_t const pos = adjusted_current_frame (event);

	boost::shared_ptr<Region> region = _primary->region ();

	if (pos < (region->position() + 64)) {
		fade_length = 64; // this should be a minimum defined somewhere
	} else if (pos > region->last_frame()) {
		fade_length = region->length();
	} else {
		fade_length = pos - region->position();
	}

	_editor->begin_reversible_command (_("change fade in length"));

	for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {

		AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);

		if (!tmp) {
			continue;
		}

		boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_in();
		XMLNode &before = alist->get_state();

		tmp->audio_region()->set_fade_in_length (fade_length);
		tmp->audio_region()->set_fade_in_active (true);

		XMLNode &after = alist->get_state();
		_editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
	}

	_editor->commit_reversible_command ();
}

void
FadeInDrag::aborted ()
{
	for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
		AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);

		if (!tmp) {
			continue;
		}

		tmp->reset_fade_in_shape_width (tmp->audio_region()->fade_in()->back()->when);
	}
}

FadeOutDrag::FadeOutDrag (Editor* e, ArdourCanvas::Item* i, RegionView* p, list<RegionView*> const & v)
	: RegionDrag (e, i, p, v)
{

}

void
FadeOutDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
{
	Drag::start_grab (event, cursor);

	AudioRegionView* a = dynamic_cast<AudioRegionView*> (_primary);
	boost::shared_ptr<AudioRegion> r = a->audio_region ();

	_pointer_frame_offset = grab_frame() - (r->length() - (nframes64_t) r->fade_out()->back()->when + r->position());
	_editor->show_verbose_duration_cursor (r->last_frame() - r->fade_out()->back()->when, r->last_frame(), 10);
}

void
FadeOutDrag::motion (GdkEvent* event, bool)
{
	nframes64_t fade_length;

	nframes64_t const pos = adjusted_current_frame (event);

	boost::shared_ptr<Region> region = _primary->region ();

	if (pos > (region->last_frame() - 64)) {
		fade_length = 64; // this should really be a minimum fade defined somewhere
	}
	else if (pos < region->position()) {
		fade_length = region->length();
	}
	else {
		fade_length = region->last_frame() - pos;
	}

	for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {

		AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);

		if (!tmp) {
			continue;
		}

		tmp->reset_fade_out_shape_width (fade_length);
	}

	_editor->show_verbose_duration_cursor (region->last_frame() - fade_length, region->last_frame(), 10);
}

void
FadeOutDrag::finished (GdkEvent* event, bool movement_occurred)
{
	if (!movement_occurred) {
		return;
	}

	nframes64_t fade_length;

	nframes64_t const pos = adjusted_current_frame (event);

	boost::shared_ptr<Region> region = _primary->region ();

	if (pos > (region->last_frame() - 64)) {
		fade_length = 64; // this should really be a minimum fade defined somewhere
	}
	else if (pos < region->position()) {
		fade_length = region->length();
	}
	else {
		fade_length = region->last_frame() - pos;
	}

	_editor->begin_reversible_command (_("change fade out length"));

	for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {

		AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);

		if (!tmp) {
			continue;
		}

		boost::shared_ptr<AutomationList> alist = tmp->audio_region()->fade_out();
		XMLNode &before = alist->get_state();

		tmp->audio_region()->set_fade_out_length (fade_length);
		tmp->audio_region()->set_fade_out_active (true);

		XMLNode &after = alist->get_state();
		_editor->session()->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &after));
	}

	_editor->commit_reversible_command ();
}

void
FadeOutDrag::aborted ()
{
	for (list<DraggingView>::iterator i = _views.begin(); i != _views.end(); ++i) {
		AudioRegionView* tmp = dynamic_cast<AudioRegionView*> (i->view);

		if (!tmp) {
			continue;
		}

		tmp->reset_fade_out_shape_width (tmp->audio_region()->fade_out()->back()->when);
	}
}

MarkerDrag::MarkerDrag (Editor* e, ArdourCanvas::Item* i)
	: Drag (e, i)
{
	_marker = reinterpret_cast<Marker*> (_item->get_data ("marker"));
	assert (_marker);

	_points.push_back (Gnome::Art::Point (0, 0));
	_points.push_back (Gnome::Art::Point (0, _editor->physical_screen_height));

	_line = new ArdourCanvas::Line (*_editor->timebar_group);
	_line->property_width_pixels() = 1;
	_line->property_points () = _points;
	_line->hide ();

	_line->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_MarkerDragLine.get();
}

MarkerDrag::~MarkerDrag ()
{
	for (list<Location*>::iterator i = _copied_locations.begin(); i != _copied_locations.end(); ++i) {
		delete *i;
	}
}

void
MarkerDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
{
	Drag::start_grab (event, cursor);

	bool is_start;

	Location *location = _editor->find_location_from_marker (_marker, is_start);
	_editor->_dragging_edit_point = true;

	_pointer_frame_offset = grab_frame() - (is_start ? location->start() : location->end());

	update_item (location);

	// _drag_line->show();
	// _line->raise_to_top();

	if (is_start) {
		_editor->show_verbose_time_cursor (location->start(), 10);
	} else {
		_editor->show_verbose_time_cursor (location->end(), 10);
	}

	Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);

	switch (op) {
	case Selection::Toggle:
		_editor->selection->toggle (_marker);
		break;
	case Selection::Set:
		if (!_editor->selection->selected (_marker)) {
			_editor->selection->set (_marker);
		}
		break;
	case Selection::Extend:
	{
		Locations::LocationList ll;
		list<Marker*> to_add;
		nframes64_t s, e;
		_editor->selection->markers.range (s, e);
		s = min (_marker->position(), s);
		e = max (_marker->position(), e);
		s = min (s, e);
		e = max (s, e);
		if (e < max_frames) {
			++e;
		}
		_editor->session()->locations()->find_all_between (s, e, ll, Location::Flags (0));
		for (Locations::LocationList::iterator i = ll.begin(); i != ll.end(); ++i) {
			Editor::LocationMarkers* lm = _editor->find_location_markers (*i);
			if (lm) {
				if (lm->start) {
					to_add.push_back (lm->start);
				}
				if (lm->end) {
					to_add.push_back (lm->end);
				}
			}
		}
		if (!to_add.empty()) {
			_editor->selection->add (to_add);
		}
		break;
	}
	case Selection::Add:
		_editor->selection->add (_marker);
		break;
	}

	/* Set up copies for us to manipulate during the drag */

	for (MarkerSelection::iterator i = _editor->selection->markers.begin(); i != _editor->selection->markers.end(); ++i) {
		Location* l = _editor->find_location_from_marker (*i, is_start);
		_copied_locations.push_back (new Location (*l));
	}
}

void
MarkerDrag::motion (GdkEvent* event, bool)
{
	nframes64_t f_delta = 0;
	bool is_start;
	bool move_both = false;
	Marker* marker;
	Location *real_location;
	Location *copy_location = 0;

	nframes64_t const newframe = adjusted_current_frame (event);

	nframes64_t next = newframe;

	if (newframe == last_pointer_frame()) {
		return;
	}

	if (Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier)) {
		move_both = true;
	}

	MarkerSelection::iterator i;
	list<Location*>::iterator x;

	/* find the marker we're dragging, and compute the delta */

	for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
	     x != _copied_locations.end() && i != _editor->selection->markers.end();
	     ++i, ++x) {

		copy_location = *x;
		marker = *i;

		if (marker == _marker) {

			if ((real_location = _editor->find_location_from_marker (marker, is_start)) == 0) {
				/* que pasa ?? */
				return;
			}

			if (real_location->is_mark()) {
				f_delta = newframe - copy_location->start();
			} else {


				switch (marker->type()) {
				case Marker::Start:
				case Marker::LoopStart:
				case Marker::PunchIn:
					f_delta = newframe - copy_location->start();
					break;

				case Marker::End:
				case Marker::LoopEnd:
				case Marker::PunchOut:
					f_delta = newframe - copy_location->end();
					break;
				default:
					/* what kind of marker is this ? */
					return;
				}
			}
			break;
		}
	}

	if (i == _editor->selection->markers.end()) {
		/* hmm, impossible - we didn't find the dragged marker */
		return;
	}

	/* now move them all */

	for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
	     x != _copied_locations.end() && i != _editor->selection->markers.end();
	     ++i, ++x) {

		copy_location = *x;
		marker = *i;

		/* call this to find out if its the start or end */

		if ((real_location = _editor->find_location_from_marker (marker, is_start)) == 0) {
			continue;
		}

		if (real_location->locked()) {
			continue;
		}

		if (copy_location->is_mark()) {

			/* now move it */

			copy_location->set_start (copy_location->start() + f_delta);

		} else {

			nframes64_t new_start = copy_location->start() + f_delta;
			nframes64_t new_end = copy_location->end() + f_delta;

			if (is_start) { // start-of-range marker

				if (move_both) {
					copy_location->set_start (new_start);
					copy_location->set_end (new_end);
				} else 	if (new_start < copy_location->end()) {
					copy_location->set_start (new_start);
				} else {
					_editor->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 (new_end);
					copy_location->set_start (new_start);
				} else if (new_end > copy_location->start()) {
					copy_location->set_end (new_end);
				} else if (newframe > 0) {
					_editor->snap_to (next, -1, true);
					copy_location->set_start (next);
					copy_location->set_end (newframe);
				}
			}
		}

		update_item (copy_location);

		Editor::LocationMarkers* lm = _editor->find_location_markers (real_location);

		if (lm) {
			lm->set_position (copy_location->start(), copy_location->end());
		}
	}

	assert (!_copied_locations.empty());

	_editor->show_verbose_time_cursor (newframe, 10);

#ifdef GTKOSX
	_editor->update_canvas_now ();
#endif
}

void
MarkerDrag::finished (GdkEvent* event, bool movement_occurred)
{
	if (!movement_occurred) {

		/* just a click, do nothing but finish
		   off the selection process
		*/

		Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);

		switch (op) {
		case Selection::Set:
			if (_editor->selection->selected (_marker) && _editor->selection->markers.size() > 1) {
				_editor->selection->set (_marker);
			}
			break;

		case Selection::Toggle:
		case Selection::Extend:
		case Selection::Add:
			break;
		}

		return;
	}

	_editor->_dragging_edit_point = false;

	_editor->begin_reversible_command ( _("move marker") );
	XMLNode &before = _editor->session()->locations()->get_state();

	MarkerSelection::iterator i;
	list<Location*>::iterator x;
	bool is_start;

	for (i = _editor->selection->markers.begin(), x = _copied_locations.begin();
	     x != _copied_locations.end() && i != _editor->selection->markers.end();
	     ++i, ++x) {

		Location * location = _editor->find_location_from_marker (*i, is_start);

		if (location) {

			if (location->locked()) {
				return;
			}

			if (location->is_mark()) {
				location->set_start ((*x)->start());
			} else {
				location->set ((*x)->start(), (*x)->end());
			}
		}
	}

	XMLNode &after = _editor->session()->locations()->get_state();
	_editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
	_editor->commit_reversible_command ();

	_line->hide();
}

void
MarkerDrag::aborted ()
{
	/* XXX: TODO */
}

void
MarkerDrag::update_item (Location* location)
{
	double const x1 = _editor->frame_to_pixel (location->start());

	_points.front().set_x(x1);
	_points.back().set_x(x1);
	_line->property_points() = _points;
}

ControlPointDrag::ControlPointDrag (Editor* e, ArdourCanvas::Item* i)
	: Drag (e, i),
	  _cumulative_x_drag (0),
	  _cumulative_y_drag (0)
{
	_point = reinterpret_cast<ControlPoint*> (_item->get_data ("control_point"));
	assert (_point);
}


void
ControlPointDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
{
	Drag::start_grab (event, _editor->fader_cursor);

	// start the grab at the center of the control point so
	// the point doesn't 'jump' to the mouse after the first drag
	_time_axis_view_grab_x = _point->get_x();
	_time_axis_view_grab_y = _point->get_y();

	float const fraction = 1 - (_point->get_y() / _point->line().height());

	_point->line().start_drag_single (_point, _time_axis_view_grab_x, fraction);

	_editor->set_verbose_canvas_cursor (_point->line().get_verbose_cursor_string (fraction),
					    event->button.x + 10, event->button.y + 10);

	_editor->show_verbose_canvas_cursor ();
}

void
ControlPointDrag::motion (GdkEvent* event, bool)
{
	double dx = _drags->current_pointer_x() - last_pointer_x();
	double dy = _drags->current_pointer_y() - last_pointer_y();

	if (event->button.state & Keyboard::SecondaryModifier) {
		dx *= 0.1;
		dy *= 0.1;
	}

	/* coordinate in TimeAxisView's space */
	double cx = _time_axis_view_grab_x + _cumulative_x_drag + dx;
	double cy = _time_axis_view_grab_y + _cumulative_y_drag + dy;

	// calculate zero crossing point. back off by .01 to stay on the
	// positive side of zero
	double const zero_gain_y = (1.0 - _zero_gain_fraction) * _point->line().height() - .01;

	// make sure we hit zero when passing through
	if ((cy < zero_gain_y && (cy - dy) > zero_gain_y) || (cy > zero_gain_y && (cy - dy) < zero_gain_y)) {
		cy = zero_gain_y;
	}

	if (_x_constrained) {
		cx = _time_axis_view_grab_x;
	}
	if (_y_constrained) {
		cy = _time_axis_view_grab_y;
	}

	_cumulative_x_drag = cx - _time_axis_view_grab_x;
	_cumulative_y_drag = cy - _time_axis_view_grab_y;

	cx = max (0.0, cx);
	cy = max (0.0, cy);
	cy = min ((double) _point->line().height(), cy);

	nframes64_t cx_frames = _editor->unit_to_frame (cx);

	if (!_x_constrained) {
		_editor->snap_to_with_modifier (cx_frames, event);
	}

	float const fraction = 1.0 - (cy / _point->line().height());

	bool const push = Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier);

	_point->line().drag_motion (_editor->frame_to_unit (cx_frames), fraction, false, push);

	_editor->set_verbose_canvas_cursor_text (_point->line().get_verbose_cursor_string (fraction));
}

void
ControlPointDrag::finished (GdkEvent* event, bool movement_occurred)
{
	if (!movement_occurred) {

		/* just a click */

		if ((event->type == GDK_BUTTON_RELEASE) && (event->button.button == 1) && Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
			_editor->reset_point_selection ();
		}

	} else {
		motion (event, false);
	}
	_point->line().end_drag ();
}

void
ControlPointDrag::aborted ()
{
	_point->line().reset ();
}

bool
ControlPointDrag::active (Editing::MouseMode m)
{
	if (m == Editing::MouseGain) {
		/* always active in mouse gain */
		return true;
	}

	/* otherwise active if the point is on an automation line (ie not if its on a region gain line) */
	return dynamic_cast<AutomationLine*> (&(_point->line())) != 0;
}

LineDrag::LineDrag (Editor* e, ArdourCanvas::Item* i)
	: Drag (e, i),
	  _line (0),
	  _cumulative_y_drag (0)
{

}
void
LineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
{
	_line = reinterpret_cast<AutomationLine*> (_item->get_data ("line"));
	assert (_line);

	_item = &_line->grab_item ();

	/* need to get x coordinate in terms of parent (TimeAxisItemView)
	   origin, and ditto for y.
	*/

	double cx = event->button.x;
	double cy = event->button.y;

	_line->parent_group().w2i (cx, cy);

	nframes64_t const frame_within_region = (nframes64_t) floor (cx * _editor->frames_per_unit);

	uint32_t before;
	uint32_t after;
	
	if (!_line->control_points_adjacent (frame_within_region, before, after)) {
		/* no adjacent points */
		return;
	}

	Drag::start_grab (event, _editor->fader_cursor);

	/* store grab start in parent frame */

	_time_axis_view_grab_x = cx;
	_time_axis_view_grab_y = cy;

	double fraction = 1.0 - (cy / _line->height());

	_line->start_drag_line (before, after, fraction);

	_editor->set_verbose_canvas_cursor (_line->get_verbose_cursor_string (fraction),
					    event->button.x + 10, event->button.y + 10);

	_editor->show_verbose_canvas_cursor ();
}

void
LineDrag::motion (GdkEvent* event, bool)
{
	double dy = _drags->current_pointer_y() - last_pointer_y();

	if (event->button.state & Keyboard::SecondaryModifier) {
		dy *= 0.1;
	}

	double cy = _time_axis_view_grab_y + _cumulative_y_drag + dy;

	_cumulative_y_drag = cy - _time_axis_view_grab_y;

	cy = max (0.0, cy);
	cy = min ((double) _line->height(), cy);

	double const fraction = 1.0 - (cy / _line->height());

	bool push;

	if (Keyboard::modifier_state_contains (event->button.state, Keyboard::PrimaryModifier)) {
		push = false;
	} else {
		push = true;
	}

	/* we are ignoring x position for this drag, so we can just pass in anything */
	_line->drag_motion (0, fraction, true, push);

	_editor->set_verbose_canvas_cursor_text (_line->get_verbose_cursor_string (fraction));
}

void
LineDrag::finished (GdkEvent* event, bool)
{
	motion (event, false);
	_line->end_drag ();
}

void
LineDrag::aborted ()
{
	_line->reset ();
}

FeatureLineDrag::FeatureLineDrag (Editor* e, ArdourCanvas::Item* i)
	: Drag (e, i),
	  _line (0),
	  _cumulative_x_drag (0)
{

}
void
FeatureLineDrag::start_grab (GdkEvent* event, Gdk::Cursor* /*cursor*/)
{
  
	Drag::start_grab (event);
	
	_line = reinterpret_cast<SimpleLine*> (_item);
	assert (_line);

	/* need to get x coordinate in terms of parent (AudioRegionView) origin. */

	double cx = event->button.x;
	double cy = event->button.y;

	_item->property_parent().get_value()->w2i(cx, cy);

	/* store grab start in parent frame */
	_region_view_grab_x = cx;
	
	_before = _line->property_x1();
	
	_arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
		
	_max_x = _editor->frame_to_pixel(_arv->get_duration());
}

void
FeatureLineDrag::motion (GdkEvent* event, bool)
{
	double dx = _drags->current_pointer_x() - last_pointer_x();
	
	double cx = _region_view_grab_x + _cumulative_x_drag + dx;
	
	_cumulative_x_drag += dx;
		
	/* Clamp the min and max extent of the drag to keep it within the region view bounds */
	
	if (cx > _max_x){
		cx = _max_x;
	}
	else if(cx < 0){
		cx = 0;
	}
	
	_line->property_x1() = cx; 
	_line->property_x2() = cx;

	_before = _line->property_x1();
}

void
FeatureLineDrag::finished (GdkEvent* event, bool)
{
	_arv = reinterpret_cast<AudioRegionView*> (_item->get_data ("regionview"));
	_arv->update_transient(_before, _line->property_x1());
}

void
FeatureLineDrag::aborted ()
{
	//_line->reset ();
}

void
RubberbandSelectDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
{
	Drag::start_grab (event);
	_editor->show_verbose_time_cursor (adjusted_current_frame (event), 10);
}

void
RubberbandSelectDrag::motion (GdkEvent* event, bool)
{
	nframes64_t start;
	nframes64_t end;
	double y1;
	double y2;

	nframes64_t const pf = adjusted_current_frame (event, Config->get_rubberbanding_snaps_to_grid ());

	nframes64_t grab = grab_frame ();
	if (Config->get_rubberbanding_snaps_to_grid ()) {
		_editor->snap_to_with_modifier (grab, event);
	}

	/* base start and end on initial click position */

	if (pf < grab) {
		start = pf;
		end = grab;
	} else {
		end = pf;
		start = grab;
	}

	if (_drags->current_pointer_y() < grab_y()) {
		y1 = _drags->current_pointer_y();
		y2 = grab_y();
	} else {
		y2 = _drags->current_pointer_y();
		y1 = grab_y();
	}


	if (start != end || y1 != y2) {

		double x1 = _editor->frame_to_pixel (start);
		double x2 = _editor->frame_to_pixel (end);

		_editor->rubberband_rect->property_x1() = x1;
		_editor->rubberband_rect->property_y1() = y1;
		_editor->rubberband_rect->property_x2() = x2;
		_editor->rubberband_rect->property_y2() = y2;

		_editor->rubberband_rect->show();
		_editor->rubberband_rect->raise_to_top();

		_editor->show_verbose_time_cursor (pf, 10);
	}
}

void
RubberbandSelectDrag::finished (GdkEvent* event, bool movement_occurred)
{
	if (movement_occurred) {

		motion (event, false);

		double y1,y2;
		if (_drags->current_pointer_y() < grab_y()) {
			y1 = _drags->current_pointer_y();
			y2 = grab_y();
		} else {
			y2 = _drags->current_pointer_y();
			y1 = grab_y();
		}


		Selection::Operation op = ArdourKeyboard::selection_type (event->button.state);
		bool committed;

		_editor->begin_reversible_command (_("rubberband selection"));

		if (grab_frame() < last_pointer_frame()) {
			committed = _editor->select_all_within (grab_frame(), last_pointer_frame() - 1, y1, y2, _editor->track_views, op);
		} else {
			committed = _editor->select_all_within (last_pointer_frame(), grab_frame() - 1, y1, y2, _editor->track_views, op);
		}

		if (!committed) {
			_editor->commit_reversible_command ();
		}

	} else {
		if (!getenv("ARDOUR_SAE")) {
			_editor->selection->clear_tracks();
		}
		_editor->selection->clear_regions();
		_editor->selection->clear_points ();
		_editor->selection->clear_lines ();
	}

	_editor->rubberband_rect->hide();
}

void
RubberbandSelectDrag::aborted ()
{
	_editor->rubberband_rect->hide ();
}

void
TimeFXDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
{
	Drag::start_grab (event);

	_editor->show_verbose_time_cursor (adjusted_current_frame (event), 10);
}

void
TimeFXDrag::motion (GdkEvent* event, bool)
{
	RegionView* rv = _primary;

	nframes64_t const pf = adjusted_current_frame (event);

	if (pf > rv->region()->position()) {
		rv->get_time_axis_view().show_timestretch (rv->region()->position(), pf);
	}

	_editor->show_verbose_time_cursor (pf, 10);
}

void
TimeFXDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
{
	_primary->get_time_axis_view().hide_timestretch ();

	if (!movement_occurred) {
		return;
	}

	if (last_pointer_frame() < _primary->region()->position()) {
		/* backwards drag of the left edge - not usable */
		return;
	}

	nframes64_t newlen = last_pointer_frame() - _primary->region()->position();

	float percentage = (double) newlen / (double) _primary->region()->length();

#ifndef USE_RUBBERBAND
	// Soundtouch uses percentage / 100 instead of normal (/ 1)
	if (_primary->region()->data_type() == DataType::AUDIO) {
		percentage = (float) ((double) newlen - (double) _primary->region()->length()) / ((double) newlen) * 100.0f;
	}
#endif

	_editor->begin_reversible_command (_("timestretch"));

	// XXX how do timeFX on multiple regions ?

	RegionSelection rs;
	rs.add (_primary);

	if (_editor->time_stretch (rs, percentage) == -1) {
		error << _("An error occurred while executing time stretch operation") << endmsg;
	}
}

void
TimeFXDrag::aborted ()
{
	_primary->get_time_axis_view().hide_timestretch ();
}


void
ScrubDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
{
	Drag::start_grab (event);
}

void
ScrubDrag::motion (GdkEvent* /*event*/, bool)
{
	_editor->scrub (adjusted_current_frame (0, false), _drags->current_pointer_x ());
}

void
ScrubDrag::finished (GdkEvent* /*event*/, bool movement_occurred)
{
	if (movement_occurred && _editor->session()) {
		/* make sure we stop */
		_editor->session()->request_transport_speed (0.0);
	}
}

void
ScrubDrag::aborted ()
{
	/* XXX: TODO */
}

SelectionDrag::SelectionDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
	: Drag (e, i)
	, _operation (o)
	, _copy (false)
	, _original_pointer_time_axis (-1)
	, _last_pointer_time_axis (-1)
{

}

void
SelectionDrag::start_grab (GdkEvent* event, Gdk::Cursor*)
{
	nframes64_t start = 0;
	nframes64_t end = 0;

	if (_editor->session() == 0) {
		return;
	}

	Gdk::Cursor* cursor = 0;

	switch (_operation) {
	case CreateSelection:
		if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
			_copy = true;
		} else {
			_copy = false;
		}
		cursor = _editor->selector_cursor;
		Drag::start_grab (event, cursor);
		break;

	case SelectionStartTrim:
		if (_editor->clicked_axisview) {
			_editor->clicked_axisview->order_selection_trims (_item, true);
		}
		Drag::start_grab (event, _editor->left_side_trim_cursor);
		start = _editor->selection->time[_editor->clicked_selection].start;
		_pointer_frame_offset = grab_frame() - start;
		break;

	case SelectionEndTrim:
		if (_editor->clicked_axisview) {
			_editor->clicked_axisview->order_selection_trims (_item, false);
		}
		Drag::start_grab (event, _editor->right_side_trim_cursor);
		end = _editor->selection->time[_editor->clicked_selection].end;
		_pointer_frame_offset = grab_frame() - end;
		break;

	case SelectionMove:
		start = _editor->selection->time[_editor->clicked_selection].start;
		Drag::start_grab (event, cursor);
		_pointer_frame_offset = grab_frame() - start;
		break;
	}

	if (_operation == SelectionMove) {
		_editor->show_verbose_time_cursor (start, 10);
	} else {
		_editor->show_verbose_time_cursor (adjusted_current_frame (event), 10);
	}

	_original_pointer_time_axis = _editor->trackview_by_y_position (_drags->current_pointer_y ()).first->order ();
}

void
SelectionDrag::motion (GdkEvent* event, bool first_move)
{
	nframes64_t start = 0;
	nframes64_t end = 0;
	nframes64_t length;

	pair<TimeAxisView*, int> const pending_time_axis = _editor->trackview_by_y_position (_drags->current_pointer_y ());
	if (pending_time_axis.first == 0) {
		return;
	}
	
	nframes64_t const pending_position = adjusted_current_frame (event);

	/* only alter selection if things have changed */

	if (pending_time_axis.first->order() == _last_pointer_time_axis && pending_position == last_pointer_frame()) {
		return;
	}

	switch (_operation) {
	case CreateSelection:
	{
		nframes64_t grab = grab_frame ();

		if (first_move) {
			_editor->snap_to (grab);
		}

		if (pending_position < grab_frame()) {
			start = pending_position;
			end = grab;
		} else {
			end = pending_position;
			start = grab;
		}

		/* first drag: Either add to the selection
		   or create a new selection
		*/

		if (first_move) {

			if (_copy) {
				/* adding to the selection */
				_editor->selection->add (_editor->clicked_axisview);
				_editor->clicked_selection = _editor->selection->add (start, end);
				_copy = false;
			} else {
				/* new selection */

				if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
					_editor->selection->set (_editor->clicked_axisview);
				}
				
				_editor->clicked_selection = _editor->selection->set (start, end);
			}
		}

		/* select the track that we're in */
		if (find (_added_time_axes.begin(), _added_time_axes.end(), pending_time_axis.first) == _added_time_axes.end()) {
			_editor->selection->add (pending_time_axis.first);
			_added_time_axes.push_back (pending_time_axis.first);
		}

		/* deselect any tracks that this drag no longer includes, being careful to only deselect
		   tracks that we selected in the first place.
		*/
		
		int min_order = min (_original_pointer_time_axis, pending_time_axis.first->order());
		int max_order = max (_original_pointer_time_axis, pending_time_axis.first->order());

		list<TimeAxisView*>::iterator i = _added_time_axes.begin();
		while (i != _added_time_axes.end()) {

			list<TimeAxisView*>::iterator tmp = i;
			++tmp;
			
			if ((*i)->order() < min_order || (*i)->order() > max_order) {
				_editor->selection->remove (*i);
				_added_time_axes.remove (*i);
			}

			i = tmp;
		}

	}
	break;

	case SelectionStartTrim:

		start = _editor->selection->time[_editor->clicked_selection].start;
		end = _editor->selection->time[_editor->clicked_selection].end;

		if (pending_position > end) {
			start = end;
		} else {
			start = pending_position;
		}
		break;

	case SelectionEndTrim:

		start = _editor->selection->time[_editor->clicked_selection].start;
		end = _editor->selection->time[_editor->clicked_selection].end;

		if (pending_position < start) {
			end = start;
		} else {
			end = pending_position;
		}

		break;

	case SelectionMove:

		start = _editor->selection->time[_editor->clicked_selection].start;
		end = _editor->selection->time[_editor->clicked_selection].end;

		length = end - start;

		start = pending_position;
		_editor->snap_to (start);

		end = start + length;

		break;
	}

	if (event->button.x >= _editor->horizontal_position() + _editor->_canvas_width) {
		_editor->start_canvas_autoscroll (1, 0);
	}

	if (start != end) {
		_editor->selection->replace (_editor->clicked_selection, start, end);
	}

	if (_operation == SelectionMove) {
		_editor->show_verbose_time_cursor(start, 10);
	} else {
		_editor->show_verbose_time_cursor(pending_position, 10);
	}
}

void
SelectionDrag::finished (GdkEvent* event, bool movement_occurred)
{
	Session* s = _editor->session();

	if (movement_occurred) {
		motion (event, false);
		/* XXX this is not object-oriented programming at all. ick */
		if (_editor->selection->time.consolidate()) {
			_editor->selection->TimeChanged ();
		}

		/* XXX what if its a music time selection? */
		if (s && (s->config.get_auto_play() || (s->get_play_range() && s->transport_rolling()))) {
			s->request_play_range (&_editor->selection->time, true);
		}


	} else {
		/* just a click, no pointer movement.*/

		if (Keyboard::no_modifier_keys_pressed (&event->button)) {
			_editor->selection->clear_time();
		}

		if (_editor->clicked_axisview && !_editor->selection->selected (_editor->clicked_axisview)) {
			_editor->selection->set (_editor->clicked_axisview);
		}
		
		if (s && s->get_play_range () && s->transport_rolling()) {
			s->request_stop (false, false);
		}

	}

	_editor->stop_canvas_autoscroll ();
}

void
SelectionDrag::aborted ()
{
	/* XXX: TODO */
}

RangeMarkerBarDrag::RangeMarkerBarDrag (Editor* e, ArdourCanvas::Item* i, Operation o)
	: Drag (e, i),
	  _operation (o),
	  _copy (false)
{
	_drag_rect = new ArdourCanvas::SimpleRect (*_editor->time_line_group, 0.0, 0.0, 0.0, _editor->physical_screen_height);
	_drag_rect->hide ();

	_drag_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_RangeDragRect.get();
	_drag_rect->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_RangeDragRect.get();
}

void
RangeMarkerBarDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
{
	if (_editor->session() == 0) {
		return;
	}

	Gdk::Cursor* cursor = 0;

	if (!_editor->temp_location) {
		_editor->temp_location = new Location;
	}

	switch (_operation) {
	case CreateRangeMarker:
	case CreateTransportMarker:
	case CreateCDMarker:

		if (Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier)) {
			_copy = true;
		} else {
			_copy = false;
		}
		cursor = _editor->selector_cursor;
		break;
	}

	Drag::start_grab (event, cursor);

	_editor->show_verbose_time_cursor (adjusted_current_frame (event), 10);
}

void
RangeMarkerBarDrag::motion (GdkEvent* event, bool first_move)
{
	nframes64_t start = 0;
	nframes64_t end = 0;
	ArdourCanvas::SimpleRect *crect;

	switch (_operation) {
	case CreateRangeMarker:
		crect = _editor->range_bar_drag_rect;
		break;
	case CreateTransportMarker:
		crect = _editor->transport_bar_drag_rect;
		break;
	case CreateCDMarker:
		crect = _editor->cd_marker_bar_drag_rect;
		break;
	default:
		cerr << "Error: unknown range marker op passed to Editor::drag_range_markerbar_op ()" << endl;
		return;
		break;
	}

	nframes64_t const pf = adjusted_current_frame (event);

	if (_operation == CreateRangeMarker || _operation == CreateTransportMarker || _operation == CreateCDMarker) {
		nframes64_t grab = grab_frame ();
		_editor->snap_to (grab);
		
		if (pf < grab_frame()) {
			start = pf;
			end = grab;
		} else {
			end = pf;
			start = grab;
		}

		/* first drag: Either add to the selection
		   or create a new selection.
		*/

		if (first_move) {

			_editor->temp_location->set (start, end);

			crect->show ();

			update_item (_editor->temp_location);
			_drag_rect->show();
			//_drag_rect->raise_to_top();

		}
	}

	if (event->button.x >= _editor->horizontal_position() + _editor->_canvas_width) {
		_editor->start_canvas_autoscroll (1, 0);
	}

	if (start != end) {
		_editor->temp_location->set (start, end);

		double x1 = _editor->frame_to_pixel (start);
		double x2 = _editor->frame_to_pixel (end);
		crect->property_x1() = x1;
		crect->property_x2() = x2;

		update_item (_editor->temp_location);
	}

	_editor->show_verbose_time_cursor (pf, 10);

}

void
RangeMarkerBarDrag::finished (GdkEvent* event, bool movement_occurred)
{
	Location * newloc = 0;
	string rangename;
	int flags;

	if (movement_occurred) {
		motion (event, false);
		_drag_rect->hide();

		switch (_operation) {
		case CreateRangeMarker:
		case CreateCDMarker:
		    {
			_editor->begin_reversible_command (_("new range marker"));
			XMLNode &before = _editor->session()->locations()->get_state();
			_editor->session()->locations()->next_available_name(rangename,"unnamed");
			if (_operation == CreateCDMarker) {
				flags = Location::IsRangeMarker | Location::IsCDMarker;
				_editor->cd_marker_bar_drag_rect->hide();
			}
			else {
				flags = Location::IsRangeMarker;
				_editor->range_bar_drag_rect->hide();
			}
			newloc = new Location(_editor->temp_location->start(), _editor->temp_location->end(), rangename, (Location::Flags) flags);
			_editor->session()->locations()->add (newloc, true);
			XMLNode &after = _editor->session()->locations()->get_state();
			_editor->session()->add_command(new MementoCommand<Locations>(*(_editor->session()->locations()), &before, &after));
			_editor->commit_reversible_command ();
			break;
		    }

		case CreateTransportMarker:
			// popup menu to pick loop or punch
			_editor->new_transport_marker_context_menu (&event->button, _item);
			break;
		}
	} else {
		/* just a click, no pointer movement. remember that context menu stuff was handled elsewhere */

		if (Keyboard::no_modifier_keys_pressed (&event->button) && _operation != CreateCDMarker) {

			nframes64_t start;
			nframes64_t end;

			_editor->session()->locations()->marks_either_side (grab_frame(), start, end);

			if (end == max_frames) {
				end = _editor->session()->current_end_frame ();
			}

			if (start == max_frames) {
				start = _editor->session()->current_start_frame ();
			}

			switch (_editor->mouse_mode) {
			case MouseObject:
				/* find the two markers on either side and then make the selection from it */
				_editor->select_all_within (start, end, 0.0f, FLT_MAX, _editor->track_views, Selection::Set);
				break;

			case MouseRange:
				/* find the two markers on either side of the click and make the range out of it */
				_editor->selection->set (start, end);
				break;

			default:
				break;
			}
		}
	}

	_editor->stop_canvas_autoscroll ();
}

void
RangeMarkerBarDrag::aborted ()
{
	/* XXX: TODO */
}

void
RangeMarkerBarDrag::update_item (Location* location)
{
	double const x1 = _editor->frame_to_pixel (location->start());
	double const x2 = _editor->frame_to_pixel (location->end());

	_drag_rect->property_x1() = x1;
	_drag_rect->property_x2() = x2;
}

void
MouseZoomDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
{
	Drag::start_grab (event, _editor->zoom_cursor);
	_editor->show_verbose_time_cursor (adjusted_current_frame (event), 10);
}

void
MouseZoomDrag::motion (GdkEvent* event, bool first_move)
{
	nframes64_t start;
	nframes64_t end;

	nframes64_t const pf = adjusted_current_frame (event);

	nframes64_t grab = grab_frame ();
	_editor->snap_to_with_modifier (grab, event);

	/* base start and end on initial click position */
	if (pf < grab) {
		start = pf;
		end = grab;
	} else {
		end = pf;
		start = grab;
	}

	if (start != end) {

		if (first_move) {
			_editor->zoom_rect->show();
			_editor->zoom_rect->raise_to_top();
		}

		_editor->reposition_zoom_rect(start, end);

		_editor->show_verbose_time_cursor (pf, 10);
	}
}

void
MouseZoomDrag::finished (GdkEvent* event, bool movement_occurred)
{
	if (movement_occurred) {
		motion (event, false);

		if (grab_frame() < last_pointer_frame()) {
			_editor->temporal_zoom_by_frame (grab_frame(), last_pointer_frame(), "mouse zoom");
		} else {
			_editor->temporal_zoom_by_frame (last_pointer_frame(), grab_frame(), "mouse zoom");
		}
	} else {
		_editor->temporal_zoom_to_frame (false, grab_frame());
		/*
		temporal_zoom_step (false);
		center_screen (grab_frame());
		*/
	}

	_editor->zoom_rect->hide();
}

void
MouseZoomDrag::aborted ()
{
	_editor->zoom_rect->hide ();
}

NoteDrag::NoteDrag (Editor* e, ArdourCanvas::Item* i)
	: Drag (e, i)
{
	CanvasNoteEvent*     cnote = dynamic_cast<CanvasNoteEvent*>(_item);
	region = &cnote->region_view();
}

void
NoteDrag::start_grab (GdkEvent* event, Gdk::Cursor *)
{
	Drag::start_grab (event);

	drag_delta_x = 0;
	drag_delta_note = 0;

	double event_x;
	double event_y;

	event_x = _drags->current_pointer_x();
	event_y = _drags->current_pointer_y();

	_item->property_parent().get_value()->w2i(event_x, event_y);

	last_x = region->snap_to_pixel(event_x);
	last_y = event_y;

	CanvasNoteEvent* cnote = dynamic_cast<CanvasNoteEvent*>(_item);

	if (!(was_selected = cnote->selected())) {

		/* tertiary-click means extend selection - we'll do that on button release,
		   so don't add it here, because otherwise we make it hard to figure
		   out the "extend-to" range.
		*/

		bool extend = Keyboard::modifier_state_equals (event->button.state, Keyboard::TertiaryModifier);

		if (!extend) {
			bool add = Keyboard::modifier_state_equals (event->button.state, Keyboard::PrimaryModifier);

			if (add) {
				region->note_selected (cnote, true);
			} else {
				region->unique_select (cnote);
			}
		}
	}
}

void
NoteDrag::motion (GdkEvent*, bool)
{
	MidiStreamView* streamview = region->midi_stream_view();
	double event_x;
	double event_y;

	event_x = _drags->current_pointer_x();
	event_y = _drags->current_pointer_y();

	_item->property_parent().get_value()->w2i(event_x, event_y);

	event_x = region->snap_to_pixel(event_x);

	double dx     = event_x - last_x;
	double dy     = event_y - last_y;
	last_x = event_x;

	drag_delta_x += dx;

	// Snap to note rows

	if (abs (dy) < streamview->note_height()) {
		dy = 0.0;
	} else {
		int8_t this_delta_note;
		if (dy > 0) {
			this_delta_note = (int8_t)ceil(dy / streamview->note_height() / 2.0);
		} else {
			this_delta_note = (int8_t)floor(dy / streamview->note_height() / 2.0);
		}
		drag_delta_note -= this_delta_note;
		dy = streamview->note_height() * this_delta_note;
		last_y = last_y + dy;
	}

	if (dx || dy) {
                
		CanvasNoteEvent* cnote = dynamic_cast<CanvasNoteEvent*>(_item);
                Evoral::MusicalTime new_time;
                
                if (drag_delta_x) {
                        nframes64_t start_frames = region->beats_to_frames(cnote->note()->time());
                        if (drag_delta_x >= 0) {
                                start_frames += region->snap_frame_to_frame(_editor->pixel_to_frame(drag_delta_x));
                        } else {
                                start_frames -= region->snap_frame_to_frame(_editor->pixel_to_frame(-drag_delta_x));
                        }
                        new_time = region->frames_to_beats(start_frames);
                } else {
                        new_time = cnote->note()->time();
                }
                
                boost::shared_ptr<Evoral::Note<Evoral::MusicalTime> > check_note (
                        new Evoral::Note<Evoral::MusicalTime> (cnote->note()->channel(), 
                                                               new_time,
                                                               cnote->note()->length(), 
                                                               cnote->note()->note() + drag_delta_note,
                                                               cnote->note()->velocity()));

		region->move_selection (dx, dy);

                char buf[12];
                snprintf (buf, sizeof (buf), "%s (%d)", Evoral::midi_note_name (cnote->note()->note()).c_str(),
                          (int) floor ((cnote->note()->note() + drag_delta_note)));
		_editor->show_verbose_canvas_cursor_with (buf);
        }
}

void
NoteDrag::finished (GdkEvent* ev, bool moved)
{
	ArdourCanvas::CanvasNote* cnote = dynamic_cast<ArdourCanvas::CanvasNote*>(_item);

	if (!moved) {
		if (_editor->current_mouse_mode() == Editing::MouseObject) {

			if (was_selected) {
				bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);
				if (add) {
					region->note_deselected (cnote);
				}
			} else {
				bool extend = Keyboard::modifier_state_equals (ev->button.state, Keyboard::TertiaryModifier);
				bool add = Keyboard::modifier_state_equals (ev->button.state, Keyboard::PrimaryModifier);

				if (!extend && !add && region->selection_size() > 1) {
					region->unique_select(cnote);
				} else if (extend) {
					region->note_selected (cnote, true, true);
				} else {
					/* it was added during button press */
				}
			}
		}
	} else {
		region->note_dropped (cnote, drag_delta_x, drag_delta_note);
	}
}

void
NoteDrag::aborted ()
{
	/* XXX: TODO */
}

AutomationRangeDrag::AutomationRangeDrag (Editor* e, ArdourCanvas::Item* i, list<AudioRange> const & r)
	: Drag (e, i)
	, _ranges (r)
	, _nothing_to_drag (false)
{
	_atav = reinterpret_cast<AutomationTimeAxisView*> (_item->get_data ("trackview"));
	assert (_atav);

	_line = _atav->line ();
}

void
AutomationRangeDrag::start_grab (GdkEvent* event, Gdk::Cursor* cursor)
{
	Drag::start_grab (event, cursor);

	list<ControlPoint*> points;

	XMLNode* state = &_line->get_state ();
	
	if (_ranges.empty()) {
		
		uint32_t const N = _line->npoints ();
		for (uint32_t i = 0; i < N; ++i) {
			points.push_back (_line->nth (i));
		}
		
	} else {

		boost::shared_ptr<AutomationList> the_list = _line->the_list ();
		for (list<AudioRange>::const_iterator j = _ranges.begin(); j != _ranges.end(); ++j) {

			/* fade into and out of the region that we're dragging;
			   64 samples length plucked out of thin air.
			*/
			nframes64_t const h = (j->start + j->end) / 2;
			nframes64_t a = j->start + 64;
			if (a > h) {
				a = h;
			}
			nframes64_t b = j->end - 64;
			if (b < h) {
				b = h;
			}
			
			the_list->add (j->start, the_list->eval (j->start));
			_line->add_always_in_view (j->start);
			the_list->add (a, the_list->eval (a));
			_line->add_always_in_view (a);
			the_list->add (b, the_list->eval (b));
			_line->add_always_in_view (b);
			the_list->add (j->end, the_list->eval (j->end));
			_line->add_always_in_view (j->end);
		}

		uint32_t const N = _line->npoints ();
		for (uint32_t i = 0; i < N; ++i) {

			ControlPoint* p = _line->nth (i);

			list<AudioRange>::const_iterator j = _ranges.begin ();
			while (j != _ranges.end() && (j->start >= (*p->model())->when || j->end <= (*p->model())->when)) {
				++j;
			}

			if (j != _ranges.end()) {
				points.push_back (p);
			}
		}
	}

	if (points.empty()) {
		_nothing_to_drag = true;
		return;
	}

	_line->start_drag_multiple (points, 1 - (_drags->current_pointer_y() / _line->height ()), state);
}

void
AutomationRangeDrag::motion (GdkEvent* event, bool first_move)
{
	if (_nothing_to_drag) {
		return;
	}
	
	float const f = 1 - (_drags->current_pointer_y() / _line->height());

	/* we are ignoring x position for this drag, so we can just pass in anything */
	_line->drag_motion (0, f, true, false);
}

void
AutomationRangeDrag::finished (GdkEvent* event, bool)
{
	if (_nothing_to_drag) {
		return;
	}
	
	motion (event, false);
	_line->end_drag ();
	_line->clear_always_in_view ();
}

void
AutomationRangeDrag::aborted ()
{
	_line->clear_always_in_view ();
	_line->reset ();
}

DraggingView::DraggingView (RegionView* v)
	: view (v)
{
	initial_y = v->get_canvas_group()->property_y ();
}
