/*
 * ***** 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) 2001-2002 by NaN Holding BV.
 * All rights reserved.
 *
 * The Original Code is: all of this file.
 *
 * Contributor(s): John Roper
 *
 * ***** END GPL LICENSE BLOCK *****
 */

/** \file blender/editors/curve/editcurve_select.c
 *  \ingroup edcurve
 */

#include "DNA_object_types.h"
#include "DNA_scene_types.h"

#include "MEM_guardedalloc.h"

#include "BLI_bitmap.h"
#include "BLI_math.h"
#include "BLI_rand.h"
#include "BLI_heap.h"

#include "BKE_context.h"
#include "BKE_curve.h"
#include "BKE_fcurve.h"
#include "BKE_report.h"

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

#include "ED_screen.h"
#include "ED_types.h"
#include "ED_view3d.h"
#include "ED_curve.h"

#include "curve_intern.h"


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


/* returns 1 in case (de)selection was successful */
bool select_beztriple(BezTriple *bezt, bool selstatus, short flag, eVisible_Types hidden)
{
	if ((bezt->hide == 0) || (hidden == HIDDEN)) {
		if (selstatus == SELECT) { /* selects */
			bezt->f1 |= flag;
			bezt->f2 |= flag;
			bezt->f3 |= flag;
			return true;
		}
		else { /* deselects */
			bezt->f1 &= ~flag;
			bezt->f2 &= ~flag;
			bezt->f3 &= ~flag;
			return true;
		}
	}

	return false;
}

/* returns 1 in case (de)selection was successful */
bool select_bpoint(BPoint *bp, bool selstatus, short flag, bool hidden)
{
	if ((bp->hide == 0) || (hidden == 1)) {
		if (selstatus == SELECT) {
			bp->f1 |= flag;
			return true;
		}
		else {
			bp->f1 &= ~flag;
			return true;
		}
	}

	return false;
}

static bool swap_selection_beztriple(BezTriple *bezt)
{
	if (bezt->f2 & SELECT)
		return select_beztriple(bezt, DESELECT, SELECT, VISIBLE);
	else
		return select_beztriple(bezt, SELECT, SELECT, VISIBLE);
}

static bool swap_selection_bpoint(BPoint *bp)
{
	if (bp->f1 & SELECT)
		return select_bpoint(bp, DESELECT, SELECT, VISIBLE);
	else
		return select_bpoint(bp, SELECT, SELECT, VISIBLE);
}

bool ED_curve_nurb_select_check(Curve *cu, Nurb *nu)
{
	if (nu->type == CU_BEZIER) {
		BezTriple *bezt;
		int i;

		for (i = nu->pntsu, bezt = nu->bezt; i--; bezt++) {
			if (BEZT_ISSEL_ANY_HIDDENHANDLES(cu, bezt)) {
				return true;
			}
		}
	}
	else {
		BPoint *bp;
		int i;

		for (i = nu->pntsu * nu->pntsv, bp = nu->bp; i--; bp++) {
			if (bp->f1 & SELECT) {
				return true;
			}
		}
	}
	return false;
}

int ED_curve_nurb_select_count(Curve *cu, Nurb *nu)
{
	int sel = 0;

	if (nu->type == CU_BEZIER) {
		BezTriple *bezt;
		int i;

		for (i = nu->pntsu, bezt = nu->bezt; i--; bezt++) {
			if (BEZT_ISSEL_ANY_HIDDENHANDLES(cu, bezt)) {
				sel++;
			}
		}
	}
	else {
		BPoint *bp;
		int i;

		for (i = nu->pntsu * nu->pntsv, bp = nu->bp; i--; bp++) {
			if (bp->f1 & SELECT) {
				sel++;
			}
		}
	}

	return sel;
}

void ED_curve_nurb_select_all(Nurb *nu)
{
	int i;

	if (nu->bezt) {
		BezTriple *bezt;
		for (i = nu->pntsu, bezt = nu->bezt; i--; bezt++) {
			if (bezt->hide == 0) {
				BEZT_SEL_ALL(bezt);
			}
		}
	}
	else if (nu->bp) {
		BPoint *bp;
		for (i = nu->pntsu * nu->pntsv, bp = nu->bp; i--; bp++) {
			if (bp->hide == 0) {
				bp->f1 |= SELECT;
			}
		}
	}
}

void ED_curve_select_all(EditNurb *editnurb)
{
	Nurb *nu;
	for (nu = editnurb->nurbs.first; nu; nu = nu->next) {
		ED_curve_nurb_select_all(nu);
	}
}

void ED_curve_nurb_deselect_all(Nurb *nu)
{
	int i;

	if (nu->bezt) {
		BezTriple *bezt;
		for (i = nu->pntsu, bezt = nu->bezt; i--; bezt++) {
			BEZT_DESEL_ALL(bezt);
		}
	}
	else if (nu->bp) {
		BPoint *bp;
		for (i = nu->pntsu * nu->pntsv, bp = nu->bp; i--; bp++) {
			bp->f1 &= ~SELECT;
		}
	}
}

bool ED_curve_select_check(Curve *cu, struct EditNurb *editnurb)
{
	Nurb *nu;

	for (nu = editnurb->nurbs.first; nu; nu = nu->next) {
		if (ED_curve_nurb_select_check(cu, nu)) {
			return true;
		}
	}

	return false;
}

void ED_curve_deselect_all(EditNurb *editnurb)
{
	Nurb *nu;

	for (nu = editnurb->nurbs.first; nu; nu = nu->next) {
		ED_curve_nurb_deselect_all(nu);
	}
}

