/*
 * ***** BEGIN GPL LICENSE BLOCK *****
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * The Original Code is Copyright (C) 2011 Blender Foundation.
 * All rights reserved.
 *
 *
 * Contributor(s): Blender Foundation,
 *                 Sergey Sharybin
 *
 * ***** END GPL LICENSE BLOCK *****
 */

/** \file blender/editors/space_clip/tracking_ops.c
 *  \ingroup spclip
 */

#include "MEM_guardedalloc.h"

#include "DNA_camera_types.h"
#include "DNA_constraint_types.h"
#include "DNA_gpencil_types.h"
#include "DNA_movieclip_types.h"
#include "DNA_object_types.h"   /* SELECT */
#include "DNA_scene_types.h"

#include "BLI_utildefines.h"
#include "BLI_math.h"
#include "BLI_listbase.h"
#include "BLI_rect.h"
#include "BLI_blenlib.h"

#include "BKE_main.h"
#include "BKE_context.h"
#include "BKE_constraint.h"
#include "BKE_movieclip.h"
#include "BKE_tracking.h"
#include "BKE_global.h"
#include "BKE_depsgraph.h"
#include "BKE_object.h"
#include "BKE_report.h"
#include "BKE_library.h"
#include "BKE_sound.h"

#include "WM_api.h"
#include "WM_types.h"

#include "ED_screen.h"
#include "ED_clip.h"

#include "IMB_imbuf_types.h"
#include "IMB_imbuf.h"

#include "UI_interface.h"

#include "RNA_access.h"
#include "RNA_define.h"

#include "BLF_translation.h"

#include "PIL_time.h"

#include "UI_view2d.h"

#include "clip_intern.h"    // own include

/********************** add marker operator *********************/

static bool add_marker(const bContext *C, float x, float y)
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	MovieTracking *tracking = &clip->tracking;
	ListBase *tracksbase = BKE_tracking_get_active_tracks(tracking);
	ListBase *plane_tracks_base = BKE_tracking_get_active_plane_tracks(tracking);
	MovieTrackingTrack *track;
	int width, height;
	int framenr = ED_space_clip_get_clip_frame_number(sc);

	ED_space_clip_get_size(sc, &width, &height);

	if (width == 0 || height == 0) {
		return false;
	}

	track = BKE_tracking_track_add(tracking, tracksbase, x, y, framenr, width, height);

	BKE_tracking_track_select(tracksbase, track, TRACK_AREA_ALL, 0);
	BKE_tracking_plane_tracks_deselect_all(plane_tracks_base);

	clip->tracking.act_track = track;
	clip->tracking.act_plane_track = NULL;

	return true;
}

static int add_marker_exec(bContext *C, wmOperator *op)
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	float pos[2];

	RNA_float_get_array(op->ptr, "location", pos);

	if (!add_marker(C, pos[0], pos[1])) {
		return OPERATOR_CANCELLED;
	}

	/* reset offset from locked position, so frame jumping wouldn't be so confusing */
	sc->xlockof = 0;
	sc->ylockof = 0;

	WM_event_add_notifier(C, NC_MOVIECLIP | NA_EDITED, clip);

	return OPERATOR_FINISHED;
}

static int add_marker_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	ARegion *ar = CTX_wm_region(C);

	if (!RNA_struct_property_is_set(op->ptr, "location")) {
		/* If location is not set, use mouse positio nas default. */
		float co[2];
		ED_clip_mouse_pos(sc, ar, event->mval, co);
		RNA_float_set_array(op->ptr, "location", co);
	}

	return add_marker_exec(C, op);
}

void CLIP_OT_add_marker(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Add Marker";
	ot->idname = "CLIP_OT_add_marker";
	ot->description = "Place new marker at specified location";

	/* api callbacks */
	ot->invoke = add_marker_invoke;
	ot->exec = add_marker_exec;
	ot->poll = ED_space_clip_tracking_poll;

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

	/* properties */
	RNA_def_float_vector(ot->srna, "location", 2, NULL, -FLT_MAX, FLT_MAX,
	                     "Location", "Location of marker on frame", -1.0f, 1.0f);
}

/********************** add marker operator *********************/

static int add_marker_at_click_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
{
	ED_area_headerprint(CTX_wm_area(C), IFACE_("Use LMB click to define location where place the marker"));

	/* add modal handler for ESC */
	WM_event_add_modal_handler(C, op);

	return OPERATOR_RUNNING_MODAL;
}

static int add_marker_at_click_modal(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	ARegion *ar = CTX_wm_region(C);
	float pos[2];

	switch (event->type) {
		case MOUSEMOVE:
			return OPERATOR_RUNNING_MODAL;

		case LEFTMOUSE:
			ED_area_headerprint(CTX_wm_area(C), NULL);

			ED_clip_point_stable_pos(sc, ar,
			                         event->x - ar->winrct.xmin,
			                         event->y - ar->winrct.ymin,
			                         &pos[0], &pos[1]);

			if (!add_marker(C, pos[0], pos[1]))
				return OPERATOR_CANCELLED;

			WM_event_add_notifier(C, NC_MOVIECLIP | NA_EDITED, clip);
			return OPERATOR_FINISHED;

		case ESCKEY:
			ED_area_headerprint(CTX_wm_area(C), NULL);
			return OPERATOR_CANCELLED;
	}

	return OPERATOR_PASS_THROUGH;
}

void CLIP_OT_add_marker_at_click(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Add Marker at Click";
	ot->idname = "CLIP_OT_add_marker_at_click";
	ot->description = "Place new marker at the desired (clicked) position";

	/* api callbacks */
	ot->invoke = add_marker_at_click_invoke;
	ot->poll = ED_space_clip_tracking_poll;
	ot->modal = add_marker_at_click_modal;

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING;
}

/********************** delete track operator *********************/

static int delete_track_exec(bContext *C, wmOperator *UNUSED(op))
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	MovieTracking *tracking = &clip->tracking;
	ListBase *tracksbase = BKE_tracking_get_active_tracks(tracking);
	ListBase *plane_tracks_base = BKE_tracking_get_active_plane_tracks(tracking);
	MovieTrackingTrack *track = tracksbase->first, *next;
	MovieTrackingPlaneTrack *plane_track, *next_plane_track;
	bool changed = false;

	/* Delete selected plane tracks. */
	for (plane_track = plane_tracks_base->first;
	     plane_track;
	     plane_track = next_plane_track)
	{
		next_plane_track = plane_track->next;

		if (PLANE_TRACK_VIEW_SELECTED(plane_track)) {
			BKE_tracking_plane_track_free(plane_track);
			BLI_freelinkN(plane_tracks_base, plane_track);
			changed = true;
		}
	}

	/* Remove selected point tracks (they'll also be removed from planes which uses them). */
	while (track) {
		next = track->next;

		if (TRACK_VIEW_SELECTED(sc, track)) {
			clip_delete_track(C, clip, track);
			changed = true;
		}

		track = next;
	}

	/* nothing selected now, unlock view so it can be scrolled nice again */
	sc->flag &= ~SC_LOCK_SELECTION;

	if (changed)
		WM_event_add_notifier(C, NC_MOVIECLIP | NA_EDITED, clip);

	return OPERATOR_FINISHED;
}

void CLIP_OT_delete_track(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Delete Track";
	ot->idname = "CLIP_OT_delete_track";
	ot->description = "Delete selected tracks";

	/* api callbacks */
	ot->invoke = WM_operator_confirm;
	ot->exec = delete_track_exec;
	ot->poll = ED_space_clip_tracking_poll;

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}

/********************** delete marker operator *********************/

static int delete_marker_exec(bContext *C, wmOperator *UNUSED(op))
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	ListBase *tracksbase = BKE_tracking_get_active_tracks(&clip->tracking);
	ListBase *plane_tracks_base = BKE_tracking_get_active_plane_tracks(&clip->tracking);
	MovieTrackingTrack *track = tracksbase->first, *next;
	MovieTrackingPlaneTrack *plane_track, *plane_track_next;
	int framenr = ED_space_clip_get_clip_frame_number(sc);
	bool has_selection = false;
	bool changed = false;

	while (track) {
		next = track->next;

		if (TRACK_VIEW_SELECTED(sc, track)) {
			MovieTrackingMarker *marker = BKE_tracking_marker_get_exact(track, framenr);

			if (marker) {
				has_selection |= track->markersnr > 1;

				clip_delete_marker(C, clip, track, marker);
				changed = true;
			}
		}

		track = next;
	}

	for (plane_track = plane_tracks_base->first;
	     plane_track;
	     plane_track = plane_track_next)
	{
		plane_track_next = plane_track->next;

		if (PLANE_TRACK_VIEW_SELECTED(plane_track)) {
			MovieTrackingPlaneMarker *plane_marker = BKE_tracking_plane_marker_get_exact(plane_track, framenr);

			if (plane_marker) {
				if (plane_track->markersnr == 1) {
					BKE_tracking_plane_track_free(plane_track);
					BLI_freelinkN(plane_tracks_base, plane_track);
				}
				else {
					BKE_tracking_plane_marker_delete(plane_track, framenr);
				}

				changed = true;
			}
		}
	}

	if (!has_selection) {
		/* nothing selected now, unlock view so it can be scrolled nice again */
		sc->flag &= ~SC_LOCK_SELECTION;
	}

	if (!changed)
		return OPERATOR_CANCELLED;

	return OPERATOR_FINISHED;
}

void CLIP_OT_delete_marker(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Delete Marker";
	ot->idname = "CLIP_OT_delete_marker";
	ot->description = "Delete marker for current frame from selected tracks";

	/* api callbacks */
	ot->invoke = WM_operator_confirm;
	ot->exec = delete_marker_exec;
	ot->poll = ED_space_clip_tracking_poll;

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}

/********************** slide marker operator *********************/

#define SLIDE_ACTION_POS       0
#define SLIDE_ACTION_SIZE      1
#define SLIDE_ACTION_OFFSET    2
#define SLIDE_ACTION_TILT_SIZE 3

typedef struct {
	short area, action;
	MovieTrackingTrack *track;
	MovieTrackingMarker *marker;

	int mval[2];
	int width, height;
	float *min, *max, *pos, *offset, (*corners)[2];
	float spos[2];

	bool lock, accurate;

	/* data to restore on cancel */
	float old_search_min[2], old_search_max[2], old_pos[2], old_offset[2];
	float old_corners[4][2];
	float (*old_markers)[2];
} SlideMarkerData;

static void slide_marker_tilt_slider(MovieTrackingMarker *marker, float slider[2])
{
	add_v2_v2v2(slider, marker->pattern_corners[1], marker->pattern_corners[2]);
	add_v2_v2(slider, marker->pos);
}

static SlideMarkerData *create_slide_marker_data(SpaceClip *sc, MovieTrackingTrack *track,
                                                 MovieTrackingMarker *marker, const wmEvent *event,
                                                 int area, int corner, int action, int width, int height)
{
	SlideMarkerData *data = MEM_callocN(sizeof(SlideMarkerData), "slide marker data");
	int framenr = ED_space_clip_get_clip_frame_number(sc);

	marker = BKE_tracking_marker_ensure(track, framenr);

	data->area = area;
	data->action = action;
	data->track = track;
	data->marker = marker;

	if (area == TRACK_AREA_POINT) {
		data->pos = marker->pos;
		data->offset = track->offset;
	}
	else if (area == TRACK_AREA_PAT) {
		if (action == SLIDE_ACTION_SIZE) {
			data->corners = marker->pattern_corners;
		}
		else if (action == SLIDE_ACTION_OFFSET) {
			int a;

			data->pos = marker->pos;
			data->offset = track->offset;

			data->old_markers = MEM_callocN(sizeof(*data->old_markers) * track->markersnr, "slide marekrs");
			for (a = 0; a < track->markersnr; a++)
				copy_v2_v2(data->old_markers[a], track->markers[a].pos);
		}
		else if (action == SLIDE_ACTION_POS) {
			data->corners = marker->pattern_corners;
			data->pos = marker->pattern_corners[corner];
			copy_v2_v2(data->spos, data->pos);
		}
		else if (action == SLIDE_ACTION_TILT_SIZE) {
			data->corners = marker->pattern_corners;
			slide_marker_tilt_slider(marker, data->spos);
		}
	}
	else if (area == TRACK_AREA_SEARCH) {
		data->min = marker->search_min;
		data->max = marker->search_max;
	}

	data->mval[0] = event->mval[0];
	data->mval[1] = event->mval[1];

	data->width = width;
	data->height = height;

	if (action == SLIDE_ACTION_SIZE)
		data->lock = true;

	/* backup marker's settings */
	memcpy(data->old_corners, marker->pattern_corners, sizeof(data->old_corners));
	copy_v2_v2(data->old_search_min, marker->search_min);
	copy_v2_v2(data->old_search_max, marker->search_max);
	copy_v2_v2(data->old_pos, marker->pos);
	copy_v2_v2(data->old_offset, track->offset);

	return data;
}

static int mouse_on_slide_zone(SpaceClip *sc, MovieTrackingMarker *marker,
                               int area, const float co[2], const float slide_zone[2],
                               float padding, int width, int height)
{
	const float size = 12.0f;
	float min[2], max[2];
	float dx, dy;

	if (area == TRACK_AREA_SEARCH) {
		copy_v2_v2(min, marker->search_min);
		copy_v2_v2(max, marker->search_max);
	}
	else {
		BKE_tracking_marker_pattern_minmax(marker, min, max);
	}

	min[0] -= padding / width;
	min[1] -= padding / height;
	max[0] += padding / width;
	max[1] += padding / height;

	dx = size / width / sc->zoom;
	dy = size / height / sc->zoom;

	dx = min_ff(dx, (max[0] - min[0]) / 6.0f);
	dy = min_ff(dy, (max[1] - min[1]) / 6.0f);

	return IN_RANGE_INCL(co[0], slide_zone[0] - dx, slide_zone[0] + dx) &&
	       IN_RANGE_INCL(co[1], slide_zone[1] - dy, slide_zone[1] + dy);
}

static int mouse_on_corner(SpaceClip *sc, MovieTrackingMarker *marker,
                           int area, const float co[2], int corner, float padding,
                           int width, int height)
{
	float min[2], max[2], crn[2];

	if (area == TRACK_AREA_SEARCH) {
		copy_v2_v2(min, marker->search_min);
		copy_v2_v2(max, marker->search_max);
	}
	else {
		BKE_tracking_marker_pattern_minmax(marker, min, max);
	}

	min[0] -= padding / width;
	min[1] -= padding / height;
	max[0] += padding / width;
	max[1] += padding / height;

	if (corner == 0) {
		crn[0] = marker->pos[0] + max[0];
		crn[1] = marker->pos[1] + min[1];
	}
	else {
		crn[0] = marker->pos[0] + min[0];
		crn[1] = marker->pos[1] + max[1];
	}

	return mouse_on_slide_zone(sc, marker, area, co, crn, padding, width, height);
}

static int get_mouse_pattern_corner(SpaceClip *sc, MovieTrackingMarker *marker, float co[2], int width, int height)
{
	int i, next;
	float len = FLT_MAX, dx, dy;

	for (i = 0; i < 4; i++) {
		float cur_len_sq;

		next = (i + 1) % 4;

		cur_len_sq = len_squared_v2v2(marker->pattern_corners[i], marker->pattern_corners[next]);

		len = min_ff(cur_len_sq, len);
	}
	len = sqrtf(len);

	dx = 12.0f / width / sc->zoom;
	dy = 12.0f / height / sc->zoom;

	dx = min_ff(dx, len * 2.0f / 3.0f);
	dy = min_ff(dy, len * width / height * 2.0f / 3.0f);

	for (i = 0; i < 4; i++) {
		float crn[2];
		int inside;

		add_v2_v2v2(crn, marker->pattern_corners[i], marker->pos);

		inside = IN_RANGE_INCL(co[0], crn[0] - dx, crn[0] + dx) &&
		         IN_RANGE_INCL(co[1], crn[1] - dy, crn[1] + dy);

		if (inside)
			return i;
	}

	return -1;
}

