/**
 * $Id: drawaction.c 17128 2008-10-20 09:24:25Z aligorith $
 *
 * ***** 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, 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): Joshua Leung
 *
 * ***** END GPL LICENSE BLOCK *****
 * Drawing routines for the Action window type
 */

/* System includes ----------------------------------------------------- */

#include <math.h>
#include <stdlib.h>
#include <string.h>

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "MEM_guardedalloc.h"

#include "BMF_Api.h"

#include "BLI_blenlib.h"
#include "BLI_arithb.h"

/* Types --------------------------------------------------------------- */
#include "DNA_listBase.h"
#include "DNA_action_types.h"
#include "DNA_armature_types.h"
#include "DNA_curve_types.h"
#include "DNA_ipo_types.h"
#include "DNA_object_types.h"
#include "DNA_screen_types.h"
#include "DNA_scene_types.h"
#include "DNA_space_types.h"
#include "DNA_constraint_types.h"
#include "DNA_key_types.h"
#include "DNA_userdef_types.h"
#include "DNA_gpencil_types.h"

#include "BKE_action.h"
#include "BKE_depsgraph.h"
#include "BKE_ipo.h"
#include "BKE_key.h"
#include "BKE_object.h"
#include "BKE_global.h"
#include "BKE_utildefines.h"

/* Everything from source (BIF, BDR, BSE) ------------------------------ */ 

#include "BIF_editaction.h"
#include "BIF_editkey.h"
#include "BIF_editnla.h"
#include "BIF_interface.h"
#include "BIF_interface_icons.h"
#include "BIF_drawgpencil.h"
#include "BIF_gl.h"
#include "BIF_glutil.h"
#include "BIF_keyframing.h"
#include "BIF_resources.h"
#include "BIF_screen.h"
#include "BIF_mywindow.h"
#include "BIF_space.h"

#include "BDR_drawaction.h"
#include "BDR_editcurve.h"
#include "BDR_gpencil.h"

#include "BSE_drawnla.h"
#include "BSE_drawipo.h"
#include "BSE_drawview.h"
#include "BSE_editaction_types.h"
#include "BSE_editipo.h"
#include "BSE_time.h"
#include "BSE_view.h"

/* 'old' stuff": defines and types, and own include -------------------- */

#include "blendef.h"
#include "interface.h"
#include "mydevice.h"

/********************************** Slider Stuff **************************** */

/* sliders for shapekeys */
static void meshactionbuts(SpaceAction *saction, Object *ob, Key *key)
{
	int           i;
	char          str[64];
	float	      x, y;
	uiBlock       *block;
	uiBut 		  *but;

#define XIC 20
#define YIC 20

	/* lets make the rvk sliders */

	/* reset the damn myortho2 or the sliders won't draw/redraw
	 * correctly *grumble*
	 */
	mywinset(curarea->win);
	myortho2(-0.375f, curarea->winx-0.375f, G.v2d->cur.ymin, G.v2d->cur.ymax);

    sprintf(str, "actionbuttonswin %d", curarea->win);
    block= uiNewBlock (&curarea->uiblocks, str, UI_EMBOSS, UI_HELV, curarea->win);

	x = NAMEWIDTH + 1;
    y = 0.0f;

	/* make the little 'open the sliders' widget */
	// should eventually be removed
    BIF_ThemeColor(TH_FACE); // this slot was open... (???... Aligorith)
	glRects(2, (short)y + 2*CHANNELHEIGHT - 2, ACTWIDTH - 2, (short)y + CHANNELHEIGHT + 2);
	glColor3ub(0, 0, 0);
	glRasterPos2f(4, y + CHANNELHEIGHT + 6);
	BMF_DrawString(G.font, "Sliders");

	uiBlockSetEmboss(block, UI_EMBOSSN);

	if (!(G.saction->flag & SACTION_SLIDERS)) {
		ACTWIDTH = NAMEWIDTH;
		but=uiDefIconButBitS(block, TOG, SACTION_SLIDERS, B_REDR, 
					  ICON_DISCLOSURE_TRI_RIGHT,
					  NAMEWIDTH - XIC - 5, (short)y + CHANNELHEIGHT,
					  XIC,YIC-2,
					  &(G.saction->flag), 0, 0, 0, 0, 
					  "Show action window sliders");
		/* no hilite, the winmatrix is not correct later on... */
		uiButSetFlag(but, UI_NO_HILITE);

	}
	else {
		but= uiDefIconButBitS(block, TOG, SACTION_SLIDERS, B_REDR, 
					  ICON_DISCLOSURE_TRI_DOWN,
					  NAMEWIDTH - XIC - 5, (short)y + CHANNELHEIGHT,
					  XIC,YIC-2,
					  &(G.saction->flag), 0, 0, 0, 0, 
					  "Hide action window sliders");
		/* no hilite, the winmatrix is not correct later on... */
		uiButSetFlag(but, UI_NO_HILITE);
		
		ACTWIDTH = NAMEWIDTH + SLIDERWIDTH;
		
		/* sliders are open so draw them */
		BIF_ThemeColor(TH_FACE); 
		
		glRects(NAMEWIDTH,  0,  NAMEWIDTH+SLIDERWIDTH,  curarea->winy);
		uiBlockSetEmboss(block, UI_EMBOSS);
		for (i=1; i < key->totkey; i++) {
			make_rvk_slider(block, ob, i, 
							(int)x, (int)y, SLIDERWIDTH-2, CHANNELHEIGHT-1, "Slider to control Shape Keys");
			
			y-=CHANNELHEIGHT+CHANNELSKIP;
			
			/* see sliderval array in editkey.c */
			if (i >= 255) break;
		}
	}
	uiDrawBlock(block);
}

static void icu_slider_func(void *voidicu, void *voidignore) 
{
	/* the callback for the icu sliders ... copies the
	 * value from the icu->curval into a bezier at the
	 * right frame on the right ipo curve (creating both the
	 * ipo curve and the bezier if needed).
	 */
	IpoCurve  *icu= voidicu;
	BezTriple *bezt=NULL;
	float cfra, icuval;

	cfra = frame_to_float(CFRA);
	if (G.saction->pin==0 && OBACT)
		cfra= get_action_frame(OBACT, cfra);
	
	/* if the ipocurve exists, try to get a bezier
	 * for this frame
	 */
	bezt = get_bezt_icu_time(icu, &cfra, &icuval);

	/* create the bezier triple if one doesn't exist,
	 * otherwise modify it's value
	 */
	if (bezt == NULL) {
		insert_vert_icu(icu, cfra, icu->curval, 0);
	}
	else {
		bezt->vec[1][1] = icu->curval;
	}

	/* make sure the Ipo's are properly processed and
	 * redraw as necessary
	 */
	sort_time_ipocurve(icu);
	testhandles_ipocurve(icu);
	
	/* nla-update (in case this affects anything) */
	synchronize_action_strips();
	
	/* do redraw pushes, and also the depsgraph flushes */
	if (OBACT->pose || ob_get_key(OBACT))
		DAG_object_flush_update(G.scene, OBACT, OB_RECALC);
	else
		DAG_object_flush_update(G.scene, OBACT, OB_RECALC_OB);
	
	allqueue(REDRAWVIEW3D, 0);
	allqueue(REDRAWACTION, 0);
	allqueue(REDRAWNLA, 0);
	allqueue(REDRAWIPO, 0);
	allspace(REMAKEIPO, 0);
	allqueue(REDRAWBUTSALL, 0);
}

static void make_icu_slider(uiBlock *block, IpoCurve *icu,
					 int x, int y, int w, int h, char *tip)
{
	/* create a slider for the ipo-curve*/
	uiBut *but;
	
	if(icu == NULL) return;
	
	if (IS_EQ(icu->slide_max, icu->slide_min)) {
		if (IS_EQ(icu->ymax, icu->ymin)) {
			if (ELEM(icu->blocktype, ID_CO, ID_KE)) {
				/* hack for constraints and shapekeys (and maybe a few others) */
				icu->slide_min= 0.0;
				icu->slide_max= 1.0;
			}
			else {
				icu->slide_min= -100;
				icu->slide_max= 100;
			}
		}
		else {
			icu->slide_min= icu->ymin;
			icu->slide_max= icu->ymax;
		}
	}
	if (icu->slide_min >= icu->slide_max) {
		SWAP(float, icu->slide_min, icu->slide_max);
	}

	but=uiDefButF(block, NUMSLI, REDRAWVIEW3D, "",
				  x, y , w, h,
				  &(icu->curval), icu->slide_min, icu->slide_max, 
				  10, 2, tip);
	
	uiButSetFunc(but, icu_slider_func, icu, NULL);
	
	// no hilite, the winmatrix is not correct later on...
	uiButSetFlag(but, UI_NO_HILITE);
}