void ED_curve_select_swap(EditNurb *editnurb, bool hide_handles)
{
	Nurb *nu;
	BPoint *bp;
	BezTriple *bezt;
	int a;

	for (nu = editnurb->nurbs.first; nu; nu = nu->next) {
		if (nu->type == CU_BEZIER) {
			bezt = nu->bezt;
			a = nu->pntsu;
			while (a--) {
				if (bezt->hide == 0) {
					bezt->f2 ^= SELECT; /* always do the center point */
					if (!hide_handles) {
						bezt->f1 ^= SELECT;
						bezt->f3 ^= SELECT;
					}
				}
				bezt++;
			}
		}
		else {
			bp = nu->bp;
			a = nu->pntsu * nu->pntsv;
			while (a--) {
				swap_selection_bpoint(bp);
				bp++;
			}
		}
	}
}

/**
 * \param next: -1/1 for prev/next
 * \param cont: when true select continuously
 * \param selstatus: inverts behavior
 */
static void select_adjacent_cp(
        ListBase *editnurb, short next,
        const bool cont, const bool selstatus)
{
	Nurb *nu;
	BezTriple *bezt;
	BPoint *bp;
	int a;
	bool lastsel = false;

	if (next == 0) return;

	for (nu = editnurb->first; nu; nu = nu->next) {
		lastsel = false;
		if (nu->type == CU_BEZIER) {
			a = nu->pntsu;
			bezt = nu->bezt;
			if (next < 0) bezt = &nu->bezt[a - 1];
			while (a--) {
				if (a - abs(next) < 0) break;
				if ((lastsel == false) && (bezt->hide == 0) && ((bezt->f2 & SELECT) || (selstatus == DESELECT))) {
					bezt += next;
					if (!(bezt->f2 & SELECT) || (selstatus == DESELECT)) {
						bool sel = select_beztriple(bezt, selstatus, SELECT, VISIBLE);
						if (sel && !cont) lastsel = true;
					}
				}
				else {
					bezt += next;
					lastsel = false;
				}
				/* move around in zigzag way so that we go through each */
				bezt -= (next - next / abs(next));
			}
		}
		else {
			a = nu->pntsu * nu->pntsv;
			bp = nu->bp;
			if (next < 0) bp = &nu->bp[a - 1];
			while (a--) {
				if (a - abs(next) < 0) break;
				if ((lastsel == false) && (bp->hide == 0) && ((bp->f1 & SELECT) || (selstatus == DESELECT))) {
					bp += next;
					if (!(bp->f1 & SELECT) || (selstatus == DESELECT)) {
						bool sel = select_bpoint(bp, selstatus, SELECT, VISIBLE);
						if (sel && !cont) lastsel = true;
					}
				}
				else {
					bp += next;
					lastsel = false;
				}
				/* move around in zigzag way so that we go through each */
				bp -= (next - next / abs(next));
			}
		}
	}
}

/**************** select start/end operators **************/

/* (de)selects first or last of visible part of each Nurb depending on selFirst
 * selFirst: defines the end of which to select
 * doswap: defines if selection state of each first/last control point is swapped
 * selstatus: selection status in case doswap is false
 */
static void selectend_nurb(Object *obedit, eEndPoint_Types selfirst, bool doswap, bool selstatus)
{
	ListBase *editnurb = object_editcurve_get(obedit);
	Nurb *nu;
	BPoint *bp;
	BezTriple *bezt;
	Curve *cu;
	int a;

	if (obedit == NULL) return;

	cu = (Curve *)obedit->data;
	cu->actvert = CU_ACT_NONE;

	for (nu = editnurb->first; nu; nu = nu->next) {
		if (nu->type == CU_BEZIER) {
			a = nu->pntsu;

			/* which point? */
			if (selfirst == LAST) { /* select last */
				bezt = &nu->bezt[a - 1];
			}
			else { /* select first */
				bezt = nu->bezt;
			}

			while (a--) {
				bool sel;
				if (doswap) sel = swap_selection_beztriple(bezt);
				else sel = select_beztriple(bezt, selstatus, SELECT, VISIBLE);

				if (sel == true) break;
			}
		}
		else {
			a = nu->pntsu * nu->pntsv;

			/* which point? */
			if (selfirst == LAST) { /* select last */
				bp = &nu->bp[a - 1];
			}
			else { /* select first */
				bp = nu->bp;
			}

			while (a--) {
				if (bp->hide == 0) {
					bool sel;
					if (doswap) sel = swap_selection_bpoint(bp);
					else sel = select_bpoint(bp, selstatus, SELECT, VISIBLE);

					if (sel == true) break;
				}
			}
		}
	}
}

static int de_select_first_exec(bContext *C, wmOperator *UNUSED(op))
{
	Object *obedit = CTX_data_edit_object(C);

	selectend_nurb(obedit, FIRST, true, DESELECT);
	WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data);
	BKE_curve_nurb_vert_active_validate(obedit->data);

	return OPERATOR_FINISHED;
}

void CURVE_OT_de_select_first(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "(De)select First";
	ot->idname = "CURVE_OT_de_select_first";
	ot->description = "(De)select first of visible part of each NURBS";

	/* api cfirstbacks */
	ot->exec = de_select_first_exec;
	ot->poll = ED_operator_editcurve;

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

static int de_select_last_exec(bContext *C, wmOperator *UNUSED(op))
{
	Object *obedit = CTX_data_edit_object(C);

	selectend_nurb(obedit, LAST, true, DESELECT);
	WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data);
	BKE_curve_nurb_vert_active_validate(obedit->data);

	return OPERATOR_FINISHED;
}

void CURVE_OT_de_select_last(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "(De)select Last";
	ot->idname = "CURVE_OT_de_select_last";
	ot->description = "(De)select last of visible part of each NURBS";

	/* api clastbacks */
	ot->exec = de_select_last_exec;
	ot->poll = ED_operator_editcurve;

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

static int de_select_all_exec(bContext *C, wmOperator *op)
{
	Object *obedit = CTX_data_edit_object(C);
	Curve *cu = obedit->data;
	int action = RNA_enum_get(op->ptr, "action");

	if (action == SEL_TOGGLE) {
		action = SEL_SELECT;
		if (ED_curve_select_check(cu, cu->editnurb)) {
			action = SEL_DESELECT;
		}
	}

	switch (action) {
		case SEL_SELECT:
			ED_curve_select_all(cu->editnurb);
			break;
		case SEL_DESELECT:
			ED_curve_deselect_all(cu->editnurb);
			break;
		case SEL_INVERT:
			ED_curve_select_swap(cu->editnurb, (cu->drawflag & CU_HIDE_HANDLES) != 0);
			break;
	}

	WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data);
	BKE_curve_nurb_vert_active_validate(cu);

	return OPERATOR_FINISHED;
}

