/**
 * $Id$
 *
 * ***** 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) 2008 Blender Foundation.
 * All rights reserved.
 *
 * 
 * Contributor(s): Blender Foundation
 *
 * ***** END GPL LICENSE BLOCK *****
 */

#include <math.h>

#include "MEM_guardedalloc.h"

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

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

#include "BLI_blenlib.h"

#include "BKE_context.h"
#include "BKE_fcurve.h"
#include "BKE_main.h"
#include "BKE_report.h"
#include "BKE_scene.h"

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

#include "BIF_gl.h"
#include "BIF_glutil.h"

#include "UI_interface.h"
#include "UI_interface_icons.h"
#include "UI_view2d.h"
#include "UI_resources.h"

#include "ED_markers.h"
#include "ED_screen.h"
#include "ED_util.h"
#include "ED_numinput.h"
#include "ED_object.h"
#include "ED_types.h"

/* ************* Marker API **************** */

static ListBase *context_get_markers(const bContext *C)
{
	
#if 0
	/* XXX get them from pose */
	if ((slink->spacetype == SPACE_ACTION) && (saction->flag & SACTION_POSEMARKERS_MOVE)) {
		if (saction->action)
			markers= &saction->action->markers;
		else
			markers= NULL;
	}
	else
#endif
	
	return &CTX_data_scene(C)->markers;
}

/* Get the marker that is closest to this point */
/* XXX for select, the min_dist should be small */
TimeMarker *ED_markers_find_nearest_marker (ListBase *markers, float x) 
{
	TimeMarker *marker, *nearest=NULL;
	float dist, min_dist= 1000000;
	
	if (markers) {
		for (marker= markers->first; marker; marker= marker->next) {
			dist = ABS((float)marker->frame - x);
			
			if (dist < min_dist) {
				min_dist= dist;
				nearest= marker;
			}
		}
	}
	
	return nearest;
}

/* Return the time of the marker that occurs on a frame closest to the given time */
int ED_markers_find_nearest_marker_time (ListBase *markers, float x)
{
	TimeMarker *nearest= ED_markers_find_nearest_marker(markers, x);
	return (nearest) ? (nearest->frame) : (int)floor(x + 0.5f);
}


void ED_markers_get_minmax (ListBase *markers, short sel, float *first, float *last)
{
	TimeMarker *marker;
	float min, max;
	int selcount = 0;
	
	/* sanity check */
	//printf("markers = %p -  %p, %p \n", markers, markers->first, markers->last);
	if (markers == NULL) {
		*first = 0.0f;
		*last = 0.0f;
		return;
	}
	
	if (markers->first && markers->last) {
		TimeMarker *fm= markers->first;
		TimeMarker *lm= markers->last;
		
		min= (float)fm->frame;
		max= (float)lm->frame;
	}
	else {
		*first = 0.0f;
		*last = 0.0f;
		return;
	}
	
	/* count how many markers are usable - see later */
	if (sel) {
		for (marker= markers->first; marker; marker= marker->next) {
			if (marker->flag & SELECT)
				selcount++;
		}
	}
	else
		selcount= BLI_countlist(markers);
	
	/* if only selected are to be considered, only consider the selected ones
	 * (optimisation for not searching list) 
	 */
	if (selcount > 1) {
		for (marker= markers->first; marker; marker= marker->next) {
			if (sel) {
				if (marker->flag & SELECT) {
					if (marker->frame < min)
						min= (float)marker->frame;
					if (marker->frame > max)
						max= (float)marker->frame;
				}
			}
			else {
				if (marker->frame < min)
					min= (float)marker->frame;
				if (marker->frame > max)
					max= (float)marker->frame;
			}	
		}
	}
	
	/* set the min/max values */
	*first= min;
	*last= max;
}

/* Adds a marker to list of cfra elems */
void add_marker_to_cfra_elem(ListBase *lb, TimeMarker *marker, short only_sel)
{
	CfraElem *ce, *cen;
	
	/* should this one only be considered if it is selected? */
	if ((only_sel) && ((marker->flag & SELECT)==0))
		return;
	
	/* insertion sort - try to find a previous cfra elem */
	for (ce= lb->first; ce; ce= ce->next) {
		if (ce->cfra == marker->frame) {
			/* do because of double keys */
			if (marker->flag & SELECT) 
				ce->sel= marker->flag;
			return;
		}
		else if (ce->cfra > marker->frame) break;
	}	
	
	cen= MEM_callocN(sizeof(CfraElem), "add_to_cfra_elem");	
	if (ce) BLI_insertlinkbefore(lb, ce, cen);
	else BLI_addtail(lb, cen);

	cen->cfra= marker->frame;
	cen->sel= marker->flag;
}