/* sliders for ipo-curves of active action-channel */
static void action_icu_buts(SpaceAction *saction)
{
	ListBase act_data = {NULL, NULL};
	bActListElem *ale;
	int filter;
	void *data;
	short datatype;
	
	char          str[64];
	float	        x, y;
	uiBlock       *block;

	/* lets make the action sliders */

	/* reset the damn myortho2 or the sliders won't draw/redraw
	 * correctly *grumble*
	 */
	mywinset(curarea->win);
	myortho2(-0.375f, curarea->winx-0.375f, G.v2d->cur.ymin, G.v2d->cur.ymax);
	
    sprintf(str, "actionbuttonswin %d", curarea->win);
    block= uiNewBlock (&curarea->uiblocks, str, 
                       UI_EMBOSS, UI_HELV, curarea->win);

	x = NAMEWIDTH + 1;
    y = 0.0f;
	
	uiBlockSetEmboss(block, UI_EMBOSSN);

	if (G.saction->flag & SACTION_SLIDERS) {
		/* sliders are open so draw them */
		
		/* get editor data */
		data= get_action_context(&datatype);
		if (data == NULL) return;
		
		/* build list of channels to draw */
		filter= (ACTFILTER_FORDRAWING|ACTFILTER_VISIBLE|ACTFILTER_CHANNELS);
		actdata_filter(&act_data, filter, data, datatype);
		
		/* draw backdrop first */
		BIF_ThemeColor(TH_FACE); // change this color... it's ugly
		glRects(NAMEWIDTH,  (short)G.v2d->cur.ymin,  NAMEWIDTH+SLIDERWIDTH,  (short)G.v2d->cur.ymax);
		
		uiBlockSetEmboss(block, UI_EMBOSS);
		for (ale= act_data.first; ale; ale= ale->next) {
			const float yminc= y-CHANNELHEIGHT/2;
			const float ymaxc= y+CHANNELHEIGHT/2;
			
			/* check if visible */
			if ( IN_RANGE(yminc, G.v2d->cur.ymin, G.v2d->cur.ymax) ||
				 IN_RANGE(ymaxc, G.v2d->cur.ymin, G.v2d->cur.ymax) ) 
			{
				/* determine what needs to be drawn */
				switch (ale->type) {
					case ACTTYPE_CONCHAN: /* constraint channel */
					{
						bActionChannel *achan = (bActionChannel *)ale->owner;
						IpoCurve *icu = (IpoCurve *)ale->key_data;
						
						/* only show if action channel is selected */
						if (SEL_ACHAN(achan)) {
							make_icu_slider(block, icu,
											(int)x, (int)y, SLIDERWIDTH-2, CHANNELHEIGHT-2, 
											"Slider to control current value of Constraint Influence");
						}
					}
						break;
					case ACTTYPE_ICU: /* ipo-curve channel */
					{
						bActionChannel *achan = (bActionChannel *)ale->owner;
						IpoCurve *icu = (IpoCurve *)ale->key_data;
						
						/* only show if action channel is selected */
						if (SEL_ACHAN(achan)) {
							make_icu_slider(block, icu,
											(int)x, (int)y, SLIDERWIDTH-2, CHANNELHEIGHT-2, 
											"Slider to control current value of IPO-Curve");
						}
					}
						break;
					case ACTTYPE_SHAPEKEY: /* shapekey channel */
					{
						// TODO...
					}
						break;
				}
			}
			
			/* adjust y-position for next one */
			y-=CHANNELHEIGHT+CHANNELSKIP;
		}
		
		/* free tempolary channels */
		BLI_freelistN(&act_data);
	}
	uiDrawBlock(block);
}

/********************************** Current Frame **************************** */

void draw_cfra_action (void)
{
	Object *ob;
	float vec[2];
	
	/* Draw a light green line to indicate current frame */
	vec[0]= (float)(G.scene->r.cfra);
	vec[0]*= G.scene->r.framelen;
	
	vec[1]= G.v2d->cur.ymin;
	BIF_ThemeColor(TH_CFRAME);
	glLineWidth(2.0);
	
	glBegin(GL_LINE_STRIP);
	glVertex2fv(vec);
	vec[1]= G.v2d->cur.ymax;
	glVertex2fv(vec);
	glEnd();
	
	/* Draw dark green line if slow-parenting/time-offset is enabled */
	ob= (G.scene->basact) ? (G.scene->basact->object) : 0;
	if ((ob) && (ob->ipoflag & OB_OFFS_OB) && (give_timeoffset(ob)!=0.0)) {
		vec[0]-= give_timeoffset(ob); /* could avoid calling twice */
		
		BIF_ThemeColorShade(TH_CFRAME, -30);
		
		glBegin(GL_LINE_STRIP);
		glVertex2fv(vec);
		vec[1]= G.v2d->cur.ymin;
		glVertex2fv(vec);
		glEnd();
	}
	
	glLineWidth(1.0);
}

/********************************** Left-Hand Panel + Generics **************************** */

