/* 
 * transferFuncDialog.cpp - Transfer function editor dialog
 * Copyright (C) 2018, D Haley
 * 
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */

#include "transferFuncDialog.h"
#include <cstdlib>
#include <algorithm>
#include <fstream>

#include <wx/colordlg.h>
#include <wx/statline.h>

#include "common/stringFuncs.h"


#ifndef ASSERT
	#define ASSERT(f) {assert((f));}
#endif

// begin wxGlade: ::extracode
// end wxGlade

#include <cstdlib>
#include <iostream>

using std::vector;
using std::pair;
using std::make_pair;
using namespace std;



enum
{
	ID_CHECK_BLUE_OR_VALUE=wxID_ANY+1,
	ID_CHECK_GREEN_OR_SAT,
	ID_CHECK_OPACITY,
	ID_CHECK_RED_OR_HUE,
	ID_NODE_COLOUR_BUTTON,
	ID_SAVE_BUTTON,
	ID_LOAD_BUTTON
};

enum
{
	DRAG_MODE_NONE,
	DRAG_MODE_SINGLE_NODE,
	DRAG_MODE_NODE_GROUP,
};

enum
{
	NODE_TYPE_SINGLE,
	NODE_TYPE_SET,
};

//Gradient panel directions
enum
{
	GRADIENT_HORIZ,
	GRADIENT_VERT
};

//Search distance when looking for vertical lines, in window fraction
const float GROUP_MATCH_TOLERANCE=0.01;
//Radius (in px) that circles should be drawn around nodes
const unsigned int NODE_CIRC_RADIUS=4.0f; 

BEGIN_EVENT_TABLE(wxGradientStopPanel, wxPanel)
	EVT_PAINT(wxGradientStopPanel::onPaint)
END_EVENT_TABLE()

BEGIN_EVENT_TABLE(wxMultiLineGraphPanel, wxPanel)
	EVT_PAINT(wxMultiLineGraphPanel::onPaint)
	EVT_MOTION(wxMultiLineGraphPanel::mouseMove)
	EVT_LEFT_DOWN(wxMultiLineGraphPanel::mouseDown)
	EVT_LEFT_UP(wxMultiLineGraphPanel::mouseReleased)
END_EVENT_TABLE()

wxGradientStopPanel::wxGradientStopPanel(wxWindow *p, wxWindowID wid) : wxPanel(p,wid)
{
	direction=GRADIENT_HORIZ;
}

void wxGradientStopPanel::setStops(std::vector<std::pair<float, ColourRGBAf> > &newStops)
{
	stops.swap(newStops);
}

void wxGradientStopPanel::clearStops()
{
	stops.clear();
}

//This routine has serious speed problems. Drawing is very juddery
void wxGradientStopPanel::onPaint(wxPaintEvent &evt)
{
	wxPaintDC dc(this);

	if(!stops.size())
	{
		dc.Clear();
		return;
	}

	int nx,ny;	
	GetClientSize(&nx,&ny);

	// Dont use wxGraphicsContext, as it uses cairo and reportedly ends up being unusably slow.
	//TODO: Use raw memory access as it is probably way faster than wx.
	// owing to no bounds checking
	wxImage im(nx,ny);

	if(!im.IsOk())
	{
		dc.Clear();
		return;
	}

	//Find out if we need to draw the chequer
	bool drawChequer=stops.empty();
	for(auto & stop : stops)
	{
		if(fabs(stop.second.a() - 1.0f) > std::numeric_limits<float>::epsilon())
		{
			drawChequer=true;
			break;
		}

	}


	//For each X column, fill with same colour
	unsigned int lStop=0;
	unsigned int rStop=1;
	ColourRGBAf lCol,rCol;
	float fracL,fracR;;
	lCol=stops[0].second;
	rCol=stops[1].second;


	//Draw lower checkerboard, if any points have non-unit opacity
	if(drawChequer)
	{
		//This seems... inefficient?
		const unsigned int CHEQUER_SIZE=6;
		for(int ui=0;ui<nx; ui++)
		{
			for(int uj=0;uj<ny;uj++) 
			{
				if(XOR(((ui/CHEQUER_SIZE) &1),( (uj/CHEQUER_SIZE) &1)))
				{
					im.SetRGB(ui,uj,128,128,128);
				}
				else
				{
					im.SetRGB(ui,uj,255,255,255);
				}
			}
		}

	}


	//don't us wxDC gradients as they don't support a passed alpha channel data
	if(direction == GRADIENT_HORIZ)
	{

		for(unsigned int ui=0;ui<(unsigned int)nx; ui++)
		{
			//Absolute fraction
			float frac;
			frac=(float)ui/(float)nx;

			//Update left and right stops and colours
			// as needed
			if( frac > stops[rStop].first)
			{
				rStop++;
				lStop++;

				lCol=rCol;
				rCol=stops[rStop].second;
			}

			fracL=stops[lStop].first;
			fracR=stops[rStop].first;

			//Compute the alpha blend value
			float blendFrac;
			if(fracR != fracL)
				blendFrac = (frac-fracL)/(fracR-fracL);
			else
				blendFrac=0.5;

			//Get colour components
			float r,g,b,a;
			r = blendFrac*rCol.r()+(1-blendFrac)*lCol.r();
			g = blendFrac*rCol.g()+(1-blendFrac)*lCol.g();
			b = blendFrac*rCol.b()+(1-blendFrac)*lCol.b();
			a = blendFrac*rCol.a()+(1-blendFrac)*lCol.a();

			//Pre-scale for wx
			r*=255;
			g*=255;
			b*=255;

			//Working down each row, blend the gradient with the 
			// existing pixel data
			for(unsigned int uj=0;uj<(unsigned int)ny; uj++)
			{
				//Compute the final pixel value
				im.SetRGB(ui,uj,r*a + (1-a)*im.GetRed(ui,uj) ,
						g*a + (1-a)*im.GetGreen(ui,uj),
						b*a + (1-a)*im.GetBlue(ui,uj));
			}

		}
	}
	else
	{
		//vertical
		for(unsigned int ui=0;ui<(unsigned int)ny; ui++)
		{
			//Absolute fraction
			float frac;
			frac=(float)ui/(float)ny;

			//Update left and right stops and colours
			// as needed
			if( frac > stops[rStop].first)
			{
				rStop++;
				lStop++;

				lCol=rCol;
				rCol=stops[rStop].second;
			}

			fracL=stops[lStop].first;
			fracR=stops[rStop].first;

			float blendFrac;
			if(fracR != fracL)
				blendFrac = (frac-fracL)/(fracR-fracL);
			else
				blendFrac=0.5;

			//Get colour components
			float r,g,b,a;
			r = blendFrac*rCol.r()+(1-blendFrac)*lCol.r();
			g = blendFrac*rCol.g()+(1-blendFrac)*lCol.g();
			b = blendFrac*rCol.b()+(1-blendFrac)*lCol.b();
			a = blendFrac*rCol.a()+(1-blendFrac)*lCol.a();

			r*=255;
			g*=255;
			b*=255;

			//Working down each row, blend the gradient with the 
			// existing pixel data
			for(unsigned int uj=0;uj<(unsigned int)nx; uj++)
			{
				
				//Compute the final pixel value
				im.SetRGB(uj,(ny-1)-ui,r*a + (1-a)*im.GetRed(uj,(ny-1)-ui) ,
						g*a + (1-a)*im.GetGreen(uj,(ny-1)-ui),
						b*a + (1-a)*im.GetBlue(uj,(ny-1)-ui));
			}

		}
	}
	//Whew,we've done it! Now blit the final bitmap onto the DC
	wxBitmap bmp(im);
	dc.DrawBitmap(bmp,0,0);
	

}