void CURVE_OT_select_all(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "(De)select All";
	ot->idname = "CURVE_OT_select_all";
	ot->description = "(De)select all control points";

	/* api callbacks */
	ot->exec = de_select_all_exec;
	ot->poll = ED_operator_editsurfcurve;

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

	/* properties */
	WM_operator_properties_select_all(ot);
}



/***************** select linked operator ******************/

static int select_linked_exec(bContext *C, wmOperator *UNUSED(op))
{
	Object *obedit = CTX_data_edit_object(C);
	Curve *cu = (Curve *)obedit->data;
	EditNurb *editnurb = cu->editnurb;
	ListBase *nurbs = &editnurb->nurbs;
	Nurb *nu;

	for (nu = nurbs->first; nu; nu = nu->next) {
		if (ED_curve_nurb_select_check(cu, nu)) {
			ED_curve_nurb_select_all(nu);
		}
	}

	WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data);

	return OPERATOR_FINISHED;
}

static int select_linked_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
{
	return select_linked_exec(C, op);
}

void CURVE_OT_select_linked(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Select Linked All";
	ot->idname = "CURVE_OT_select_linked";
	ot->description = "Select all control points linked to active one";

	/* api callbacks */
	ot->exec = select_linked_exec;
	ot->invoke = select_linked_invoke;
	ot->poll = ED_operator_editsurfcurve;

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

	/* properties */
}


/***************** select linked pick operator ******************/

static int select_linked_pick_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
	Object *obedit = CTX_data_edit_object(C);
	ViewContext vc;
	Nurb *nu;
	BezTriple *bezt;
	BPoint *bp;
	int a;
	const bool select = !RNA_boolean_get(op->ptr, "deselect");

	view3d_operator_needs_opengl(C);
	view3d_set_viewcontext(C, &vc);

	if (!ED_curve_pick_vert(&vc, 1, event->mval, &nu, &bezt, &bp, NULL)) {
		return OPERATOR_CANCELLED;
	}

	if (bezt) {
		a = nu->pntsu;
		bezt = nu->bezt;
		while (a--) {
			select_beztriple(bezt, select, SELECT, VISIBLE);
			bezt++;
		}
	}
	else if (bp) {
		a = nu->pntsu * nu->pntsv;
		bp = nu->bp;
		while (a--) {
			select_bpoint(bp, select, SELECT, VISIBLE);
			bp++;
		}
	}

	WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data);
	if (!select) {
		BKE_curve_nurb_vert_active_validate(obedit->data);
	}

	return OPERATOR_FINISHED;
}

void CURVE_OT_select_linked_pick(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Select Linked";
	ot->idname = "CURVE_OT_select_linked_pick";
	ot->description = "Select all control points linked to already selected ones";

	/* api callbacks */
	ot->invoke = select_linked_pick_invoke;
	ot->poll = ED_operator_editsurfcurve_region_view3d;

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

	/* properties */
	RNA_def_boolean(ot->srna, "deselect", 0, "Deselect", "Deselect linked control points rather than selecting them");
}

/***************** select row operator **********************/

static int select_row_exec(bContext *C, wmOperator *UNUSED(op))
{
	Object *obedit = CTX_data_edit_object(C);
	Curve *cu = obedit->data;
	ListBase *editnurb = object_editcurve_get(obedit);
	static BPoint *last = NULL;
	static int direction = 0;
	Nurb *nu = NULL;
	BPoint *bp = NULL;
	int u = 0, v = 0, a, b;

	if (!BKE_curve_nurb_vert_active_get(cu, &nu, (void *)&bp))
		return OPERATOR_CANCELLED;

	if (last == bp) {
		direction = 1 - direction;
		BKE_nurbList_flag_set(editnurb, 0);
	}
	last = bp;

	u = cu->actvert % nu->pntsu;
	v = cu->actvert / nu->pntsu;
	bp = nu->bp;
	for (a = 0; a < nu->pntsv; a++) {
		for (b = 0; b < nu->pntsu; b++, bp++) {
			if (direction) {
				if (a == v) select_bpoint(bp, SELECT, SELECT, VISIBLE);
			}
			else {
				if (b == u) select_bpoint(bp, SELECT, SELECT, VISIBLE);
			}
		}
	}

	WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data);

	return OPERATOR_FINISHED;
}

void CURVE_OT_select_row(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Select Control Point Row";
	ot->idname = "CURVE_OT_select_row";
	ot->description = "Select a row of control points including active one";

	/* api callbacks */
	ot->exec = select_row_exec;
	ot->poll = ED_operator_editsurf;

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

/***************** select next operator **********************/

static int select_next_exec(bContext *C, wmOperator *UNUSED(op))
{
	Object *obedit = CTX_data_edit_object(C);
	ListBase *editnurb = object_editcurve_get(obedit);

	select_adjacent_cp(editnurb, 1, 0, SELECT);
	WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data);

	return OPERATOR_FINISHED;
}

void CURVE_OT_select_next(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Select Next";
	ot->idname = "CURVE_OT_select_next";
	ot->description = "Select control points following already selected ones along the curves";

	/* api callbacks */
	ot->exec = select_next_exec;
	ot->poll = ED_operator_editcurve;

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

/***************** select previous operator **********************/

static int select_previous_exec(bContext *C, wmOperator *UNUSED(op))
{
	Object *obedit = CTX_data_edit_object(C);
	ListBase *editnurb = object_editcurve_get(obedit);

	select_adjacent_cp(editnurb, -1, 0, SELECT);
	WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data);

	return OPERATOR_FINISHED;
}