/* left hand part */
static void draw_channel_names(void) 
{
	ListBase act_data = {NULL, NULL};
	bActListElem *ale;
	int filter;
	void *data;
	short datatype;
	short ofsx = 0, ofsy = 0; 
	float x= 0.0f, y= 0.0f;
	
	/* determine what type of data we are operating on */
	data = get_action_context(&datatype);
	if (data == NULL) return;
	
	/* Clip to the scrollable area */
	if (curarea->winx>SCROLLB+10 && curarea->winy>SCROLLH+10) {
		if(G.v2d->scroll) {	
			ofsx= curarea->winrct.xmin;	
			ofsy= curarea->winrct.ymin;
			glViewport(ofsx,  ofsy+G.v2d->mask.ymin, NAMEWIDTH, 
					   (ofsy+G.v2d->mask.ymax) -
					   (ofsy+G.v2d->mask.ymin)); 
			glScissor(ofsx,	 ofsy+G.v2d->mask.ymin, NAMEWIDTH, 
					  (ofsy+G.v2d->mask.ymax) -
					  (ofsy+G.v2d->mask.ymin));
		}
	}
	
	/* prepare scaling for LHS panel */
	myortho2(0,	NAMEWIDTH, G.v2d->cur.ymin, G.v2d->cur.ymax);
	
	/* set default color back to black */
	glColor3ub(0x00, 0x00, 0x00);
	
	/* build list of channels to draw */
	filter= (ACTFILTER_FORDRAWING|ACTFILTER_VISIBLE|ACTFILTER_CHANNELS);
	actdata_filter(&act_data, filter, data, datatype);
	
	/* loop through channels, and set up drawing depending on their type  */
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	
	for (ale= act_data.first; ale; ale= ale->next) {
		const float yminc= y-CHANNELHEIGHT/2;
		const float ymaxc= y+CHANNELHEIGHT/2;
		
		/* check if visible */
		if ( IN_RANGE(yminc, G.v2d->cur.ymin, G.v2d->cur.ymax) ||
			 IN_RANGE(ymaxc, G.v2d->cur.ymin, G.v2d->cur.ymax) ) 
		{
			bActionGroup *grp = NULL;
			short indent= 0, offset= 0, sel= 0, group=0;
			int expand= -1, protect = -1, special= -1, mute = -1;
			char name[64];
			
			/* determine what needs to be drawn */
			switch (ale->type) {
				case ACTTYPE_GROUP: /* action group */
				{
					bActionGroup *agrp= (bActionGroup *)ale->data;
					
					group= 2;
					indent= 0;
					special= -1;
					
					/* only show expand if there are any channels */
					if (agrp->channels.first) {
						if (EXPANDED_AGRP(agrp))
							expand = ICON_TRIA_DOWN;
						else
							expand = ICON_TRIA_RIGHT;
					}
						
					if (EDITABLE_AGRP(agrp))
						protect = ICON_UNLOCKED;
					else
						protect = ICON_LOCKED;
						
					sel = SEL_AGRP(agrp);
					sprintf(name, agrp->name);
				}
					break;
				case ACTTYPE_ACHAN: /* action channel */
				{
					bActionChannel *achan= (bActionChannel *)ale->data;
					
					group= (ale->grp) ? 1 : 0;
					grp= ale->grp;
					
					indent = 0;
					special = -1;
					
					if (EXPANDED_ACHAN(achan))
						expand = ICON_TRIA_DOWN;
					else
						expand = ICON_TRIA_RIGHT;
						
					if (EDITABLE_ACHAN(achan))
						protect = ICON_UNLOCKED;
					else
						protect = ICON_LOCKED;
						
					if (achan->ipo) {
						if (achan->ipo->muteipo)
							mute = ICON_MUTE_IPO_ON;
						else
							mute = ICON_MUTE_IPO_OFF;
					}
					
					sel = SEL_ACHAN(achan);
					sprintf(name, achan->name);
				}
					break;
				case ACTTYPE_CONCHAN: /* constraint channel */
				{
					bConstraintChannel *conchan = (bConstraintChannel *)ale->data;
					
					indent = 2;
					
					group= (ale->grp) ? 1 : 0;
					grp= ale->grp;
					
					if (EDITABLE_CONCHAN(conchan))
						protect = ICON_UNLOCKED;
					else
						protect = ICON_LOCKED;
						
					if (conchan->ipo) {
						if (conchan->ipo->muteipo)
							mute = ICON_MUTE_IPO_ON;
						else
							mute = ICON_MUTE_IPO_OFF;
					}
					
					sel = SEL_CONCHAN(conchan);
					sprintf(name, conchan->name);
				}
					break;
				case ACTTYPE_ICU: /* ipo-curve channel */
				{
					IpoCurve *icu = (IpoCurve *)ale->data;
					
					indent = 2;
					protect = -1; // for now, until this can be supported by others
					
					group= (ale->grp) ? 1 : 0;
					grp= ale->grp;
					
					if (icu->flag & IPO_MUTE)
						mute = ICON_MUTE_IPO_ON;
					else	
						mute = ICON_MUTE_IPO_OFF;
					
					sel = SEL_ICU(icu);
					if (G.saction->pin)
						sprintf(name, getname_ipocurve(icu, NULL));
					else
						sprintf(name, getname_ipocurve(icu, OBACT));
				}
					break;
				case ACTTYPE_SHAPEKEY: /* shapekey channel */
				{
					KeyBlock *kb = (KeyBlock *)ale->data;
					
					indent = 0;
					special = -1;
					
					if (kb->name[0] == '\0')
						sprintf(name, "Key %d", ale->index);
					else
						sprintf(name, kb->name);
				}
					break;
				case ACTTYPE_FILLIPO: /* ipo expand widget */
				{
					bActionChannel *achan = (bActionChannel *)ale->data;
					
					indent = 1;
					special = geticon_ipo_blocktype(achan->ipo->blocktype);
					
					group= (ale->grp) ? 1 : 0;
					grp= ale->grp;
					
					if (FILTER_IPO_ACHAN(achan))	
						expand = ICON_TRIA_DOWN;
					else
						expand = ICON_TRIA_RIGHT;
					
					sel = SEL_ACHAN(achan);
					sprintf(name, "IPO Curves");
				}
					break;
				case ACTTYPE_FILLCON: /* constraint expand widget */
				{
					bActionChannel *achan = (bActionChannel *)ale->data;
					
					indent = 1;
					special = ICON_CONSTRAINT;
					
					group= (ale->grp) ? 1 : 0;
					grp= ale->grp;
					
					if (FILTER_CON_ACHAN(achan))	
						expand = ICON_TRIA_DOWN;
					else
						expand = ICON_TRIA_RIGHT;
						
					sel = SEL_ACHAN(achan);
					sprintf(name, "Constraint");
				}
					break;
				case ACTTYPE_GPDATABLOCK: /* gpencil datablock */
				{
					bGPdata *gpd = (bGPdata *)ale->data;
					ScrArea *sa = (ScrArea *)ale->owner;
					
					indent = 0;
					group= 3;
					
					/* only show expand if there are any channels */
					if (gpd->layers.first) {
						if (gpd->flag & GP_DATA_EXPAND)
							expand = ICON_TRIA_DOWN;
						else
							expand = ICON_TRIA_RIGHT;
					}
					
					switch (sa->spacetype) {
						case SPACE_VIEW3D:
						{
							/* this shouldn't cause any overflow... */
							sprintf(name, "3DView[%02d]:%s", sa->win, view3d_get_name(sa->spacedata.first));
							special= ICON_VIEW3D;
						}
							break;
						case SPACE_NODE:
						{
							SpaceNode *snode= sa->spacedata.first;
							char treetype[12];
							
							if (snode->treetype == 1)
								sprintf(treetype, "Composite");
							else
								sprintf(treetype, "Material");
							sprintf(name, "Nodes[%02d]:%s", sa->win, treetype);
							
							special= ICON_NODE;
						}
							break;
						case SPACE_SEQ:
						{
							SpaceSeq *sseq= sa->spacedata.first;
							char imgpreview[10];
							
							switch (sseq->mainb) {
								case 1: 	sprintf(imgpreview, "Image..."); 	break;
								case 2: 	sprintf(imgpreview, "Luma..."); 	break;
								case 3: 	sprintf(imgpreview, "Chroma...");	break;
								case 4: 	sprintf(imgpreview, "Histogram");	break;
								
								default:	sprintf(imgpreview, "Sequence");	break;
							}
							sprintf(name, "Sequencer[%02d]:%s", sa->win, imgpreview);
							
							special= ICON_SEQUENCE;
						}
							break;
						case SPACE_IMAGE:
						{
							SpaceImage *sima= sa->spacedata.first;
							
							if (sima->image)
								sprintf(name, "Image[%02d]:%s", sa->win, sima->image->id.name+2);
							else
								sprintf(name, "Image[%02d]:<None>", sa->win);
								
							special= ICON_IMAGE_COL;
						}
							break;
						
						default:
						{
							sprintf(name, "[%02d]<Unknown GP-Data Source>", sa->win);
							special= -1;
						}
							break;
					}
				}
					break;
				case ACTTYPE_GPLAYER: /* gpencil layer */
				{
					bGPDlayer *gpl = (bGPDlayer *)ale->data;
					
					indent = 0;
					special = -1;
					expand = -1;
					group = 1;
					
					if (EDITABLE_GPL(gpl))
						protect = ICON_UNLOCKED;
					else
						protect = ICON_LOCKED;
						
					if (gpl->flag & GP_LAYER_HIDE)
						mute = ICON_MUTE_IPO_ON;
					else
						mute = ICON_MUTE_IPO_OFF;
					
					sel = SEL_GPL(gpl);
					BLI_snprintf(name, 32, gpl->info);
				}
					break;
			}	

			/* now, start drawing based on this information */
			/* draw backing strip behind channel name */
			if (group == 3) {
				/* only for gp-data channels */
				if (ale->owner == curarea) // fixme... currently useless
					BIF_ThemeColorShade(TH_GROUP_ACTIVE, 10);
				else
					BIF_ThemeColorShade(TH_GROUP, 20);
				uiSetRoundBox((expand == ICON_TRIA_DOWN)? (1):(1|8));
				gl_round_box(GL_POLYGON, x,  yminc, (float)NAMEWIDTH, ymaxc, 8);
				
				offset = 0;
			}
			else if (group == 2) {
				/* only for action group channels */
				if (ale->flag & AGRP_ACTIVE)
					BIF_ThemeColorShade(TH_GROUP_ACTIVE, 10);
				else
					BIF_ThemeColorShade(TH_GROUP, 20);
				uiSetRoundBox((expand == ICON_TRIA_DOWN)? (1):(1|8));
				gl_round_box(GL_POLYGON, x,  yminc, (float)NAMEWIDTH, ymaxc, 8);
				
				offset = 0;
			}
			else {
				/* for normal channels 
				 *	- use 3 shades of color group/standard color for 3 indention level
				 *	- only use group colors if allowed to, and if actually feasible
				 */
				if ( !(G.saction->flag & SACTION_NODRAWGCOLORS) && 
					 (grp) && (grp->customCol) ) 
				{
					char cp[3];
					
					if (indent == 2) {
						VECCOPY(cp, grp->cs.solid);
					}
					else if (indent == 1) {
						VECCOPY(cp, grp->cs.select);
					}
					else {
						VECCOPY(cp, grp->cs.active);
					}
					
					glColor3ub(cp[0], cp[1], cp[2]);
				}
				else
					BIF_ThemeColorShade(TH_HEADER, ((indent==0)?20: (indent==1)?-20: -40));
				
				indent += group;
				offset = 7 * indent;
				glRectf(x+offset,  yminc, (float)NAMEWIDTH, ymaxc);
			}
			
			/* draw expand/collapse triangle */
			if (expand > 0) {
				BIF_icon_draw(x+offset, yminc, expand);
				offset += 17;
			}
			
			/* draw special icon indicating certain data-types */
			if (special > -1) {
				if (group == 3) {
					/* for gpdatablock channels */
					BIF_icon_draw(x+offset, yminc, special);
					offset += 17;
				}
				else {
					/* for ipo/constraint channels */
					offset = (group) ? 29 : 24;
					BIF_icon_draw(x+offset, yminc, special);
					offset += 17;
				}
			}
				
			/* draw name */
			if (sel)
				BIF_ThemeColor(TH_TEXT_HI);
			else
				BIF_ThemeColor(TH_TEXT);
			offset += 3;
			glRasterPos2f(x+offset, y-4);
			BMF_DrawString(G.font, name);
			
			/* reset offset - for RHS of panel */
			offset = 0;
			
			/* draw protect 'lock' */
			if (protect > -1) {
				offset = 16;
				BIF_icon_draw((float)NAMEWIDTH-offset, yminc, protect);
			}
			
			/* draw mute 'eye' */
			if (mute > -1) {
				offset += 16;
				BIF_icon_draw((float)(NAMEWIDTH-offset), yminc, mute);
			}
		}
		
		/* adjust y-position for next one */
		y-=CHANNELHEIGHT+CHANNELSKIP;
	}
	
	/* free tempolary channels */
	BLI_freelistN(&act_data);
	
	/* re-adjust view matrices for correct scaling */
    myortho2(0,	NAMEWIDTH, 0, (float)(ofsy+G.v2d->mask.ymax) - (ofsy+G.v2d->mask.ymin));	//	Scaling
}