bool MultiLineGraph::isSelfConsistent() const
{
	if(lineYData.empty())
		return false;

	if(lineColour.size() !=lineYData.size())
		return false;

	//All y data points should be the same size as the x data
	for(const auto & data : lineYData)
	{
		if(data.size() != xData.size())
			return false;
	}

	if(boundMin.first >= boundMax.first ||
		boundMin.second >=boundMax.second)
		return false;

	return true;
}

void MultiLineGraph::toWindowFraction(float graphX, float graphY, 
		float &wx,float &wy) const
{
	//x/y bounding dimensions in graph coordinates
	float xWidth, yWidth;
	xWidth = boundMax.first - boundMin.first;
	yWidth = boundMax.second - boundMin.second;

	
	wx = graphX - boundMin.first/xWidth;
	wy = graphY - boundMin.second/yWidth;
	wy=1.0-wy; //Drawing coords are flipped (y-down)

	ASSERT(isSelfConsistent());
}

void MultiLineGraph::toGraph(float wX, float wY, 
		float &gx,float &gy) const
{
	//x/y bounding dimensions in graph coordinates
	float xWidth, yWidth;
	xWidth = boundMax.first - boundMin.first;
	yWidth = boundMax.second - boundMin.second;

	//Convert from fractional to graph	
	gx=wX*xWidth+boundMin.first; 
	gy=(1.0-wY)*yWidth+boundMin.second;

	ASSERT(isSelfConsistent());
}

void MultiLineGraph::setActiveNode(unsigned int set, unsigned int n)
{
	selectedNode.first=set;
	selectedNode.second=n;
}

void MultiLineGraph::clearActiveNode()
{
	selectedNode.first=selectedNode.second =(unsigned int)-1;
}

void MultiLineGraph::draw(wxDC &dc)
{
	//If no data, or not self consistent, abort
	if(xData.size() < 2 || !isSelfConsistent())
		return;

	//Draw the lines between each point
	int nx,ny;
	wxWindow *w=nullptr;
	w=dc.GetWindow();

	if(!w)
		return;
	w->GetClientSize(&nx,&ny);

	//If we can't see the window don't draw
	if(!nx || !ny)
		return;

	wxPen pen;
	//Draw vertical bars for each set
	for(unsigned int ui=0;ui<xData.size();ui++)
	{
		float fx,fy;
		toWindowFraction(xData[ui],0,fx,fy);

		dc.DrawLine(fx*nx,0,fx*nx,ny);
	}



	//Loop over each trace to draw line segments
	for(unsigned int ui=0;ui<lineYData.size();ui++)
	{
		//Don't draw masked-out lines
		if(!visibilityMask[ui])
			continue;

		//Set line colour
		pen.SetColour(lineColour[ui].r()*255,
				lineColour[ui].g()*255,
				lineColour[ui].b()*255);

		dc.SetPen(pen);

		float fracX,fracY;
		float fracX2, fracY2;
		toWindowFraction(xData[0],lineYData[ui][0],fracX,fracY);

		//Loop over each line segment in the trace
		for(unsigned int uj=1;uj<lineYData[ui].size();uj++)
		{
			toWindowFraction(xData[uj],lineYData[ui][uj],fracX2,fracY2);
			//remember, wx drawing is upside-down, so invert Y
			dc.DrawLine(fracX*nx,fracY*ny,
					fracX2*nx,fracY2*ny);
			fracX=fracX2; fracY=fracY2;
		}

	}

	//Loop over each vertex to draw node control
	pen.SetColour(*wxBLACK);
	dc.SetPen(pen);
	for(unsigned int ui=0;ui<lineYData.size();ui++)
	{
		if(!visibilityMask[ui])
			continue;

		for(unsigned int uj=0;uj<lineYData[ui].size();uj++)
		{
			float fracX,fracY;
			toWindowFraction(xData[uj],lineYData[ui][uj],fracX,fracY);
			int px,py;
			px=fracX*nx; py=fracY*ny;

			const int NODE_HALF_WIDTH=3;
			if(selectedNode.first == ui && selectedNode.second ==uj)
			{
				//Draw filled circle if selected
				dc.SetBrush(*wxBLACK_BRUSH);
				dc.DrawRectangle(px-NODE_HALF_WIDTH,py-NODE_HALF_WIDTH,
							2.0*NODE_HALF_WIDTH,2.0*NODE_HALF_WIDTH);
				dc.SetBrush(*wxTRANSPARENT_BRUSH);
			}
			else
				dc.DrawRectangle(px-NODE_HALF_WIDTH,py-NODE_HALF_WIDTH,
							2.0*NODE_HALF_WIDTH,2.0*NODE_HALF_WIDTH);
		}	
	}


	ASSERT(isSelfConsistent());
}

void MultiLineGraph::setBounds(float minX,float minY, float maxX,float maxY)
{
	boundMin = make_pair(minX,minY);
	boundMax = make_pair(maxX,maxY);

}

void MultiLineGraph::setData(const std::vector<float> &xd, 
		const std::vector<std::vector<float > > &yd, const std::vector<ColourRGBAf> &colour)
{
	//Set X Data
	xData=xd;
	//Set Y Data
	lineYData=yd;

	//Resize visiblity mask, making lines visible
	visibilityMask.resize(lineYData.size());
	std::fill(visibilityMask.begin(),visibilityMask.end(),true);

	//reset selected node
	selectedNode=make_pair((unsigned int)-1,(unsigned int)-1);

	lineColour=colour;

	ASSERT(isSelfConsistent());
}

float MultiLineGraph::getXValue(unsigned int node) const
{
	ASSERT(isSelfConsistent());

	ASSERT(node < xData.size());
	return xData[node];
}