/* This function makes a list of all the markers. The only_sel
 * argument is used to specify whether only the selected markers
 * are added.
 */
void ED_markers_make_cfra_list(ListBase *markers, ListBase *lb, short only_sel)
{
	TimeMarker *marker;
	
	if (markers == NULL)
		return;
	
	for (marker= markers->first; marker; marker= marker->next)
		add_marker_to_cfra_elem(lb, marker, only_sel);
}

/* ************* Marker Drawing ************ */

/* function to draw markers */
static void draw_marker(View2D *v2d, TimeMarker *marker, int cfra, int flag)
{
	float xpos, ypixels, xscale, yscale;
	int icon_id= 0;
	
	xpos = marker->frame;
	
	/* no time correction for framelen! space is drawn with old values */
	ypixels= v2d->mask.ymax-v2d->mask.ymin;
	UI_view2d_getscale(v2d, &xscale, &yscale);
	
	glScalef(1.0f/xscale, 1.0f, 1.0f);
	
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);			
	
	/* vertical line - dotted */
#ifdef DURIAN_CAMERA_SWITCH
	if ((marker->camera) || (flag & DRAW_MARKERS_LINES)) {
#else
	if (flag & DRAW_MARKERS_LINES) {
#endif
		setlinestyle(3);
		
		if (marker->flag & SELECT)
			glColor4ub(255, 255, 255, 96);
		else
			glColor4ub(0, 0, 0, 96);
		
		glBegin(GL_LINES);
			glVertex2f((xpos*xscale)+0.5f, 12.0f);
			glVertex2f((xpos*xscale)+0.5f, (v2d->cur.ymax+12.0f)*yscale);
		glEnd();
		
		setlinestyle(0);
	}
	
	/* 5 px to offset icon to align properly, space / pixels corrects for zoom */
	if (flag & DRAW_MARKERS_LOCAL) {
		icon_id= (marker->flag & ACTIVE) ? ICON_PMARKER_ACT : 
		(marker->flag & SELECT) ? ICON_PMARKER_SEL : 
		ICON_PMARKER;
	}
	else {
		icon_id= (marker->flag & SELECT) ? ICON_MARKER_HLT : 
		ICON_MARKER;
	}
	
	UI_icon_draw(xpos*xscale-5.0f, 16.0f, icon_id);
	
	glBlendFunc(GL_ONE, GL_ZERO);
	glDisable(GL_BLEND);
	
	/* and the marker name too, shifted slightly to the top-right */
	if (marker->name && marker->name[0]) {
		float x, y;
		
		if (marker->flag & SELECT) {
			UI_ThemeColor(TH_TEXT_HI);
			x= xpos*xscale + 4.0f;
			y= (ypixels <= 39.0f)? (ypixels-10.0f) : 29.0f;
		}
		else {
			UI_ThemeColor(TH_TEXT);
			if((marker->frame <= cfra) && (marker->frame+5 > cfra)) {
				x= xpos*xscale + 4.0f;
				y= (ypixels <= 39.0f)? (ypixels - 10.0f) : 29.0f;
			}
			else {
				x= xpos*xscale + 4.0f;
				y= 17.0f;
			}
		}

#ifdef DURIAN_CAMERA_SWITCH
		if(marker->camera && marker->camera->restrictflag & OB_RESTRICT_RENDER) {
			float col[4];
			glGetFloatv(GL_CURRENT_COLOR, col);
			col[3]= 0.4;
			glColor4fv(col);
		}
#endif

		UI_DrawString(x, y, marker->name);
	}
	
	glScalef(xscale, 1.0f, 1.0f);
}

/* Draw Scene-Markers in time window */
void draw_markers_time(const bContext *C, int flag)
{
	ListBase *markers= context_get_markers(C);
	View2D *v2d= UI_view2d_fromcontext(C);
	TimeMarker *marker;
	
	if (markers == NULL)
		return;
	
	/* unselected markers are drawn at the first time */
	for (marker= markers->first; marker; marker= marker->next) {
		if ((marker->flag & SELECT) == 0) 
			draw_marker(v2d, marker, CTX_data_scene(C)->r.cfra, flag);
	}
	
	/* selected markers are drawn later */
	for (marker= markers->first; marker; marker= marker->next) {
		if (marker->flag & SELECT) 
			draw_marker(v2d, marker, CTX_data_scene(C)->r.cfra, flag);
	}
}

/* ************************** add markers *************************** */