void CURVE_OT_select_previous(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Select Previous";
	ot->idname = "CURVE_OT_select_previous";
	ot->description = "Select control points preceding already selected ones along the curves";

	/* api callbacks */
	ot->exec = select_previous_exec;
	ot->poll = ED_operator_editcurve;

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

/***************** select more operator **********************/

static int select_more_exec(bContext *C, wmOperator *UNUSED(op))
{
	Object *obedit = CTX_data_edit_object(C);
	ListBase *editnurb = object_editcurve_get(obedit);
	Nurb *nu;
	BPoint *bp, *tempbp;
	int a;
	short sel = 0;

	/* note that NURBS surface is a special case because we mimic */
	/* the behavior of "select more" of mesh tools.	      */
	/* The algorithm is designed to work in planar cases so it    */
	/* may not be optimal always (example: end of NURBS sphere)   */
	if (obedit->type == OB_SURF) {
		for (nu = editnurb->first; nu; nu = nu->next) {
			BLI_bitmap *selbpoints;
			a = nu->pntsu * nu->pntsv;
			bp = nu->bp;
			selbpoints = BLI_BITMAP_NEW(a, "selectlist");
			while (a > 0) {
				if ((!BLI_BITMAP_TEST(selbpoints, a)) && (bp->hide == 0) && (bp->f1 & SELECT)) {
					/* upper control point */
					if (a % nu->pntsu != 0) {
						tempbp = bp - 1;
						if (!(tempbp->f1 & SELECT)) select_bpoint(tempbp, SELECT, SELECT, VISIBLE);
					}

					/* left control point. select only if it is not selected already */
					if (a - nu->pntsu > 0) {
						sel = 0;
						tempbp = bp + nu->pntsu;
						if (!(tempbp->f1 & SELECT)) sel = select_bpoint(tempbp, SELECT, SELECT, VISIBLE);
						/* make sure selected bpoint is discarded */
						if (sel == 1) BLI_BITMAP_ENABLE(selbpoints, a - nu->pntsu);
					}

					/* right control point */
					if (a + nu->pntsu < nu->pntsu * nu->pntsv) {
						tempbp = bp - nu->pntsu;
						if (!(tempbp->f1 & SELECT)) select_bpoint(tempbp, SELECT, SELECT, VISIBLE);
					}

					/* lower control point. skip next bp in case selection was made */
					if (a % nu->pntsu != 1) {
						sel = 0;
						tempbp = bp + 1;
						if (!(tempbp->f1 & SELECT)) sel = select_bpoint(tempbp, SELECT, SELECT, VISIBLE);
						if (sel) {
							bp++;
							a--;
						}
					}
				}

				bp++;
				a--;
			}

			MEM_freeN(selbpoints);
		}
	}
	else {
		select_adjacent_cp(editnurb, 1, 0, SELECT);
		select_adjacent_cp(editnurb, -1, 0, SELECT);
	}

	WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data);

	return OPERATOR_FINISHED;
}

void CURVE_OT_select_more(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Select More";
	ot->idname = "CURVE_OT_select_more";
	ot->description = "Select control points directly linked to already selected ones";

	/* api callbacks */
	ot->exec = select_more_exec;
	ot->poll = ED_operator_editsurfcurve;

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

/******************** select less operator *****************/

/* basic method: deselect if control point doesn't have all neighbors selected */
static int select_less_exec(bContext *C, wmOperator *UNUSED(op))
{
	Object *obedit = CTX_data_edit_object(C);
	ListBase *editnurb = object_editcurve_get(obedit);
	Nurb *nu;
	BPoint *bp;
	BezTriple *bezt;
	int a;
	int sel = 0;
	bool lastsel = false;

	if (obedit->type == OB_SURF) {
		for (nu = editnurb->first; nu; nu = nu->next) {
			BLI_bitmap *selbpoints;
			a = nu->pntsu * nu->pntsv;
			bp = nu->bp;
			selbpoints = BLI_BITMAP_NEW(a, "selectlist");
			while (a--) {
				if ((bp->hide == 0) && (bp->f1 & SELECT)) {
					sel = 0;

					/* check if neighbors have been selected */
					/* edges of surface are an exception */
					if ((a + 1) % nu->pntsu == 0) {
						sel++;
					}
					else {
						bp--;
						if (BLI_BITMAP_TEST(selbpoints, a + 1) || ((bp->hide == 0) && (bp->f1 & SELECT))) sel++;
						bp++;
					}

					if ((a + 1) % nu->pntsu == 1) {
						sel++;
					}
					else {
						bp++;
						if ((bp->hide == 0) && (bp->f1 & SELECT)) sel++;
						bp--;
					}

					if (a + 1 > nu->pntsu * nu->pntsv - nu->pntsu) {
						sel++;
					}
					else {
						bp -= nu->pntsu;
						if (BLI_BITMAP_TEST(selbpoints, a + nu->pntsu) || ((bp->hide == 0) && (bp->f1 & SELECT))) sel++;
						bp += nu->pntsu;
					}

					if (a < nu->pntsu) {
						sel++;
					}
					else {
						bp += nu->pntsu;
						if ((bp->hide == 0) && (bp->f1 & SELECT)) sel++;
						bp -= nu->pntsu;
					}

					if (sel != 4) {
						select_bpoint(bp, DESELECT, SELECT, VISIBLE);
						BLI_BITMAP_ENABLE(selbpoints, a);
					}
				}
				else {
					lastsel = false;
				}

				bp++;
			}

			MEM_freeN(selbpoints);
		}
	}
	else {
		for (nu = editnurb->first; nu; nu = nu->next) {
			lastsel = false;
			/* check what type of curve/nurb it is */
			if (nu->type == CU_BEZIER) {
				a = nu->pntsu;
				bezt = nu->bezt;
				while (a--) {
					if ((bezt->hide == 0) && (bezt->f2 & SELECT)) {
						sel = (lastsel == 1);

						/* check if neighbors have been selected */
						/* first and last are exceptions */
						if (a == nu->pntsu - 1) {
							sel++;
						}
						else {
							bezt--;
							if ((bezt->hide == 0) && (bezt->f2 & SELECT)) sel++;
							bezt++;
						}

						if (a == 0) {
							sel++;
						}
						else {
							bezt++;
							if ((bezt->hide == 0) && (bezt->f2 & SELECT)) sel++;
							bezt--;
						}

						if (sel != 2) {
							select_beztriple(bezt, DESELECT, SELECT, VISIBLE);
							lastsel = true;
						}
						else {
							lastsel = false;
						}
					}
					else {
						lastsel = false;
					}

					bezt++;
				}
			}
			else {
				a = nu->pntsu * nu->pntsv;
				bp = nu->bp;
				while (a--) {
					if ((lastsel == false) && (bp->hide == 0) && (bp->f1 & SELECT)) {
						sel = 0;

						/* first and last are exceptions */
						if (a == nu->pntsu * nu->pntsv - 1) {
							sel++;
						}
						else {
							bp--;
							if ((bp->hide == 0) && (bp->f1 & SELECT)) sel++;
							bp++;
						}

						if (a == 0) {
							sel++;
						}
						else {
							bp++;
							if ((bp->hide == 0) && (bp->f1 & SELECT)) sel++;
							bp--;
						}

						if (sel != 2) {
							select_bpoint(bp, DESELECT, SELECT, VISIBLE);
							lastsel = true;
						}
						else {
							lastsel = false;
						}
					}
					else {
						lastsel = false;
					}

					bp++;
				}
			}
		}
	}

	WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data);
	BKE_curve_nurb_vert_active_validate(obedit->data);

	return OPERATOR_FINISHED;
}