void MultiLineGraph::setNodeByFraction(unsigned int set, unsigned int node, float fx, float fy)
{
	ASSERT(node < xData.size());
	ASSERT(lineYData.size() && node< lineYData[0].size());

	float gx,gy;
	toGraph(fx,fy,gx,gy);

	gy=std::max(boundMin.second,gy);
	gy=std::min(boundMax.second,gy);

	lineYData[set][node]=gy;

	ASSERT(isSelfConsistent());
}

unsigned int MultiLineGraph::projectToNearestLine(float gx, float gy, float  &projY) const
{
	unsigned int greaterOrEqIdx=0;
	//Find the X coordinates that bounds the line
	for(unsigned int ui=0;ui<xData.size();ui++)
	{
		if(xData[ui] >=gx)
		{
			greaterOrEqIdx=ui;
			break;
		}
	}

	//find the two adjacent points
	unsigned int lx,rx;
	if(greaterOrEqIdx== 0)
	{
		lx=0;
		rx=1;
	}
	else
	{
		lx=greaterOrEqIdx-1;
		rx=greaterOrEqIdx;
	}

	//Interpolation fraction
	float frac =(gx - xData[lx])/ (xData[rx] - xData[lx]);

	//Loop through the differing traces
	// then find the intersection at our actual
	// X position.
	unsigned int minIdx=(unsigned int)-1;
	float curMinDelta=std::numeric_limits<float>::max();
	float bestY=0;
	for(unsigned int ui=0;ui<lineYData.size();ui++)
	{
		if(!visibilityMask[ui])
			continue;

		float newY;
		newY= frac*(lineYData[ui][rx] - lineYData[ui][lx]) + 
				lineYData[ui][lx];

		float delta;
		delta = fabs(gy-newY);

		if(curMinDelta  > delta)
		{
			curMinDelta = delta;
			minIdx=ui;
			bestY=newY;
		}
	}

	projY=bestY;	
	return  minIdx;
}

bool MultiLineGraph::spliceNewNodes(float gx)
{
	//Find the splice coordinate
	unsigned int greaterOrEqIdx=0;
	//Find the X coordinates that bounds the line
	for(unsigned int ui=0;ui<xData.size();ui++)
	{
		if(xData[ui] >=gx)
		{
			greaterOrEqIdx=ui;
			break;
		}
	}

	//Can't insert node at position 0
	// as we have to interpolate
	if(greaterOrEqIdx == 0)
		return false;

	//TODO: Refactor into insertion routine

	//Extend vector by 1 to make way for our new point

	//Shuffle X data up a little
	xData.resize(xData.size()+1);
	for(unsigned int ui=xData.size(); ui--!=greaterOrEqIdx;)
		xData[ui]=xData[ui-1];

	//Shuffle Y data up a little
	for(unsigned int uj=0;uj<lineYData.size();uj++)
	{
		//Extend vector by 1
		lineYData[uj].resize(lineYData[uj].size()+1);
		for(unsigned int ui=lineYData[uj].size(); ui--!=greaterOrEqIdx;)
			lineYData[uj][ui]=lineYData[uj][ui-1];
	}

	//OK, so now we have to place  the new node
	xData[greaterOrEqIdx] = gx;
	float frac =(gx - xData[greaterOrEqIdx-1])/ (xData[greaterOrEqIdx+1] - xData[greaterOrEqIdx-1]);

	//Position new node's y coordinate on straight line
	for(unsigned int uj=0;uj<lineYData.size();uj++)
	{
		lineYData[uj][greaterOrEqIdx]= frac*(lineYData[uj][greaterOrEqIdx+1] - 
			lineYData[uj][greaterOrEqIdx-1]) + lineYData[uj][greaterOrEqIdx-1];
	}

	return true;
}

void MultiLineGraph::setVisibilityMask(const vector<bool> &mask)
{
	ASSERT(mask.size() == lineYData.size());
	visibilityMask=mask;
}

void MultiLineGraph::getVisibilityMask(vector<bool> &mask) const
{
	mask=visibilityMask;
}

void MultiLineGraph::removeSelectedNode()
{
	unsigned int node = selectedNode.second;

	if(selectedNode.second == (unsigned int)-1)
		return;

	ASSERT(xData.size());
	//Disallow deletion of edge nodes
	if(!(node && node < xData.size()-1))
		return;

	for(unsigned int ui=node;ui<xData.size()-1;ui++)
	{
		xData[ui] = xData[ui+1];
	}

	xData.resize(xData.size()-1);

	for(unsigned int ui=0;ui<lineYData.size();ui++)
	{
		for(unsigned int uj=node;uj<lineYData[ui].size()-1;uj++)
			lineYData[ui][uj]=lineYData[ui][uj+1];

		lineYData[ui].resize(lineYData[ui].size()-1);		
	}

	//Set the selected node to invalid
	selectedNode = make_pair((unsigned int)-1,(unsigned int)-1);
}

void wxMultiLineGraphPanel::mouseMove(wxMouseEvent &event)
{
	

	int w,h;
	GetClientSize(&w,&h);
	wxPoint mousePos =event.GetPosition();

	lastMouseX=mousePos.x;
	lastMouseY=mousePos.y;

	//Do our calculations in reduced coordinates (0->1);
	float xMouse,yMouse;

	//Add a 1px border around control
	xMouse=(float)(mousePos.x+1)/(float)(w-2);
	yMouse=(float)(mousePos.y+1)/(float)(h-2);

	switch(dragMode)
	{
		case DRAG_MODE_NONE:
		{


			//Perform refresh if required
			if(mouseHover[0] != xMouse|| mouseHover[1] !=yMouse)
			{
				Refresh();
			}
			

			break;
		}
		case DRAG_MODE_SINGLE_NODE:
		case DRAG_MODE_NODE_GROUP:
		{
			//Perform refresh if required
			if(mouseHover[0] !=xMouse)
				Refresh();
			break;
		}
	}

	//Set node hover coordinates
	mouseHover[0]=xMouse;
	mouseHover[1]=yMouse;

}

void wxMultiLineGraphPanel::mouseLeftClick(wxMouseEvent &event)
{

	int w,h;
	GetClientSize(&w,&h);
	wxPoint mousePos =event.GetPosition();

	float xMouse,yMouse;
	xMouse=(float)(mousePos.x+1)/(float)(w-2);
	yMouse=(float)(mousePos.y+1)/(float)(h-2);

	//Check to see if we are close to any lines
	float gX,gY;
	graph.toGraph(xMouse,yMouse,gX,gY);

	//Find the distance tolerance in window coordinates
	// we have to do it in window coords, as a circle in window coords is elliptical in 
	// graph coords, which will be surprising to interaction
	const float MAX_DISTANCE_FRACTION=0.030;

	//Find the nearest line
	unsigned int set;
	float projY;
	set=graph.projectToNearestLine(gX,gY,projY);

	//if no line, abort
	if(set == (unsigned int)-1)
		return;

	float dummyX,winYProj;
	graph.toWindowFraction(0,projY,dummyX,winYProj);
	if( fabs(winYProj- yMouse) < MAX_DISTANCE_FRACTION)
	{
		graph.spliceNewNodes(gX);
		Refresh();
	}
	else
	{
		//Do nothing
	}
	
}