/* add TimeMarker at curent frame */
static int ed_marker_add(bContext *C, wmOperator *op)
{
	ListBase *markers= context_get_markers(C);
	TimeMarker *marker;
	int frame= CTX_data_scene(C)->r.cfra;
	
	if (markers == NULL)
		return OPERATOR_CANCELLED;
	
	/* two markers can't be at the same place */
	for (marker= markers->first; marker; marker= marker->next) {
		if (marker->frame == frame) 
			return OPERATOR_CANCELLED;
	}
	
	/* deselect all */
	for(marker= markers->first; marker; marker= marker->next)
		marker->flag &= ~SELECT;
	
	marker = MEM_callocN(sizeof(TimeMarker), "TimeMarker");
	marker->flag= SELECT;
	marker->frame= frame;
	sprintf(marker->name, "F_%02d", frame); // XXX - temp code only
	BLI_addtail(markers, marker);
	
	WM_event_add_notifier(C, NC_SCENE|ND_MARKERS, NULL);
	
	return OPERATOR_FINISHED;
}

static void MARKER_OT_add(wmOperatorType *ot)
{
	/* identifiers */
	ot->name= "Add Time Marker";
	ot->description= "Add a new time marker";
	ot->idname= "MARKER_OT_add";
	
	/* api callbacks */
	ot->exec= ed_marker_add;
	ot->poll= ED_operator_areaactive;
	
	/* flags */
	ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
}

/* ************************** transform markers *************************** */


/* operator state vars used:  
	frs: delta movement

functions:

	init()   check selection, add customdata with old values and some lookups

	apply()  do the actual movement

	exit()	cleanup, send notifier

	cancel() to escpae from modal

callbacks:

	exec()	calls init, apply, exit 

	invoke() calls init, adds modal handler

	modal()	accept modal events while doing it, ends with apply and exit, or cancel

*/

typedef struct MarkerMove {
	SpaceLink *slink;
	ListBase *markers;
	int event_type;		/* store invoke-event, to verify */
	int *oldframe, evtx, firstx;
	NumInput num;
} MarkerMove;

/* copy selection to temp buffer */
/* return 0 if not OK */
static int ed_marker_move_init(bContext *C, wmOperator *op)
{
	ListBase *markers= context_get_markers(C);
	MarkerMove *mm;
	TimeMarker *marker;
	int totmark=0;
	int a;

	if(markers == NULL) return 0;
	
	for (marker= markers->first; marker; marker= marker->next)
		if (marker->flag & SELECT) totmark++;
	
	if (totmark==0) return 0;
	
	op->customdata= mm= MEM_callocN(sizeof(MarkerMove), "Markermove");
	mm->slink= CTX_wm_space_data(C);
	mm->markers= markers;
	mm->oldframe= MEM_callocN(totmark*sizeof(int), "MarkerMove oldframe");

	initNumInput(&mm->num);
	mm->num.idx_max = 0; /* one axis */
	mm->num.flag |= NUM_NO_FRACTION;
	mm->num.increment = 1.0f;
	
	for (a=0, marker= markers->first; marker; marker= marker->next) {
		if (marker->flag & SELECT) {
			mm->oldframe[a]= marker->frame;
			a++;
		}
	}
	
	return 1;
}

/* free stuff */
static void ed_marker_move_exit(bContext *C, wmOperator *op)
{
	MarkerMove *mm= op->customdata;
	
	/* free data */
	MEM_freeN(mm->oldframe);
	MEM_freeN(op->customdata);
	op->customdata= NULL;
	
	/* clear custom header prints */
	ED_area_headerprint(CTX_wm_area(C), NULL);
}

static int ed_marker_move_invoke(bContext *C, wmOperator *op, wmEvent *evt)
{
	if(ed_marker_move_init(C, op)) {
		MarkerMove *mm= op->customdata;
		
		mm->evtx= evt->x;
		mm->firstx= evt->x;
		mm->event_type= evt->type;
		
		/* add temp handler */
		WM_event_add_modal_handler(C, op);
		
		/* reset frs delta */
		RNA_int_set(op->ptr, "frames", 0);
		
		return OPERATOR_RUNNING_MODAL;
	}
	
	return OPERATOR_CANCELLED;
}

/* note, init has to be called succesfully */
static void ed_marker_move_apply(bContext *C, wmOperator *op)
{
	MarkerMove *mm= op->customdata;
	TimeMarker *marker;
	int a, offs;
	
	offs= RNA_int_get(op->ptr, "frames");
	for (a=0, marker= mm->markers->first; marker; marker= marker->next) {
		if (marker->flag & SELECT) {
			marker->frame= mm->oldframe[a] + offs;
			a++;
		}
	}
}

/* only for modal */
static void ed_marker_move_cancel(bContext *C, wmOperator *op)
{
	RNA_int_set(op->ptr, "frames", 0);
	ed_marker_move_apply(C, op);
	ed_marker_move_exit(C, op);	
	
	WM_event_add_notifier(C, NC_SCENE|ND_MARKERS, NULL);
}