void CURVE_OT_select_less(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Select Less";
	ot->idname = "CURVE_OT_select_less";
	ot->description = "Reduce current selection by deselecting boundary elements";

	/* api callbacks */
	ot->exec = select_less_exec;
	ot->poll = ED_operator_editsurfcurve;

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

/********************** select random *********************/

static void curve_select_random(ListBase *editnurb, float randfac, int seed, bool select)
{
	Nurb *nu;
	BezTriple *bezt;
	BPoint *bp;
	int a;

	RNG *rng = BLI_rng_new_srandom(seed);

	for (nu = editnurb->first; nu; nu = nu->next) {
		if (nu->type == CU_BEZIER) {
			bezt = nu->bezt;
			a = nu->pntsu;
			while (a--) {
				if (!bezt->hide) {
					if (BLI_rng_get_float(rng) < randfac) {
						select_beztriple(bezt, select, SELECT, VISIBLE);
					}
				}
				bezt++;
			}
		}
		else {
			bp = nu->bp;
			a = nu->pntsu * nu->pntsv;

			while (a--) {
				if (!bp->hide) {
					if (BLI_rng_get_float(rng) < randfac) {
						select_bpoint(bp, select, SELECT, VISIBLE);
					}
				}
				bp++;
			}
		}
	}

	BLI_rng_free(rng);
}

static int curve_select_random_exec(bContext *C, wmOperator *op)
{
	Object *obedit = CTX_data_edit_object(C);
	ListBase *editnurb = object_editcurve_get(obedit);
	const bool select = (RNA_enum_get(op->ptr, "action") == SEL_SELECT);
	const float randfac = RNA_float_get(op->ptr, "percent") / 100.0f;
	const int seed = WM_operator_properties_select_random_seed_increment_get(op);

	curve_select_random(editnurb, randfac, seed, select);
	BKE_curve_nurb_vert_active_validate(obedit->data);

	WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data);

	return OPERATOR_FINISHED;
}

void CURVE_OT_select_random(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Select Random";
	ot->idname = "CURVE_OT_select_random";
	ot->description = "Randomly select some control points";

	/* api callbacks */
	ot->exec = curve_select_random_exec;
	ot->poll = ED_operator_editsurfcurve;

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

	/* properties */
	WM_operator_properties_select_random(ot);
}

/********************* every nth number of point *******************/

static void select_nth_bezt(Nurb *nu, BezTriple *bezt, const struct CheckerIntervalParams *params)
{
	int a, start;

	start = bezt - nu->bezt;
	a = nu->pntsu;
	bezt = &nu->bezt[a - 1];

	while (a--) {
		const int depth = abs(start - a);
		if (WM_operator_properties_checker_interval_test(params, depth)) {
			select_beztriple(bezt, DESELECT, SELECT, HIDDEN);
		}

		bezt--;
	}
}

static void select_nth_bp(Nurb *nu, BPoint *bp, const struct CheckerIntervalParams *params)
{
	int a, startrow, startpnt;
	int row, pnt;

	startrow = (bp - nu->bp) / nu->pntsu;
	startpnt = (bp - nu->bp) % nu->pntsu;

	a = nu->pntsu * nu->pntsv;
	bp = &nu->bp[a - 1];
	row = nu->pntsv - 1;
	pnt = nu->pntsu - 1;

	while (a--) {
		const int depth = abs(pnt - startpnt) + abs(row - startrow);
		if (WM_operator_properties_checker_interval_test(params, depth)) {
			select_bpoint(bp, DESELECT, SELECT, HIDDEN);
		}

		pnt--;
		if (pnt < 0) {
			pnt = nu->pntsu - 1;
			row--;
		}

		bp--;
	}
}

static bool ed_curve_select_nth(Curve *cu, const struct CheckerIntervalParams *params)
{
	Nurb *nu = NULL;
	void *vert = NULL;

	if (!BKE_curve_nurb_vert_active_get(cu, &nu, &vert))
		return false;

	if (nu->bezt) {
		select_nth_bezt(nu, vert, params);
	}
	else {
		select_nth_bp(nu, vert, params);
	}

	return true;
}