/* sets or clears hidden flags */
void check_action_context(SpaceAction *saction)
{
	bActionChannel *achan;
	
	if (saction->action==NULL) return;
	
	for (achan=saction->action->chanbase.first; achan; achan=achan->next)
		achan->flag &= ~ACHAN_HIDDEN;
	
	if ((saction->pin==0) && ((saction->flag & SACTION_NOHIDE)==0) && (OBACT)) {
		Object *ob= OBACT;
		bPoseChannel *pchan;
		bArmature *arm= ob->data;
		
		for (achan=saction->action->chanbase.first; achan; achan=achan->next) {
			pchan= get_pose_channel(ob->pose, achan->name);
			if (pchan && pchan->bone) {
				if ((pchan->bone->layer & arm->layer)==0)
					achan->flag |= ACHAN_HIDDEN;
				else if (pchan->bone->flag & BONE_HIDDEN_P)
					achan->flag |= ACHAN_HIDDEN;
			}
		}
	}
}

static void draw_channel_strips(void)
{
	ListBase act_data = {NULL, NULL};
	bActListElem *ale;
	int filter;
	void *data;
	short datatype;
	
	rcti scr_rct;
	gla2DDrawInfo *di;
	float y, sta, end;
	int act_start, act_end, dummy;
	char col1[3], col2[3];
	char col1a[3], col2a[3];
	
	BIF_GetThemeColor3ubv(TH_SHADE2, col2);
	BIF_GetThemeColor3ubv(TH_HILITE, col1);
	BIF_GetThemeColor3ubv(TH_GROUP, col2a);
	BIF_GetThemeColor3ubv(TH_GROUP_ACTIVE, col1a);

	/* get editor data */
	data= get_action_context(&datatype);
	if (data == NULL) return;

	scr_rct.xmin= G.saction->area->winrct.xmin + G.saction->v2d.mask.xmin;
	scr_rct.ymin= G.saction->area->winrct.ymin + G.saction->v2d.mask.ymin;
	scr_rct.xmax= G.saction->area->winrct.xmin + G.saction->v2d.hor.xmax;
	scr_rct.ymax= G.saction->area->winrct.ymin + G.saction->v2d.mask.ymax; 
	di= glaBegin2DDraw(&scr_rct, &G.v2d->cur);

	/* if in NLA there's a strip active, map the view */
	if (datatype == ACTCONT_ACTION) {
		if (NLA_ACTION_SCALED)
			map_active_strip(di, OBACT, 0);
		
		/* start and end of action itself */
		calc_action_range(data, &sta, &end, 0);
		gla2DDrawTranslatePt(di, sta, 0.0f, &act_start, &dummy);
		gla2DDrawTranslatePt(di, end, 0.0f, &act_end, &dummy);
		
		if (NLA_ACTION_SCALED)
			map_active_strip(di, OBACT, 1);
	}
	
	/* build list of channels to draw */
	filter= (ACTFILTER_FORDRAWING|ACTFILTER_VISIBLE|ACTFILTER_CHANNELS);
	actdata_filter(&act_data, filter, data, datatype);
	
	/* first backdrop strips */
	y = 0.0;
	glEnable(GL_BLEND);
	for (ale= act_data.first; ale; ale= ale->next) {
		int frame1_x, channel_y, sel=0;
		
		/* determine if any need to draw channel */
		if (ale->datatype != ALE_NONE) {
			/* determine if channel is selected */
			switch (ale->type) {
				case ACTTYPE_GROUP:
				{
					bActionGroup *agrp = (bActionGroup *)ale->data;
					sel = SEL_AGRP(agrp);
				}
					break;
				case ACTTYPE_ACHAN:
				{
					bActionChannel *achan = (bActionChannel *)ale->data;
					sel = SEL_ACHAN(achan);
				}
					break;
				case ACTTYPE_CONCHAN:
				{
					bConstraintChannel *conchan = (bConstraintChannel *)ale->data;
					sel = SEL_CONCHAN(conchan);
				}
					break;
				case ACTTYPE_ICU:
				{
					IpoCurve *icu = (IpoCurve *)ale->data;
					sel = SEL_ICU(icu);
				}
					break;
				case ACTTYPE_GPLAYER:
				{
					bGPDlayer *gpl = (bGPDlayer *)ale->data;
					sel = SEL_GPL(gpl);
				}
					break;
			}
			
			if (datatype == ACTCONT_ACTION) {
				gla2DDrawTranslatePt(di, G.v2d->cur.xmin, y, &frame1_x, &channel_y);
				
				if (ale->datatype == ALE_GROUP) {
					if (sel) glColor4ub(col1a[0], col1a[1], col1a[2], 0x22);
					else glColor4ub(col2a[0], col2a[1], col2a[2], 0x22);
				}
				else {
					if (sel) glColor4ub(col1[0], col1[1], col1[2], 0x22);
					else glColor4ub(col2[0], col2[1], col2[2], 0x22);
				}
				glRectf((float)frame1_x,  (float)channel_y-CHANNELHEIGHT/2,  (float)G.v2d->hor.xmax,  (float)channel_y+CHANNELHEIGHT/2);
				
				if (ale->datatype == ALE_GROUP) {
					if (sel) glColor4ub(col1a[0], col1a[1], col1a[2], 0x22);
					else glColor4ub(col2a[0], col2a[1], col2a[2], 0x22);
				}
				else {
					if (sel) glColor4ub(col1[0], col1[1], col1[2], 0x22);
					else glColor4ub(col2[0], col2[1], col2[2], 0x22);
				}
				glRectf((float)act_start,  (float)channel_y-CHANNELHEIGHT/2,  (float)act_end,  (float)channel_y+CHANNELHEIGHT/2);
			}
			else if (datatype == ACTCONT_SHAPEKEY) {
				gla2DDrawTranslatePt(di, 1, y, &frame1_x, &channel_y);
				
				/* all frames that have a frame number less than one
				 * get a desaturated orange background
				 */
				glColor4ub(col2[0], col2[1], col2[2], 0x22);
				glRectf(0.0f, (float)channel_y-CHANNELHEIGHT/2, (float)frame1_x, (float)channel_y+CHANNELHEIGHT/2);
				
				/* frames one and higher get a saturated orange background */
				glColor4ub(col2[0], col2[1], col2[2], 0x44);
				glRectf((float)frame1_x, (float)channel_y-CHANNELHEIGHT/2, (float)G.v2d->hor.xmax,  (float)channel_y+CHANNELHEIGHT/2.0f);
			}
			else if (datatype == ACTCONT_GPENCIL) {
				gla2DDrawTranslatePt(di, G.v2d->cur.xmin, y, &frame1_x, &channel_y);
				
				/* frames less than one get less saturated background */
				if (sel) glColor4ub(col1[0], col1[1], col1[2], 0x22);
				else glColor4ub(col2[0], col2[1], col2[2], 0x22);
				glRectf(0.0f, (float)channel_y-CHANNELHEIGHT/2, (float)frame1_x, (float)channel_y+CHANNELHEIGHT/2);
				
				/* frames one and higher get a saturated background */
				if (sel) glColor4ub(col1[0], col1[1], col1[2], 0x44);
				else glColor4ub(col2[0], col2[1], col2[2], 0x44);
				glRectf((float)frame1_x, (float)channel_y-CHANNELHEIGHT/2, (float)G.v2d->hor.xmax,  (float)channel_y+CHANNELHEIGHT/2);
			}
		}
		
		/*	Increment the step */
		y-=CHANNELHEIGHT+CHANNELSKIP;
	}		
	glDisable(GL_BLEND);
	
	if (NLA_ACTION_SCALED)
		map_active_strip(di, OBACT, 0);
	
	/* Draw keyframes 
	 *	1) Only channels that are visible in the Action Editor get drawn/evaluated.
	 *	   This is to try to optimise this for heavier data sets
	 *	2) Keyframes which are out of view horizontally are disregarded 
	 */
	y = 0.0;
	for (ale= act_data.first; ale; ale= ale->next) {
		const float yminc= y-CHANNELHEIGHT/2;
		const float ymaxc= y+CHANNELHEIGHT/2;
		
		/* check if visible */
		if ( IN_RANGE(yminc, G.v2d->cur.ymin, G.v2d->cur.ymax) ||
			 IN_RANGE(ymaxc, G.v2d->cur.ymin, G.v2d->cur.ymax) ) 
		{
			switch (ale->datatype) {
				case ALE_GROUP:
					draw_agroup_channel(di, ale->data, y);
					break;
				case ALE_IPO:
					draw_ipo_channel(di, ale->key_data, y);
					break;
				case ALE_ICU:
					draw_icu_channel(di, ale->key_data, y);
					break;
				case ALE_GPFRAME:
					draw_gpl_channel(di, ale->data, y);
					break;
			}
		}
		
		y-=CHANNELHEIGHT+CHANNELSKIP;
	}
	
	/* free tempolary channels used for drawing */
	BLI_freelistN(&act_data);

	/* black line marking 'current frame' for Time-Slide transform mode */
	if (G.saction->flag & SACTION_MOVING) {
		int frame1_x, channel_y;
		
		gla2DDrawTranslatePt(di, G.saction->timeslide, 0, &frame1_x, &channel_y);
		cpack(0x0);
		
		glBegin(GL_LINES);
		glVertex2f((float)frame1_x, (float)G.v2d->mask.ymin - 100);
		glVertex2f((float)frame1_x, (float)G.v2d->mask.ymax);
		glEnd();
	}
	
	glaEnd2DDraw(di);
}