static int ed_marker_move_modal(bContext *C, wmOperator *op, wmEvent *evt)
{
	Scene *scene= CTX_data_scene(C);
	MarkerMove *mm= op->customdata;
	View2D *v2d= UI_view2d_fromcontext(C);
	TimeMarker *marker, *selmarker=NULL;
	float dx, fac;
	char str[256];
		
	switch(evt->type) {
		case ESCKEY:
			ed_marker_move_cancel(C, op);
			return OPERATOR_CANCELLED;
		
		case RETKEY:
		case PADENTER:
		case LEFTMOUSE:
		case MIDDLEMOUSE:
		case RIGHTMOUSE:
			if(WM_modal_tweak_exit(evt, mm->event_type)) {
				ed_marker_move_exit(C, op);
				WM_event_add_notifier(C, NC_SCENE|ND_MARKERS, NULL);
				return OPERATOR_FINISHED;
			}
			
			break;
		case MOUSEMOVE:
			if(hasNumInput(&mm->num))
				break;

			dx= v2d->mask.xmax-v2d->mask.xmin;
			dx= (v2d->cur.xmax-v2d->cur.xmin)/dx;
			
			if (evt->x != mm->evtx) {	/* XXX maybe init for firsttime */
				int a, offs, totmark=0;
				
				mm->evtx= evt->x;
				
				fac= ((float)(evt->x - mm->firstx)*dx);
				
				if (ELEM(mm->slink->spacetype, SPACE_TIME, SPACE_SOUND)) 
					apply_keyb_grid(evt->shift, evt->ctrl, &fac, 0.0, FPS, 0.1*FPS, 0);
				else
					apply_keyb_grid(evt->shift, evt->ctrl, &fac, 0.0, 1.0, 0.1, U.flag & USER_AUTOGRABGRID);
				
				offs= (int)fac;
				RNA_int_set(op->ptr, "frames", offs);
				ed_marker_move_apply(C, op);
				
				/* cruft below is for header print */
				for (a=0, marker= mm->markers->first; marker; marker= marker->next) {
					if (marker->flag & SELECT) {
						selmarker= marker;
						a++; totmark++;
					}
				}
				
				if (totmark==1) {	
					/* we print current marker value */
					if (ELEM(mm->slink->spacetype, SPACE_TIME, SPACE_SOUND)) {
						SpaceTime *stime= (SpaceTime *)mm->slink;
						if (stime->flag & TIME_DRAWFRAMES) 
							sprintf(str, "Marker %d offset %d", selmarker->frame, offs);
						else 
							sprintf(str, "Marker %.2f offset %.2f", FRA2TIME(selmarker->frame), FRA2TIME(offs));
					}
					else if (mm->slink->spacetype == SPACE_ACTION) {
						SpaceAction *saction= (SpaceAction *)mm->slink;
						if (saction->flag & SACTION_DRAWTIME)
							sprintf(str, "Marker %.2f offset %.2f", FRA2TIME(selmarker->frame), FRA2TIME(offs));
						else
							sprintf(str, "Marker %.2f offset %.2f", (double)(selmarker->frame), (double)(offs));
					}
					else {
						sprintf(str, "Marker %.2f offset %.2f", (double)(selmarker->frame), (double)(offs));
					}
				}
				else {
					/* we only print the offset */
					if (ELEM(mm->slink->spacetype, SPACE_TIME, SPACE_SOUND)) { 
						SpaceTime *stime= (SpaceTime *)mm->slink;
						if (stime->flag & TIME_DRAWFRAMES) 
							sprintf(str, "Marker offset %d ", offs);
						else 
							sprintf(str, "Marker offset %.2f ", FRA2TIME(offs));
					}
					else if (mm->slink->spacetype == SPACE_ACTION) {
						SpaceAction *saction= (SpaceAction *)mm->slink;
						if (saction->flag & SACTION_DRAWTIME)
							sprintf(str, "Marker offset %.2f ", FRA2TIME(offs));
						else
							sprintf(str, "Marker offset %.2f ", (double)(offs));
					}
					else {
						sprintf(str, "Marker offset %.2f ", (double)(offs));
					}
				}
				
				WM_event_add_notifier(C, NC_SCENE|ND_MARKERS, NULL);
				ED_area_headerprint(CTX_wm_area(C), str);
			}
	}

	if(evt->val==KM_PRESS) {
		float vec[3];
		char str_tx[256];

		if (handleNumInput(&mm->num, evt))
		{
			applyNumInput(&mm->num, vec);
			outputNumInput(&mm->num, str_tx);

			RNA_int_set(op->ptr, "frames", vec[0]);
			ed_marker_move_apply(C, op);
			// ed_marker_header_update(C, op, str, (int)vec[0]);
			// strcat(str, str_tx);
			sprintf(str, "Marker offset %s", str_tx);
			ED_area_headerprint(CTX_wm_area(C), str);

			WM_event_add_notifier(C, NC_SCENE|ND_MARKERS, NULL);
		}
	}

	return OPERATOR_RUNNING_MODAL;
}