TransferEditorDialog::TransferEditorDialog(wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style):
	wxDialog(parent, id, title, pos, size, style)
{
	// begin wxGlade: TransferEditorDialog::TransferEditorDialog
	leftColourBar = new wxGradientStopPanel(this, wxID_ANY);
	leftColourBar->setDirection(GRADIENT_VERT);
	graphPanel = new wxMultiLineGraphPanel(this, wxID_ANY);
	textLabelMax = new wxStaticText(this, wxID_ANY, _("Max"), wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT);
	labelMin = new wxStaticText(this, wxID_ANY, _("Min"));
	bottomColourBar = new wxGradientStopPanel(this, wxID_ANY);
	labelTraces = new wxStaticText(this, wxID_ANY, _("Traces"));
	checkRed_or_Hue = new wxCheckBox(this, ID_CHECK_RED_OR_HUE, _("Red"));
	checkGreen_or_Sat = new wxCheckBox(this, ID_CHECK_GREEN_OR_SAT, _("Green"));
	checkBlue_or_value = new wxCheckBox(this, ID_CHECK_BLUE_OR_VALUE, _("Blue"));
	checkOpacity = new wxCheckBox(this, ID_CHECK_OPACITY, _("Opacity"));
	colourPickButton = new wxButton(this, ID_NODE_COLOUR_BUTTON, _("Colour"));
	nodeRemoveButton = new wxButton(this, wxID_REMOVE, wxEmptyString);
	static_line_1 = new wxStaticLine(this, wxID_ANY);
	lblFunction = new wxStaticText(this, wxID_ANY, _("Transfer Function"));
	buttonLoad = new wxButton(this, wxID_OPEN, wxEmptyString);
	buttonSave = new wxButton(this, wxID_SAVE, wxEmptyString);
	buttonOK = new wxButton(this, wxID_OK, wxEmptyString);
	buttonCancel = new wxButton(this, wxID_CANCEL, wxEmptyString);


	graphPanel->setHorizGradientPanel(bottomColourBar);	
	graphPanel->setVertGradientPanel(leftColourBar);
	graphPanel->setColourPickButton(colourPickButton);

	colourPickButton->Enable(false);

	set_properties();
	do_layout();
	// end wxGlade

	initDefault();

	completeOK=false;
}

void TransferEditorDialog::initDefault()
{
	
	vector<float> xData;
	vector<vector<float> > yData;
	vector<ColourRGBAf> colData;

	xData.push_back(0);
	xData.push_back(1);
	yData.resize(4);
	for(unsigned int ui=0;ui<yData.size(); ui++)
	{
		yData[ui].push_back(0);
		yData[ui].push_back(1);
	}

	colData.resize(yData.size());
	colData[0] = ColourRGBAf(1,0,0,0);
	colData[1] = ColourRGBAf(0,1,0,0);
	colData[2] = ColourRGBAf(0,0,1,0);
	colData[3] = ColourRGBAf(0,0,0,1);

	graphPanel->graph.setBounds(0,0,1,1);
	graphPanel->graph.setData(xData,yData,colData);

	graphPanel->update();
}

void TransferEditorDialog::set_properties()
{
	// begin wxGlade: TransferEditorDialog::set_properties
	SetTitle(_("Transfer Function Editor"));
	leftColourBar->SetMinSize(wxSize(20, 1));
	leftColourBar->SetBackgroundColour(wxColour(255, 255, 255));
	leftColourBar->SetToolTip(_("Colour chosen by moving selected node up-down"));
	graphPanel->SetBackgroundColour(wxColour(255, 255, 255));
	graphPanel->SetToolTip(_("Drag nodes to move, click to add nodes"));
	bottomColourBar->SetBackgroundColour(wxColour(255, 255, 255));
	bottomColourBar->SetToolTip(_("Final colour scale"));
	checkRed_or_Hue->SetValue(1);
	checkGreen_or_Sat->SetValue(1);
	checkBlue_or_value->SetValue(1);
	checkOpacity->SetValue(1);
	colourPickButton->SetToolTip(_("Adjust node group's colour"));
	nodeRemoveButton->SetToolTip(_("Remove the selected node"));
	// end wxGlade
}