static int mouse_on_offset(SpaceClip *sc, MovieTrackingTrack *track, MovieTrackingMarker *marker,
                           const float co[2], int width, int height)
{
	float pos[2], dx, dy;
	float pat_min[2], pat_max[2];

	BKE_tracking_marker_pattern_minmax(marker, pat_min, pat_max);

	add_v2_v2v2(pos, marker->pos, track->offset);

	dx = 12.0f / width / sc->zoom;
	dy = 12.0f / height / sc->zoom;

	dx = min_ff(dx, (pat_max[0] - pat_min[0]) / 2.0f);
	dy = min_ff(dy, (pat_max[1] - pat_min[1]) / 2.0f);

	return co[0] >= pos[0] - dx && co[0] <= pos[0] + dx && co[1] >= pos[1] - dy && co[1] <= pos[1] + dy;
}

static int mouse_on_tilt(SpaceClip *sc, MovieTrackingMarker *marker, float co[2], int width, int height)
{
	float slider[2];

	slide_marker_tilt_slider(marker, slider);

	return mouse_on_slide_zone(sc, marker, TRACK_AREA_PAT, co, slider, 0.0f, width, height);
}

static bool slide_check_corners(float (*corners)[2])
{
	int i, next, prev;
	float cross = 0.0f;
	float p[2] = {0.0f, 0.0f};

	if (!isect_point_quad_v2(p, corners[0], corners[1], corners[2], corners[3]))
		return false;

	for (i = 0; i < 4; i++) {
		float v1[2], v2[2], cur_cross;

		next = (i + 1) % 4;
		prev = (4 + i - 1) % 4;

		sub_v2_v2v2(v1, corners[i], corners[prev]);
		sub_v2_v2v2(v2, corners[next], corners[i]);

		cur_cross = cross_v2v2(v1, v2);

		if (fabsf(cur_cross) > FLT_EPSILON) {
			if (cross == 0.0f) {
				cross = cur_cross;
			}
			else if (cross * cur_cross < 0.0f) {
				return false;
			}
		}
	}

	return true;
}

static void hide_cursor(bContext *C)
{
	wmWindow *win = CTX_wm_window(C);

	WM_cursor_set(win, CURSOR_NONE);
}

static void show_cursor(bContext *C)
{
	wmWindow *win = CTX_wm_window(C);

	WM_cursor_set(win, CURSOR_STD);
}

MovieTrackingTrack *tracking_marker_check_slide(bContext *C, const wmEvent *event, int *area_r, int *action_r, int *corner_r)
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	ARegion *ar = CTX_wm_region(C);

	MovieClip *clip = ED_space_clip_get_clip(sc);
	MovieTrackingTrack *track;
	int width, height;
	float co[2];
	ListBase *tracksbase = BKE_tracking_get_active_tracks(&clip->tracking);
	int framenr = ED_space_clip_get_clip_frame_number(sc);
	int action = -1, area = 0, corner = -1;

	ED_space_clip_get_size(sc, &width, &height);

	if (width == 0 || height == 0)
		return NULL;

	ED_clip_mouse_pos(sc, ar, event->mval, co);

	track = tracksbase->first;
	while (track) {
		if (TRACK_VIEW_SELECTED(sc, track) && (track->flag & TRACK_LOCKED) == 0) {
			MovieTrackingMarker *marker = BKE_tracking_marker_get(track, framenr);
			bool ok = false;

			if ((marker->flag & MARKER_DISABLED) == 0) {
				if (mouse_on_offset(sc, track, marker, co, width, height)) {
					area = TRACK_AREA_POINT;
					action = SLIDE_ACTION_POS;
					ok = true;
				}

				if (!ok && (sc->flag & SC_SHOW_MARKER_SEARCH)) {
					if (mouse_on_corner(sc, marker, TRACK_AREA_SEARCH, co, 1, 0.0f, width, height)) {
						area = TRACK_AREA_SEARCH;
						action = SLIDE_ACTION_OFFSET;
						ok = true;
					}
					else if (mouse_on_corner(sc, marker, TRACK_AREA_SEARCH, co, 0, 0.0f, width, height)) {
						area = TRACK_AREA_SEARCH;
						action = SLIDE_ACTION_SIZE;
						ok = true;
					}
				}

				if (!ok && (sc->flag & SC_SHOW_MARKER_PATTERN)) {
					int current_corner = get_mouse_pattern_corner(sc, marker, co, width, height);

					if (current_corner != -1) {
						area = TRACK_AREA_PAT;
						action = SLIDE_ACTION_POS;
						corner = current_corner;
						ok = true;
					}
					else {
#if 0
						/* TODO: disable for now, needs better approaches for visualization */

						if (mouse_on_corner(sc, marker, TRACK_AREA_PAT, co, 1, 12.0f, width, height)) {
							area = TRACK_AREA_PAT;
							action = SLIDE_ACTION_OFFSET;
							ok = true;
						}
						if (!ok && mouse_on_corner(sc, marker, TRACK_AREA_PAT, co, 0, 12.0f, width, height)) {
							area = TRACK_AREA_PAT;
							action = SLIDE_ACTION_SIZE;
							ok = true;
						}
#endif
						if (!ok && mouse_on_tilt(sc, marker, co, width, height)) {
							area = TRACK_AREA_PAT;
							action = SLIDE_ACTION_TILT_SIZE;
							ok = true;
						}
					}
				}

				if (ok) {
					if (area_r)
						*area_r = area;

					if (action_r)
						*action_r = action;

					if (corner_r)
						*corner_r = corner;

					return track;
				}
			}
		}

		track = track->next;
	}

	return NULL;
}

static void *slide_marker_customdata(bContext *C, const wmEvent *event)
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	ARegion *ar = CTX_wm_region(C);

	MovieTrackingTrack *track;
	int width, height;
	float co[2];
	void *customdata = NULL;
	int framenr = ED_space_clip_get_clip_frame_number(sc);
	int area, action, corner;

	ED_space_clip_get_size(sc, &width, &height);

	if (width == 0 || height == 0)
		return NULL;

	ED_clip_mouse_pos(sc, ar, event->mval, co);

	track = tracking_marker_check_slide(C, event, &area, &action, &corner);
	if (track) {
		MovieTrackingMarker *marker = BKE_tracking_marker_get(track, framenr);

		customdata = create_slide_marker_data(sc, track, marker, event, area, corner, action, width, height);
	}

	return customdata;
}

static int slide_marker_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
	SlideMarkerData *slidedata = slide_marker_customdata(C, event);

	if (slidedata) {
		SpaceClip *sc = CTX_wm_space_clip(C);
		MovieClip *clip = ED_space_clip_get_clip(sc);
		MovieTracking *tracking = &clip->tracking;

		tracking->act_track = slidedata->track;
		tracking->act_plane_track = NULL;

		op->customdata = slidedata;

		hide_cursor(C);
		WM_event_add_modal_handler(C, op);

		WM_event_add_notifier(C, NC_GEOM | ND_SELECT, NULL);

		return OPERATOR_RUNNING_MODAL;
	}

	return OPERATOR_PASS_THROUGH;
}

static void cancel_mouse_slide(SlideMarkerData *data)
{
	MovieTrackingTrack *track = data->track;
	MovieTrackingMarker *marker = data->marker;

	memcpy(marker->pattern_corners, data->old_corners, sizeof(marker->pattern_corners));
	copy_v2_v2(marker->search_min, data->old_search_min);
	copy_v2_v2(marker->search_max, data->old_search_max);
	copy_v2_v2(marker->pos, data->old_pos);
	copy_v2_v2(track->offset, data->old_offset);

	if (data->old_markers) {
		int a;

		for (a = 0; a < data->track->markersnr; a++)
			copy_v2_v2(data->track->markers[a].pos, data->old_markers[a]);
	}
}

static void apply_mouse_slide(bContext *C, SlideMarkerData *data)
{
	if (data->area == TRACK_AREA_POINT) {
		SpaceClip *sc = CTX_wm_space_clip(C);
		MovieClip *clip = ED_space_clip_get_clip(sc);
		MovieTrackingPlaneTrack *plane_track;
		ListBase *plane_tracks_base = BKE_tracking_get_active_plane_tracks(&clip->tracking);
		int framenr = ED_space_clip_get_clip_frame_number(sc);

		for (plane_track = plane_tracks_base->first;
		     plane_track;
		     plane_track = plane_track->next)
		{
			if ((plane_track->flag & PLANE_TRACK_AUTOKEY) == 0) {
				int i;
				for (i = 0; i < plane_track->point_tracksnr; i++) {
					if (plane_track->point_tracks[i] == data->track) {
						BKE_tracking_track_plane_from_existing_motion(plane_track, framenr);
						break;
					}
				}
			}
		}
	}
}

static void free_slide_data(SlideMarkerData *data)
{
	if (data->old_markers)
		MEM_freeN(data->old_markers);

	MEM_freeN(data);
}

static int slide_marker_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	ARegion *ar = CTX_wm_region(C);

	SlideMarkerData *data = (SlideMarkerData *)op->customdata;
	float dx, dy, mdelta[2];

	switch (event->type) {
		case LEFTCTRLKEY:
		case RIGHTCTRLKEY:
		case LEFTSHIFTKEY:
		case RIGHTSHIFTKEY:
			if (data->action == SLIDE_ACTION_SIZE)
				if (ELEM(event->type, LEFTCTRLKEY, RIGHTCTRLKEY))
					data->lock = event->val == KM_RELEASE;

			if (ELEM(event->type, LEFTSHIFTKEY, RIGHTSHIFTKEY))
				data->accurate = event->val == KM_PRESS;

			/* fall-through */
		case MOUSEMOVE:
			mdelta[0] = event->mval[0] - data->mval[0];
			mdelta[1] = event->mval[1] - data->mval[1];

			dx = mdelta[0] / data->width / sc->zoom;

			if (data->lock)
				dy = -dx / data->height * data->width;
			else
				dy = mdelta[1] / data->height / sc->zoom;

			if (data->accurate) {
				dx /= 5;
				dy /= 5;
			}

			if (data->area == TRACK_AREA_POINT) {
				if (data->action == SLIDE_ACTION_OFFSET) {
					data->offset[0] = data->old_offset[0] + dx;
					data->offset[1] = data->old_offset[1] + dy;
				}
				else {
					data->pos[0] = data->old_pos[0] + dx;
					data->pos[1] = data->old_pos[1] + dy;
				}

				WM_event_add_notifier(C, NC_OBJECT | ND_TRANSFORM, NULL);
				DAG_id_tag_update(&sc->clip->id, 0);
			}
			else if (data->area == TRACK_AREA_PAT) {
				if (data->action == SLIDE_ACTION_SIZE) {
					float start[2], end[2];
					float scale;

					ED_clip_point_stable_pos(sc, ar, data->mval[0], data->mval[1], &start[0], &start[1]);

					sub_v2_v2(start, data->old_pos);

					if (len_squared_v2(start) != 0.0f) {
						float mval[2];

						if (data->accurate) {
							mval[0] = data->mval[0] + (event->mval[0] - data->mval[0]) / 5.0f;
							mval[1] = data->mval[1] + (event->mval[1] - data->mval[1]) / 5.0f;
						}
						else {
							mval[0] = event->mval[0];
							mval[1] = event->mval[1];
						}

						ED_clip_point_stable_pos(sc, ar, mval[0], mval[1], &end[0], &end[1]);

						sub_v2_v2(end, data->old_pos);

						scale = len_v2(end) / len_v2(start);

						if (scale > 0.0f) {
							int a;

							for (a = 0; a < 4; a++) {
								mul_v2_v2fl(data->corners[a], data->old_corners[a], scale);
							}
						}
					}

					BKE_tracking_marker_clamp(data->marker, CLAMP_PAT_DIM);
				}
				else if (data->action == SLIDE_ACTION_OFFSET) {
					float d[2] = {dx, dy};
					int a;

					for (a = 0; a < data->track->markersnr; a++)
						add_v2_v2v2(data->track->markers[a].pos, data->old_markers[a], d);

					sub_v2_v2v2(data->offset, data->old_offset, d);
				}
				else if (data->action == SLIDE_ACTION_POS) {
					float spos[2];

					copy_v2_v2(spos, data->pos);

					data->pos[0] = data->spos[0] + dx;
					data->pos[1] = data->spos[1] + dy;

					if (!slide_check_corners(data->corners)) {
						copy_v2_v2(data->pos, spos);
					}

					/* currently only patterns are allowed to have such combination of event and data */
					BKE_tracking_marker_clamp(data->marker, CLAMP_PAT_DIM);
				}
				else if (data->action == SLIDE_ACTION_TILT_SIZE) {
					float start[2], end[2];
					float scale = 1.0f, angle = 0.0f;
					int a;
					float mval[2];

					if (data->accurate) {
						mval[0] = data->mval[0] + (event->mval[0] - data->mval[0]) / 5.0f;
						mval[1] = data->mval[1] + (event->mval[1] - data->mval[1]) / 5.0f;
					}
					else {
						mval[0] = event->mval[0];
						mval[1] = event->mval[1];
					}

					sub_v2_v2v2(start, data->spos, data->old_pos);

					ED_clip_point_stable_pos(sc, ar, mval[0], mval[1], &end[0], &end[1]);
					sub_v2_v2(end, data->old_pos);

					if (len_squared_v2(start) != 0.0f) {
						scale = len_v2(end) / len_v2(start);

						if (scale < 0.0f) {
							scale = 0.0;
						}
					}

					angle = -angle_signed_v2v2(start, end);

					for (a = 0; a < 4; a++) {
						float vec[2];

						mul_v2_v2fl(data->corners[a], data->old_corners[a], scale);

						copy_v2_v2(vec, data->corners[a]);
						vec[0] *= data->width;
						vec[1] *= data->height;

						data->corners[a][0] = (vec[0] * cosf(angle) - vec[1] * sinf(angle)) / data->width;
						data->corners[a][1] = (vec[1] * cosf(angle) + vec[0] * sinf(angle)) / data->height;
					}

					BKE_tracking_marker_clamp(data->marker, CLAMP_PAT_DIM);

				}
			}
			else if (data->area == TRACK_AREA_SEARCH) {
				if (data->action == SLIDE_ACTION_SIZE) {
					data->min[0] = data->old_search_min[0] - dx;
					data->max[0] = data->old_search_max[0] + dx;

					data->min[1] = data->old_search_min[1] + dy;
					data->max[1] = data->old_search_max[1] - dy;

					BKE_tracking_marker_clamp(data->marker, CLAMP_SEARCH_DIM);
				}
				else if (data->area == TRACK_AREA_SEARCH) {
					float d[2] = {dx, dy};

					add_v2_v2v2(data->min, data->old_search_min, d);
					add_v2_v2v2(data->max, data->old_search_max, d);
				}

				BKE_tracking_marker_clamp(data->marker, CLAMP_SEARCH_POS);
			}

			data->marker->flag &= ~MARKER_TRACKED;

			WM_event_add_notifier(C, NC_MOVIECLIP | NA_EDITED, NULL);

			break;

		case LEFTMOUSE:
			if (event->val == KM_RELEASE) {
				apply_mouse_slide(C, op->customdata);
				free_slide_data(op->customdata);

				show_cursor(C);

				return OPERATOR_FINISHED;
			}

			break;

		case ESCKEY:
			cancel_mouse_slide(op->customdata);

			free_slide_data(op->customdata);

			show_cursor(C);

			WM_event_add_notifier(C, NC_MOVIECLIP | NA_EDITED, NULL);

			return OPERATOR_CANCELLED;
	}

	return OPERATOR_RUNNING_MODAL;
}

void CLIP_OT_slide_marker(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Slide Marker";
	ot->description = "Slide marker areas";
	ot->idname = "CLIP_OT_slide_marker";

	/* api callbacks */
	ot->poll = ED_space_clip_tracking_poll;
	ot->invoke = slide_marker_invoke;
	ot->modal = slide_marker_modal;

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_GRAB_POINTER | OPTYPE_BLOCKING;

	/* properties */
	RNA_def_float_vector(ot->srna, "offset", 2, NULL, -FLT_MAX, FLT_MAX,
	                     "Offset", "Offset in floating point units, 1.0 is the width and height of the image", -FLT_MAX, FLT_MAX);
}

/********************** track operator *********************/

typedef struct TrackMarkersJob {
	struct MovieTrackingContext *context;   /* tracking context */
	int sfra, efra, lastfra;    /* Start, end and recently tracked frames */
	int backwards;              /* Backwards tracking flag */
	MovieClip *clip;            /* Clip which is tracking */
	float delay;                /* Delay in milliseconds to allow tracking at fixed FPS */

	struct Main *main;
	struct Scene *scene;
	struct bScreen *screen;
} TrackMarkersJob;

static bool track_markers_testbreak(void)
{
	return G.is_break;
}