static int ed_marker_move_exec(bContext *C, wmOperator *op)
{
	if(ed_marker_move_init(C, op)) {
		ed_marker_move_apply(C, op);
		ed_marker_move_exit(C, op);
		return OPERATOR_FINISHED;
	}
	return OPERATOR_PASS_THROUGH;
}

static void MARKER_OT_move(wmOperatorType *ot)
{
	/* identifiers */
	ot->name= "Move Time Marker";
	ot->description= "Move selected time marker(s)";
	ot->idname= "MARKER_OT_move";
	
	/* api callbacks */
	ot->exec= ed_marker_move_exec;
	ot->invoke= ed_marker_move_invoke;
	ot->modal= ed_marker_move_modal;
	ot->poll= ED_operator_areaactive;
	
	/* flags */
	ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO|OPTYPE_BLOCKING|OPTYPE_GRAB_POINTER;
	
	/* rna storage */
	RNA_def_int(ot->srna, "frames", 0, INT_MIN, INT_MAX, "Frames", "", INT_MIN, INT_MAX);
}

/* ************************** duplicate markers *************************** */

/* operator state vars used:  
	frs: delta movement

functions:

	apply()  do the actual duplicate

callbacks:

	exec()	calls apply, move_exec

	invoke() calls apply, move_invoke

	modal()	uses move_modal

*/


/* duplicate selected TimeMarkers */
static void ed_marker_duplicate_apply(bContext *C, wmOperator *op)
{
	ListBase *markers= context_get_markers(C);
	TimeMarker *marker, *newmarker;
	
	if (markers == NULL) 
		return;

	/* go through the list of markers, duplicate selected markers and add duplicated copies
	 * to the begining of the list (unselect original markers) 
	 */
	for (marker= markers->first; marker; marker= marker->next) {
		if (marker->flag & SELECT) {
			/* unselect selected marker */
			marker->flag &= ~SELECT;
			
			/* create and set up new marker */
			newmarker = MEM_callocN(sizeof(TimeMarker), "TimeMarker");
			newmarker->flag= SELECT;
			newmarker->frame= marker->frame;
			BLI_strncpy(newmarker->name, marker->name, sizeof(marker->name));
			
#ifdef DURIAN_CAMERA_SWITCH
			newmarker->camera= marker->camera;
#endif

			/* new marker is added to the begining of list */
			BLI_addhead(markers, newmarker);
		}
	}
}

static int ed_marker_duplicate_exec(bContext *C, wmOperator *op)
{
	ed_marker_duplicate_apply(C, op);
	ed_marker_move_exec(C, op);	/* assumes frs delta set */
	
	return OPERATOR_FINISHED;
	
}

static int ed_marker_duplicate_invoke(bContext *C, wmOperator *op, wmEvent *evt)
{
	ed_marker_duplicate_apply(C, op);
	return ed_marker_move_invoke(C, op, evt);
}

static void MARKER_OT_duplicate(wmOperatorType *ot)
{
	/* identifiers */
	ot->name= "Duplicate Time Marker";
	ot->description= "Duplicate selected time marker(s)";
	ot->idname= "MARKER_OT_duplicate";
	
	/* api callbacks */
	ot->exec= ed_marker_duplicate_exec;
	ot->invoke= ed_marker_duplicate_invoke;
	ot->modal= ed_marker_move_modal;
	ot->poll= ED_operator_areaactive;
	
	/* flags */
	ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
	
	/* rna storage */
	RNA_def_int(ot->srna, "frames", 0, INT_MIN, INT_MAX, "Frames", "", INT_MIN, INT_MAX);
}

/* ************************** selection ************************************/

/* select/deselect TimeMarker at current frame */
static void select_timeline_marker_frame(ListBase *markers, int frame, unsigned char shift)
{
	TimeMarker *marker;
	int select=0;
	
	for (marker= markers->first; marker; marker= marker->next) {
		/* if Shift is not set, then deselect Markers */
		if (!shift) marker->flag &= ~SELECT;
		
		/* this way a not-shift select will allways give 1 selected marker */
		if ((marker->frame == frame) && (!select)) {
			if (marker->flag & SELECT) 
				marker->flag &= ~SELECT;
			else
				marker->flag |= SELECT;
			select = 1;
		}
	}
}