/* ********* action panel *********** */


void do_actionbuts(unsigned short event)
{
	switch(event) {
		/* general */
	case REDRAWVIEW3D:
		allqueue(REDRAWVIEW3D, 0);
		break;
	case B_REDR:
		allqueue(REDRAWACTION, 0);
		break;
		
		/* action-groups */
	case B_ACTCUSTCOLORS:	/* only when of the color wells is edited */
	{
		bActionGroup *agrp= get_active_actiongroup(G.saction->action);
		
		if (agrp)
			agrp->customCol= -1;
			
		allqueue(REDRAWACTION, 0);
	}
		break;
	case B_ACTCOLSSELECTOR: /* sync color set after using selector */
	{
		bActionGroup *agrp= get_active_actiongroup(G.saction->action);
		
		if (agrp) 
			actionbone_group_copycolors(agrp, 1);
			
		allqueue(REDRAWACTION, 0);
	}
		break;
	case B_ACTGRP_SELALL: /* select all grouped channels */
	{
		bAction *act= G.saction->action;
		bActionGroup *agrp= get_active_actiongroup(act);
		
		/* select all in group, then reselect/activate group as the previous operation clears that */
		select_action_group_channels(act, agrp);
		agrp->flag |= (AGRP_ACTIVE|AGRP_SELECTED);
		
		allqueue(REDRAWACTION, 0);
	}
		break;
	case B_ACTGRP_ADDTOSELF: /* add all selected action channels to self */
		action_groups_group(0);
		break;
	case B_ACTGRP_UNGROUP: /* remove channels from active group */
		// FIXME: todo...
		printf("FIXME: remove achans from active Action-Group not implemented yet! \n");
		break;
	
	}
}

// currently not used...
static void action_panel_properties(short cntrl)	// ACTION_HANDLER_PROPERTIES
{
	uiBlock *block;
	void *data;
	short datatype;
	
	block= uiNewBlock(&curarea->uiblocks, "action_panel_properties", UI_EMBOSS, UI_HELV, curarea->win);
	uiPanelControl(UI_PNL_SOLID | UI_PNL_CLOSE | cntrl);
	uiSetPanelHandler(ACTION_HANDLER_PROPERTIES);  // for close and esc
	
	/* get datatype */
	data= get_action_context(&datatype);
	//if (data == NULL) return;
	
	if (uiNewPanel(curarea, block, "Active Channel Properties", "Action", 10, 230, 318, 204)==0) 
		return;
	
	/* currently, only show data for actions */
	if (datatype == ACTCONT_ACTION) {
		bActionGroup *agrp= get_active_actiongroup(data);
		//bActionChannel *achan= get_hilighted_action_channel(data);
		char *menustr;
		
		/* only for action-groups */
		if (agrp) {
			/* general stuff */
			uiDefBut(block, LABEL, 1, "Action Group:",					10, 180, 150, 19, NULL, 0.0, 0.0, 0, 0, "");
			
			uiDefBut(block, TEX, B_REDR, "Name: ",	10,160,150,20, agrp->name, 0.0, 31.0, 0, 0, "");
			uiBlockBeginAlign(block);
				uiDefButBitI(block, TOG, AGRP_EXPANDED, B_REDR, "Expanded", 170, 160, 75, 20, &agrp->flag, 0, 0, 0, 0, "Action Group is expanded");
				uiDefButBitI(block, TOG, AGRP_PROTECTED, B_REDR, "Protected", 245, 160, 75, 20, &agrp->flag, 0, 0, 0, 0, "Action Group is protected");
			uiBlockEndAlign(block);
			
			/* color stuff */
			uiDefBut(block, LABEL, 1, "Group Colors:",	10, 107, 150, 19, NULL, 0.0, 0.0, 0, 0, "");
			uiBlockBeginAlign(block);
				menustr= BIF_ThemeColorSetsPup(1);
				uiDefButI(block, MENU,B_ACTCOLSSELECTOR, menustr, 10,85,150,19, &agrp->customCol, -1, 20, 0.0, 0.0, "Index of set of Custom Colors to shade Group's bones with. 0 = Use Default Color Scheme, -1 = Use Custom Color Scheme");						
				MEM_freeN(menustr);
				
				/* show color-selection/preview */
				if (agrp->customCol) {
					/* do color copying/init (to stay up to date) */
					actionbone_group_copycolors(agrp, 1);
					
					/* color changing */
					uiDefButC(block, COL, B_ACTCUSTCOLORS, "",		10, 65, 50, 19, agrp->cs.active, 0, 0, 0, 0, "Color to use for 'top-level' channels");
					uiDefButC(block, COL, B_ACTCUSTCOLORS, "",		60, 65, 50, 19, agrp->cs.select, 0, 0, 0, 0, "Color to use for '2nd-level' channels");
					uiDefButC(block, COL, B_ACTCUSTCOLORS, "",		110, 65, 50, 19, agrp->cs.solid, 0, 0, 0, 0, "Color to use for '3rd-level' channels");
				}
			uiBlockEndAlign(block);
			
			/* commands for active group */
			uiDefBut(block, BUT, B_ACTGRP_SELALL, "Select Grouped",	170,85,150,20, 0, 21, 0, 0, 0, "Select all action-channels belonging to this group (same as doing Ctrl-Shift-LMB)");
			
			uiBlockBeginAlign(block);
				uiDefBut(block, BUT, B_ACTGRP_ADDTOSELF, "Add to Group",	170,60,150,20, 0, 21, 0, 0, 0, "Add selected action-channels to this group");
				uiDefBut(block, BUT, B_ACTGRP_UNGROUP, "Un-Group",	170,40,150,20, 0, 21, 0, 0, 0, "Remove selected action-channels from this group (unimplemented)");
			uiBlockEndAlign(block);
		}
	}
	else {
		/* Currently, there isn't anything to display for these types ... */
	}
}