static int track_count_markers(SpaceClip *sc, MovieClip *clip, int framenr)
{
	int tot = 0;
	ListBase *tracksbase = BKE_tracking_get_active_tracks(&clip->tracking);
	MovieTrackingTrack *track;

	track = tracksbase->first;
	while (track) {
		bool selected = sc ? TRACK_VIEW_SELECTED(sc, track) : TRACK_SELECTED(track);
		if (selected && (track->flag & TRACK_LOCKED) == 0) {
			MovieTrackingMarker *marker = BKE_tracking_marker_get(track, framenr);

			if (!marker || (marker->flag & MARKER_DISABLED) == 0)
				tot++;
		}

		track = track->next;
	}

	return tot;
}

static void clear_invisible_track_selection(SpaceClip *sc, MovieClip *clip)
{
	ListBase *tracksbase = BKE_tracking_get_active_tracks(&clip->tracking);
	int hidden = 0;

	if ((sc->flag & SC_SHOW_MARKER_PATTERN) == 0)
		hidden |= TRACK_AREA_PAT;

	if ((sc->flag & SC_SHOW_MARKER_SEARCH) == 0)
		hidden |= TRACK_AREA_SEARCH;

	if (hidden) {
		MovieTrackingTrack *track = tracksbase->first;

		while (track) {
			if ((track->flag & TRACK_HIDDEN) == 0)
				BKE_tracking_track_flag_clear(track, hidden, SELECT);

			track = track->next;
		}
	}
}

static void track_init_markers(SpaceClip *sc, MovieClip *clip, int framenr, int *frames_limit_r)
{
	ListBase *tracksbase = BKE_tracking_get_active_tracks(&clip->tracking);
	MovieTrackingTrack *track;
	int frames_limit = 0;

	if (sc != NULL) {
		clear_invisible_track_selection(sc, clip);
	}

	track = tracksbase->first;
	while (track) {
		bool selected = sc ? TRACK_VIEW_SELECTED(sc, track) : TRACK_SELECTED(track);
		if (selected) {
			if ((track->flag & TRACK_HIDDEN) == 0 && (track->flag & TRACK_LOCKED) == 0) {
				BKE_tracking_marker_ensure(track, framenr);

				if (track->frames_limit) {
					if (frames_limit == 0)
						frames_limit = track->frames_limit;
					else
						frames_limit = min_ii(frames_limit, (int)track->frames_limit);
				}
			}
		}

		track = track->next;
	}

	*frames_limit_r = frames_limit;
}

static bool track_markers_check_direction(int backwards, int curfra, int efra)
{
	if (backwards) {
		if (curfra < efra)
			return false;
	}
	else {
		if (curfra > efra)
			return false;
	}

	return true;
}

static int track_markers_initjob(bContext *C, TrackMarkersJob *tmj, int backwards)
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	Scene *scene = CTX_data_scene(C);
	MovieTrackingSettings *settings = &clip->tracking.settings;
	int frames_limit;
	int framenr = ED_space_clip_get_clip_frame_number(sc);

	track_init_markers(sc, clip, framenr, &frames_limit);

	tmj->sfra = ED_space_clip_get_clip_frame_number(sc);
	tmj->clip = clip;
	tmj->backwards = backwards;

	if (backwards)
		tmj->efra = SFRA;
	else
		tmj->efra = EFRA;

	/* limit frames to be tracked by user setting */
	if (frames_limit) {
		if (backwards)
			tmj->efra = MAX2(tmj->efra, tmj->sfra - frames_limit);
		else
			tmj->efra = MIN2(tmj->efra, tmj->sfra + frames_limit);
	}

	tmj->efra = BKE_movieclip_remap_scene_to_clip_frame(clip, tmj->efra);

	if (settings->speed != TRACKING_SPEED_FASTEST) {
		tmj->delay = 1.0f / scene->r.frs_sec * 1000.0f;

		if (settings->speed == TRACKING_SPEED_HALF)
			tmj->delay *= 2;
		else if (settings->speed == TRACKING_SPEED_QUARTER)
			tmj->delay *= 4;
		else if (settings->speed == TRACKING_SPEED_DOUBLE)
			tmj->delay /= 2;
	}

	tmj->context = BKE_tracking_context_new(clip, &sc->user, backwards, 1);

	clip->tracking_context = tmj->context;

	tmj->lastfra = tmj->sfra;

	/* XXX: silly to store this, but this data is needed to update scene and movie-clip
	 *      frame numbers when tracking is finished. This introduces better feedback for artists.
	 *      Maybe there's another way to solve this problem, but can't think better way atm.
	 *      Anyway, this way isn't more unstable as animation rendering animation
	 *      which uses the same approach (except storing screen). */
	tmj->scene = scene;
	tmj->main = CTX_data_main(C);
	tmj->screen = CTX_wm_screen(C);

	return track_markers_check_direction(backwards, tmj->sfra, tmj->efra);
}

static void track_markers_startjob(void *tmv, short *stop, short *do_update, float *progress)
{
	TrackMarkersJob *tmj = (TrackMarkersJob *)tmv;
	int framenr = tmj->sfra;
	// double t = PIL_check_seconds_timer();

	while (framenr != tmj->efra) {
		if (tmj->delay > 0) {
			/* tracking should happen with fixed fps. Calculate time
			 * using current timer value before tracking frame and after.
			 *
			 * Small (and maybe unneeded optimization): do not calculate exec_time
			 * for "Fastest" tracking */

			double start_time = PIL_check_seconds_timer(), exec_time;

			if (!BKE_tracking_context_step(tmj->context))
				break;

			exec_time = PIL_check_seconds_timer() - start_time;
			if (tmj->delay > (float)exec_time)
				PIL_sleep_ms(tmj->delay - (float)exec_time);
		}
		else if (!BKE_tracking_context_step(tmj->context))
			break;

		*do_update = true;
		*progress = (float)(framenr - tmj->sfra) / (tmj->efra - tmj->sfra);

		if (tmj->backwards)
			framenr--;
		else
			framenr++;

		tmj->lastfra = framenr;

		if (*stop || track_markers_testbreak())
			break;
	}

	// printf("Tracking time: %lf\n", PIL_check_seconds_timer()-t);
}

static void track_markers_updatejob(void *tmv)
{
	TrackMarkersJob *tmj = (TrackMarkersJob *)tmv;

	BKE_tracking_context_sync(tmj->context);
}

static void track_markers_endjob(void *tmv)
{
	TrackMarkersJob *tmj = (TrackMarkersJob *)tmv;
	wmWindowManager *wm = tmj->main->wm.first;

	tmj->clip->tracking_context = NULL;
	tmj->scene->r.cfra = BKE_movieclip_remap_clip_to_scene_frame(tmj->clip, tmj->lastfra);
	if (wm != NULL) {
		ED_update_for_newframe(tmj->main, tmj->scene, 0);
	}

	BKE_tracking_context_sync(tmj->context);
	BKE_tracking_context_finish(tmj->context);

	WM_main_add_notifier(NC_SCENE | ND_FRAME, tmj->scene);
}

static void track_markers_freejob(void *tmv)
{
	TrackMarkersJob *tmj = (TrackMarkersJob *)tmv;
	BKE_tracking_context_free(tmj->context);
	MEM_freeN(tmj);
}

static int track_markers_exec(bContext *C, wmOperator *op)
{
	SpaceClip *sc;
	MovieClip *clip;
	Scene *scene = CTX_data_scene(C);
	struct MovieTrackingContext *context;
	MovieClipUser *user, fake_user = {0};
	int framenr, sfra, efra;
	const bool backwards = RNA_boolean_get(op->ptr, "backwards");
	const bool sequence = RNA_boolean_get(op->ptr, "sequence");
	int frames_limit;

	if (RNA_struct_property_is_set(op->ptr, "clip")) {
		Main *bmain = CTX_data_main(C);
		char clip_name[MAX_ID_NAME - 2];

		RNA_string_get(op->ptr, "clip", clip_name);
		clip = (MovieClip *)BLI_findstring(&bmain->movieclip, clip_name, offsetof(ID, name) + 2);
		sc = NULL;

		if (clip == NULL) {
			return OPERATOR_CANCELLED;
		}
		framenr = BKE_movieclip_remap_scene_to_clip_frame(clip, CFRA);
		fake_user.framenr = framenr;
		user = &fake_user;
	}
	else {
		sc = CTX_wm_space_clip(C);

		if (sc == NULL) {
			return OPERATOR_CANCELLED;
		}

		clip = ED_space_clip_get_clip(sc);
		framenr = ED_space_clip_get_clip_frame_number(sc);
		user = &sc->user;
	}

	sfra = framenr;

	if (track_count_markers(sc, clip, framenr) == 0)
		return OPERATOR_CANCELLED;

	track_init_markers(sc, clip, framenr, &frames_limit);

	if (backwards)
		efra = SFRA;
	else
		efra = EFRA;

	/* limit frames to be tracked by user setting */
	if (frames_limit) {
		if (backwards)
			efra = MAX2(efra, sfra - frames_limit);
		else
			efra = MIN2(efra, sfra + frames_limit);
	}

	efra = BKE_movieclip_remap_scene_to_clip_frame(clip, efra);

	if (!track_markers_check_direction(backwards, framenr, efra))
		return OPERATOR_CANCELLED;

	/* do not disable tracks due to threshold when tracking frame-by-frame */
	context = BKE_tracking_context_new(clip, user, backwards, sequence);

	while (framenr != efra) {
		if (!BKE_tracking_context_step(context))
			break;

		if (backwards) framenr--;
		else framenr++;

		if (!sequence)
			break;
	}

	BKE_tracking_context_sync(context);
	BKE_tracking_context_finish(context);
	BKE_tracking_context_free(context);

	/* update scene current frame to the lastes tracked frame */
	scene->r.cfra = BKE_movieclip_remap_clip_to_scene_frame(clip, framenr);

	WM_event_add_notifier(C, NC_MOVIECLIP | NA_EVALUATED, clip);
	WM_event_add_notifier(C, NC_SCENE | ND_FRAME, scene);

	return OPERATOR_FINISHED;
}

static int track_markers_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
{
	TrackMarkersJob *tmj;
	ScrArea *sa = CTX_wm_area(C);
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip;
	wmJob *wm_job;
	bool backwards = RNA_boolean_get(op->ptr, "backwards");
	bool sequence = RNA_boolean_get(op->ptr, "sequence");
	int framenr;

	if (sc == NULL) {
		/* TODO(sergey): Support clip for invoke as well. */
		BKE_report(op->reports, RPT_ERROR,
		           "Invoking this operator only supported from Clip Editor space");
		return OPERATOR_CANCELLED;
	}

	clip = ED_space_clip_get_clip(sc);
	framenr = ED_space_clip_get_clip_frame_number(sc);

	if (WM_jobs_test(CTX_wm_manager(C), sa, WM_JOB_TYPE_ANY)) {
		/* only one tracking is allowed at a time */
		return OPERATOR_CANCELLED;
	}

	if (clip->tracking_context)
		return OPERATOR_CANCELLED;

	if (track_count_markers(sc, clip, framenr) == 0)
		return OPERATOR_CANCELLED;

	if (!sequence)
		return track_markers_exec(C, op);

	tmj = MEM_callocN(sizeof(TrackMarkersJob), "TrackMarkersJob data");
	if (!track_markers_initjob(C, tmj, backwards)) {
		track_markers_freejob(tmj);

		return OPERATOR_CANCELLED;
	}

	/* setup job */
	wm_job = WM_jobs_get(CTX_wm_manager(C), CTX_wm_window(C), sa, "Track Markers",
	                     WM_JOB_PROGRESS, WM_JOB_TYPE_CLIP_TRACK_MARKERS);
	WM_jobs_customdata_set(wm_job, tmj, track_markers_freejob);

	/* if there's delay set in tracking job, tracking should happen
	 * with fixed FPS. To deal with editor refresh we have to synchronize
	 * tracks from job and tracks in clip. Do this in timer callback
	 * to prevent threading conflicts. */
	if (tmj->delay > 0)
		WM_jobs_timer(wm_job, tmj->delay / 1000.0f, NC_MOVIECLIP | NA_EVALUATED, 0);
	else
		WM_jobs_timer(wm_job, 0.2, NC_MOVIECLIP | NA_EVALUATED, 0);

	WM_jobs_callbacks(wm_job, track_markers_startjob, NULL, track_markers_updatejob, track_markers_endjob);

	G.is_break = false;

	WM_jobs_start(CTX_wm_manager(C), wm_job);
	WM_cursor_wait(0);

	/* add modal handler for ESC */
	WM_event_add_modal_handler(C, op);

	return OPERATOR_RUNNING_MODAL;
}

static int track_markers_modal(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
{
	/* no running tracking, remove handler and pass through */
	if (0 == WM_jobs_test(CTX_wm_manager(C), CTX_wm_area(C), WM_JOB_TYPE_ANY))
		return OPERATOR_FINISHED | OPERATOR_PASS_THROUGH;

	/* running tracking */
	switch (event->type) {
		case ESCKEY:
			return OPERATOR_RUNNING_MODAL;
	}

	return OPERATOR_PASS_THROUGH;
}

void CLIP_OT_track_markers(wmOperatorType *ot)
{
	PropertyRNA *prop;

	/* identifiers */
	ot->name = "Track Markers";
	ot->description = "Track selected markers";
	ot->idname = "CLIP_OT_track_markers";

	/* api callbacks */
	ot->exec = track_markers_exec;
	ot->invoke = track_markers_invoke;
	ot->modal = track_markers_modal;

	/* flags */
	ot->flag = OPTYPE_UNDO;

	/* properties */
	RNA_def_boolean(ot->srna, "backwards", 0, "Backwards", "Do backwards tracking");
	RNA_def_boolean(ot->srna, "sequence", 0, "Track Sequence", "Track marker during image sequence rather than single image");
	prop = RNA_def_string(ot->srna, "clip", NULL, MAX_NAME, "Movie Clip", "Movie Clip to be tracked");
	RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}

/********************** refine track position operator *********************/

static int refine_marker_exec(bContext *C, wmOperator *op)
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	MovieTracking *tracking = &clip->tracking;
	ListBase *tracksbase = BKE_tracking_get_active_tracks(tracking);
	MovieTrackingTrack *track;
	bool backwards = RNA_boolean_get(op->ptr, "backwards");
	int framenr = ED_space_clip_get_clip_frame_number(sc);

	for (track = tracksbase->first; track; track = track->next) {
		if (TRACK_VIEW_SELECTED(sc, track)) {
			MovieTrackingMarker *marker = BKE_tracking_marker_get(track, framenr);

			BKE_tracking_refine_marker(clip, track, marker, backwards);
		}
	}

	WM_event_add_notifier(C, NC_MOVIECLIP | NA_EVALUATED, clip);

	return OPERATOR_FINISHED;
}

void CLIP_OT_refine_markers(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Refine Markers";
	ot->description = "Refine selected markers positions "
	                  "by running the tracker from track's reference to current frame";
	ot->idname = "CLIP_OT_refine_markers";

	/* api callbacks */
	ot->exec = refine_marker_exec;
	ot->poll = ED_space_clip_tracking_poll;

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

	/* properties */
	RNA_def_boolean(ot->srna, "backwards", 0, "Backwards", "Do backwards tracking");
}

/********************** solve camera operator *********************/

typedef struct {
	Scene *scene;
	MovieClip *clip;
	MovieClipUser user;

	ReportList *reports;

	char stats_message[256];

	struct MovieReconstructContext *context;
} SolveCameraJob;

static int solve_camera_initjob(bContext *C, SolveCameraJob *scj, wmOperator *op, char *error_msg, int max_error)
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	Scene *scene = CTX_data_scene(C);
	MovieTracking *tracking = &clip->tracking;
	MovieTrackingObject *object = BKE_tracking_object_get_active(tracking);
	int width, height;

	if (!BKE_tracking_reconstruction_check(tracking, object, error_msg, max_error))
		return 0;

	/* could fail if footage uses images with different sizes */
	BKE_movieclip_get_size(clip, &sc->user, &width, &height);

	scj->clip = clip;
	scj->scene = scene;
	scj->reports = op->reports;
	scj->user = sc->user;

	scj->context = BKE_tracking_reconstruction_context_new(clip, object,
	                                                       object->keyframe1, object->keyframe2, width, height);

	tracking->stats = MEM_callocN(sizeof(MovieTrackingStats), "solve camera stats");

	return 1;
}