static int ed_marker_select(bContext *C, wmEvent *evt, int extend, int camera)
{
	ListBase *markers= context_get_markers(C);
	View2D *v2d= UI_view2d_fromcontext(C);
	float viewx;
	int x, y, cfra;
	
	if(markers == NULL)
		return OPERATOR_PASS_THROUGH;

	x= evt->x - CTX_wm_region(C)->winrct.xmin;
	y= evt->y - CTX_wm_region(C)->winrct.ymin;
	
	UI_view2d_region_to_view(v2d, x, y, &viewx, NULL);	
	
	cfra= ED_markers_find_nearest_marker_time(markers, viewx);
	
	if (extend)
		select_timeline_marker_frame(markers, cfra, 1);
	else
		select_timeline_marker_frame(markers, cfra, 0);
	
#ifdef DURIAN_CAMERA_SWITCH

	if(camera) {
		Scene *scene= CTX_data_scene(C);
		Base *base;
		TimeMarker *marker;
		int sel= 0;

		if (!extend)
			scene_deselect_all(scene);

		for (marker= markers->first; marker; marker= marker->next) {
			if(marker->frame==cfra) {
				sel= (marker->flag & SELECT);
				break;
			}
		}

		for (marker= markers->first; marker; marker= marker->next) {
			if(marker->camera) {
				if(marker->frame==cfra) {
					base= object_in_scene(marker->camera, scene);
					if(base) {
						ED_base_object_select(base, sel);
						if(sel)
							ED_base_object_activate(C, base);
					}
				}
			}
		}

		WM_event_add_notifier(C, NC_SCENE|ND_OB_SELECT, scene);
	}
#endif

	WM_event_add_notifier(C, NC_SCENE|ND_MARKERS, NULL);

	/* allowing tweaks */
	return OPERATOR_PASS_THROUGH;
}

static int ed_marker_select_invoke(bContext *C, wmOperator *op, wmEvent *evt)
{
	short extend= RNA_boolean_get(op->ptr, "extend");
	short camera= 0;
#ifdef DURIAN_CAMERA_SWITCH
	camera= RNA_boolean_get(op->ptr, "camera");
#endif
	return ed_marker_select(C, evt, extend, camera);
}

static void MARKER_OT_select(wmOperatorType *ot)
{
	/* identifiers */
	ot->name= "Select Time Marker";
	ot->description= "Select time marker(s)";
	ot->idname= "MARKER_OT_select";
	
	/* api callbacks */
	ot->invoke= ed_marker_select_invoke;
	ot->poll= ED_operator_areaactive;
	
	/* flags */
	ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;

	RNA_def_boolean(ot->srna, "extend", 0, "Extend", "extend the selection");
#ifdef DURIAN_CAMERA_SWITCH
	RNA_def_boolean(ot->srna, "camera", 0, "Camera", "Select the camera");
#endif
}

/* *************************** border select markers **************** */

/* operator state vars used: (added by default WM callbacks)   
	xmin, ymin     
	xmax, ymax     

customdata: the wmGesture pointer, with subwindow

callbacks:

	exec()	has to be filled in by user

	invoke() default WM function
			adds modal handler

	modal()	default WM function 
			accept modal events while doing it, calls exec(), handles ESC and border drawing

	poll()	has to be filled in by user for context
*/

static int ed_marker_border_select_exec(bContext *C, wmOperator *op)
{
	View2D *v2d= UI_view2d_fromcontext(C);
	ListBase *markers= context_get_markers(C);
	TimeMarker *marker;
	float xminf, xmaxf, yminf, ymaxf;
	int gesture_mode= RNA_int_get(op->ptr, "gesture_mode");
	int xmin= RNA_int_get(op->ptr, "xmin");
	int xmax= RNA_int_get(op->ptr, "xmax");
	int ymin= RNA_int_get(op->ptr, "ymin");
	int ymax= RNA_int_get(op->ptr, "ymax");
	
	UI_view2d_region_to_view(v2d, xmin, ymin, &xminf, &yminf);	
	UI_view2d_region_to_view(v2d, xmax, ymax, &xmaxf, &ymaxf);	
	
	/* XXX disputable */
	if(yminf > 30.0f || ymaxf < 0.0f)
		return 0;
	
	if(markers == NULL)
		return 0;
	
	/* XXX marker context */
	for(marker= markers->first; marker; marker= marker->next) {
		if ((marker->frame > xminf) && (marker->frame <= xmaxf)) {
			switch (gesture_mode) {
				case GESTURE_MODAL_SELECT:
					if ((marker->flag & SELECT) == 0) 
						marker->flag |= SELECT;
					break;
				case GESTURE_MODAL_DESELECT:
					if (marker->flag & SELECT) 
						marker->flag &= ~SELECT;
					break;
			}
		}
	}
	
	WM_event_add_notifier(C, NC_SCENE|ND_MARKERS, NULL);

	return 1;
}