static void action_blockhandlers(ScrArea *sa)
{
	SpaceAction *sact= sa->spacedata.first;
	short a;
	
	for (a=0; a<SPACE_MAXHANDLER; a+=2) {
		switch(sact->blockhandler[a]) {
			case ACTION_HANDLER_PROPERTIES:
				action_panel_properties(sact->blockhandler[a+1]);
				break;
		}
		
		/* clear action value for event */
		sact->blockhandler[a+1]= 0;
	}
	
	uiDrawBlocksPanels(sa, 0);
}

/* ************************* Action Editor Space ***************************** */

void drawactionspace(ScrArea *sa, void *spacedata)
{
	bAction *act = NULL;
	Key *key = NULL;
	void *data;
	short datatype;
	
	short ofsx = 0, ofsy = 0;
	float col[3];

	/* this is unlikely to occur, but it may */
	if (G.saction == NULL)
		return;

	/* warning: blocks need to be freed each time, handlers dont remove  */
	uiFreeBlocksWin(&sa->uiblocks, sa->win);

	/* only try to refresh action that's displayed if not pinned */
	if (G.saction->pin==0) {
		/* depends on mode */
		switch (G.saction->mode) {
			case SACTCONT_ACTION:
			{
				if (OBACT)
					G.saction->action = OBACT->action;
				else
					G.saction->action= NULL;
			}
				break;
		}
	}
	
	/* get data */
	data = get_action_context(&datatype);
	switch (datatype) {
		case ACTCONT_ACTION:
			act = data;
			break;
		case ACTCONT_SHAPEKEY:
			key = data;
			break;
		case ACTCONT_GPENCIL:
			/* currently, 'data' value for grease-pencil is G.curscreen! */
			break;
	}
	
	/* Lets make sure the width of the left hand of the screen
	 * is set to an appropriate value based on whether sliders
	 * are showing of not
	 */
	if ((data) && (G.saction->flag & SACTION_SLIDERS)) 
		ACTWIDTH = NAMEWIDTH + SLIDERWIDTH;
	else 
		ACTWIDTH = NAMEWIDTH;

	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	calc_scrollrcts(sa, G.v2d, curarea->winx, curarea->winy);

	/* background color for entire window (used in lefthand part though) */
	BIF_GetThemeColor3fv(TH_HEADER, col);
	glClearColor(col[0], col[1], col[2], 0.0); 
	glClear(GL_COLOR_BUFFER_BIT);
	
	if (curarea->winx>SCROLLB+10 && curarea->winy>SCROLLH+10) {
		if(G.v2d->scroll) {	
			ofsx= curarea->winrct.xmin;	
			ofsy= curarea->winrct.ymin;
			glViewport(ofsx+G.v2d->mask.xmin,  ofsy+G.v2d->mask.ymin, 
					   ( ofsx+G.v2d->mask.xmax-1)-(ofsx+G.v2d->mask.xmin)+1, 
					   ( ofsy+G.v2d->mask.ymax-1)-( ofsy+G.v2d->mask.ymin)+1); 
			glScissor(ofsx+G.v2d->mask.xmin,  ofsy+G.v2d->mask.ymin, 
					  ( ofsx+G.v2d->mask.xmax-1)-(ofsx+G.v2d->mask.xmin)+1, 
					  ( ofsy+G.v2d->mask.ymax-1)-( ofsy+G.v2d->mask.ymin)+1);
		}
	}

	BIF_GetThemeColor3fv(TH_BACK, col);
	glClearColor(col[0], col[1], col[2], 0.0);
	glClear(GL_COLOR_BUFFER_BIT);

	myortho2(G.v2d->cur.xmin, G.v2d->cur.xmax, G.v2d->cur.ymin, G.v2d->cur.ymax);
	bwin_clear_viewmat(sa->win);	/* clear buttons view */
	glLoadIdentity();

	/*	Draw backdrop */
	calc_ipogrid();	
	draw_ipogrid();

	check_action_context(G.saction);
	
	/* Draw channel strips */
	draw_channel_strips();
	
	/* reset matrices for stuff to be drawn on top of keys*/
	glViewport(ofsx+G.v2d->mask.xmin,  
             ofsy+G.v2d->mask.ymin, 
             ( ofsx+G.v2d->mask.xmax-1)-(ofsx+G.v2d->mask.xmin)+1, 
             ( ofsy+G.v2d->mask.ymax-1)-( ofsy+G.v2d->mask.ymin)+1); 
	glScissor(ofsx+G.v2d->mask.xmin,  
            ofsy+G.v2d->mask.ymin, 
            ( ofsx+G.v2d->mask.xmax-1)-(ofsx+G.v2d->mask.xmin)+1, 
            ( ofsy+G.v2d->mask.ymax-1)-( ofsy+G.v2d->mask.ymin)+1);
	myortho2(G.v2d->cur.xmin, G.v2d->cur.xmax,  G.v2d->cur.ymin, G.v2d->cur.ymax);
	
	/* Draw current frame */
	draw_cfra_action();
	
	/* Draw markers (local behind scene ones, as local obscure scene markers) */
	if (act) 
		draw_markers_timespace(&act->markers, DRAW_MARKERS_LOCAL);
	draw_markers_timespace(SCE_MARKERS, 0);
	
	/* Draw 'curtains' for preview */
	draw_anim_preview_timespace();

	/* Draw scroll */
	mywinset(curarea->win);	// reset scissor too
	if (curarea->winx>SCROLLB+10 && curarea->winy>SCROLLH+10) {
		myortho2(-0.375f, curarea->winx-0.375f, -0.375f, curarea->winy-0.375f);
		if (G.v2d->scroll) drawscroll(0);
	}

	/* Draw Left-Hand Panel if enough space in window */
	if (G.v2d->mask.xmin!=0) {
		/* Draw channel names */
		draw_channel_names();
		
		if(sa->winx > 50 + NAMEWIDTH + SLIDERWIDTH) {
			if (act) {
				/* if there is an action, draw sliders for its
				 * ipo-curve channels in the action window
				 */
				action_icu_buts(G.saction);
			}
			else if (key) {
				/* if there is a mesh with rvk's selected,
				 * then draw the key frames in the action window
				 */
				meshactionbuts(G.saction, OBACT, key);
			}
		}
	}
	
	mywinset(curarea->win);	// reset scissor too
	myortho2(-0.375f, curarea->winx-0.375f, -0.375f, curarea->winy-0.375f);
	draw_area_emboss(sa);

	/* it is important to end a view in a transform compatible with buttons */
	bwin_scalematrix(sa->win, G.saction->blockscale, G.saction->blockscale, G.saction->blockscale);
	action_blockhandlers(sa);

	curarea->win_swap= WIN_BACK_OK;
}

/* *************************** Keyframe Drawing *************************** */

static void add_bezt_to_keycolumnslist(ListBase *keys, BezTriple *bezt)
{
	/* The equivilant of add_to_cfra_elem except this version 
	 * makes ActKeyColumns - one of the two datatypes required
	 * for action editor drawing.
	 */
	ActKeyColumn *ak, *akn;
	
	if (ELEM(NULL, keys, bezt)) return;
	
	/* try to any existing key to replace, or where to insert after */
	for (ak= keys->last; ak; ak= ak->prev) {
		/* do because of double keys */
		if (ak->cfra == bezt->vec[1][0]) {			
			/* set selection status and 'touched' status */
			if (BEZSELECTED(bezt)) ak->sel = SELECT;
			ak->modified += 1;
			
			return;
		}
		else if (ak->cfra < bezt->vec[1][0]) break;
	}
	
	/* add new block */
	akn= MEM_callocN(sizeof(ActKeyColumn), "ActKeyColumn");
	if (ak) BLI_insertlinkafter(keys, ak, akn);
	else BLI_addtail(keys, akn);
	
	akn->cfra= bezt->vec[1][0];
	akn->modified += 1;
	
	// TODO: handle type = bezt->h1 or bezt->h2
	akn->handle_type= 0; 
	
	if (BEZSELECTED(bezt))
		akn->sel = SELECT;
	else
		akn->sel = 0;
}