void TransferEditorDialog::do_layout()
{
	// begin wxGlade: TransferFunctionDialog::do_layout
	wxBoxSizer* topLevelSizer = new wxBoxSizer(wxHORIZONTAL);
	wxBoxSizer* rightSizer = new wxBoxSizer(wxVERTICAL);
	wxBoxSizer* exitButtonSizer = new wxBoxSizer(wxHORIZONTAL);
	wxStaticBoxSizer* nodeControlSizer = new wxStaticBoxSizer(new wxStaticBox(this, wxID_ANY, _("Node Properties")), wxVERTICAL);
	wxBoxSizer* sizer_2 = new wxBoxSizer(wxHORIZONTAL);
	wxBoxSizer* sizer_1 = new wxBoxSizer(wxHORIZONTAL);
	wxBoxSizer* checkLabelSizer = new wxBoxSizer(wxHORIZONTAL);
	wxBoxSizer* checkSizer = new wxBoxSizer(wxVERTICAL);
	wxBoxSizer* leftSizer = new wxBoxSizer(wxVERTICAL);
	wxBoxSizer* bottomBarSizer = new wxBoxSizer(wxHORIZONTAL);
	wxBoxSizer* spacerBarSizer = new wxBoxSizer(wxHORIZONTAL);
	wxBoxSizer* graphHorizSizer = new wxBoxSizer(wxHORIZONTAL);
	graphHorizSizer->Add(leftColourBar, 0, wxALIGN_CENTER|wxALL|wxEXPAND, 5);
	graphHorizSizer->Add(graphPanel, 1, wxALIGN_CENTER|wxALL|wxEXPAND, 5);
	leftSizer->Add(graphHorizSizer, 15, wxEXPAND, 0);
	spacerBarSizer->Add(20, 20, 1, wxEXPAND|wxLEFT|wxRIGHT, 0);
	spacerBarSizer->Add(textLabelMax, 0, wxALIGN_BOTTOM|wxALIGN_RIGHT, 0);
	leftSizer->Add(spacerBarSizer, 0, wxEXPAND, 0);
	bottomBarSizer->Add(labelMin, 0, 0, 0);
	bottomBarSizer->Add(bottomColourBar, 1, wxALIGN_CENTER|wxALL|wxEXPAND, 1);
	leftSizer->Add(bottomBarSizer, 0, wxEXPAND, 0);
	topLevelSizer->Add(leftSizer, 10, wxEXPAND, 0);
	nodeControlSizer->Add(20, 20, 1, 0, 0);
	checkLabelSizer->Add(labelTraces, 1, wxALIGN_CENTER|wxALL, 5);
	checkSizer->Add(checkRed_or_Hue, 0, 0, 0);
	checkSizer->Add(checkGreen_or_Sat, 0, 0, 0);
	checkSizer->Add(checkBlue_or_value, 0, 0, 0);
	checkSizer->Add(checkOpacity, 0, 0, 0);
	checkLabelSizer->Add(checkSizer, 1, wxLEFT|wxRIGHT, 5);
	checkLabelSizer->Add(20, 20, 1, 0, 0);
	nodeControlSizer->Add(checkLabelSizer, 1, 0, 0);
	nodeControlSizer->Add(20, 20, 1, 0, 0);
	sizer_1->Add(colourPickButton, 0, wxALIGN_CENTER|wxALL, 5);
	sizer_1->Add(nodeRemoveButton, 0, wxALIGN_CENTER|wxALIGN_RIGHT|wxALL, 5);
	nodeControlSizer->Add(sizer_1, 1, 0, 0);
	nodeControlSizer->Add(static_line_1, 0, wxALL|wxEXPAND, 2);
	nodeControlSizer->Add(lblFunction, 0, 0, 0);
	sizer_2->Add(buttonLoad, 0, wxALIGN_CENTER|wxALL, 5);
	sizer_2->Add(buttonSave, 0, wxALIGN_CENTER|wxALL, 5);
	nodeControlSizer->Add(sizer_2, 1, 0, 0);
	rightSizer->Add(nodeControlSizer, 15, wxEXPAND, 0);
	exitButtonSizer->Add(buttonOK, 0, wxALL, 5);
	exitButtonSizer->Add(buttonCancel, 0, wxALL, 4);
	rightSizer->Add(exitButtonSizer, 1, wxALIGN_BOTTOM|wxALL, 5);
	topLevelSizer->Add(rightSizer, 0, wxEXPAND, 0);
	SetSizer(topLevelSizer);
	topLevelSizer->Fit(this);

	SetMinSize(wxSize(600,200));
	Layout();
	// end wxGlade
}

wxMultiLineGraphPanel::wxMultiLineGraphPanel(wxWindow* parent, wxWindowID id) 
	:wxPanel(parent,id)
{
	dragMode=DRAG_MODE_NONE;
	curSet=curNode=-1;
	graphTieX[0]=graphTieY[0] = graphTieX[1]=graphTieY[1]=-1;
		mouseHover[0]=mouseHover[1]=-1;
	horizGradientPanel=0;
	vertGradientPanel=0;
		
}

void wxMultiLineGraphPanel::mouseDown(wxMouseEvent &event)
{
	int w,h;
	w=0;h=0;

	GetClientSize(&w,&h);

	if(!w || !h)
		return;

	if(!graph.numXPts())
		return;

	//Do our calculations in reduced coordinates (0->1);
	float xMouse,yMouse;
	wxPoint mousePos =event.GetPosition();

	//Add a 1px border around control
	xMouse=(float)(mousePos.x+1)/(float)(w-2);
	yMouse=(float)(mousePos.y+1)/(float)(h-2);
	

	switch(dragMode)
	{
		case DRAG_MODE_NONE:
		{
			unsigned int index,lineIndex;
			index=graph.getNodeIndex(xMouse,yMouse,lineIndex);

			if(index != (unsigned int) -1)
			{
				//Change the selection mode
				dragMode=DRAG_MODE_SINGLE_NODE;
				curNode=index;
				curSet=lineIndex;

				graph.setActiveNode(curSet,curNode);


				//Set tie points, so we can draw lines during
				// user drag
				//--
				graph.getAdjacentNodeCoords(curSet,curNode,
							graphTieX,graphTieY);

				for(unsigned int ui=0;ui<2;ui++)
				{
					if(graphTieX[ui] >=0 && graphTieY[ui] >=0)
					{
						float fx,fy;
						graph.toWindowFraction(graphTieX[ui],graphTieY[ui],
								fx,fy);
						graphTieX[ui]=fx*w;
						graphTieY[ui]=fy*h;
					}
				}
				update();
			}
			else
			{
				//Check for grabbing a vertical line

				//Try to see if we have a valid set index
				index=graph.getGroupIndex(xMouse,GROUP_MATCH_TOLERANCE);
				if(index !=(unsigned int)-1)
				{
					dragMode=DRAG_MODE_NODE_GROUP;
					curNode=index;

				}
			}


			break;
		}
		case DRAG_MODE_SINGLE_NODE:
			 //Do nothing
			break;
		default:
//			ASSERT(false);
			;	
	}


	Refresh();
}

unsigned int MultiLineGraph::getNodeIndex(float fracX, float fracY, unsigned int &lineIndex) const
{
	lineIndex=(unsigned int)-1;

	if(!xData.size())
		return (unsigned int)-1;

	//Node search radius in Window Fraction
	const float SEARCH_RADIUS_SQR=0.05f*0.05f;

	
	unsigned int minIndex[2]={(unsigned int)-1,(unsigned int) -1};
	float minSqrRadius=SEARCH_RADIUS_SQR;
	//First perform a 2D search for nodes
	//-----
	for(unsigned int ui=0;ui<lineYData.size();ui++)
	{
		//Skip hidden lines
		if(!visibilityMask[ui])
			continue;

		for(unsigned int uj=0;uj<lineYData[ui].size(); uj++)
		{
			float fX,fY;
			toWindowFraction(xData[uj],lineYData[ui][uj],fX,fY);

			float sqrDist;
			sqrDist=(fX-fracX)*(fX-fracX) + (fY-fracY)*(fY-fracY); 
			if(sqrDist< minSqrRadius)
			{
				minIndex[0] = ui;
				minIndex[1] = uj;
				minSqrRadius=sqrDist;
			}
		}
	}


	if(minIndex[0] !=(unsigned int)-1)
	{
		ASSERT(minIndex[1] !=(unsigned int)-1);
		lineIndex=minIndex[0];
		return minIndex[1];
	}
	//-----
	
	//So we didn't find any nodes eh? 
	// Look for lines, using a 1D search
	//------	
	minSqrRadius=SEARCH_RADIUS_SQR;	
	for(unsigned int ui=0;ui<xData.size();ui++)
	{
		float fX,fY;
		toWindowFraction(xData[ui],0,fX,fY);

		float sqrDist;
		sqrDist=(fX-fracX)*(fX-fracX);
		if(sqrDist< minSqrRadius)
		{
			minIndex[0] = ui;
			minSqrRadius=sqrDist;
		}
	}
	

	if(minIndex[0]	!=(unsigned int)-1)
		lineIndex=minIndex[0];

	return (unsigned int )-1;	
}
	