static void MARKER_OT_select_border(wmOperatorType *ot)
{
	/* identifiers */
	ot->name= "Marker Border select";
	ot->description= "Select all time markers using border selection";
	ot->idname= "MARKER_OT_select_border";
	
	/* api callbacks */
	ot->exec= ed_marker_border_select_exec;
	ot->invoke= WM_border_select_invoke;
	ot->modal= WM_border_select_modal;
	
	ot->poll= ED_operator_areaactive;
	
	/* flags */
	ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
	
	/* rna */
	WM_operator_properties_gesture_border(ot, FALSE);
}

/* *********************** (de)select all ***************** */

static int ed_marker_select_all_exec(bContext *C, wmOperator *op)
{
	ListBase *markers= context_get_markers(C);
	TimeMarker *marker;
	int action = RNA_enum_get(op->ptr, "action");

	if(markers == NULL)
		return OPERATOR_CANCELLED;

	if (action == SEL_TOGGLE) {
		action = SEL_SELECT;
		for(marker= markers->first; marker; marker= marker->next) {
			if(marker->flag & SELECT) {
				action = SEL_DESELECT;
				break;
			}
		}
	}
	
	for(marker= markers->first; marker; marker= marker->next) {
		switch (action) {
		case SEL_SELECT:
			marker->flag |= SELECT;
			break;
		case SEL_DESELECT:
			marker->flag &= ~SELECT;
			break;
		case SEL_INVERT:
			if (marker->flag & SELECT) {
				marker->flag &= ~SELECT;
			} else {
				marker->flag |= SELECT;
			}
			break;
		}
	}
	
	WM_event_add_notifier(C, NC_SCENE|ND_MARKERS, NULL);

	return OPERATOR_FINISHED;
}

static void MARKER_OT_select_all(wmOperatorType *ot)
{
	/* identifiers */
	ot->name= "(De)select all markers";
	ot->description= "Change selection of all time markers";
	ot->idname= "MARKER_OT_select_all";
	
	/* api callbacks */
	ot->exec= ed_marker_select_all_exec;
	ot->poll= ED_operator_areaactive;
	
	/* flags */
	ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
	
	/* rna */
	WM_operator_properties_select_all(ot);
}

/* ******************************* remove marker ***************** */

/* remove selected TimeMarkers */
static int ed_marker_delete_exec(bContext *C, wmOperator *op)
{
	ListBase *markers= context_get_markers(C);
	TimeMarker *marker, *nmarker;
	short changed= 0;
	
	if(markers == NULL)
		return OPERATOR_CANCELLED;
	
	for(marker= markers->first; marker; marker= nmarker) {
		nmarker= marker->next;
		if(marker->flag & SELECT) {
			BLI_freelinkN(markers, marker);
			changed= 1;
		}
	}
	
	if (changed)
		WM_event_add_notifier(C, NC_SCENE|ND_MARKERS, NULL);
	
	return OPERATOR_FINISHED;
}


static void MARKER_OT_delete(wmOperatorType *ot)
{
	/* identifiers */
	ot->name= "Delete Markers";
	ot->description= "Delete selected time marker(s)";
	ot->idname= "MARKER_OT_delete";
	
	/* api callbacks */
	ot->invoke= WM_operator_confirm;
	ot->exec= ed_marker_delete_exec;
	ot->poll= ED_operator_areaactive;
	
	/* flags */
	ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
	
}

static int ed_marker_make_links_scene_exec(bContext *C, wmOperator *op)
{
	ListBase *markers= context_get_markers(C);
	Scene *scene_to= BLI_findlink(&CTX_data_main(C)->scene, RNA_enum_get(op->ptr, "scene"));
	TimeMarker *marker, *marker_new;

	if(scene_to==NULL) {
		BKE_report(op->reports, RPT_ERROR, "Scene not found");
		return OPERATOR_CANCELLED;
	}

	if(scene_to == CTX_data_scene(C)) {
		BKE_report(op->reports, RPT_ERROR, "Can't link objects into the same scene");
		return OPERATOR_CANCELLED;
	}

	/* copy markers */
	for (marker= markers->first; marker; marker= marker->next) {
		if(marker->flag & SELECT) {
			marker_new= MEM_dupallocN(marker);
			BLI_addtail(&scene_to->markers, marker_new);
		}
	}

	return OPERATOR_FINISHED;
}