static void add_bezt_to_keyblockslist(ListBase *blocks, IpoCurve *icu, int index)
{
	/* The equivilant of add_to_cfra_elem except this version 
	 * makes ActKeyBlocks - one of the two datatypes required
	 * for action editor drawing.
	 */
	ActKeyBlock *ab, *abn;
	BezTriple *beztn=NULL, *prev=NULL;
	BezTriple *bezt;
	int v;
	
	/* get beztriples */
	beztn= (icu->bezt + index);
	
	/* we need to go through all beztriples, as they may not be in order (i.e. during transform) */
	for (v=0, bezt=icu->bezt; v<icu->totvert; v++, bezt++) {
		/* skip if beztriple is current */
		if (v != index) {
			/* check if beztriple is immediately before */
			if (beztn->vec[1][0] > bezt->vec[1][0]) {
				/* check if closer than previous was */
				if (prev) {
					if (prev->vec[1][0] < bezt->vec[1][0])
						prev= bezt;
				}
				else {
					prev= bezt;
				}
			}
		}
	}
	
	/* check if block needed - same value(s)?
	 *	-> firstly, handles must have same central value as each other
	 *	-> secondly, handles which control that section of the curve must be constant
	 */
	if ((!prev) || (!beztn)) return;
	if (IS_EQ(beztn->vec[1][1], prev->vec[1][1])==0) return;
	if (IS_EQ(beztn->vec[1][1], beztn->vec[0][1])==0) return;
	if (IS_EQ(prev->vec[1][1], prev->vec[2][1])==0) return;
	
	/* try to find a keyblock that starts on the previous beztriple 
	 * Note: we can't search from end to try to optimise this as it causes errors there's
	 * 		an A ___ B |---| B situation
	 */
	// FIXME: here there is a bug where we are trying to get the summary for the following channels
	//		A|--------------|A ______________ B|--------------|B
	//		A|------------------------------------------------|A
	//		A|----|A|---|A|-----------------------------------|A
	for (ab= blocks->first; ab; ab= ab->next) {
		/* check if alter existing block or add new block */
		if (ab->start == prev->vec[1][0]) {			
			/* set selection status and 'touched' status */
			if (BEZSELECTED(beztn)) ab->sel = SELECT;
			ab->modified += 1;
			
			return;
		}
		else if (ab->start < prev->vec[1][0]) break;
	}
	
	/* add new block */
	abn= MEM_callocN(sizeof(ActKeyBlock), "ActKeyBlock");
	if (ab) BLI_insertlinkbefore(blocks, ab, abn);
	else BLI_addtail(blocks, abn);
	
	abn->start= prev->vec[1][0];
	abn->end= beztn->vec[1][0];
	abn->val= beztn->vec[1][1];
	
	if (BEZSELECTED(prev) || BEZSELECTED(beztn))
		abn->sel = SELECT;
	else
		abn->sel = 0;
	abn->modified = 1;
}

/* helper function - find actkeycolumn that occurs on cframe */
static ActKeyColumn *cfra_find_actkeycolumn (ListBase *keys, float cframe)
{
	ActKeyColumn *ak, *ak2;
	
	if (keys==NULL) 
		return NULL;
	 
	/* search from both ends at the same time, and stop if we find match or if both ends meet */ 
	for (ak=keys->first, ak2=keys->last; ak && ak2; ak=ak->next, ak2=ak2->prev) {
		/* return whichever end encounters the frame */
		if (ak->cfra == cframe)
			return ak;
		if (ak2->cfra == cframe)
			return ak2;
		
		/* no matches on either end, so return NULL */
		if (ak == ak2)
			return NULL;
	}
	
	return NULL;
}

#if 0  // disabled, as some intel cards have problems with this
/* Draw a simple diamond shape with a filled in center (in screen space) */
static void draw_key_but(int x, int y, short w, short h, int sel)
{
	int xmin= x, ymin= y;
	int xmax= x+w-1, ymax= y+h-1;
	int xc= (xmin+xmax)/2, yc= (ymin+ymax)/2;
	
	/* interior - hardcoded colors (for selected and unselected only) */
	if (sel) glColor3ub(0xF1, 0xCA, 0x13);
	else glColor3ub(0xE9, 0xE9, 0xE9);
	
	glBegin(GL_QUADS);
	glVertex2i(xc, ymin);
	glVertex2i(xmax, yc);
	glVertex2i(xc, ymax);
	glVertex2i(xmin, yc);
	glEnd();
	
	
	/* outline */
	glColor3ub(0, 0, 0);
	
	glBegin(GL_LINE_LOOP);
	glVertex2i(xc, ymin);
	glVertex2i(xmax, yc);
	glVertex2i(xc, ymax);
	glVertex2i(xmin, yc);
	glEnd();
}
#endif

static void draw_keylist(gla2DDrawInfo *di, ListBase *keys, ListBase *blocks, float ypos)
{
	ActKeyColumn *ak;
	ActKeyBlock *ab;
	
	glEnable(GL_BLEND);
	
	/* draw keyblocks */
	if (blocks) {
		for (ab= blocks->first; ab; ab= ab->next) {
			short startCurves, endCurves, totCurves;
			
			/* find out how many curves occur at each keyframe */
			ak= cfra_find_actkeycolumn(keys, ab->start);
			startCurves = (ak)? ak->totcurve: 0;
			
			ak= cfra_find_actkeycolumn(keys, ab->end);
			endCurves = (ak)? ak->totcurve: 0;
			
			/* only draw keyblock if it appears in at all of the keyframes at lowest end */
			if (!startCurves && !endCurves) 
				continue;
			else
				totCurves = (startCurves>endCurves)? endCurves: startCurves;
				
			if (ab->totcurve >= totCurves) {
				int sc_xa, sc_ya;
				int sc_xb, sc_yb;
				
				/* get co-ordinates of block */
				gla2DDrawTranslatePt(di, ab->start, ypos, &sc_xa, &sc_ya);
				gla2DDrawTranslatePt(di, ab->end, ypos, &sc_xb, &sc_yb);
				
				/* draw block */
				if (ab->sel)
					BIF_ThemeColor4(TH_STRIP_SELECT);
				else
					BIF_ThemeColor4(TH_STRIP);
				glRectf((float)sc_xa, (float)sc_ya-3, (float)sc_xb, (float)sc_yb+5);
			}
		}
	}
	
	/* draw keys */
	if (keys) {
		for (ak= keys->first; ak; ak= ak->next) {
			int sc_x, sc_y;
			
			/* get co-ordinate to draw at */
			gla2DDrawTranslatePt(di, ak->cfra, ypos, &sc_x, &sc_y);
			
			/* draw using icons - old way which is slower but more proven */
			if (ak->sel & SELECT) BIF_icon_draw_aspect((float)sc_x-7, (float)sc_y-6, ICON_SPACE2, 1.0f);
			else BIF_icon_draw_aspect((float)sc_x-7, (float)sc_y-6, ICON_SPACE3, 1.0f);
			
			/* draw using OpenGL - slightly uglier but faster */
			// 	NOTE: disabled for now, as some intel cards seem to have problems with this
			//draw_key_but(sc_x-5, sc_y-4, 11, 11, (ak->sel & SELECT));
		}	
	}
	
	glDisable(GL_BLEND);
}


static ActKeysInc *init_aki_data()
{
	static ActKeysInc aki;
	
	/* init data of static struct here */
	if ((curarea->spacetype == SPACE_ACTION) && NLA_ACTION_SCALED &&
		(G.saction->mode == SACTCONT_ACTION))
	{
		aki.ob= OBACT;
	}
	else if (curarea->spacetype == SPACE_NLA)
	{
		aki.ob= NULL; // FIXME
	}
	else
		aki.ob= NULL;
		
	aki.start= G.v2d->cur.xmin - 10;
	aki.end= G.v2d->cur.xmax + 10;
	
	/* only pass pointer for Action Editor if enabled (for now) */
	if ((curarea->spacetype == SPACE_ACTION) && (G.saction->flag & SACTION_HORIZOPTIMISEON))
		return &aki;
	else	
		return NULL;
}