static void solve_camera_updatejob(void *scv)
{
	SolveCameraJob *scj = (SolveCameraJob *)scv;
	MovieTracking *tracking = &scj->clip->tracking;

	BLI_strncpy(tracking->stats->message, scj->stats_message, sizeof(tracking->stats->message));
}

static void solve_camera_startjob(void *scv, short *stop, short *do_update, float *progress)
{
	SolveCameraJob *scj = (SolveCameraJob *)scv;

	BKE_tracking_reconstruction_solve(scj->context, stop, do_update, progress,
	                                  scj->stats_message, sizeof(scj->stats_message));
}

static void solve_camera_freejob(void *scv)
{
	SolveCameraJob *scj = (SolveCameraJob *)scv;
	MovieTracking *tracking = &scj->clip->tracking;
	Scene *scene = scj->scene;
	MovieClip *clip = scj->clip;
	int solved;

	if (!scj->context) {
		/* job weren't fully initialized due to some error */
		MEM_freeN(scj);
		return;
	}

	solved = BKE_tracking_reconstruction_finish(scj->context, tracking);
	if (!solved)
		BKE_report(scj->reports, RPT_WARNING, "Some data failed to reconstruct (see console for details)");
	else
		BKE_reportf(scj->reports, RPT_INFO, "Average re-projection error: %.3f", tracking->reconstruction.error);

	/* set currently solved clip as active for scene */
	if (scene->clip)
		id_us_min(&clip->id);

	scene->clip = clip;
	id_us_plus(&clip->id);

	/* set blender camera focal length so result would look fine there */
	if (scene->camera && scene->camera->data && GS(((ID *) scene->camera->data)->name) == ID_CA) {
		Camera *camera = (Camera *)scene->camera->data;
		int width, height;

		BKE_movieclip_get_size(clip, &scj->user, &width, &height);

		BKE_tracking_camera_to_blender(tracking, scene, camera, width, height);

		WM_main_add_notifier(NC_OBJECT, camera);
	}

	MEM_freeN(tracking->stats);
	tracking->stats = NULL;

	DAG_id_tag_update(&clip->id, 0);

	WM_main_add_notifier(NC_MOVIECLIP | NA_EVALUATED, clip);
	WM_main_add_notifier(NC_OBJECT | ND_TRANSFORM, NULL);

	/* update active clip displayed in scene buttons */
	WM_main_add_notifier(NC_SCENE, scene);

	BKE_tracking_reconstruction_context_free(scj->context);
	MEM_freeN(scj);
}

static int solve_camera_exec(bContext *C, wmOperator *op)
{
	SolveCameraJob *scj;
	char error_msg[256] = "\0";

	scj = MEM_callocN(sizeof(SolveCameraJob), "SolveCameraJob data");
	if (!solve_camera_initjob(C, scj, op, error_msg, sizeof(error_msg))) {
		if (error_msg[0])
			BKE_report(op->reports, RPT_ERROR, error_msg);

		solve_camera_freejob(scj);

		return OPERATOR_CANCELLED;
	}

	solve_camera_startjob(scj, NULL, NULL, NULL);

	solve_camera_freejob(scj);

	return OPERATOR_FINISHED;
}

static int solve_camera_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
{
	SolveCameraJob *scj;
	ScrArea *sa = CTX_wm_area(C);
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	MovieTracking *tracking = &clip->tracking;
	MovieTrackingReconstruction *reconstruction = BKE_tracking_get_active_reconstruction(tracking);
	wmJob *wm_job;
	char error_msg[256] = "\0";

	if (WM_jobs_test(CTX_wm_manager(C), sa, WM_JOB_TYPE_ANY)) {
		/* only one solve is allowed at a time */
		return OPERATOR_CANCELLED;
	}

	scj = MEM_callocN(sizeof(SolveCameraJob), "SolveCameraJob data");
	if (!solve_camera_initjob(C, scj, op, error_msg, sizeof(error_msg))) {
		if (error_msg[0])
			BKE_report(op->reports, RPT_ERROR, error_msg);

		solve_camera_freejob(scj);

		return OPERATOR_CANCELLED;
	}

	BLI_strncpy(tracking->stats->message, "Solving camera | Preparing solve", sizeof(tracking->stats->message));

	/* hide reconstruction statistics from previous solve */
	reconstruction->flag &= ~TRACKING_RECONSTRUCTED;
	WM_event_add_notifier(C, NC_MOVIECLIP | NA_EVALUATED, clip);

	/* setup job */
	wm_job = WM_jobs_get(CTX_wm_manager(C), CTX_wm_window(C), sa, "Solve Camera",
	                     WM_JOB_PROGRESS, WM_JOB_TYPE_CLIP_SOLVE_CAMERA);
	WM_jobs_customdata_set(wm_job, scj, solve_camera_freejob);
	WM_jobs_timer(wm_job, 0.1, NC_MOVIECLIP | NA_EVALUATED, 0);
	WM_jobs_callbacks(wm_job, solve_camera_startjob, NULL, solve_camera_updatejob, NULL);

	G.is_break = false;

	WM_jobs_start(CTX_wm_manager(C), wm_job);
	WM_cursor_wait(0);

	/* add modal handler for ESC */
	WM_event_add_modal_handler(C, op);

	return OPERATOR_RUNNING_MODAL;
}

static int solve_camera_modal(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
{
	/* no running solver, remove handler and pass through */
	if (0 == WM_jobs_test(CTX_wm_manager(C), CTX_wm_area(C), WM_JOB_TYPE_ANY))
		return OPERATOR_FINISHED | OPERATOR_PASS_THROUGH;

	/* running tracking */
	switch (event->type) {
		case ESCKEY:
			return OPERATOR_RUNNING_MODAL;
	}

	return OPERATOR_PASS_THROUGH;
}

void CLIP_OT_solve_camera(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Solve Camera";
	ot->description = "Solve camera motion from tracks";
	ot->idname = "CLIP_OT_solve_camera";

	/* api callbacks */
	ot->exec = solve_camera_exec;
	ot->invoke = solve_camera_invoke;
	ot->modal = solve_camera_modal;
	ot->poll = ED_space_clip_tracking_poll;

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}

/********************** clear solution operator *********************/

static int clear_solution_exec(bContext *C, wmOperator *UNUSED(op))
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	MovieTracking *tracking = &clip->tracking;
	ListBase *tracksbase = BKE_tracking_get_active_tracks(&clip->tracking);
	MovieTrackingReconstruction *reconstruction = BKE_tracking_get_active_reconstruction(tracking);
	MovieTrackingTrack *track = tracksbase->first;

	while (track) {
		track->flag &= ~TRACK_HAS_BUNDLE;

		track = track->next;
	}

	if (reconstruction->cameras)
		MEM_freeN(reconstruction->cameras);

	reconstruction->cameras = NULL;
	reconstruction->camnr = 0;

	reconstruction->flag &= ~TRACKING_RECONSTRUCTED;

	DAG_id_tag_update(&clip->id, 0);

	WM_event_add_notifier(C, NC_MOVIECLIP | NA_EVALUATED, clip);
	WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, NULL);

	return OPERATOR_FINISHED;
}

void CLIP_OT_clear_solution(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Clear Solution";
	ot->description = "Clear all calculated data";
	ot->idname = "CLIP_OT_clear_solution";

	/* api callbacks */
	ot->exec = clear_solution_exec;
	ot->poll = ED_space_clip_tracking_poll;

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}

/********************** clear track operator *********************/

static int clear_track_path_exec(bContext *C, wmOperator *op)
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	MovieTracking *tracking = &clip->tracking;
	MovieTrackingTrack *track;
	ListBase *tracksbase = BKE_tracking_get_active_tracks(tracking);
	int action = RNA_enum_get(op->ptr, "action");
	const bool clear_active = RNA_boolean_get(op->ptr, "clear_active");
	int framenr = ED_space_clip_get_clip_frame_number(sc);

	if (clear_active) {
		track = BKE_tracking_track_get_active(tracking);
		if (track) {
			BKE_tracking_track_path_clear(track, framenr, action);
		}
	}
	else {
		track = tracksbase->first;
		while (track) {
			if (TRACK_VIEW_SELECTED(sc, track))
				BKE_tracking_track_path_clear(track, framenr, action);

			track = track->next;
		}
	}

	BKE_tracking_dopesheet_tag_update(tracking);
	WM_event_add_notifier(C, NC_MOVIECLIP | NA_EVALUATED, clip);

	return OPERATOR_FINISHED;
}

void CLIP_OT_clear_track_path(wmOperatorType *ot)
{
	static EnumPropertyItem clear_path_actions[] = {
		{TRACK_CLEAR_UPTO, "UPTO", 0, "Clear up-to", "Clear path up to current frame"},
		{TRACK_CLEAR_REMAINED, "REMAINED", 0, "Clear remained", "Clear path at remaining frames (after current)"},
		{TRACK_CLEAR_ALL, "ALL", 0, "Clear all", "Clear the whole path"},
		{0, NULL, 0, NULL, NULL}
	};

	/* identifiers */
	ot->name = "Clear Track Path";
	ot->description = "Clear tracks after/before current position or clear the whole track";
	ot->idname = "CLIP_OT_clear_track_path";

	/* api callbacks */
	ot->exec = clear_track_path_exec;
	ot->poll = ED_space_clip_tracking_poll;

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

	/* proeprties */
	RNA_def_enum(ot->srna, "action", clear_path_actions, TRACK_CLEAR_REMAINED, "Action", "Clear action to execute");
	RNA_def_boolean(ot->srna, "clear_active", 0, "Clear Active", "Clear active track only instead of all selected tracks");
}

/********************** disable markers operator *********************/

static int disable_markers_exec(bContext *C, wmOperator *op)
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	MovieTracking *tracking = &clip->tracking;
	ListBase *tracksbase = BKE_tracking_get_active_tracks(tracking);
	MovieTrackingTrack *track = tracksbase->first;
	int action = RNA_enum_get(op->ptr, "action");
	int framenr = ED_space_clip_get_clip_frame_number(sc);

	while (track) {
		if (TRACK_VIEW_SELECTED(sc, track) && (track->flag & TRACK_LOCKED) == 0) {
			MovieTrackingMarker *marker = BKE_tracking_marker_ensure(track, framenr);

			if (action == 0)
				marker->flag |= MARKER_DISABLED;
			else if (action == 1)
				marker->flag &= ~MARKER_DISABLED;
			else marker->flag ^= MARKER_DISABLED;
		}

		track = track->next;
	}

	DAG_id_tag_update(&clip->id, 0);

	WM_event_add_notifier(C, NC_MOVIECLIP | NA_EVALUATED, clip);

	return OPERATOR_FINISHED;
}

void CLIP_OT_disable_markers(wmOperatorType *ot)
{
	static EnumPropertyItem actions_items[] = {
		{0, "DISABLE", 0, "Disable", "Disable selected markers"},
		{1, "ENABLE", 0, "Enable", "Enable selected markers"},
		{2, "TOGGLE", 0, "Toggle", "Toggle disabled flag for selected markers"},
		{0, NULL, 0, NULL, NULL}
	};

	/* identifiers */
	ot->name = "Disable Markers";
	ot->description = "Disable/enable selected markers";
	ot->idname = "CLIP_OT_disable_markers";

	/* api callbacks */
	ot->exec = disable_markers_exec;
	ot->poll = ED_space_clip_tracking_poll;

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

	/* properties */
	RNA_def_enum(ot->srna, "action", actions_items, 0, "Action", "Disable action to execute");
}

/********************** set origin operator *********************/

static Object *get_camera_with_movieclip(Scene *scene, MovieClip *clip)
{
	Object *camera = scene->camera;
	Base *base;

	if (camera && BKE_object_movieclip_get(scene, camera, false) == clip)
		return camera;

	base = scene->base.first;
	while (base) {
		if (base->object->type == OB_CAMERA) {
			if (BKE_object_movieclip_get(scene, base->object, false) == clip) {
				camera = base->object;
				break;
			}
		}

		base = base->next;
	}

	return camera;
}

static Object *get_orientation_object(bContext *C)
{
	Scene *scene = CTX_data_scene(C);
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	MovieTracking *tracking = &clip->tracking;
	MovieTrackingObject *tracking_object = BKE_tracking_object_get_active(tracking);
	Object *object = NULL;

	if (tracking_object->flag & TRACKING_OBJECT_CAMERA) {
		object = get_camera_with_movieclip(scene, clip);
	}
	else {
		object = OBACT;
	}

	if (object && object->parent)
		object = object->parent;

	return object;
}

static int set_orientation_poll(bContext *C)
{
	SpaceClip *sc = CTX_wm_space_clip(C);

	if (sc) {
		Scene *scene = CTX_data_scene(C);
		MovieClip *clip = ED_space_clip_get_clip(sc);

		if (clip) {
			MovieTracking *tracking = &clip->tracking;
			MovieTrackingObject *tracking_object = BKE_tracking_object_get_active(tracking);

			if (tracking_object->flag & TRACKING_OBJECT_CAMERA) {
				return true;
			}
			else {
				return OBACT != NULL;
			}
		}
	}

	return false;
}

static int count_selected_bundles(bContext *C)
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	ListBase *tracksbase = BKE_tracking_get_active_tracks(&clip->tracking);
	MovieTrackingTrack *track;
	int tot = 0;

	track = tracksbase->first;
	while (track) {
		if (TRACK_VIEW_SELECTED(sc, track) && (track->flag & TRACK_HAS_BUNDLE))
			tot++;

		track = track->next;
	}

	return tot;
}

static void object_solver_inverted_matrix(Scene *scene, Object *ob, float invmat[4][4])
{
	bConstraint *con;
	bool found = false;

	for (con = ob->constraints.first; con; con = con->next) {
		bConstraintTypeInfo *cti = BKE_constraint_typeinfo_get(con);

		if (!cti)
			continue;

		if (cti->type == CONSTRAINT_TYPE_OBJECTSOLVER) {
			bObjectSolverConstraint *data = (bObjectSolverConstraint *)con->data;

			if (!found) {
				Object *cam = data->camera ? data->camera : scene->camera;

				BKE_object_where_is_calc_mat4(scene, cam, invmat);
			}

			mul_m4_m4m4(invmat, invmat, data->invmat);

			found = true;
		}
	}

	if (found)
		invert_m4(invmat);
	else
		unit_m4(invmat);
}

static Object *object_solver_camera(Scene *scene, Object *ob)
{
	bConstraint *con;

	for (con = ob->constraints.first; con; con = con->next) {
		bConstraintTypeInfo *cti = BKE_constraint_typeinfo_get(con);

		if (!cti)
			continue;

		if (cti->type == CONSTRAINT_TYPE_OBJECTSOLVER) {
			bObjectSolverConstraint *data = (bObjectSolverConstraint *)con->data;

			return data->camera ? data->camera : scene->camera;
		}
	}

	return NULL;
}

static int set_origin_exec(bContext *C, wmOperator *op)
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	MovieTracking *tracking = &clip->tracking;
	MovieTrackingTrack *track;
	MovieTrackingObject *tracking_object;
	Scene *scene = CTX_data_scene(C);
	Object *object;
	Object *camera = get_camera_with_movieclip(scene, clip);
	ListBase *tracksbase;
	float mat[4][4], vec[3], median[3];
	int selected_count = count_selected_bundles(C);

	if (selected_count == 0) {
		BKE_report(op->reports, RPT_ERROR,
		           "At least one track with bundle should be selected to define origin position");

		return OPERATOR_CANCELLED;
	}

	object = get_orientation_object(C);
	if (!object) {
		BKE_report(op->reports, RPT_ERROR, "No object to apply orientation on");

		return OPERATOR_CANCELLED;
	}

	tracking_object = BKE_tracking_object_get_active(tracking);

	tracksbase = BKE_tracking_object_get_tracks(tracking, tracking_object);

	track = tracksbase->first;
	zero_v3(median);
	while (track) {
		if (TRACK_VIEW_SELECTED(sc, track) && (track->flag & TRACK_HAS_BUNDLE)) {
			add_v3_v3(median, track->bundle_pos);
		}

		track = track->next;
	}
	mul_v3_fl(median, 1.0f / selected_count);

	BKE_tracking_get_camera_object_matrix(scene, camera, mat);

	mul_v3_m4v3(vec, mat, median);

	if (tracking_object->flag & TRACKING_OBJECT_CAMERA) {
		sub_v3_v3(object->loc, vec);
	}
	else {
		object_solver_inverted_matrix(scene, object, mat);
		mul_v3_m4v3(vec, mat, vec);
		copy_v3_v3(object->loc, vec);
	}

	DAG_id_tag_update(&clip->id, 0);
	DAG_id_tag_update(&object->id, OB_RECALC_OB);

	WM_event_add_notifier(C, NC_MOVIECLIP | NA_EVALUATED, clip);
	WM_event_add_notifier(C, NC_OBJECT | ND_TRANSFORM, NULL);

	return OPERATOR_FINISHED;
}