static void MARKER_OT_make_links_scene(wmOperatorType *ot)
{
	PropertyRNA *prop;

	/* identifiers */
	ot->name= "Make Links to Scene";
	ot->description= "Link markers to another scene";
	ot->idname= "MARKER_OT_make_links_scene";

	/* api callbacks */
	ot->exec= ed_marker_make_links_scene_exec;
	ot->poll= ED_operator_areaactive;

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

	/* properties */
	prop= RNA_def_enum(ot->srna, "scene", DummyRNA_NULL_items, 0, "Scene", "");
	RNA_def_enum_funcs(prop, RNA_scene_itemf);
	ot->prop= prop;

}

#ifdef DURIAN_CAMERA_SWITCH
/* ******************************* camera bind marker ***************** */

/* remove selected TimeMarkers */
static int ed_marker_camera_bind_exec(bContext *C, wmOperator *op)
{
	Scene *scene= CTX_data_scene(C);
	ListBase *markers= context_get_markers(C);
	TimeMarker *marker;
	short changed= 0;

	if(markers == NULL)
		return OPERATOR_CANCELLED;

	for(marker= markers->first; marker; marker= marker->next) {
		if(marker->flag & SELECT) {
			marker->camera= scene->camera;
		}
	}

	if (changed)
		WM_event_add_notifier(C, NC_SCENE|ND_MARKERS, NULL);

	return OPERATOR_FINISHED;
}

static void MARKER_OT_camera_bind(wmOperatorType *ot)
{
	/* identifiers */
	ot->name= "Bind Camera to Markers";
	ot->description= "Bind the active camera to selected markers(s)";
	ot->idname= "MARKER_OT_camera_bind";

	/* api callbacks */
	ot->exec= ed_marker_camera_bind_exec;
	ot->poll= ED_operator_areaactive;

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

/* ************************** registration **********************************/

/* called in screen_ops.c:ED_operatortypes_screen() */
void ED_operatortypes_marker(void)
{
	WM_operatortype_append(MARKER_OT_add);
	WM_operatortype_append(MARKER_OT_move);
	WM_operatortype_append(MARKER_OT_duplicate);
	WM_operatortype_append(MARKER_OT_select);
	WM_operatortype_append(MARKER_OT_select_border);
	WM_operatortype_append(MARKER_OT_select_all);
	WM_operatortype_append(MARKER_OT_delete);
	WM_operatortype_append(MARKER_OT_make_links_scene);
#ifdef DURIAN_CAMERA_SWITCH
	WM_operatortype_append(MARKER_OT_camera_bind);
#endif
}

/* called in screen_ops.c:ED_keymap_screen() */
void ED_marker_keymap(wmKeyConfig *keyconf)
{
	wmKeyMap *keymap= WM_keymap_find(keyconf, "Markers", 0, 0);
	wmKeyMapItem *kmi;
	
	WM_keymap_verify_item(keymap, "MARKER_OT_add", MKEY, KM_PRESS, 0, 0);
	WM_keymap_verify_item(keymap, "MARKER_OT_move", EVT_TWEAK_S, KM_ANY, 0, 0);
	WM_keymap_verify_item(keymap, "MARKER_OT_duplicate", DKEY, KM_PRESS, KM_SHIFT, 0);
	WM_keymap_verify_item(keymap, "MARKER_OT_select", SELECTMOUSE, KM_PRESS, 0, 0);
	RNA_boolean_set(WM_keymap_add_item(keymap, "MARKER_OT_select", SELECTMOUSE, KM_PRESS, KM_SHIFT, 0)->ptr, "extend", 1);

#ifdef DURIAN_CAMERA_SWITCH
	kmi= WM_keymap_add_item(keymap, "MARKER_OT_select", SELECTMOUSE, KM_PRESS, KM_CTRL, 0);
	RNA_boolean_set(kmi->ptr, "camera", 1);

	kmi= WM_keymap_add_item(keymap, "MARKER_OT_select", SELECTMOUSE, KM_PRESS, KM_SHIFT|KM_CTRL, 0);
	RNA_boolean_set(kmi->ptr, "extend", 1);
	RNA_boolean_set(kmi->ptr, "camera", 1);
#endif
	
	WM_keymap_verify_item(keymap, "MARKER_OT_select_border", BKEY, KM_PRESS, 0, 0);
	WM_keymap_verify_item(keymap, "MARKER_OT_select_all", AKEY, KM_PRESS, 0, 0);
	WM_keymap_verify_item(keymap, "MARKER_OT_delete", XKEY, KM_PRESS, 0, 0);
	WM_keymap_verify_item(keymap, "MARKER_OT_delete", DELKEY, KM_PRESS, 0, 0);
	
	WM_keymap_add_item(keymap, "MARKER_OT_move", GKEY, KM_PRESS, 0, 0);
#ifdef DURIAN_CAMERA_SWITCH
	WM_keymap_add_item(keymap, "MARKER_OT_camera_bind", BKEY, KM_PRESS, KM_CTRL, 0);
#endif
}