unsigned int MultiLineGraph::getGroupIndex(float fracX, float fracTol) const
{
	float xP,xTol,dummyY;
	toGraph(fracX,0,xP,dummyY);

	xTol=fracTol*(boundMax.first -boundMin.first);


	for(unsigned int ui=0;ui<xData.size(); ui++)
	{
		if(fabs(xData[ui]-xP) < xTol)
			return ui;
		
	}
	return (unsigned int) -1;
}


void MultiLineGraph::getAdjacentNodeCoords(unsigned int curSet, unsigned int curNode,
				float *fx, float *fy) const
{
	ASSERT(curSet < lineYData.size());
	ASSERT(curNode < xData.size());
	ASSERT(visibilityMask[curSet]);

	//Check if at lower bound
	if(curNode ==0)
	{
		ASSERT(xData.size() >=2);
		fx[0]=fy[0]=-1;
		getNodeCoords(curSet,curNode+1,fx[1],fy[1]);
		return;
	}

	//Get left node
	getNodeCoords(curSet,curNode-1,fx[0],fy[0]);

	//Check if we hit upper bound
	if(curNode == xData.size() -1)
	{
		fx[1]=fy[1]=-1;
		return;
	}

	//get Right node
	getNodeCoords(curSet,curNode+1,fx[1],fy[1]);

}


void MultiLineGraph::getNodeCoords(unsigned int curSet, unsigned int curNode,
		float &fracX, float &fracY) const
{
	fracX= xData[curNode];
	fracY=lineYData[curSet][curNode];
}


void MultiLineGraph::moveXData(unsigned int node, float nx)
{
	vector<pair<float, unsigned int> > pVec;

	pVec.resize(xData.size());
	for(unsigned int ui=0;ui<pVec.size();ui++)
	{
		pVec[ui] =make_pair(xData[ui],ui);
	}
	pVec[node].first=nx;

	ComparePairFirst cmp;
	std::sort(pVec.begin(),pVec.end(),cmp);


	//Sort the x data
	for(unsigned int ui=0;ui<xData.size();ui++)
		xData[ui]=pVec[ui].first;

	//Now, *remap* the y data using the keys from the x data
	
	for(unsigned int ui=0;ui<lineYData.size();ui++)
	{
		vector<float> f;
		f.resize(lineYData[ui].size());
		//Remap this  line set
		for(unsigned int uj=0;uj<lineYData[ui].size();uj++)
			f[uj] = lineYData[ui][pVec[uj].second];

		std::swap(lineYData[ui],f);
	}


}

void wxMultiLineGraphPanel::mouseReleased(wxMouseEvent &event)
{
	if(dragMode == DRAG_MODE_NONE)
	{
		//Process as mouse left click
		mouseLeftClick(event);
		return;
	}

	int w,h;
	w=0;h=0;
	GetClientSize(&w,&h);

	if(!w || !h)
		return;

	//Do our calculations in reduced coordinates (0->1);
	float xMouse,yMouse;
	wxPoint mousePos =event.GetPosition();

	//Add a 1px border around control
	xMouse=(float)(mousePos.x+1)/(float)(w-2);
	yMouse=(float)(mousePos.y+1)/(float)(h-2);
	

	switch(dragMode)
	{
		case DRAG_MODE_SINGLE_NODE:
		{
			ASSERT(curNode != (unsigned int)-1);
			ASSERT(curSet!=(unsigned int) -1);

			graph.setNodeByFraction(curSet,curNode,xMouse,yMouse);
			curNode=curSet=(unsigned int)-1;
			graphTieX[0]=graphTieY[0]=graphTieX[1]=graphTieY[1]=-1;

			//Set hover coords out of frame, as otherwise we sometimes
			// get a residual circle at the old position
			mouseHover[0]=-1;
			mouseHover[1]=-1;

			update();
	
			break;
		}
		case DRAG_MODE_NODE_GROUP:
		{
			//Don't allow dragging the edge values
			if(curNode > 0 && curNode !=graph.numXPts()-1)
			{
				float gx,gy;
				graph.toGraph(xMouse,0,gx,gy);

				graph.moveXData(curNode,gx);
			}
			curNode=(unsigned int)-1;
				
			update();
			break;
		}
		default:
			ASSERT(false);
	}
	dragMode=DRAG_MODE_NONE;

	Refresh();
}

void wxMultiLineGraphPanel::onPaint(wxPaintEvent &event)
{
	wxPaintDC dc(this);


	//Draw the graph
	graph.draw(dc);

	int nx,ny;
	GetClientSize(&nx,&ny);


	bool haveNode=false;
	//Draw a circle around the current node if needed
	if(curSet != (unsigned int)-1 && curNode != (unsigned int)-1)
	{
		haveNode=true;

		//Graph coords
		float px,py;
		graph.getNodeCoords(curSet,curNode,px,py);

		//Convert to window fraction
		float wfX,wfY;
		graph.toWindowFraction(px,py,wfX,wfY);

		//Convert to window position
		int wx;
		wx =wfX*nx;

		//If we are dragging draw the "tie-line" that connects
		// adjacent nodes to the newly dragged position
		if((dragMode == DRAG_MODE_SINGLE_NODE) && 
				(lastMouseY != (unsigned int) -1))
		{
			for(unsigned int ui=0;ui<2;ui++)
			{
				if(graphTieX[ui]>=0 && graphTieY[ui] >=0)
				{
#ifdef __WXGTK__
					//FIXME: dashes don't work under wxGTK! 
					// You have to use size 2 lines or higher, 
					//see debian bug #869832 
				 	dc.SetPen( wxPen( *wxBLACK, 2, wxPENSTYLE_DOT_DASH) );
#else
				 	dc.SetPen( wxPen( *wxBLACK, 2, wxPENSTYLE_DOT_DASH) );
#endif
					dc.DrawLine(wx,lastMouseY,graphTieX[ui],graphTieY[ui]);
				}
			}
			
			dc.SetPen(*wxBLACK_PEN);

			//Draw a circle at the current position
			dc.DrawCircle(wx,lastMouseY,NODE_CIRC_RADIUS);
				
		}
	}

	unsigned int lineIndex,hoverNode;
	hoverNode=graph.getNodeIndex(mouseHover[0],mouseHover[1],lineIndex);
	if(hoverNode != (unsigned int) -1)
	{
		//Find the node position on which to draw the circle
		float nodeX,nodeY;
		graph.getNodeCoords(lineIndex,hoverNode, nodeX,nodeY);
		float fX,fY;
		graph.toWindowFraction(nodeX,nodeY,fX,fY);

		dc.DrawCircle(fX*nx,fY*ny,NODE_CIRC_RADIUS);
	}


	//Draw lines for the set grouping if we did not reach a node
	// and do have a set
	if(!haveNode && mouseHover[0] >=0)
	{

		unsigned int groupIdx = graph.getGroupIndex(mouseHover[0],GROUP_MATCH_TOLERANCE);
		if(groupIdx != (unsigned int) -1)
		{
				
			float gx=graph.getXValue(groupIdx);
			float fx,fy;
			graph.toWindowFraction(gx,0,fx,fy);

			const unsigned int LINE_LEN=10;
			const unsigned int SEPARATION=5;

			//Draw right facing arrow
			dc.DrawLine(fx*nx-LINE_LEN-SEPARATION,ny/2-LINE_LEN,fx*nx-SEPARATION, ny/2);
			dc.DrawLine(fx*nx-LINE_LEN-SEPARATION,ny/2+LINE_LEN,fx*nx-SEPARATION, ny/2);
			//Draw left facing arrow
			dc.DrawLine(fx*nx+LINE_LEN+SEPARATION,ny/2-LINE_LEN,fx*nx+SEPARATION, ny/2);
			dc.DrawLine(fx*nx+LINE_LEN+SEPARATION,ny/2+LINE_LEN,fx*nx+SEPARATION, ny/2);
		}
	
	}


	//Draw vertical line if dragging node group
	if(dragMode == DRAG_MODE_NODE_GROUP)
	{
		dc.SetPen( wxPen( *wxBLACK, 2, wxPENSTYLE_DOT_DASH) );
		dc.DrawLine(mouseHover[0]*nx,0,mouseHover[0]*nx,ny);
		dc.SetPen(*wxBLACK_PEN);
	}


}