static int select_nth_exec(bContext *C, wmOperator *op)
{
	Object *obedit = CTX_data_edit_object(C);
	struct CheckerIntervalParams op_params;

	WM_operator_properties_checker_interval_from_op(op, &op_params);

	if (!ed_curve_select_nth(obedit->data, &op_params)) {
		if (obedit->type == OB_SURF) {
			BKE_report(op->reports, RPT_ERROR, "Surface has not got active point");
		}
		else {
			BKE_report(op->reports, RPT_ERROR, "Curve has not got active point");
		}

		return OPERATOR_CANCELLED;
	}

	WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data);

	return OPERATOR_FINISHED;
}

void CURVE_OT_select_nth(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Checker Deselect";
	ot->description = "Deselect every other vertex";
	ot->idname = "CURVE_OT_select_nth";

	/* api callbacks */
	ot->exec = select_nth_exec;
	ot->poll = ED_operator_editsurfcurve;

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

	WM_operator_properties_checker_interval(ot, false);
}


/* -------------------------------------------------------------------- */
/* Select Similar */

/** \name Select Similar
 * \{ */

enum {
	SIM_CMP_EQ = 0,
	SIM_CMP_GT,
	SIM_CMP_LT,
};

static EnumPropertyItem curve_prop_similar_compare_types[] = {
	{SIM_CMP_EQ, "EQUAL", 0, "Equal", ""},
	{SIM_CMP_GT, "GREATER", 0, "Greater", ""},
	{SIM_CMP_LT, "LESS", 0, "Less", ""},

	{0, NULL, 0, NULL, NULL}
};

enum {
	SIMCURHAND_TYPE = 0,
	SIMCURHAND_RADIUS,
	SIMCURHAND_WEIGHT,
	SIMCURHAND_DIRECTION,
};

static EnumPropertyItem curve_prop_similar_types[] = {
	{SIMCURHAND_TYPE, "TYPE", 0, "Type", ""},
	{SIMCURHAND_RADIUS, "RADIUS", 0, "Radius", ""},
	{SIMCURHAND_WEIGHT, "WEIGHT", 0, "Weight", ""},
	{SIMCURHAND_DIRECTION, "DIRECTION", 0, "Direction", ""},
	{0, NULL, 0, NULL, NULL}
};

static int curve_select_similar_cmp_fl(const float delta, const float thresh, const int compare)
{
	switch (compare) {
		case SIM_CMP_EQ:
			return (fabsf(delta) <= thresh);
		case SIM_CMP_GT:
			return ((delta + thresh) >= 0.0f);
		case SIM_CMP_LT:
			return ((delta - thresh) <= 0.0f);
		default:
			BLI_assert(0);
			return 0;
	}
}


static void curve_select_similar_direction__bezt(Nurb *nu, const float dir_ref[3], float angle_cos)
{
	BezTriple *bezt;
	int i;

	for (i = nu->pntsu, bezt = nu->bezt; i--; bezt++) {
		if (!bezt->hide) {
			float dir[3];
			BKE_nurb_bezt_calc_normal(nu, bezt, dir);
			if (fabsf(dot_v3v3(dir_ref, dir)) >= angle_cos) {
				select_beztriple(bezt, SELECT, SELECT, VISIBLE);
			}
		}
	}
}

static void curve_select_similar_direction__bp(Nurb *nu, const float dir_ref[3], float angle_cos)
{
	BPoint *bp;
	int i;

	for (i = nu->pntsu * nu->pntsv, bp = nu->bp; i--; bp++) {
		if (!bp->hide) {
			float dir[3];
			BKE_nurb_bpoint_calc_normal(nu, bp, dir);
			if (fabsf(dot_v3v3(dir_ref, dir)) >= angle_cos) {
				select_bpoint(bp, SELECT, SELECT, VISIBLE);
			}
		}
	}
}

static bool curve_select_similar_direction(ListBase *editnurb, Curve *cu, float thresh)
{
	Nurb *nu, *act_nu;
	void *act_vert;
	float dir[3];
	float angle_cos;

	if (!BKE_curve_nurb_vert_active_get(cu, &act_nu, &act_vert)) {
		return false;
	}

	if (act_nu->type == CU_BEZIER) {
		BKE_nurb_bezt_calc_normal(act_nu, act_vert, dir);
	}
	else {
		BKE_nurb_bpoint_calc_normal(act_nu, act_vert, dir);
	}

	/* map 0-1 to radians, 'cos' for comparison */
	angle_cos = cosf(thresh * (float)M_PI_2);

	for (nu = editnurb->first; nu; nu = nu->next) {
		if (nu->type == CU_BEZIER) {
			curve_select_similar_direction__bezt(nu, dir, angle_cos);
		}
		else {
			curve_select_similar_direction__bp(nu, dir, angle_cos);
		}
	}

	return true;
}

static void curve_select_similar_radius__bezt(Nurb *nu, float radius_ref, int compare, float thresh)
{
	BezTriple *bezt;
	int i;

	for (i = nu->pntsu, bezt = nu->bezt; i--; bezt++) {
		if (!bezt->hide) {
			if (curve_select_similar_cmp_fl(bezt->radius - radius_ref, thresh, compare)) {
				select_beztriple(bezt, SELECT, SELECT, VISIBLE);
			}
		}
	}
}

static void curve_select_similar_radius__bp(Nurb *nu, float radius_ref, int compare, float thresh)
{
	BPoint *bp;
	int i;

	for (i = nu->pntsu * nu->pntsv, bp = nu->bp; i--; bp++) {
		if (!bp->hide) {
			if (curve_select_similar_cmp_fl(bp->radius - radius_ref, thresh, compare)) {
				select_bpoint(bp, SELECT, SELECT, VISIBLE);
			}
		}
	}
}