void CLIP_OT_set_origin(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Set Origin";
	ot->description = "Set active marker as origin by moving camera (or its parent if present) in 3D space";
	ot->idname = "CLIP_OT_set_origin";

	/* api callbacks */
	ot->exec = set_origin_exec;
	ot->poll = set_orientation_poll;

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

	/* properties */
	RNA_def_boolean(ot->srna, "use_median", 0, "Use Median", "Set origin to median point of selected bundles");
}

/********************** set floor operator *********************/

static void set_axis(Scene *scene,  Object *ob, MovieClip *clip, MovieTrackingObject *tracking_object,
                     MovieTrackingTrack *track, char axis)
{
	Object *camera = get_camera_with_movieclip(scene, clip);
	const bool is_camera = (tracking_object->flag & TRACKING_OBJECT_CAMERA) != 0;
	bool flip = false;
	float mat[4][4], vec[3], obmat[4][4], dvec[3];

	BKE_object_to_mat4(ob, obmat);

	BKE_tracking_get_camera_object_matrix(scene, camera, mat);
	mul_v3_m4v3(vec, mat, track->bundle_pos);
	copy_v3_v3(dvec, vec);

	if (!is_camera) {
		float imat[4][4];

		object_solver_inverted_matrix(scene, ob, imat);
		mul_v3_m4v3(vec, imat, vec);

		invert_m4_m4(imat, obmat);
		mul_v3_m4v3(dvec, imat, vec);

		sub_v3_v3(vec, obmat[3]);
	}

	if (len_squared_v2(vec) < (1e-3f * 1e-3f))
		return;

	unit_m4(mat);

	if (axis == 'X') {
		if (fabsf(dvec[1]) < 1e-3f) {
			flip = true;

			mat[0][0] = -1.0f; mat[0][1] = 0.0f; mat[0][2] = 0.0f;
			mat[1][0] = 0.0f; mat[1][1] = -1.0f; mat[1][2] = 0.0f;
			mat[2][0] = 0.0f; mat[2][1] = 0.0f; mat[2][2] = 1.0f;
		}
		else {
			copy_v3_v3(mat[0], vec);

			if (is_camera || fabsf(vec[2]) < 1e-3f) {
				mat[0][2] = 0.0f;
				mat[2][0] = 0.0f; mat[2][1] = 0.0f; mat[2][2] = 1.0f;
				cross_v3_v3v3(mat[1], mat[2], mat[0]);
			}
			else {
				vec[2] = 0.0f;

				cross_v3_v3v3(mat[1], mat[0], vec);
				cross_v3_v3v3(mat[2], mat[0], mat[1]);
			}
		}
	}
	else {
		if (fabsf(dvec[0]) < 1e-3f) {
			flip = true;

			mat[0][0] = -1.0f; mat[0][1] = 0.0f; mat[0][2] = 0.0f;
			mat[1][0] = 0.0f; mat[1][1] = -1.0f; mat[1][2] = 0.0f;
			mat[2][0] = 0.0f; mat[2][1] = 0.0f; mat[2][2] = 1.0f;
		}
		else {
			copy_v3_v3(mat[1], vec);

			if (is_camera || fabsf(vec[2]) < 1e-3f) {
				mat[1][2] = 0.0f;
				mat[2][0] = 0.0f; mat[2][1] = 0.0f; mat[2][2] = 1.0f;
				cross_v3_v3v3(mat[0], mat[1], mat[2]);
			}
			else {
				vec[2] = 0.0f;

				cross_v3_v3v3(mat[0], vec, mat[1]);
				cross_v3_v3v3(mat[2], mat[0], mat[1]);
			}
		}
	}

	normalize_v3(mat[0]);
	normalize_v3(mat[1]);
	normalize_v3(mat[2]);

	if (is_camera) {
		invert_m4(mat);

		mul_m4_m4m4(mat, mat, obmat);
	}
	else {
		if (!flip) {
			float lmat[4][4], ilmat[4][4], rmat[3][3];

			BKE_object_rot_to_mat3(ob, rmat, true);
			invert_m3(rmat);
			mul_m4_m4m3(mat, mat, rmat);

			unit_m4(lmat);
			copy_v3_v3(lmat[3], obmat[3]);
			invert_m4_m4(ilmat, lmat);

			mul_m4_series(mat, lmat, mat, ilmat, obmat);
		}
		else {
			mul_m4_m4m4(mat, obmat, mat);
		}
	}

	BKE_object_apply_mat4(ob, mat, 0, 0);
}

static int set_plane_exec(bContext *C, wmOperator *op)
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	Scene *scene = CTX_data_scene(C);
	MovieTracking *tracking = &clip->tracking;
	MovieTrackingObject *tracking_object;
	MovieTrackingTrack *track, *axis_track = NULL, *act_track;
	ListBase *tracksbase;
	Object *object;
	Object *camera = get_camera_with_movieclip(scene, clip);
	int tot = 0;
	float vec[3][3], mat[4][4], obmat[4][4], newmat[4][4], orig[3] = {0.0f, 0.0f, 0.0f};
	int plane = RNA_enum_get(op->ptr, "plane");
	float rot[4][4] = {{0.0f, 0.0f, -1.0f, 0.0f},
	                   {0.0f, 1.0f, 0.0f, 0.0f},
	                   {1.0f, 0.0f, 0.0f, 0.0f},
	                   {0.0f, 0.0f, 0.0f, 1.0f}};  /* 90 degrees Y-axis rotation matrix */

	if (count_selected_bundles(C) != 3) {
		BKE_report(op->reports, RPT_ERROR, "Three tracks with bundles are needed to orient the floor");

		return OPERATOR_CANCELLED;
	}

	tracking_object = BKE_tracking_object_get_active(tracking);
	tracksbase = BKE_tracking_object_get_tracks(tracking, tracking_object);
	act_track = BKE_tracking_track_get_active(tracking);

	object = get_orientation_object(C);
	if (!object) {
		BKE_report(op->reports, RPT_ERROR, "No object to apply orientation on");

		return OPERATOR_CANCELLED;
	}

	BKE_tracking_get_camera_object_matrix(scene, camera, mat);

	/* get 3 bundles to use as reference */
	track = tracksbase->first;
	while (track && tot < 3) {
		if (track->flag & TRACK_HAS_BUNDLE && TRACK_VIEW_SELECTED(sc, track)) {
			mul_v3_m4v3(vec[tot], mat, track->bundle_pos);

			if (tot == 0 || track == act_track)
				copy_v3_v3(orig, vec[tot]);
			else
				axis_track = track;

			tot++;
		}

		track = track->next;
	}

	sub_v3_v3(vec[1], vec[0]);
	sub_v3_v3(vec[2], vec[0]);

	/* construct ortho-normal basis */
	unit_m4(mat);

	if (plane == 0) { /* floor */
		cross_v3_v3v3(mat[0], vec[1], vec[2]);
		copy_v3_v3(mat[1], vec[1]);
		cross_v3_v3v3(mat[2], mat[0], mat[1]);
	}
	else if (plane == 1) { /* wall */
		cross_v3_v3v3(mat[2], vec[1], vec[2]);
		copy_v3_v3(mat[1], vec[1]);
		cross_v3_v3v3(mat[0], mat[1], mat[2]);
	}

	normalize_v3(mat[0]);
	normalize_v3(mat[1]);
	normalize_v3(mat[2]);

	/* move to origin point */
	mat[3][0] = orig[0];
	mat[3][1] = orig[1];
	mat[3][2] = orig[2];

	if (tracking_object->flag & TRACKING_OBJECT_CAMERA) {
		invert_m4(mat);

		BKE_object_to_mat4(object, obmat);
		mul_m4_m4m4(mat, mat, obmat);
		mul_m4_m4m4(newmat, rot, mat);
		BKE_object_apply_mat4(object, newmat, 0, 0);

		/* make camera have positive z-coordinate */
		if (object->loc[2] < 0) {
			invert_m4(rot);
			mul_m4_m4m4(newmat, rot, mat);
			BKE_object_apply_mat4(object, newmat, 0, 0);
		}
	}
	else {
		BKE_object_apply_mat4(object, mat, 0, 0);
	}

	BKE_object_where_is_calc(scene, object);
	set_axis(scene, object, clip, tracking_object, axis_track, 'X');

	DAG_id_tag_update(&clip->id, 0);
	DAG_id_tag_update(&object->id, OB_RECALC_OB);

	WM_event_add_notifier(C, NC_MOVIECLIP | NA_EVALUATED, clip);
	WM_event_add_notifier(C, NC_OBJECT | ND_TRANSFORM, NULL);

	return OPERATOR_FINISHED;
}

void CLIP_OT_set_plane(wmOperatorType *ot)
{
	static EnumPropertyItem plane_items[] = {
		{0, "FLOOR", 0, "Floor", "Set floor plane"},
		{1, "WALL", 0, "Wall", "Set wall plane"},
		{0, NULL, 0, NULL, NULL}
	};

	/* identifiers */
	ot->name = "Set Plane";
	ot->description = "Set plane based on 3 selected bundles by moving camera (or its parent if present) in 3D space";
	ot->idname = "CLIP_OT_set_plane";

	/* api callbacks */
	ot->exec = set_plane_exec;
	ot->poll = set_orientation_poll;

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

	/* properties */
	RNA_def_enum(ot->srna, "plane", plane_items, 0, "Plane", "Plane to be used for orientation");
}

/********************** set axis operator *********************/

static int set_axis_exec(bContext *C, wmOperator *op)
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	MovieTracking *tracking = &clip->tracking;
	MovieTrackingObject *tracking_object = BKE_tracking_object_get_active(tracking);
	MovieTrackingTrack *track;
	Scene *scene = CTX_data_scene(C);
	Object *object;
	ListBase *tracksbase;
	int axis = RNA_enum_get(op->ptr, "axis");

	if (count_selected_bundles(C) != 1) {
		BKE_report(op->reports, RPT_ERROR, "Single track with bundle should be selected to define axis");

		return OPERATOR_CANCELLED;
	}

	object = get_orientation_object(C);
	if (!object) {
		BKE_report(op->reports, RPT_ERROR, "No object to apply orientation on");

		return OPERATOR_CANCELLED;
	}

	tracksbase = BKE_tracking_object_get_tracks(tracking, tracking_object);

	track = tracksbase->first;
	while (track) {
		if (TRACK_VIEW_SELECTED(sc, track) && (track->flag & TRACK_HAS_BUNDLE))
			break;

		track = track->next;
	}

	set_axis(scene, object, clip, tracking_object, track, axis == 0 ? 'X' : 'Y');

	DAG_id_tag_update(&clip->id, 0);
	DAG_id_tag_update(&object->id, OB_RECALC_OB);

	WM_event_add_notifier(C, NC_MOVIECLIP | NA_EVALUATED, clip);
	WM_event_add_notifier(C, NC_OBJECT | ND_TRANSFORM, NULL);

	return OPERATOR_FINISHED;
}

void CLIP_OT_set_axis(wmOperatorType *ot)
{
	static EnumPropertyItem axis_actions[] = {
		{0, "X", 0, "X", "Align bundle align X axis"},
		{1, "Y", 0, "Y", "Align bundle align Y axis"},
		{0, NULL, 0, NULL, NULL}
	};

	/* identifiers */
	ot->name = "Set Axis";
	ot->description = "Set direction of scene axis rotating camera (or its parent if present) and assume selected track lies on real axis, joining it with the origin";
	ot->idname = "CLIP_OT_set_axis";

	/* api callbacks */
	ot->exec = set_axis_exec;
	ot->poll = set_orientation_poll;

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

	/* properties */
	RNA_def_enum(ot->srna, "axis", axis_actions, 0, "Axis", "Axis to use to align bundle along");
}

/********************** set scale operator *********************/

static int do_set_scale(bContext *C, wmOperator *op, bool scale_solution, bool apply_scale)
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	MovieTracking *tracking = &clip->tracking;
	MovieTrackingObject *tracking_object = BKE_tracking_object_get_active(tracking);
	MovieTrackingTrack *track;
	Scene *scene = CTX_data_scene(C);
	Object *object = NULL;
	Object *camera = get_camera_with_movieclip(scene, clip);
	ListBase *tracksbase = BKE_tracking_get_active_tracks(tracking);
	int tot = 0;
	float vec[2][3], mat[4][4], scale;
	float dist = RNA_float_get(op->ptr, "distance");

	if (count_selected_bundles(C) != 2) {
		BKE_report(op->reports, RPT_ERROR, "Two tracks with bundles should be selected to set scale");

		return OPERATOR_CANCELLED;
	}

	if (!scale_solution && !apply_scale) {
		object = get_orientation_object(C);
		if (!object) {
			BKE_report(op->reports, RPT_ERROR, "No object to apply orientation on");

			return OPERATOR_CANCELLED;
		}
	}

	BKE_tracking_get_camera_object_matrix(scene, camera, mat);

	track = tracksbase->first;
	while (track) {
		if (TRACK_VIEW_SELECTED(sc, track)) {
			mul_v3_m4v3(vec[tot], mat, track->bundle_pos);
			tot++;
		}

		track = track->next;
	}

	sub_v3_v3(vec[0], vec[1]);

	if (len_v3(vec[0]) > 1e-5f) {
		scale = dist / len_v3(vec[0]);

		if (apply_scale) {
			/* Apply scale on reconstructed scene itself */
			MovieTrackingReconstruction *reconstruction = BKE_tracking_get_active_reconstruction(tracking);
			MovieReconstructedCamera *reconstructed_cameras;
			int i;

			for (track = tracksbase->first; track; track = track->next) {
				mul_v3_fl(track->bundle_pos, scale);
			}

			reconstructed_cameras = reconstruction->cameras;
			for (i = 0; i < reconstruction->camnr; i++) {
				mul_v3_fl(reconstructed_cameras[i].mat[3], scale);
			}

			WM_event_add_notifier(C, NC_MOVIECLIP | NA_EVALUATED, clip);
			WM_event_add_notifier(C, NC_OBJECT | ND_TRANSFORM, NULL);
		}
		else {
			if (tracking_object->flag & TRACKING_OBJECT_CAMERA) {
				mul_v3_fl(object->size, scale);
				mul_v3_fl(object->loc, scale);
			}
			else if (!scale_solution) {
				Object *solver_camera = object_solver_camera(scene, object);

				object->size[0] = object->size[1] = object->size[2] = 1.0f / scale;

				if (solver_camera) {
					object->size[0] /= solver_camera->size[0];
					object->size[1] /= solver_camera->size[1];
					object->size[2] /= solver_camera->size[2];
				}
			}
			else {
				tracking_object->scale = scale;
			}

			DAG_id_tag_update(&clip->id, 0);

			if (object)
				DAG_id_tag_update(&object->id, OB_RECALC_OB);

			WM_event_add_notifier(C, NC_MOVIECLIP | NA_EVALUATED, clip);
			WM_event_add_notifier(C, NC_OBJECT | ND_TRANSFORM, NULL);
		}
	}

	return OPERATOR_FINISHED;
}

static int set_scale_exec(bContext *C, wmOperator *op)
{
	return do_set_scale(C, op, false, false);
}

static int set_scale_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);

	if (!RNA_struct_property_is_set(op->ptr, "distance"))
		RNA_float_set(op->ptr, "distance", clip->tracking.settings.dist);

	return set_scale_exec(C, op);
}

void CLIP_OT_set_scale(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Set Scale";
	ot->description = "Set scale of scene by scaling camera (or its parent if present)";
	ot->idname = "CLIP_OT_set_scale";

	/* api callbacks */
	ot->exec = set_scale_exec;
	ot->invoke = set_scale_invoke;
	ot->poll = set_orientation_poll;

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

	/* properties */
	RNA_def_float(ot->srna, "distance", 0.0f, -FLT_MAX, FLT_MAX,
	              "Distance", "Distance between selected tracks", -100.0f, 100.0f);
}

/********************** set solution scale operator *********************/