void wxMultiLineGraphPanel::onResize(wxSizeEvent &event)
{
}


void wxMultiLineGraphPanel::removeCurrentNode()
{
	graph.removeSelectedNode();
	Refresh();
}

void wxMultiLineGraphPanel::update()
{

	vector<pair<float,ColourRGBAf > > stops;

	stops.resize(graph.numXPts());
	for(unsigned int ui=0;ui<stops.size();ui++)
	{
		stops[ui].first  = graph.getXValue(ui);
		vector<float> yVals;
		yVals= graph.sliceYData(ui);

		ColourRGBAf col;
		col.r(yVals[0]);
		col.g(yVals[1]);
		col.b(yVals[2]);
		col.a(yVals[3]);

		stops[ui].second = col;

	}

	ASSERT(horizGradientPanel);
	horizGradientPanel->setStops(stops);

	pair<unsigned int,unsigned int> nodePairing;
	nodePairing= graph.getSelectedNode();
	unsigned int selNode=nodePairing.second;
	unsigned int selSet=nodePairing.first;
	ASSERT(vertGradientPanel);
	if(selNode !=(unsigned int)-1)
	{
		//Fixed number of stops
		const unsigned int NUM_STOPS=2;
		vector<pair<float,ColourRGBAf > > vStops;
		vStops.resize(NUM_STOPS);

		vector<float> yVals;
		yVals = graph.sliceYData(selNode);

		ColourRGBAf col;
		col.r(yVals[0]);
		col.g(yVals[1]);
		col.b(yVals[2]);
		col.a(yVals[3]);

		for(unsigned int ui=0;ui<NUM_STOPS;ui++)
		{
			float frac;
			frac=(float)ui/(float)(NUM_STOPS-1);
			vStops[ui].first = frac;

			switch(selSet)
			{
				case 0:
					col.r(frac);
					break;
				case 1:
					col.g(frac);
					break;
				case 2:
					col.b(frac);
					break;
				case 3:
					col.a(frac);
					break;
				default:
					ASSERT(false);
			}

			vStops[ui].second=col;
		}


		vertGradientPanel->setStops(vStops);
	}
	else
	{
		vertGradientPanel->clearStops();
	}

	if(colourPickButton)
		colourPickButton->Enable(selNode !=(unsigned int)-1);

	horizGradientPanel->Refresh();
	vertGradientPanel->Refresh();
}

void wxMultiLineGraphPanel::setHorizGradientPanel(wxGradientStopPanel *gPanel)
{
	ASSERT(gPanel);
	horizGradientPanel=gPanel;
}

void wxMultiLineGraphPanel::setVertGradientPanel(wxGradientStopPanel *gPanel)
{
	ASSERT(gPanel);
	vertGradientPanel=gPanel;
}

void wxMultiLineGraphPanel::setColourPickButton(wxButton *btn)
{
	colourPickButton=btn;
}

vector<float> MultiLineGraph::sliceYData(unsigned int node) const
{
	vector<float> v;
	v.resize(lineYData.size());

	for(unsigned int ui=0;ui<lineYData.size();ui++)
	{
		ASSERT(lineYData[ui].size() > node);
		v[ui] = lineYData[ui][node];
	}

	return v;
}

void MultiLineGraph::setYSlice(unsigned int node, float *y) 
{
	for(unsigned int ui=0;ui<lineYData.size();ui++)
	{
		ASSERT(lineYData[ui].size() > node);
		lineYData[ui][node] = y[ui];
	}
}

void MultiLineGraph::getXData(std::vector<float> &f) const
{
	f = xData;
}
void MultiLineGraph::getYData(std::vector<std::vector<float> > &f) const
{
	f=lineYData;
}
BEGIN_EVENT_TABLE(TransferEditorDialog, wxDialog)
	// begin wxGlade: TransferEditorDialog::event_table
	EVT_CHECKBOX(ID_CHECK_RED_OR_HUE, TransferEditorDialog::OnCheckRed)
	EVT_CHECKBOX(ID_CHECK_GREEN_OR_SAT, TransferEditorDialog::OnCheckGreen)
	EVT_CHECKBOX(ID_CHECK_BLUE_OR_VALUE, TransferEditorDialog::OnCheckBlue)
	EVT_CHECKBOX(ID_CHECK_OPACITY, TransferEditorDialog::OnCheckOpacity)
	EVT_BUTTON(ID_NODE_COLOUR_BUTTON, TransferEditorDialog::OnNodeColourButton)
	EVT_BUTTON(wxID_REMOVE, TransferEditorDialog::OnNodeRemoveButton)
	EVT_BUTTON(wxID_OPEN, TransferEditorDialog::OnButtonLoad)
	EVT_BUTTON(wxID_SAVE, TransferEditorDialog::OnButtonSave)
	EVT_BUTTON(wxID_OK, TransferEditorDialog::OnOK)
	EVT_BUTTON(wxID_CANCEL, TransferEditorDialog::OnCancel)
	// end wxGlade