static bool curve_select_similar_radius(ListBase *editnurb, Curve *cu, float compare, float thresh)
{
	Nurb *nu, *act_nu;
	void *act_vert;
	float radius_ref;

	if (!BKE_curve_nurb_vert_active_get(cu, &act_nu, &act_vert)) {
		return false;
	}

	if (act_nu->type == CU_BEZIER) {
		radius_ref = ((BezTriple *)act_vert)->radius;
	}
	else {
		radius_ref = ((BPoint *)act_vert)->radius;
	}

	/* make relative */
	thresh *= radius_ref;

	for (nu = editnurb->first; nu; nu = nu->next) {
		if (nu->type == CU_BEZIER) {
			curve_select_similar_radius__bezt(nu, radius_ref, compare, thresh);
		}
		else {
			curve_select_similar_radius__bp(nu, radius_ref, compare, thresh);
		}
	}

	return true;
}

static void curve_select_similar_weight__bezt(Nurb *nu, float weight_ref, int compare, float thresh)
{
	BezTriple *bezt;
	int i;

	for (i = nu->pntsu, bezt = nu->bezt; i--; bezt++) {
		if (!bezt->hide) {
			if (curve_select_similar_cmp_fl(bezt->weight - weight_ref, thresh, compare)) {
				select_beztriple(bezt, SELECT, SELECT, VISIBLE);
			}
		}
	}
}

static void curve_select_similar_weight__bp(Nurb *nu, float weight_ref, int compare, float thresh)
{
	BPoint *bp;
	int i;

	for (i = nu->pntsu * nu->pntsv, bp = nu->bp; i--; bp++) {
		if (!bp->hide) {
			if (curve_select_similar_cmp_fl(bp->weight - weight_ref, thresh, compare)) {
				select_bpoint(bp, SELECT, SELECT, VISIBLE);
			}
		}
	}
}

static bool curve_select_similar_weight(ListBase *editnurb, Curve *cu, float compare, float thresh)
{
	Nurb *nu, *act_nu;
	void *act_vert;
	float weight_ref;

	if (!BKE_curve_nurb_vert_active_get(cu, &act_nu, &act_vert))
		return false;

	if (act_nu->type == CU_BEZIER) {
		weight_ref = ((BezTriple *)act_vert)->weight;
	}
	else {
		weight_ref = ((BPoint *)act_vert)->weight;
	}

	for (nu = editnurb->first; nu; nu = nu->next) {
		if (nu->type == CU_BEZIER) {
			curve_select_similar_weight__bezt(nu, weight_ref, compare, thresh);
		}
		else {
			curve_select_similar_weight__bp(nu, weight_ref, compare, thresh);
		}
	}

	return true;
}

static bool curve_select_similar_type(ListBase *editnurb, Curve *cu)
{
	Nurb *nu, *act_nu;
	int type_ref;

	/* Get active nurb type */
	act_nu = BKE_curve_nurb_active_get(cu);

	if (!act_nu)
		return false;

	/* Get the active nurb type */
	type_ref = act_nu->type;

	for (nu = editnurb->first; nu; nu = nu->next) {
		if (nu->type == type_ref) {
			ED_curve_nurb_select_all(nu);
		}
	}

	return true;
}

static int curve_select_similar_exec(bContext *C, wmOperator *op)
{
	Object *obedit = CTX_data_edit_object(C);
	Curve *cu = obedit->data;
	ListBase *editnurb = object_editcurve_get(obedit);
	bool changed = false;

	/* Get props */
	const int type = RNA_enum_get(op->ptr, "type");
	const float thresh = RNA_float_get(op->ptr, "threshold");
	const int compare = RNA_enum_get(op->ptr, "compare");

	switch (type) {
		case SIMCURHAND_TYPE:
			changed = curve_select_similar_type(editnurb, cu);
			break;
		case SIMCURHAND_RADIUS:
			changed = curve_select_similar_radius(editnurb, cu, compare, thresh);
			break;
		case SIMCURHAND_WEIGHT:
			changed = curve_select_similar_weight(editnurb, cu, compare, thresh);
			break;
		case SIMCURHAND_DIRECTION:
			changed = curve_select_similar_direction(editnurb, cu, thresh);
			break;
	}

	if (changed) {
		WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data);
		return OPERATOR_FINISHED;
	}
	else {
		return OPERATOR_CANCELLED;
	}
}

void CURVE_OT_select_similar(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Select Similar";
	ot->idname = "CURVE_OT_select_similar";
	ot->description = "Select similar curve points by property type";

	/* api callbacks */
	ot->invoke = WM_menu_invoke;
	ot->exec = curve_select_similar_exec;
	ot->poll = ED_operator_editsurfcurve;

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

	/* properties */
	ot->prop = RNA_def_enum(ot->srna, "type", curve_prop_similar_types, SIMCURHAND_WEIGHT, "Type", "");
	RNA_def_enum(ot->srna, "compare", curve_prop_similar_compare_types, SIM_CMP_EQ, "Compare", "");
	RNA_def_float(ot->srna, "threshold", 0.1, 0.0, FLT_MAX, "Threshold", "", 0.0, 100.0);
}

/** \} */


/* -------------------------------------------------------------------- */
/* Select Shortest Path */

/** \name Select Path
 * \{ */

static float curve_calc_dist_pair(const Nurb *nu, int a, int b)
{
	const float *a_fl, *b_fl;

	if (nu->type == CU_BEZIER) {
		a_fl = nu->bezt[a].vec[1];
		b_fl = nu->bezt[b].vec[1];
	}
	else {
		a_fl = nu->bp[a].vec;
		b_fl = nu->bp[b].vec;
	}

	return len_v3v3(a_fl, b_fl);
}