static int set_solution_scale_poll(bContext *C)
{
	SpaceClip *sc = CTX_wm_space_clip(C);

	if (sc) {
		MovieClip *clip = ED_space_clip_get_clip(sc);

		if (clip) {
			MovieTracking *tracking = &clip->tracking;
			MovieTrackingObject *tracking_object = BKE_tracking_object_get_active(tracking);

			return (tracking_object->flag & TRACKING_OBJECT_CAMERA) == 0;
		}
	}

	return false;
}

static int set_solution_scale_exec(bContext *C, wmOperator *op)
{
	return do_set_scale(C, op, true, false);
}

static int set_solution_scale_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);

	if (!RNA_struct_property_is_set(op->ptr, "distance"))
		RNA_float_set(op->ptr, "distance", clip->tracking.settings.object_distance);

	return set_solution_scale_exec(C, op);
}

void CLIP_OT_set_solution_scale(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Set Solution Scale";
	ot->description = "Set object solution scale using distance between two selected tracks";
	ot->idname = "CLIP_OT_set_solution_scale";

	/* api callbacks */
	ot->exec = set_solution_scale_exec;
	ot->invoke = set_solution_scale_invoke;
	ot->poll = set_solution_scale_poll;

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

	/* properties */
	RNA_def_float(ot->srna, "distance", 0.0f, -FLT_MAX, FLT_MAX,
	              "Distance", "Distance between selected tracks", -100.0f, 100.0f);
}

/********************** apply solution scale operator *********************/

static int apply_solution_scale_poll(bContext *C)
{
	SpaceClip *sc = CTX_wm_space_clip(C);

	if (sc) {
		MovieClip *clip = ED_space_clip_get_clip(sc);

		if (clip) {
			MovieTracking *tracking = &clip->tracking;
			MovieTrackingObject *tracking_object = BKE_tracking_object_get_active(tracking);

			return tracking_object->flag & TRACKING_OBJECT_CAMERA;
		}
	}

	return false;
}

static int apply_solution_scale_exec(bContext *C, wmOperator *op)
{
	return do_set_scale(C, op, false, true);
}

static int apply_solution_scale_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);

	if (!RNA_struct_property_is_set(op->ptr, "distance"))
		RNA_float_set(op->ptr, "distance", clip->tracking.settings.dist);

	return apply_solution_scale_exec(C, op);
}

void CLIP_OT_apply_solution_scale(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Apply Solution Scale";
	ot->description = "Apply scale on solution itself to make distance between selected tracks equals to desired";
	ot->idname = "CLIP_OT_apply_solution_scale";

	/* api callbacks */
	ot->exec = apply_solution_scale_exec;
	ot->invoke = apply_solution_scale_invoke;
	ot->poll = apply_solution_scale_poll;

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

	/* properties */
	RNA_def_float(ot->srna, "distance", 0.0f, -FLT_MAX, FLT_MAX,
	              "Distance", "Distance between selected tracks", -100.0f, 100.0f);
}

/********************** set principal center operator *********************/

static int set_center_principal_exec(bContext *C, wmOperator *UNUSED(op))
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	int width, height;

	BKE_movieclip_get_size(clip, &sc->user, &width, &height);

	if (width == 0 || height == 0)
		return OPERATOR_CANCELLED;

	clip->tracking.camera.principal[0] = ((float)width) / 2.0f;
	clip->tracking.camera.principal[1] = ((float)height) / 2.0f;

	WM_event_add_notifier(C, NC_MOVIECLIP | NA_EDITED, clip);

	return OPERATOR_FINISHED;
}

void CLIP_OT_set_center_principal(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Set Principal to Center";
	ot->description = "Set optical center to center of footage";
	ot->idname = "CLIP_OT_set_center_principal";

	/* api callbacks */
	ot->exec = set_center_principal_exec;
	ot->poll = ED_space_clip_tracking_poll;

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}

/********************** hide tracks operator *********************/

static int hide_tracks_exec(bContext *C, wmOperator *op)
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	MovieTrackingTrack *track;
	MovieTrackingPlaneTrack *plane_track;
	MovieTracking *tracking = &clip->tracking;
	ListBase *tracksbase = BKE_tracking_get_active_tracks(tracking);
	ListBase *plane_tracks_base = BKE_tracking_get_active_plane_tracks(tracking);
	MovieTrackingTrack *act_track = BKE_tracking_track_get_active(tracking);
	MovieTrackingPlaneTrack *act_plane_track = BKE_tracking_plane_track_get_active(&clip->tracking);
	int unselected;

	unselected = RNA_boolean_get(op->ptr, "unselected");

	track = tracksbase->first;
	while (track) {
		if (unselected == 0 && TRACK_VIEW_SELECTED(sc, track)) {
			track->flag |= TRACK_HIDDEN;
		}
		else if (unselected == 1 && !TRACK_VIEW_SELECTED(sc, track)) {
			track->flag |= TRACK_HIDDEN;
		}

		track = track->next;
	}

	if (act_track && act_track->flag & TRACK_HIDDEN)
		clip->tracking.act_track = NULL;

	if (unselected == 0) {
		/* no selection on screen now, unlock view so it can be scrolled nice again */
		sc->flag &= ~SC_LOCK_SELECTION;
	}

	for (plane_track = plane_tracks_base->first;
	     plane_track;
	     plane_track = plane_track->next)
	{
		if (unselected == 0 && plane_track->flag & SELECT) {
			plane_track->flag |= PLANE_TRACK_HIDDEN;
		}
		else if (unselected == 1 && (plane_track->flag & SELECT) == 0) {
			plane_track->flag |= PLANE_TRACK_HIDDEN;
		}
	}

	if (act_plane_track && act_plane_track->flag & TRACK_HIDDEN) {
		clip->tracking.act_plane_track = NULL;
	}

	BKE_tracking_dopesheet_tag_update(tracking);

	WM_event_add_notifier(C, NC_MOVIECLIP | ND_DISPLAY, NULL);

	return OPERATOR_FINISHED;
}

void CLIP_OT_hide_tracks(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Hide Tracks";
	ot->description = "Hide selected tracks";
	ot->idname = "CLIP_OT_hide_tracks";

	/* api callbacks */
	ot->exec = hide_tracks_exec;
	ot->poll = ED_space_clip_tracking_poll;

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

	/* properties */
	RNA_def_boolean(ot->srna, "unselected", 0, "Unselected", "Hide unselected tracks");
}

/********************** hide tracks clear operator *********************/

static int hide_tracks_clear_exec(bContext *C, wmOperator *UNUSED(op))
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	MovieTracking *tracking = &clip->tracking;
	ListBase *tracksbase = BKE_tracking_get_active_tracks(tracking);
	ListBase *plane_tracks_base = BKE_tracking_get_active_plane_tracks(tracking);
	MovieTrackingTrack *track;
	MovieTrackingPlaneTrack *plane_track;

	track = tracksbase->first;
	while (track) {
		track->flag &= ~TRACK_HIDDEN;

		track = track->next;
	}

	for (plane_track = plane_tracks_base->first;
	     plane_track;
	     plane_track = plane_track->next)
	{
		plane_track->flag &= ~PLANE_TRACK_HIDDEN;
	}

	BKE_tracking_dopesheet_tag_update(tracking);

	WM_event_add_notifier(C, NC_MOVIECLIP | ND_DISPLAY, NULL);

	return OPERATOR_FINISHED;
}

void CLIP_OT_hide_tracks_clear(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Hide Tracks Clear";
	ot->description = "Clear hide selected tracks";
	ot->idname = "CLIP_OT_hide_tracks_clear";

	/* api callbacks */
	ot->exec = hide_tracks_clear_exec;
	ot->poll = ED_space_clip_tracking_poll;

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}

/********************** detect features operator *********************/

static bGPDlayer *detect_get_layer(MovieClip *clip)
{
	bGPDlayer *layer;

	if (!clip->gpd)
		return NULL;

	layer = clip->gpd->layers.first;
	while (layer) {
		if (layer->flag & GP_LAYER_ACTIVE)
			return layer;

		layer = layer->next;
	}

	return NULL;
}

static int detect_features_exec(bContext *C, wmOperator *op)
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	int clip_flag = clip->flag & MCLIP_TIMECODE_FLAGS;
	ImBuf *ibuf = BKE_movieclip_get_ibuf_flag(clip, &sc->user, clip_flag, MOVIECLIP_CACHE_SKIP);
	MovieTracking *tracking = &clip->tracking;
	ListBase *tracksbase = BKE_tracking_get_active_tracks(tracking);
	MovieTrackingTrack *track = tracksbase->first;
	int placement = RNA_enum_get(op->ptr, "placement");
	int margin = RNA_int_get(op->ptr, "margin");
	int min_distance = RNA_int_get(op->ptr, "min_distance");
	float threshold = RNA_float_get(op->ptr, "threshold");
	int place_outside_layer = 0;
	int framenr = ED_space_clip_get_clip_frame_number(sc);
	bGPDlayer *layer = NULL;

	if (!ibuf) {
		BKE_report(op->reports, RPT_ERROR, "Feature detection requires valid clip frame");
		return OPERATOR_CANCELLED;
	}

	if (placement != 0) {
		layer = detect_get_layer(clip);
		place_outside_layer = placement == 2;
	}

	/* deselect existing tracks */
	while (track) {
		track->flag &= ~SELECT;
		track->pat_flag &= ~SELECT;
		track->search_flag &= ~SELECT;

		track = track->next;
	}

	BKE_tracking_detect_harris(tracking, tracksbase, ibuf, framenr, margin,
	                           threshold / 100000.0f, min_distance, layer, place_outside_layer);

	IMB_freeImBuf(ibuf);

	BKE_tracking_dopesheet_tag_update(tracking);
	WM_event_add_notifier(C, NC_MOVIECLIP | NA_EDITED, NULL);

	return OPERATOR_FINISHED;
}

void CLIP_OT_detect_features(wmOperatorType *ot)
{
	static EnumPropertyItem placement_items[] = {
		{0, "FRAME",            0, "Whole Frame",           "Place markers across the whole frame"},
		{1, "INSIDE_GPENCIL",   0, "Inside grease pencil",  "Place markers only inside areas outlined with grease pencil"},
		{2, "OUTSIDE_GPENCIL",  0, "Outside grease pencil", "Place markers only outside areas outlined with grease pencil"},
		{0, NULL, 0, NULL, NULL}
	};

	/* identifiers */
	ot->name = "Detect Features";
	ot->description = "Automatically detect features and place markers to track";
	ot->idname = "CLIP_OT_detect_features";

	/* api callbacks */
	ot->exec = detect_features_exec;
	ot->poll = ED_space_clip_tracking_poll;

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

	/* properties */
	RNA_def_enum(ot->srna, "placement", placement_items, 0, "Placement", "Placement for detected features");
	RNA_def_int(ot->srna, "margin", 16, 0, INT_MAX, "Margin", "Only features further than margin pixels from the image edges are considered", 0, 300);
	RNA_def_float(ot->srna, "threshold", 0.5f, 0.0001f, FLT_MAX, "Threshold", "Threshold level to consider feature good enough for tracking", 0.0001f, FLT_MAX);
	RNA_def_int(ot->srna, "min_distance", 120, 0, INT_MAX, "Distance", "Minimal distance accepted between two features", 0, 300);
}

/********************** frame jump operator *********************/

static int frame_jump_exec(bContext *C, wmOperator *op)
{
	Scene *scene = CTX_data_scene(C);
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	MovieTrackingTrack *track;
	int pos = RNA_enum_get(op->ptr, "position");
	int delta;

	if (pos <= 1) { /* jump to path */
		track = BKE_tracking_track_get_active(&clip->tracking);

		if (!track)
			return OPERATOR_CANCELLED;

		delta = pos == 1 ? 1 : -1;

		while (sc->user.framenr + delta >= SFRA && sc->user.framenr + delta <= EFRA) {
			int framenr = BKE_movieclip_remap_scene_to_clip_frame(clip, sc->user.framenr + delta);
			MovieTrackingMarker *marker = BKE_tracking_marker_get_exact(track, framenr);

			if (!marker || marker->flag & MARKER_DISABLED)
				break;

			sc->user.framenr += delta;
		}
	}
	else {  /* to to failed frame */
		if (clip->tracking.reconstruction.flag & TRACKING_RECONSTRUCTED) {
			int a = ED_space_clip_get_clip_frame_number(sc);
			MovieTracking *tracking = &clip->tracking;
			MovieTrackingObject *object = BKE_tracking_object_get_active(tracking);

			delta = pos == 3 ? 1 : -1;

			a += delta;

			while (a + delta >= SFRA && a + delta <= EFRA) {
				MovieReconstructedCamera *cam;

				cam = BKE_tracking_camera_get_reconstructed(tracking, object, a);

				if (!cam) {
					sc->user.framenr = BKE_movieclip_remap_clip_to_scene_frame(clip, a);

					break;
				}

				a += delta;
			}
		}
	}

	if (CFRA != sc->user.framenr) {
		CFRA = sc->user.framenr;
		sound_seek_scene(CTX_data_main(C), scene);

		WM_event_add_notifier(C, NC_SCENE | ND_FRAME, scene);
	}

	WM_event_add_notifier(C, NC_MOVIECLIP | ND_DISPLAY, NULL);

	return OPERATOR_FINISHED;
}

void CLIP_OT_frame_jump(wmOperatorType *ot)
{
	static EnumPropertyItem position_items[] = {
		{0, "PATHSTART",    0, "Path Start",        "Jump to start of current path"},
		{1, "PATHEND",      0, "Path End",          "Jump to end of current path"},
		{2, "FAILEDPREV",   0, "Previous Failed",   "Jump to previous failed frame"},
		{2, "FAILNEXT",     0, "Next Failed",       "Jump to next failed frame"},
		{0, NULL, 0, NULL, NULL}
	};

	/* identifiers */
	ot->name = "Jump to Frame";
	ot->description = "Jump to special frame";
	ot->idname = "CLIP_OT_frame_jump";

	/* api callbacks */
	ot->exec = frame_jump_exec;
	ot->poll = ED_space_clip_poll;

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

	/* properties */
	RNA_def_enum(ot->srna, "position", position_items, 0, "Position", "Position to jump to");
}

/********************** join tracks operator *********************/

static int join_tracks_exec(bContext *C, wmOperator *op)
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	MovieTracking *tracking = &clip->tracking;
	ListBase *tracksbase = BKE_tracking_get_active_tracks(tracking);
	MovieTrackingTrack *act_track, *track, *next;

	act_track = BKE_tracking_track_get_active(tracking);

	if (!act_track) {
		BKE_report(op->reports, RPT_ERROR, "No active track to join to");
		return OPERATOR_CANCELLED;
	}

	track = tracksbase->first;
	while (track) {
		next = track->next;

		if (TRACK_VIEW_SELECTED(sc, track) && track != act_track) {
			BKE_tracking_tracks_join(tracking, act_track, track);

			if (tracking->stabilization.rot_track == track)
				tracking->stabilization.rot_track = act_track;

			BKE_tracking_track_free(track);
			BLI_freelinkN(tracksbase, track);
		}

		track = next;
	}

	WM_event_add_notifier(C, NC_MOVIECLIP | NA_EDITED, clip);

	return OPERATOR_FINISHED;
}

void CLIP_OT_join_tracks(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Join Tracks";
	ot->description = "Join selected tracks";
	ot->idname = "CLIP_OT_join_tracks";

	/* api callbacks */
	ot->exec = join_tracks_exec;
	ot->poll = ED_space_clip_tracking_poll;

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}

/********************** lock tracks operator *********************/

static int lock_tracks_exec(bContext *C, wmOperator *op)
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	MovieTracking *tracking = &clip->tracking;
	ListBase *tracksbase = BKE_tracking_get_active_tracks(tracking);
	MovieTrackingTrack *track = tracksbase->first;
	int action = RNA_enum_get(op->ptr, "action");

	while (track) {
		if (TRACK_VIEW_SELECTED(sc, track)) {
			if (action == 0)
				track->flag |= TRACK_LOCKED;
			else if (action == 1)
				track->flag &= ~TRACK_LOCKED;
			else track->flag ^= TRACK_LOCKED;
		}

		track = track->next;
	}

	WM_event_add_notifier(C, NC_MOVIECLIP | NA_EVALUATED, clip);

	return OPERATOR_FINISHED;
}