END_EVENT_TABLE();


std::vector<std::pair<float,ColourRGBAf> > TransferEditorDialog::getTransferFunction() const
{
	std::vector<std::pair<float,ColourRGBAf> > v;

	vector<float> fx;
	graphPanel->graph.getXData(fx);
	vector<vector<float> > fy;
	graphPanel->graph.getYData(fy);

	ASSERT(fx.size());
	ASSERT(fy.size());
	ASSERT(fy[0].size() == fx.size());


	//RGBA is 4 points
	ASSERT(fy.size() == 4);
	for(unsigned int ui=0;ui<fx.size();ui++)
	{
		ColourRGBAf col;
		col.r(fy[0][ui]);
		col.g(fy[1][ui]);
		col.b(fy[2][ui]);
		col.a(fy[3][ui]);
		v.emplace_back(fx[ui],col);
	}

	return v;
}


void TransferEditorDialog::OnButtonSave(wxCommandEvent &event)
{
	wxFileDialog wxF(this,("Transfer File..."), wxT(""),
		wxT(""),wxT("Transfer Function (*.tfn)|*.tfn"),wxFD_SAVE);

	if(wxF.ShowModal() == wxID_CANCEL)
		return;

	vector<pair<float,ColourRGBAf> > tFunc;
	tFunc =  getTransferFunction();
	
	ofstream f(wxF.GetFilename());

	if(!f)
	{
		wxMessageDialog wxM(this,"Failed - Unable to open file for save","Failed",wxOK|wxICON_ERROR);
		wxM.ShowModal();
		return;
	}

	f << transferFunctionString(tFunc);

}

void TransferEditorDialog::OnButtonLoad(wxCommandEvent &event)
{
	wxFileDialog wxF(this,("Transfer File..."), wxT(""),

		wxT(""),wxT("Transfer Function (*.tfn)|*.tfn"),wxFD_SAVE);

	if(wxF.ShowModal() == wxID_CANCEL)
		return;


	string s;

	ifstream f(wxF.GetFilename());

	if(!f)
	{
		wxMessageDialog wxM(this,"Failed - Unable to open file","Failed",wxOK|wxICON_ERROR);
		wxM.ShowModal();
		return;
	}


	getline(f,s);
	

	vector<pair<float,ColourRGBAf>  > cols;
	if(!fromTransferFunctionString(s,cols))
	{
		wxMessageDialog wxM(this,"Failed - Unable to interpret file. Is this really a transfer function?","Failed",wxOK|wxICON_ERROR);
		wxM.ShowModal();
		return;
	}

	graphPanel->setTransferFunction(cols);
}

void wxMultiLineGraphPanel::setTransferFunction(const std::vector<std::pair<float,ColourRGBAf> > &cols)
{
	//Re-layout the data to pass to graph
	vector<float> xData;
	vector<vector<float> > yData;
	vector<ColourRGBAf> colData;

	xData.resize(cols.size());
	yData.resize(cols.size());

	yData.resize(4);
	for(unsigned int ui=0;ui<xData.size();ui++)
	{
		yData[0].push_back(cols[ui].second.r());
		yData[1].push_back(cols[ui].second.g());
		yData[2].push_back(cols[ui].second.b());
		yData[3].push_back(cols[ui].second.a());

		xData[ui] = cols[ui].first;
	}



	colData.resize(yData.size());
	colData[0] = ColourRGBAf(1,0,0,0);
	colData[1] = ColourRGBAf(0,1,0,0);
	colData[2] = ColourRGBAf(0,0,1,0);
	colData[3] = ColourRGBAf(0,0,0,1);

	graph.setData(xData,yData,colData);

	update();
}

void TransferEditorDialog::OnCheckRed(wxCommandEvent &event)
{
	vector<bool> mask;
	graphPanel->graph.getVisibilityMask(mask);
	ASSERT(mask.size() ==4);

	mask[0]=event.IsChecked();
	graphPanel->graph.setVisibilityMask(mask);
	graphPanel->Refresh();
}

void TransferEditorDialog::OnCheckGreen(wxCommandEvent &event)
{
	vector<bool> mask;
	graphPanel->graph.getVisibilityMask(mask);
	ASSERT(mask.size() ==4);

	mask[1]=event.IsChecked();
	graphPanel->graph.setVisibilityMask(mask);
	graphPanel->Refresh();
}

void TransferEditorDialog::OnCheckBlue(wxCommandEvent &event)
{
	vector<bool> mask;
	graphPanel->graph.getVisibilityMask(mask);

	ASSERT(mask.size() ==4);

	mask[2]=event.IsChecked();
	graphPanel->graph.setVisibilityMask(mask);
	graphPanel->Refresh();
}

void TransferEditorDialog::OnCheckOpacity(wxCommandEvent &event)
{
	vector<bool> mask;
	graphPanel->graph.getVisibilityMask(mask);

	ASSERT(mask.size() ==4);

	mask[3]=event.IsChecked();
	graphPanel->graph.setVisibilityMask(mask);
	graphPanel->Refresh();
}

void TransferEditorDialog::OnNodeRemoveButton(wxCommandEvent &event)
{
	graphPanel->removeCurrentNode();
	graphPanel->update();
}

void TransferEditorDialog::OnNodeColourButton(wxCommandEvent &event)
{


	
	pair<unsigned int, unsigned int> selPair;
	selPair = graphPanel->graph.getSelectedNode();
	if(selPair.second ==(unsigned int)-1)
		return;


	vector<float> yVals;
	yVals = graphPanel->graph.sliceYData(selPair.second);
	

	//Do a little dance for wx to set up the dialog
	//--
	wxColour col(	yVals[0]*255,yVals[1]*255,
			yVals[2]*255,yVals[3]*255);
	wxColourData cData;
	cData.SetColour(col);

	wxColourDialog cd(this,&cData);
	if(cd.ShowModal() != wxID_OK)
		return;
	//--

	wxColour cRes;
	cData= cd.GetColourData();
	cRes=cData.GetColour();

	//Set the colour
	float yNew[4];
	yNew[0] = cRes.Red()/255.0f;
	yNew[1] = cRes.Green()/255.0f;
	yNew[2] = cRes.Blue()/255.0f;
	yNew[3] = yVals[3];

	graphPanel->graph.setYSlice(selPair.second,yNew);

	graphPanel->update();
}

void TransferEditorDialog::OnOK(wxCommandEvent &event)
{
	completeOK=true;
	EndModal(wxID_OK);
}

void TransferEditorDialog::OnCancel(wxCommandEvent &event)
{
	completeOK=false;
	EndModal(wxID_CANCEL);
}


// wxGlade: add TransferEditorDialog event handlers

// -*- C++ -*-