static float curve_calc_dist_span(Nurb *nu, int vert_src, int vert_dst)
{
	const int u = nu->pntsu;
	int i_prev, i;
	float dist = 0.0f;

	BLI_assert(nu->pntsv == 1);

	i_prev = vert_src;
	i = (i_prev + 1) % u;

	while (true) {
		dist += curve_calc_dist_pair(nu, i_prev, i);

		if (i == vert_dst) {
			break;
		}
		i = (i + 1) % u;
	}
	return dist;
}

static void curve_select_shortest_path_curve(Nurb *nu, int vert_src, int vert_dst)
{
	const int u = nu->pntsu;
	int i;

	if (vert_src > vert_dst) {
		SWAP(int, vert_src, vert_dst);
	}

	if (nu->flagu & CU_NURB_CYCLIC) {
		if (curve_calc_dist_span(nu, vert_src, vert_dst) >
		    curve_calc_dist_span(nu, vert_dst, vert_src))
		{
			SWAP(int, vert_src, vert_dst);
		}
	}

	i = vert_src;
	while (true) {
		if (nu->type & CU_BEZIER) {
			select_beztriple(&nu->bezt[i], SELECT, SELECT, HIDDEN);
		}
		else {
			select_bpoint(&nu->bp[i], SELECT, SELECT, HIDDEN);
		}

		if (i == vert_dst) {
			break;
		}
		i = (i + 1) % u;
	}
}

static void curve_select_shortest_path_surf(Nurb *nu, int vert_src, int vert_dst)
{
	Heap *heap;

	int i, vert_curr;

	int totu = nu->pntsu;
	int totv = nu->pntsv;
	int vert_num = totu * totv;

	/* custom data */
	struct PointAdj {
		int vert, vert_prev;
		float cost;
	} *data;

	/* init connectivity data */
	data = MEM_mallocN(sizeof(*data) * vert_num, __func__);
	for (i = 0; i < vert_num; i++) {
		data[i].vert = i;
		data[i].vert_prev  = -1;
		data[i].cost  = FLT_MAX;
	}

	/* init heap */
	heap = BLI_heap_new();

	BLI_heap_insert(heap, 0.0f, &data[vert_src].vert);
	data[vert_src].cost = 0.0f;
	data[vert_src].vert_prev = vert_src;  /* nop */

	while (!BLI_heap_is_empty(heap)) {
		int axis, sign;
		int u, v;

		vert_curr = *((int *)BLI_heap_popmin(heap));
		if (vert_curr == vert_dst) {
			break;
		}

		BKE_nurb_index_to_uv(nu, vert_curr, &u, &v);

		/* loop over 4 adjacent verts */
		for (sign = -1; sign != 3; sign += 2) {
			for (axis = 0; axis != 2; axis += 1) {
				int uv_other[2] = {u, v};
				int vert_other;

				uv_other[axis] += sign;

				vert_other = BKE_nurb_index_from_uv(nu, uv_other[0], uv_other[1]);
				if (vert_other != -1) {
					const float dist = data[vert_curr].cost + curve_calc_dist_pair(nu, vert_curr, vert_other);

					if (data[vert_other].cost > dist) {
						data[vert_other].cost = dist;
						if (data[vert_other].vert_prev == -1) {
							BLI_heap_insert(heap, data[vert_other].cost, &data[vert_other].vert);
						}
						data[vert_other].vert_prev = vert_curr;
					}
				}

			}
		}

	}

	BLI_heap_free(heap, NULL);

	if (vert_curr == vert_dst) {
		i = 0;
		while (vert_curr != vert_src && i++ < vert_num) {
			if (nu->type == CU_BEZIER) {
				select_beztriple(&nu->bezt[vert_curr], SELECT, SELECT, HIDDEN);
			}
			else {
				select_bpoint(&nu->bp[vert_curr], SELECT, SELECT, HIDDEN);
			}
			vert_curr = data[vert_curr].vert_prev;
		}
	}

	MEM_freeN(data);
}

static int edcu_shortest_path_pick_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
	Object *obedit = CTX_data_edit_object(C);
	Curve *cu = obedit->data;
	Nurb *nu_src = BKE_curve_nurb_active_get(cu);
	int vert_src = cu->actvert;

	ViewContext vc;
	Nurb *nu_dst;
	BezTriple *bezt_dst;
	BPoint *bp_dst;
	int vert_dst;
	void *vert_dst_p;

	if (vert_src == CU_ACT_NONE) {
		return OPERATOR_PASS_THROUGH;
	}

	view3d_operator_needs_opengl(C);
	view3d_set_viewcontext(C, &vc);

	if (!ED_curve_pick_vert(&vc, 1, event->mval, &nu_dst, &bezt_dst, &bp_dst, NULL)) {
		return OPERATOR_PASS_THROUGH;
	}

	if (nu_src != nu_dst) {
		BKE_report(op->reports, RPT_ERROR, "Control point belongs to another spline");
		return OPERATOR_CANCELLED;
	}

	vert_dst_p = bezt_dst ? (void *)bezt_dst : (void *)bp_dst;
	vert_dst = BKE_curve_nurb_vert_index_get(nu_dst, vert_dst_p);
	if (vert_src == vert_dst) {
		return OPERATOR_CANCELLED;
	}

	if ((obedit->type == OB_SURF) && (nu_src->pntsv > 1)) {
		curve_select_shortest_path_surf(nu_src, vert_src, vert_dst);
	}
	else {
		curve_select_shortest_path_curve(nu_src, vert_src, vert_dst);
	}

	BKE_curve_nurb_vert_active_set(cu, nu_dst, vert_dst_p);

	WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data);
	return OPERATOR_FINISHED;
}

void CURVE_OT_shortest_path_pick(wmOperatorType *ot)
{
	/* identifiers */
	ot->name = "Pick Shortest Path";
	ot->idname = "CURVE_OT_shortest_path_pick";
	ot->description = "Select shortest path between two selections";

	/* api callbacks */
	ot->invoke = edcu_shortest_path_pick_invoke;
	ot->poll = ED_operator_editsurfcurve_region_view3d;

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

/** \} */