void CLIP_OT_lock_tracks(wmOperatorType *ot)
{
	static EnumPropertyItem actions_items[] = {
		{0, "LOCK", 0, "Lock", "Lock selected tracks"},
		{1, "UNLOCK", 0, "Unlock", "Unlock selected tracks"},
		{2, "TOGGLE", 0, "Toggle", "Toggle locked flag for selected tracks"},
		{0, NULL, 0, NULL, NULL}
	};

	/* identifiers */
	ot->name = "Lock Tracks";
	ot->description = "Lock/unlock selected tracks";
	ot->idname = "CLIP_OT_lock_tracks";

	/* api callbacks */
	ot->exec = lock_tracks_exec;
	ot->poll = ED_space_clip_tracking_poll;

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

	/* properties */
	RNA_def_enum(ot->srna, "action", actions_items, 0, "Action", "Lock action to execute");
}

/********************** set keyframe operator *********************/

static int set_solver_keyframe_exec(bContext *C, wmOperator *op)
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	MovieTracking *tracking = &clip->tracking;
	MovieTrackingObject *object = BKE_tracking_object_get_active(tracking);
	int keyframe = RNA_enum_get(op->ptr, "keyframe");
	int framenr = BKE_movieclip_remap_scene_to_clip_frame(clip, sc->user.framenr);

	if (keyframe == 0)
		object->keyframe1 = framenr;
	else
		object->keyframe2 = framenr;

	WM_event_add_notifier(C, NC_MOVIECLIP | ND_DISPLAY, clip);

	return OPERATOR_FINISHED;
}

void CLIP_OT_set_solver_keyframe(wmOperatorType *ot)
{
	static EnumPropertyItem keyframe_items[] = {
		{0, "KEYFRAME_A", 0, "Keyframe A", ""},
		{1, "KEYFRAME_B", 0, "Keyframe B", ""},
		{0, NULL, 0, NULL, NULL}
	};

	/* identifiers */
	ot->name = "Set Solver Keyframe";
	ot->description = "Set keyframe used by solver";
	ot->idname = "CLIP_OT_set_solver_keyframe";

	/* api callbacks */
	ot->exec = set_solver_keyframe_exec;
	ot->poll = ED_space_clip_tracking_poll;

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

	/* properties */
	RNA_def_enum(ot->srna, "keyframe", keyframe_items, 0, "Keyframe", "Keyframe to set");
}

/********************** track copy color operator *********************/

static int track_copy_color_exec(bContext *C, wmOperator *UNUSED(op))
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	MovieTracking *tracking = &clip->tracking;
	ListBase *tracksbase = BKE_tracking_get_active_tracks(tracking);
	MovieTrackingTrack *track, *act_track = BKE_tracking_track_get_active(tracking);

	if (!act_track)
		return OPERATOR_CANCELLED;

	track = tracksbase->first;
	while (track) {
		if (TRACK_VIEW_SELECTED(sc, track) && track != act_track) {
			track->flag &= ~TRACK_CUSTOMCOLOR;

			if (act_track->flag & TRACK_CUSTOMCOLOR) {
				copy_v3_v3(track->color, act_track->color);
				track->flag |= TRACK_CUSTOMCOLOR;
			}
		}

		track = track->next;
	}

	WM_event_add_notifier(C, NC_MOVIECLIP | ND_DISPLAY, clip);

	return OPERATOR_FINISHED;
}

void CLIP_OT_track_copy_color(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Copy Color";
	ot->description = "Copy color to all selected tracks";
	ot->idname = "CLIP_OT_track_copy_color";

	/* api callbacks */
	ot->exec = track_copy_color_exec;
	ot->poll = ED_space_clip_tracking_poll;

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}

/********************** add 2d stabilization tracks operator *********************/

static int stabilize_2d_poll(bContext *C)
{
	if (ED_space_clip_tracking_poll(C)) {
		SpaceClip *sc = CTX_wm_space_clip(C);
		MovieClip *clip = ED_space_clip_get_clip(sc);
		MovieTrackingObject *tracking_object = BKE_tracking_object_get_active(&clip->tracking);

		return tracking_object->flag & TRACKING_OBJECT_CAMERA;
	}

	return false;
}

static int stabilize_2d_add_exec(bContext *C, wmOperator *UNUSED(op))
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	MovieTracking *tracking = &clip->tracking;
	ListBase *tracksbase = BKE_tracking_get_active_tracks(tracking);
	MovieTrackingTrack *track;
	MovieTrackingStabilization *stab = &tracking->stabilization;
	int update = 0;

	track = tracksbase->first;
	while (track) {
		if (TRACK_VIEW_SELECTED(sc, track) && (track->flag & TRACK_USE_2D_STAB) == 0) {
			track->flag |= TRACK_USE_2D_STAB;
			stab->tot_track++;

			update = 1;
		}

		track = track->next;
	}

	if (update) {
		stab->ok = 0;

		DAG_id_tag_update(&clip->id, 0);
		WM_event_add_notifier(C, NC_MOVIECLIP | ND_DISPLAY, clip);
	}

	return OPERATOR_FINISHED;
}

void CLIP_OT_stabilize_2d_add(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Add Stabilization Tracks";
	ot->description = "Add selected tracks to 2D stabilization tool";
	ot->idname = "CLIP_OT_stabilize_2d_add";

	/* api callbacks */
	ot->exec = stabilize_2d_add_exec;
	ot->poll = stabilize_2d_poll;

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}

/********************** remove 2d stabilization tracks operator *********************/

static int stabilize_2d_remove_exec(bContext *C, wmOperator *UNUSED(op))
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	MovieTracking *tracking = &clip->tracking;
	MovieTrackingStabilization *stab = &tracking->stabilization;
	ListBase *tracksbase = BKE_tracking_get_active_tracks(tracking);
	MovieTrackingTrack *track;
	int a = 0, update = 0;

	track = tracksbase->first;
	while (track) {
		if (track->flag & TRACK_USE_2D_STAB) {
			if (a == stab->act_track) {
				track->flag &= ~TRACK_USE_2D_STAB;

				stab->act_track--;
				stab->tot_track--;

				if (stab->act_track < 0)
					stab->act_track = 0;

				update = 1;

				break;
			}

			a++;
		}

		track = track->next;
	}

	if (update) {
		stab->ok = 0;

		DAG_id_tag_update(&clip->id, 0);
		WM_event_add_notifier(C, NC_MOVIECLIP | ND_DISPLAY, clip);
	}

	return OPERATOR_FINISHED;
}

void CLIP_OT_stabilize_2d_remove(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Remove Stabilization Track";
	ot->description = "Remove selected track from stabilization";
	ot->idname = "CLIP_OT_stabilize_2d_remove";

	/* api callbacks */
	ot->exec = stabilize_2d_remove_exec;
	ot->poll = stabilize_2d_poll;

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}

/********************** select 2d stabilization tracks operator *********************/

static int stabilize_2d_select_exec(bContext *C, wmOperator *UNUSED(op))
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	MovieTracking *tracking = &clip->tracking;
	ListBase *tracksbase = BKE_tracking_get_active_tracks(tracking);
	MovieTrackingTrack *track;
	int update = 0;

	track = tracksbase->first;
	while (track) {
		if (track->flag & TRACK_USE_2D_STAB) {
			BKE_tracking_track_flag_set(track, TRACK_AREA_ALL, SELECT);

			update = 1;
		}

		track = track->next;
	}

	if (update)
		WM_event_add_notifier(C, NC_MOVIECLIP | ND_SELECT, clip);

	return OPERATOR_FINISHED;
}

void CLIP_OT_stabilize_2d_select(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Select Stabilization Tracks";
	ot->description = "Select tracks which are used for stabilization";
	ot->idname = "CLIP_OT_stabilize_2d_select";

	/* api callbacks */
	ot->exec = stabilize_2d_select_exec;
	ot->poll = stabilize_2d_poll;

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}

/********************** set 2d stabilization rotation track operator *********************/

static int stabilize_2d_set_rotation_exec(bContext *C, wmOperator *UNUSED(op))
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	MovieTracking *tracking = &clip->tracking;
	MovieTrackingTrack *act_track = BKE_tracking_track_get_active(tracking);

	if (act_track) {
		MovieTrackingStabilization *stab = &tracking->stabilization;

		stab->rot_track = act_track;
		stab->ok = 0;

		DAG_id_tag_update(&clip->id, 0);
		WM_event_add_notifier(C, NC_MOVIECLIP | ND_DISPLAY, clip);
	}

	return OPERATOR_FINISHED;
}

void CLIP_OT_stabilize_2d_set_rotation(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Set Rotation Track";
	ot->description = "Use active track to compensate rotation when doing 2D stabilization";
	ot->idname = "CLIP_OT_stabilize_2d_set_rotation";

	/* api callbacks */
	ot->exec = stabilize_2d_set_rotation_exec;
	ot->poll = stabilize_2d_poll;

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}

/********************** clean tracks operator *********************/

static bool is_track_clean(MovieTrackingTrack *track, int frames, int del)
{
	bool ok = true;
	int a, prev = -1, count = 0;
	MovieTrackingMarker *markers = track->markers, *new_markers = NULL;
	int start_disabled = 0;
	int markersnr = track->markersnr;

	if (del)
		new_markers = MEM_callocN(markersnr * sizeof(MovieTrackingMarker), "track cleaned markers");

	for (a = 0; a < markersnr; a++) {
		int end = 0;

		if (prev == -1) {
			if ((markers[a].flag & MARKER_DISABLED) == 0)
				prev = a;
			else
				start_disabled = 1;
		}

		if (prev >= 0) {
			end =  a == markersnr - 1;
			end |= (a < markersnr - 1) && (markers[a].framenr != markers[a + 1].framenr - 1 ||
			                               markers[a].flag & MARKER_DISABLED);
		}

		if (end) {
			int segok = 1, len = 0;

			if (a != prev && markers[a].framenr != markers[a - 1].framenr + 1)
				len = a - prev;
			else if (markers[a].flag & MARKER_DISABLED)
				len = a - prev;
			else len = a - prev + 1;

			if (frames) {
				if (len < frames) {
					segok = 0;
					ok = 0;

					if (!del)
						break;
				}
			}

			if (del) {
				if (segok) {
					int t = len;

					if (markers[a].flag & MARKER_DISABLED)
						t++;

					/* place disabled marker in front of current segment */
					if (start_disabled) {
						memcpy(new_markers + count, markers + prev, sizeof(MovieTrackingMarker));
						new_markers[count].framenr--;
						new_markers[count].flag |= MARKER_DISABLED;

						count++;
						start_disabled = 0;
					}

					memcpy(new_markers + count, markers + prev, t * sizeof(MovieTrackingMarker));
					count += t;
				}
				else if (markers[a].flag & MARKER_DISABLED) {
					/* current segment which would be deleted was finished by disabled marker,
					 * so next segment should be started from disabled marker */
					start_disabled = 1;
				}
			}

			prev = -1;
		}
	}

	if (del) {
		MEM_freeN(track->markers);

		if (count) {
			track->markers = new_markers;
		}
		else {
			track->markers = NULL;
			MEM_freeN(new_markers);
		}

		track->markersnr = count;
	}

	return ok;
}

static int clean_tracks_exec(bContext *C, wmOperator *op)
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	MovieTracking *tracking = &clip->tracking;
	ListBase *tracksbase = BKE_tracking_get_active_tracks(tracking);
	MovieTrackingTrack *track, *next, *act_track = BKE_tracking_track_get_active(tracking);
	int frames = RNA_int_get(op->ptr, "frames");
	int action = RNA_enum_get(op->ptr, "action");
	float error = RNA_float_get(op->ptr, "error");

	if (error && action == TRACKING_CLEAN_DELETE_SEGMENT)
		action = TRACKING_CLEAN_DELETE_TRACK;

	track = tracksbase->first;
	while (track) {
		next = track->next;

		if ((track->flag & TRACK_HIDDEN) == 0 && (track->flag & TRACK_LOCKED) == 0) {
			bool ok;

			ok = (is_track_clean(track, frames, action == TRACKING_CLEAN_DELETE_SEGMENT)) &&
			     (error == 0.0f || (track->flag & TRACK_HAS_BUNDLE) == 0  || track->error < error);

			if (!ok) {
				if (action == TRACKING_CLEAN_SELECT) {
					BKE_tracking_track_flag_set(track, TRACK_AREA_ALL, SELECT);
				}
				else if (action == TRACKING_CLEAN_DELETE_TRACK) {
					if (track == act_track)
						clip->tracking.act_track = NULL;

					BKE_tracking_track_free(track);
					BLI_freelinkN(tracksbase, track);
					track = NULL;
				}

				/* happens when all tracking segments are not long enough */
				if (track && track->markersnr == 0) {
					if (track == act_track)
						clip->tracking.act_track = NULL;

					BKE_tracking_track_free(track);
					BLI_freelinkN(tracksbase, track);
				}
			}
		}

		track = next;
	}

	BKE_tracking_dopesheet_tag_update(tracking);

	WM_event_add_notifier(C, NC_MOVIECLIP | ND_SELECT, clip);

	return OPERATOR_FINISHED;
}

static int clean_tracks_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);

	if (!RNA_struct_property_is_set(op->ptr, "frames"))
		RNA_int_set(op->ptr, "frames", clip->tracking.settings.clean_frames);

	if (!RNA_struct_property_is_set(op->ptr, "error"))
		RNA_float_set(op->ptr, "error", clip->tracking.settings.clean_error);

	if (!RNA_struct_property_is_set(op->ptr, "action"))
		RNA_enum_set(op->ptr, "action", clip->tracking.settings.clean_action);

	return clean_tracks_exec(C, op);
}

void CLIP_OT_clean_tracks(wmOperatorType *ot)
{
	static EnumPropertyItem actions_items[] = {
		{TRACKING_CLEAN_SELECT, "SELECT", 0, "Select", "Select unclean tracks"},
		{TRACKING_CLEAN_DELETE_TRACK, "DELETE_TRACK", 0, "Delete Track", "Delete unclean tracks"},
		{TRACKING_CLEAN_DELETE_SEGMENT, "DELETE_SEGMENTS", 0, "Delete Segments", "Delete unclean segments of tracks"},
		{0, NULL, 0, NULL, NULL}
	};

	/* identifiers */
	ot->name = "Clean Tracks";
	ot->description = "Clean tracks with high error values or few frames";
	ot->idname = "CLIP_OT_clean_tracks";

	/* api callbacks */
	ot->exec = clean_tracks_exec;
	ot->invoke = clean_tracks_invoke;
	ot->poll = ED_space_clip_tracking_poll;

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

	/* properties */
	RNA_def_int(ot->srna, "frames", 0, 0, INT_MAX, "Tracked Frames",
	            "Effect on tracks which are tracked less than specified amount of frames", 0, INT_MAX);
	RNA_def_float(ot->srna, "error", 0.0f, 0.0f, FLT_MAX, "Reprojection Error",
	              "Effect on tracks which have got larger re-projection error", 0.0f, 100.0f);
	RNA_def_enum(ot->srna, "action", actions_items, 0, "Action", "Cleanup action to execute");
}

/********************** add tracking object *********************/

static int tracking_object_new_exec(bContext *C, wmOperator *UNUSED(op))
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	MovieTracking *tracking = &clip->tracking;

	BKE_tracking_object_add(tracking, "Object");

	WM_event_add_notifier(C, NC_MOVIECLIP | NA_EDITED, clip);

	return OPERATOR_FINISHED;
}

void CLIP_OT_tracking_object_new(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Add Tracking Object";
	ot->description = "Add new object for tracking";
	ot->idname = "CLIP_OT_tracking_object_new";

	/* api callbacks */
	ot->exec = tracking_object_new_exec;
	ot->poll = ED_space_clip_tracking_poll;

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}

/********************** remove tracking object *********************/

static int tracking_object_remove_exec(bContext *C, wmOperator *op)
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	MovieTracking *tracking = &clip->tracking;
	MovieTrackingObject *object;

	object = BKE_tracking_object_get_active(tracking);

	if (object->flag & TRACKING_OBJECT_CAMERA) {
		BKE_report(op->reports, RPT_WARNING, "Object used for camera tracking cannot be deleted");
		return OPERATOR_CANCELLED;
	}

	BKE_tracking_object_delete(tracking, object);

	WM_event_add_notifier(C, NC_MOVIECLIP | NA_EDITED, clip);

	return OPERATOR_FINISHED;
}