void draw_object_channel(gla2DDrawInfo *di, Object *ob, float ypos)
{
	ListBase keys = {0, 0};
	ListBase blocks = {0, 0};
	ActKeysInc *aki = init_aki_data();

	ob_to_keylist(ob, &keys, &blocks, aki);
	draw_keylist(di, &keys, &blocks, ypos);
	
	BLI_freelistN(&keys);
	BLI_freelistN(&blocks);
}

void draw_ipo_channel(gla2DDrawInfo *di, Ipo *ipo, float ypos)
{
	ListBase keys = {0, 0};
	ListBase blocks = {0, 0};
	ActKeysInc *aki = init_aki_data();

	ipo_to_keylist(ipo, &keys, &blocks, aki);
	draw_keylist(di, &keys, &blocks, ypos);
	
	BLI_freelistN(&keys);
	BLI_freelistN(&blocks);
}

void draw_icu_channel(gla2DDrawInfo *di, IpoCurve *icu, float ypos)
{
	ListBase keys = {0, 0};
	ListBase blocks = {0, 0};
	ActKeysInc *aki = init_aki_data();

	icu_to_keylist(icu, &keys, &blocks, aki);
	draw_keylist(di, &keys, &blocks, ypos);
	
	BLI_freelistN(&keys);
	BLI_freelistN(&blocks);
}

void draw_agroup_channel(gla2DDrawInfo *di, bActionGroup *agrp, float ypos)
{
	ListBase keys = {0, 0};
	ListBase blocks = {0, 0};
	ActKeysInc *aki = init_aki_data();

	agroup_to_keylist(agrp, &keys, &blocks, aki);
	draw_keylist(di, &keys, &blocks, ypos);
	BLI_freelistN(&keys);
	BLI_freelistN(&blocks);
}

void draw_action_channel(gla2DDrawInfo *di, bAction *act, float ypos)
{
	ListBase keys = {0, 0};
	ActKeysInc *aki = init_aki_data();

	action_to_keylist(act, &keys, NULL, aki);
	draw_keylist(di, &keys, NULL, ypos);
	BLI_freelistN(&keys);
}

void draw_gpl_channel(gla2DDrawInfo *di, bGPDlayer *gpl, float ypos)
{
	ListBase keys = {0, 0};
	ActKeysInc *aki = init_aki_data();
	
	gpl_to_keylist(gpl, &keys, NULL, aki);
	draw_keylist(di, &keys, NULL, ypos);
	BLI_freelistN(&keys);
}

/* --------------- Conversion: data -> keyframe list ------------------ */

void ob_to_keylist(Object *ob, ListBase *keys, ListBase *blocks, ActKeysInc *aki)
{
	bConstraintChannel *conchan;

	if (ob) {
		/* Add object keyframes */
		if (ob->ipo)
			ipo_to_keylist(ob->ipo, keys, blocks, aki);
		
		/* Add constraint keyframes */
		for (conchan=ob->constraintChannels.first; conchan; conchan=conchan->next) {
			if (conchan->ipo)
				ipo_to_keylist(conchan->ipo, keys, blocks, aki);		
		}
			
		/* Add object data keyframes */
		// 		TODO??
	}
}

static short bezt_in_aki_range (ActKeysInc *aki, BezTriple *bezt)
{
	/* when aki == NULL, we don't care about range */
	if (aki == NULL) 
		return 1;
		
	/* if nla-scaling is in effect, apply appropriate scaling adjustments */
	if (aki->ob) {
		float frame= get_action_frame_inv(aki->ob, bezt->vec[1][0]);
		return IN_RANGE(frame, aki->start, aki->end);
	}
	else {
		/* check if in range */
		return IN_RANGE(bezt->vec[1][0], aki->start, aki->end);
	}
}

void icu_to_keylist(IpoCurve *icu, ListBase *keys, ListBase *blocks, ActKeysInc *aki)
{
	BezTriple *bezt;
	ActKeyColumn *ak, *ak2;
	ActKeyBlock *ab, *ab2;
	int v;
	
	if (icu && icu->totvert) {
		/* loop through beztriples, making ActKeys and ActKeyBlocks */
		bezt= icu->bezt;
		
		for (v=0; v<icu->totvert; v++, bezt++) {
			/* only if keyframe is in range (optimisation) */
			if (bezt_in_aki_range(aki, bezt)) {
				add_bezt_to_keycolumnslist(keys, bezt);
				if (blocks) add_bezt_to_keyblockslist(blocks, icu, v);
			}
		}
		
		/* update the number of curves that elements have appeared in  */
		if (keys) {
			for (ak=keys->first, ak2=keys->last; ak && ak2; ak=ak->next, ak2=ak2->prev) {
				if (ak->modified) {
					ak->modified = 0;
					ak->totcurve += 1;
				}
				
				if (ak == ak2)
					break;
				
				if (ak2->modified) {
					ak2->modified = 0;
					ak2->totcurve += 1;
				}
			}
		}
		if (blocks) {
			for (ab=blocks->first, ab2=blocks->last; ab && ab2; ab=ab->next, ab2=ab2->prev) {
				if (ab->modified) {
					ab->modified = 0;
					ab->totcurve += 1;
				}
				
				if (ab == ab2)
					break;
				
				if (ab2->modified) {
					ab2->modified = 0;
					ab2->totcurve += 1;
				}
			}
		}
	}
}

void ipo_to_keylist(Ipo *ipo, ListBase *keys, ListBase *blocks, ActKeysInc *aki)
{
	IpoCurve *icu;
	
	if (ipo) {
		for (icu= ipo->curve.first; icu; icu= icu->next)
			icu_to_keylist(icu, keys, blocks, aki);
	}
}

void agroup_to_keylist(bActionGroup *agrp, ListBase *keys, ListBase *blocks, ActKeysInc *aki)
{
	bActionChannel *achan;
	bConstraintChannel *conchan;

	if (agrp) {
		/* loop through action channels */
		for (achan= agrp->channels.first; achan && achan->grp==agrp; achan= achan->next) {
			if (VISIBLE_ACHAN(achan)) {
				/* firstly, add keys from action channel's ipo block */
				if (achan->ipo)
					ipo_to_keylist(achan->ipo, keys, blocks, aki);
				
				/* then, add keys from constraint channels */
				for (conchan= achan->constraintChannels.first; conchan; conchan= conchan->next) {
					if (conchan->ipo)
						ipo_to_keylist(conchan->ipo, keys, blocks, aki);
				}
			}
		}
	}
}

void action_to_keylist(bAction *act, ListBase *keys, ListBase *blocks, ActKeysInc *aki)
{
	bActionChannel *achan;
	bConstraintChannel *conchan;

	if (act) {
		/* loop through action channels */
		for (achan= act->chanbase.first; achan; achan= achan->next) {
			/* firstly, add keys from action channel's ipo block */
			if (achan->ipo)
				ipo_to_keylist(achan->ipo, keys, blocks, aki);
			
			/* then, add keys from constraint channels */
			for (conchan= achan->constraintChannels.first; conchan; conchan= conchan->next) {
				if (conchan->ipo)
					ipo_to_keylist(conchan->ipo, keys, blocks, aki);
			}
		}
	}
}

void gpl_to_keylist(bGPDlayer *gpl, ListBase *keys, ListBase *blocks, ActKeysInc *aki)
{
	bGPDframe *gpf;
	ActKeyColumn *ak;
	
	if (gpl && keys) {
		/* loop over frames, converting directly to 'keyframes' (should be in order too) */
		for (gpf= gpl->frames.first; gpf; gpf= gpf->next) {
			ak= MEM_callocN(sizeof(ActKeyColumn), "ActKeyColumn");
			BLI_addtail(keys, ak);
			
			ak->cfra= (float)gpf->framenum;
			ak->modified = 1;
			ak->handle_type= 0; 
			
			if (gpf->flag & GP_FRAME_SELECT)
				ak->sel = SELECT;
			else
				ak->sel = 0;
		}
	}
}