void CLIP_OT_tracking_object_remove(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Remove Tracking Object";
	ot->description = "Remove object for tracking";
	ot->idname = "CLIP_OT_tracking_object_remove";

	/* api callbacks */
	ot->exec = tracking_object_remove_exec;
	ot->poll = ED_space_clip_tracking_poll;

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}

/********************** copy tracks to clipboard operator *********************/

static int copy_tracks_exec(bContext *C, wmOperator *UNUSED(op))
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	MovieTracking *tracking = &clip->tracking;
	MovieTrackingObject *object = BKE_tracking_object_get_active(tracking);

	clear_invisible_track_selection(sc, clip);

	BKE_tracking_clipboard_copy_tracks(tracking, object);

	return OPERATOR_FINISHED;
}

void CLIP_OT_copy_tracks(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Copy Tracks";
	ot->description = "Copy selected tracks to clipboard";
	ot->idname = "CLIP_OT_copy_tracks";

	/* api callbacks */
	ot->exec = copy_tracks_exec;
	ot->poll = ED_space_clip_tracking_poll;

	/* flags */
	ot->flag = OPTYPE_REGISTER;
}

/********************** paste tracks from clipboard operator *********************/

static int paste_tracks_poll(bContext *C)
{
	if (ED_space_clip_tracking_poll(C)) {
		return BKE_tracking_clipboard_has_tracks();
	}

	return 0;
}

static int paste_tracks_exec(bContext *C, wmOperator *UNUSED(op))
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	MovieTracking *tracking = &clip->tracking;
	MovieTrackingObject *object = BKE_tracking_object_get_active(tracking);
	ListBase *tracks_base = BKE_tracking_object_get_tracks(tracking, object);

	BKE_tracking_tracks_deselect_all(tracks_base);
	BKE_tracking_clipboard_paste_tracks(tracking, object);

	WM_event_add_notifier(C, NC_MOVIECLIP | NA_EDITED, clip);

	return OPERATOR_FINISHED;
}

void CLIP_OT_paste_tracks(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Paste Tracks";
	ot->description = "Paste tracks from clipboard";
	ot->idname = "CLIP_OT_paste_tracks";

	/* api callbacks */
	ot->exec = paste_tracks_exec;
	ot->poll = paste_tracks_poll;

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}

/********************** Create plane track operator *********************/

static int create_plane_track_tracks_exec(bContext *C, wmOperator *op)
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	MovieTracking *tracking = &clip->tracking;
	MovieTrackingPlaneTrack *plane_track;
	ListBase *tracks_base = BKE_tracking_get_active_tracks(tracking);
	ListBase *plane_tracks_base = BKE_tracking_get_active_plane_tracks(tracking);
	int framenr = ED_space_clip_get_clip_frame_number(sc);

	plane_track = BKE_tracking_plane_track_add(tracking, plane_tracks_base, tracks_base, framenr);

	if (plane_track == NULL) {
		BKE_report(op->reports, RPT_ERROR, "Need at least 4 selected point tracks to create a plane");
		return OPERATOR_CANCELLED;
	}
	else {
		BKE_tracking_tracks_deselect_all(tracks_base);

		plane_track->flag |= SELECT;
		clip->tracking.act_track = NULL;
		clip->tracking.act_plane_track = plane_track;

		/* Copute homoraphies and apply them on marker's corner, so we've got
		 * quite nice motion from the very beginning.
		 */
		BKE_tracking_track_plane_from_existing_motion(plane_track, framenr);
	}

	WM_event_add_notifier(C, NC_MOVIECLIP | NA_EDITED, clip);

	return OPERATOR_FINISHED;
}

void CLIP_OT_create_plane_track(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Create Plane Track";
	ot->description = "Create new plane track out of selected point tracks";
	ot->idname = "CLIP_OT_create_plane_track";

	/* api callbacks */
	ot->exec = create_plane_track_tracks_exec;
	ot->poll = ED_space_clip_tracking_poll;

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}

/********************** Slide plane marker corner operator *********************/

typedef struct SlidePlaneMarkerData {
	int event_type;
	MovieTrackingPlaneTrack *plane_track;
	MovieTrackingPlaneMarker *plane_marker;
	int width, height;
	int corner_index;
	float *corner;
	int previous_mval[2];
	float previous_corner[2];
	float old_corner[2];
	bool accurate;
} SlidePlaneMarkerData;

static bool mouse_on_plane_slide_zone(SpaceClip *sc, float co[2], float slide_zone[2], int width, int height)
{
	const float size = 12.0f;
	float dx, dy;

	dx = size / width / sc->zoom;
	dy = size / height / sc->zoom;

	return IN_RANGE_INCL(co[0], slide_zone[0] - dx, slide_zone[0] + dx) &&
	       IN_RANGE_INCL(co[1], slide_zone[1] - dy, slide_zone[1] + dy);
}

static MovieTrackingPlaneTrack *tracking_plane_marker_check_slide(bContext *C, const wmEvent *event, int *corner_r)
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	ARegion *ar = CTX_wm_region(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	MovieTrackingPlaneTrack *plane_track;
	int width, height;
	float co[2];
	ListBase *plane_tracks_base = BKE_tracking_get_active_plane_tracks(&clip->tracking);
	int framenr = ED_space_clip_get_clip_frame_number(sc);

	ED_space_clip_get_size(sc, &width, &height);

	if (width == 0 || height == 0) {
		return NULL;
	}

	ED_clip_mouse_pos(sc, ar, event->mval, co);

	for (plane_track = plane_tracks_base->first;
	     plane_track;
	     plane_track = plane_track->next)
	{
		if (PLANE_TRACK_VIEW_SELECTED(plane_track)) {
			MovieTrackingPlaneMarker *plane_marker = BKE_tracking_plane_marker_get(plane_track, framenr);
			bool ok = false;
			int i;

			for (i = 0; i < 4; i++) {
				if (mouse_on_plane_slide_zone(sc, co, plane_marker->corners[i], width, height)) {
					if (corner_r) {
						*corner_r = i;
					}
					ok = true;
					break;
				}
			}

			if (ok) {
				return plane_track;
			}
		}
	}

	return NULL;
}

static void *slide_plane_marker_customdata(bContext *C, const wmEvent *event)
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	ARegion *ar = CTX_wm_region(C);
	MovieTrackingPlaneTrack *plane_track;
	int width, height;
	float co[2];
	SlidePlaneMarkerData *customdata = NULL;
	int framenr = ED_space_clip_get_clip_frame_number(sc);
	int corner;

	ED_space_clip_get_size(sc, &width, &height);

	if (width == 0 || height == 0) {
		return NULL;
	}

	ED_clip_mouse_pos(sc, ar, event->mval, co);

	plane_track = tracking_plane_marker_check_slide(C, event, &corner);
	if (plane_track) {
		MovieTrackingPlaneMarker *plane_marker;

		customdata = MEM_callocN(sizeof(SlidePlaneMarkerData), "slide plane marker data");

		customdata->event_type = event->type;

		plane_marker = BKE_tracking_plane_marker_ensure(plane_track, framenr);

		customdata->plane_track = plane_track;
		customdata->plane_marker = plane_marker;
		customdata->width = width;
		customdata->height = height;

		customdata->previous_mval[0] = event->mval[0];
		customdata->previous_mval[1] = event->mval[1];

		customdata->corner_index = corner;
		customdata->corner = plane_marker->corners[corner];

		copy_v2_v2(customdata->previous_corner, customdata->corner);
		copy_v2_v2(customdata->old_corner, customdata->corner);
	}

	return customdata;
}

static int slide_plane_marker_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
	SlidePlaneMarkerData *slidedata = slide_plane_marker_customdata(C, event);

	if (slidedata) {
		SpaceClip *sc = CTX_wm_space_clip(C);
		MovieClip *clip = ED_space_clip_get_clip(sc);
		MovieTracking *tracking = &clip->tracking;

		tracking->act_plane_track = slidedata->plane_track;
		tracking->act_track = NULL;

		op->customdata = slidedata;

		hide_cursor(C);
		WM_event_add_modal_handler(C, op);

		WM_event_add_notifier(C, NC_GEOM | ND_SELECT, NULL);

		return OPERATOR_RUNNING_MODAL;
	}

	return OPERATOR_PASS_THROUGH;
}

static void cancel_mouse_slide_plane_marker(SlidePlaneMarkerData *data)
{
	copy_v2_v2(data->corner, data->old_corner);
}

static void free_slide_plane_marker_data(SlidePlaneMarkerData *data)
{
	MEM_freeN(data);
}

static void slide_plane_marker_update_homographies(SpaceClip *sc, SlidePlaneMarkerData *data)
{
	int framenr = ED_space_clip_get_clip_frame_number(sc);

	BKE_tracking_track_plane_from_existing_motion(data->plane_track, framenr);
}

static int slide_plane_marker_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	SlidePlaneMarkerData *data = (SlidePlaneMarkerData *) op->customdata;
	float dx, dy, mdelta[2];
	int next_corner_index, prev_corner_index, diag_corner_index;
	const float *next_corner, *prev_corner, *diag_corner;
	float next_edge[2], prev_edge[2], next_diag_edge[2], prev_diag_edge[2];

	switch (event->type) {
		case LEFTCTRLKEY:
		case RIGHTCTRLKEY:
		case LEFTSHIFTKEY:
		case RIGHTSHIFTKEY:
			if (ELEM(event->type, LEFTSHIFTKEY, RIGHTSHIFTKEY))
				data->accurate = event->val == KM_PRESS;

			/* fall-through */
		case MOUSEMOVE:
			mdelta[0] = event->mval[0] - data->previous_mval[0];
			mdelta[1] = event->mval[1] - data->previous_mval[1];

			dx = mdelta[0] / data->width / sc->zoom;
			dy = mdelta[1] / data->height / sc->zoom;

			if (data->accurate) {
				dx /= 5.0f;
				dy /= 5.0f;
			}

			data->corner[0] = data->previous_corner[0] + dx;
			data->corner[1] = data->previous_corner[1] + dy;


			/*
			                               prev_edge
			    (Corner 3, current) <-----------------------   (Corner 2, previous)
			            |                                              ^
			            |                                              |
			            |                                              |
			            |                                              |
			  next_edge |                                              | next_diag_edge
			            |                                              |
			            |                                              |
			            |                                              |
			            v                                              |
			     (Corner 0, next)   ----------------------->   (Corner 1, diagonal)
			                              prev_diag_edge
			 */

			next_corner_index = (data->corner_index + 1) % 4;
			prev_corner_index = (data->corner_index + 3) % 4;
			diag_corner_index = (data->corner_index + 2) % 4;

			next_corner = data->plane_marker->corners[next_corner_index];
			prev_corner = data->plane_marker->corners[prev_corner_index];
			diag_corner = data->plane_marker->corners[diag_corner_index];

			sub_v2_v2v2(next_edge, next_corner, data->corner);
			sub_v2_v2v2(prev_edge, data->corner, prev_corner);
			sub_v2_v2v2(next_diag_edge, prev_corner, diag_corner);
			sub_v2_v2v2(prev_diag_edge, diag_corner, next_corner);

			if (cross_v2v2(prev_edge, next_edge) < 0.0f) {
				closest_to_line_v2(data->corner, data->corner, prev_corner, next_corner);
			}

			if (cross_v2v2(next_diag_edge, prev_edge) < 0.0f) {
				closest_to_line_v2(data->corner, data->corner, prev_corner, diag_corner);
			}

			if (cross_v2v2(next_edge, prev_diag_edge) < 0.0f) {
				closest_to_line_v2(data->corner, data->corner, next_corner, diag_corner);
			}

			data->previous_mval[0] = event->mval[0];
			data->previous_mval[1] = event->mval[1];
			copy_v2_v2(data->previous_corner, data->corner);

			DAG_id_tag_update(&sc->clip->id, 0);

			WM_event_add_notifier(C, NC_MOVIECLIP | NA_EDITED, NULL);

			break;

		case LEFTMOUSE:
		case RIGHTMOUSE:
			if (event->type == data->event_type && event->val == KM_RELEASE) {
				/* Marker is now keyframed. */
				data->plane_marker->flag &= ~PLANE_MARKER_TRACKED;

				slide_plane_marker_update_homographies(sc, data);

				free_slide_plane_marker_data(op->customdata);

				show_cursor(C);

				DAG_id_tag_update(&sc->clip->id, 0);
				WM_event_add_notifier(C, NC_MOVIECLIP | NA_EDITED, clip);

				return OPERATOR_FINISHED;
			}

			break;

		case ESCKEY:
			cancel_mouse_slide_plane_marker(op->customdata);

			free_slide_plane_marker_data(op->customdata);

			show_cursor(C);

			WM_event_add_notifier(C, NC_MOVIECLIP | NA_EDITED, clip);

			return OPERATOR_CANCELLED;
	}

	return OPERATOR_RUNNING_MODAL;
}

void CLIP_OT_slide_plane_marker(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Slide Plane Marker";
	ot->description = "Slide plane marker areas";
	ot->idname = "CLIP_OT_slide_plane_marker";

	/* api callbacks */
	ot->poll = ED_space_clip_tracking_poll;
	ot->invoke = slide_plane_marker_invoke;
	ot->modal = slide_plane_marker_modal;

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_GRAB_POINTER | OPTYPE_BLOCKING;
}

/********************** Insert track keyframe operator *********************/

static void keyframe_set_flag(bContext *C, bool set)
{
	SpaceClip *sc = CTX_wm_space_clip(C);
	MovieClip *clip = ED_space_clip_get_clip(sc);
	MovieTracking *tracking = &clip->tracking;
	ListBase *tracks_base = BKE_tracking_get_active_tracks(tracking);
	ListBase *plane_tracks_base = BKE_tracking_get_active_plane_tracks(tracking);
	MovieTrackingTrack *track;
	MovieTrackingPlaneTrack *plane_track;
	int framenr = ED_space_clip_get_clip_frame_number(sc);

	for (track = tracks_base->first; track; track = track->next) {
		if (TRACK_VIEW_SELECTED(sc, track)) {
			if (set) {
				MovieTrackingMarker *marker = BKE_tracking_marker_ensure(track, framenr);
				marker->flag &= ~MARKER_TRACKED;
			}
			else {
				MovieTrackingMarker *marker = BKE_tracking_marker_get_exact(track, framenr);
				if (marker) {
					marker->flag |= MARKER_TRACKED;
				}
			}
		}
	}

	for (plane_track = plane_tracks_base->first; plane_track; plane_track = plane_track->next) {
		if (PLANE_TRACK_VIEW_SELECTED(plane_track)) {
			if (set) {
				MovieTrackingPlaneMarker *plane_marker = BKE_tracking_plane_marker_ensure(plane_track, framenr);
				if (plane_marker->flag & PLANE_MARKER_TRACKED) {
					plane_marker->flag &= ~PLANE_MARKER_TRACKED;
					BKE_tracking_track_plane_from_existing_motion(plane_track, plane_marker->framenr);
				}
			}
			else {
				MovieTrackingPlaneMarker *plane_marker = BKE_tracking_plane_marker_get_exact(plane_track, framenr);
				if (plane_marker) {
					if ((plane_marker->flag & PLANE_MARKER_TRACKED) == 0) {
						plane_marker->flag |= PLANE_MARKER_TRACKED;
						BKE_tracking_retrack_plane_from_existing_motion_at_segment(plane_track, plane_marker->framenr);
					}
				}
			}
		}
	}

	WM_event_add_notifier(C, NC_MOVIECLIP | NA_EDITED, clip);
}

static int keyframe_insert_exec(bContext *C, wmOperator *UNUSED(op))
{
	keyframe_set_flag(C, true);
	return OPERATOR_FINISHED;
}

void CLIP_OT_keyframe_insert(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Insert keyframe";
	ot->description = "Insert a keyframe to selected tracks at current frame";
	ot->idname = "CLIP_OT_keyframe_insert";

	/* api callbacks */
	ot->poll = ED_space_clip_tracking_poll;
	ot->exec = keyframe_insert_exec;

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}

/********************** Delete track keyframe operator *********************/

static int keyframe_delete_exec(bContext *C, wmOperator *UNUSED(op))
{
	keyframe_set_flag(C, false);
	return OPERATOR_FINISHED;
}

void CLIP_OT_keyframe_delete(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Delete keyframe";
	ot->description = "Delete a keyframe from selected tracks at current frame";
	ot->idname = "CLIP_OT_keyframe_delete";

	/* api callbacks */
	ot->poll = ED_space_clip_tracking_poll;
	ot->exec = keyframe_delete_exec;

	/* flags */
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
