#include "colourpicker.h"
#include "textparser.h"
#include "numberinput.h"


using namespace VRUT;


#define TO_COLOUR_COMPONENT(COL) (unsigned char)(255.0*COL + 0.5)


ColourDialog::ColourDialog(wxWindow *parent, wxWindowID id, const wxString &title, double red, double green, double blue, double a, DialogMode dialogMode) : wxDialog(parent, id, title), r(red), g(green), b(blue), alpha(a), r0(red), g0(green), b0(blue), alpha0(a), mode(dialogMode)
{
	struct ColourSystem
		/*               Name                       xRed    yRed    xGreen  yGreen  xBlue  yBlue    White point                   Gamma   */
		NTSCsystem  =  { wxT("NTSC"),               0.67,   0.33,   0.21,   0.71,   0.14,   0.08,   CIE_STANDARD_ILLUMINANT_C,    GAMMA_REC709 },
		EBUsystem   =  { wxT("EBU (PAL/SECAM)"),    0.64,   0.33,   0.29,   0.60,   0.15,   0.06,   CIE_STANDARD_ILLUMINANT_D65,  GAMMA_REC709 },
		SMPTEsystem =  { wxT("SMPTE"),              0.630,  0.340,  0.310,  0.595,  0.155,  0.070,  CIE_STANDARD_ILLUMINANT_D65,  GAMMA_REC709 },
		HDTVsystem  =  { wxT("HDTV"),               0.670,  0.330,  0.210,  0.710,  0.150,  0.060,  CIE_STANDARD_ILLUMINANT_D65,  GAMMA_REC709 },
		CIEsystem   =  { wxT("CIE"),                0.7355, 0.2645, 0.2658, 0.7243, 0.1669, 0.0085, CIE_STANDARD_ILLUMINANT_E,    GAMMA_REC709 },
		Rec709system = { wxT("CIE REC 709"),        0.64,   0.33,   0.30,   0.60,   0.15,   0.06,   CIE_STANDARD_ILLUMINANT_D65,  GAMMA_REC709 };

	const double rttTmp[31] = {       /* reciprocal temperature (K) */
		 DBL_MIN,  10.0e-6,  20.0e-6,  30.0e-6,  40.0e-6,  50.0e-6,
		 60.0e-6,  70.0e-6,  80.0e-6,  90.0e-6, 100.0e-6, 125.0e-6,
		150.0e-6, 175.0e-6, 200.0e-6, 225.0e-6, 250.0e-6, 275.0e-6,
		300.0e-6, 325.0e-6, 350.0e-6, 375.0e-6, 400.0e-6, 425.0e-6,
		450.0e-6, 475.0e-6, 500.0e-6, 525.0e-6, 550.0e-6, 575.0e-6,
		600.0e-6
	};			
	memcpy(rtt, rttTmp, sizeof(float) * 31);

	const UVT uvtTmp[31] = {
		{0.18006, 0.26352, -0.24341},
		{0.18066, 0.26589, -0.25479},
		{0.18133, 0.26846, -0.26876},
		{0.18208, 0.27119, -0.28539},
		{0.18293, 0.27407, -0.30470},
		{0.18388, 0.27709, -0.32675},
		{0.18494, 0.28021, -0.35156},
		{0.18611, 0.28342, -0.37915},
		{0.18740, 0.28668, -0.40955},
		{0.18880, 0.28997, -0.44278},
		{0.19032, 0.29326, -0.47888},
		{0.19462, 0.30141, -0.58204},
		{0.19962, 0.30921, -0.70471},
		{0.20525, 0.31647, -0.84901},
		{0.21142, 0.32312, -1.0182},
		{0.21807, 0.32909, -1.2168},
		{0.22511, 0.33439, -1.4512},
		{0.23247, 0.33904, -1.7298},
		{0.24010, 0.34308, -2.0637},
		{0.24792, 0.34655, -2.4681},	/* Note: 0.24792 is a corrected value for the error found in W&S as 0.24702 */
		{0.25591, 0.34951, -2.9641},
		{0.26400, 0.35200, -3.5814},
		{0.27218, 0.35407, -4.3633},
		{0.28039, 0.35577, -5.3762},
		{0.28863, 0.35714, -6.7262},
		{0.29685, 0.35823, -8.5955},
		{0.30505, 0.35907, -11.324},
		{0.31320, 0.35968, -15.628},
		{0.32129, 0.36011, -23.325},
		{0.32931, 0.36038, -40.770},
		{0.33724, 0.36051, -116.45}
	};
	memcpy(uvt, uvtTmp, sizeof(UVT) * 31);

	type = 0;
	blackBodyTemperature = 5000.0;

	colourSystemList.push_back(NTSCsystem);
	colourSystemList.push_back(EBUsystem);
	colourSystemList.push_back(SMPTEsystem);
	colourSystemList.push_back(HDTVsystem);
	colourSystemList.push_back(CIEsystem);
	colourSystemList.push_back(Rec709system);

	for(size_t i = 0; i < colourSystemList.size(); i++)
	{
		typeValuesList.push_back(i);
		typeStringList.push_back(colourSystemList[i].name);
	}

	// Init GUI
	wxBoxSizer* mainBoxSizer = new wxBoxSizer( wxVERTICAL );
	
	wxPanel* mainPanel = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
	wxBoxSizer* panelBoxSizer = new wxBoxSizer( wxVERTICAL );
	
	wxStaticBoxSizer* colourSizer = new wxStaticBoxSizer( new wxStaticBox( mainPanel, wxID_ANY, wxT("Colour preview") ), wxHORIZONTAL );

	colourSizer->Add( 0, 0, 1, wxEXPAND, 5 );

	wxStaticText* staticText1 = new wxStaticText( mainPanel, wxID_ANY, wxT("current color"), wxDefaultPosition, wxDefaultSize, 0 );
	staticText1->Wrap( -1 );
	colourSizer->Add( staticText1, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 );

	actColourPreview = new wxWindow(mainPanel, wxID_ANY, wxDefaultPosition, wxSize(50, 25));
	colourSizer->Add( actColourPreview, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 );

	wxStaticText* staticText2 = new wxStaticText( mainPanel, wxID_ANY, wxT("original color"), wxDefaultPosition, wxDefaultSize, 0 );
	staticText2->Wrap( -1 );
	colourSizer->Add( staticText2, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 );

	origColourPreview = new wxWindow(mainPanel, wxID_ANY, wxDefaultPosition, wxSize(50, 25));
	colourSizer->Add( origColourPreview, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 );

	colourSizer->Add( 0, 0, 1, wxEXPAND, 5 );

	panelBoxSizer->Add( colourSizer, 0, wxEXPAND, 5 );

	wxGridSizer* gridSizer = new wxGridSizer( 0, 2, 0, 0 );
	wxStaticBoxSizer* rgbSizer = new wxStaticBoxSizer( new wxStaticBox( mainPanel, wxID_ANY, wxT("RGB") ), wxVERTICAL );
	
	redSpinSlider = new NumberInput(mainPanel, wxID_ANY, wxT("R"), 0.0, 1.0, r, 0.01, NumberInput::FLAG_SLIDER_LINEAR);
	redSpinSlider->SetSliderWidth(200);
	rgbSizer->Add( redSpinSlider, 1, wxALL, 5 );
	
	greenSpinSlider = new NumberInput(mainPanel, wxID_ANY, wxT("G"), 0.0, 1.0, g, 0.01, NumberInput::FLAG_SLIDER_LINEAR);
	greenSpinSlider->SetSliderWidth(200);
	rgbSizer->Add( greenSpinSlider, 0, wxALL, 5 );
	
	blueSpinSlider = new NumberInput(mainPanel, wxID_ANY, wxT("B"), 0.0, 1.0, b, 0.01, NumberInput::FLAG_SLIDER_LINEAR);
	blueSpinSlider->SetSliderWidth(200);
	rgbSizer->Add( blueSpinSlider, 0, wxALL, 5 );
	
	gridSizer->Add( rgbSizer, 0, wxEXPAND, 5 );
	
	wxStaticBoxSizer* hsvSizer = new wxStaticBoxSizer( new wxStaticBox( mainPanel, wxID_ANY, wxT("HSV") ), wxVERTICAL );
	
	hueSpinSlider = new NumberInput(mainPanel, wxID_ANY, wxT("H"), 0.0, 360.0, 0.0, 0.1, NumberInput::FLAG_SLIDER_LINEAR);
	hueSpinSlider->SetSliderWidth(200);
	hsvSizer->Add( hueSpinSlider, 0, wxALL, 5 );
	
	saturationSpinSlider = new NumberInput(mainPanel, wxID_ANY, wxT("S"), 0.0, 1.0, 0.0, 0.01, NumberInput::FLAG_SLIDER_LINEAR);
	saturationSpinSlider->SetSliderWidth(200);
	hsvSizer->Add( saturationSpinSlider, 0, wxALL, 5 );
	
	valueSpinSlider = new NumberInput(mainPanel, wxID_ANY, wxT("V"), 0.0, 1.0, 0.0, 0.01, NumberInput::FLAG_SLIDER_LINEAR);
	valueSpinSlider->SetSliderWidth(200);
	hsvSizer->Add( valueSpinSlider, 0, wxALL, 5 );
	
	gridSizer->Add( hsvSizer, 0, wxEXPAND, 5 );
	
	wxStaticBoxSizer* cieSizer = new wxStaticBoxSizer( new wxStaticBox( mainPanel, wxID_ANY, wxT("CIE Lab") ), wxVERTICAL );

	lSpinSlider = new NumberInput(mainPanel, wxID_ANY, wxT("L"), 0.0, 100.0, 0.0, 0.1, NumberInput::FLAG_SLIDER_LINEAR);
	lSpinSlider->SetSliderWidth(200);
	cieSizer->Add( lSpinSlider, 0, wxALL, 5 );
	
	aSpinSlider = new NumberInput(mainPanel, wxID_ANY, wxT("a"), -127.0, 127.0, 0.0, 0.1, NumberInput::FLAG_SLIDER_LINEAR);
	aSpinSlider->SetSliderWidth(200);
	cieSizer->Add( aSpinSlider, 0, wxALL, 5 );
	
	bSpinSlider = new NumberInput(mainPanel, wxID_ANY, wxT("a"), -127.0, 127.0, 0.0, 0.1, NumberInput::FLAG_SLIDER_LINEAR);
	bSpinSlider->SetSliderWidth(200);
	cieSizer->Add( bSpinSlider, 0, wxALL, 5 );
	
	gridSizer->Add( cieSizer, 1, wxEXPAND, 5 );
	
	wxStaticBoxSizer* tempSizer = new wxStaticBoxSizer( new wxStaticBox( mainPanel, wxID_ANY, wxT("Illuminant color temperature - CIE xy") ), wxVERTICAL );

	colourProfile = new wxComboBox(mainPanel, wxID_ANY, typeStringList[type], wxPoint(200,83), wxSize(100,20), typeStringList, wxCB_READONLY, wxDefaultValidator, wxT("name"));			
	tempSizer->Add( colourProfile, 0, wxALL, 5 );
	
	temperatureSpinSlider = new NumberInput( mainPanel, wxID_ANY, wxT("T"), 1000.0, 40000.0, 1000.0, 0.1, NumberInput::FLAG_SLIDER_LINEAR);
	temperatureSpinSlider->SetSliderWidth(200);
	tempSizer->Add( temperatureSpinSlider, 0, wxALL, 5 );
		
	gridSizer->Add( tempSizer, 1, wxEXPAND, 5 );
	panelBoxSizer->Add( gridSizer, 1, wxEXPAND, 5 );
	
	wxStaticBoxSizer* alphaSizer = new wxStaticBoxSizer( new wxStaticBox( mainPanel, wxID_ANY, wxEmptyString ), wxHORIZONTAL );

	if (mode == EE_COLOUR_DIALOG_ALPHA)
	{
		alphaSpinSlider = new NumberInput(mainPanel, wxID_ANY, wxT("Alpha"), 0.0, 1.0, alpha, 0.01, NumberInput::FLAG_SLIDER_LINEAR);
		alphaSpinSlider->SetSliderWidth(200);
	}
	else
	{
		alphaSpinSlider = new NumberInput(mainPanel, wxID_ANY, wxT("Intensity"), 0.0, std::numeric_limits<float>::max(), alpha, 0.1, 0);
	}

	alphaSizer->Add( 0, 0, 1, wxEXPAND, 5 );
	alphaSizer->Add( alphaSpinSlider, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 );
	alphaSizer->Add( 0, 0, 1, wxEXPAND, 5 );
	
	panelBoxSizer->Add( alphaSizer, 0, wxEXPAND, 5 );
	
	mainPanel->SetSizer( panelBoxSizer );
	mainPanel->Layout();
	panelBoxSizer->Fit( mainPanel );
	mainBoxSizer->Add( mainPanel, 0, wxEXPAND | wxALL, 5 );
	
	wxBoxSizer* buttonSizer = new wxBoxSizer( wxHORIZONTAL );
	
	buttonSizer->Add( 0, 0, 1, wxEXPAND, 5 );
	
	resetButton = new wxButton( this, wxID_ANY, wxT("Reset"), wxDefaultPosition, wxDefaultSize, 0 );
	buttonSizer->Add( resetButton, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 );

	buttonSizer->Add( 0, 0, 1, wxEXPAND, 5 );
	
	mainBoxSizer->Add( buttonSizer, 0, wxEXPAND, 5 );
	
	this->SetSizer( mainBoxSizer );
	this->Layout();
	this->Centre( wxBOTH );

	// set initial values for controls
	SetHSV();
	SetTemperature();
	SetLab();

	origColourPreview->SetBackgroundColour(wxColour(TO_COLOUR_COMPONENT(r0), TO_COLOUR_COMPONENT(g0), TO_COLOUR_COMPONENT(b0)));
	actColourPreview->SetBackgroundColour(wxColour(TO_COLOUR_COMPONENT(r), TO_COLOUR_COMPONENT(g), TO_COLOUR_COMPONENT(b)));

	this->Fit();
	KERNEL->WinPosSizeFromVRUTConfig(this, wxT("/ColourDialog/"), wxPoint(100, 100), wxSize(), false);

	// connect events to handlers
#ifdef USE_wxDEFINE_EVENT
	redSpinSlider->Connect(wxID_ANY, EVT_NUMBER_INPUT, wxCommandEventHandler(ColourDialog::OnSlider), NULL, this);
	greenSpinSlider->Connect(wxID_ANY, EVT_NUMBER_INPUT, wxCommandEventHandler(ColourDialog::OnSlider), NULL, this);
	blueSpinSlider->Connect(wxID_ANY, EVT_NUMBER_INPUT, wxCommandEventHandler(ColourDialog::OnSlider), NULL, this);

	hueSpinSlider->Connect(wxID_ANY, EVT_NUMBER_INPUT, wxCommandEventHandler(ColourDialog::OnSlider), NULL, this);
	saturationSpinSlider->Connect(wxID_ANY, EVT_NUMBER_INPUT, wxCommandEventHandler(ColourDialog::OnSlider), NULL, this);
	valueSpinSlider->Connect(wxID_ANY, EVT_NUMBER_INPUT, wxCommandEventHandler(ColourDialog::OnSlider), NULL, this);

	lSpinSlider->Connect(wxID_ANY, EVT_NUMBER_INPUT, wxCommandEventHandler(ColourDialog::OnSlider), NULL, this);
	aSpinSlider->Connect(wxID_ANY, EVT_NUMBER_INPUT, wxCommandEventHandler(ColourDialog::OnSlider), NULL, this);
	bSpinSlider->Connect(wxID_ANY, EVT_NUMBER_INPUT, wxCommandEventHandler(ColourDialog::OnSlider), NULL, this);

	alphaSpinSlider->Connect(wxID_ANY, EVT_NUMBER_INPUT, wxCommandEventHandler(ColourDialog::OnSlider), NULL, this);

	temperatureSpinSlider->Connect(wxID_ANY, EVT_NUMBER_INPUT, wxCommandEventHandler(ColourDialog::OnSlider), NULL, this);
#endif

	colourProfile->Connect(wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler(ColourDialog::ComboBoxSelected), NULL, this);

	resetButton->Connect(wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(ColourDialog::OnClick), NULL, this);
}

void ColourDialog::ResetValue(double red, double green, double blue, double a)
{
	this->r = red;
	this->g = green;
	this->b = blue;
	this->alpha = a;

	this->r0 = red;
	this->g0 = green;
	this->b0 = blue;
	this->alpha0 = a;

	UpdateGUI();
}

#define MIN3(a, b, c) ((a < b) ? ((a < c) ? a : c) : ((b < c) ? b : c))
#define MAX3(a, b, c) ((a > b) ? ((a > c) ? a : c) : ((b > c) ? b : c))

/**
* Converts from RGB to HSV values. r,g,b are in [0..1], s,v in [0..1] and h in [0..360]
*/
void ColourDialog::RGBtoHSV(double red, double green, double blue, double &hue, double &saturation, double &value)
{
	double min, max, delta;

	min = MIN3(red, green, blue);
	max = MAX3(red, green, blue);

	value = max;						// value

	delta = max - min;

	if (max != 0.0)
		saturation = delta / max;		// saturation
	else
	{
		// red = green = blue = 0		// saturation = 0, value is undefined
		saturation = 0.0;
		hue = 0.0;
		return;
	}

    if (max == min) { 
        hue = 0.0;								// achromatic
	}
	else {
		if (red == max)
			hue = (green - blue) / delta;		// between yellow & magenta
		else if (green == max)
			hue = 2.0 + (blue - red) / delta;	// between cyan & yellow
		else
			hue = 4.0 + (red - green) / delta;	// between magenta & cyan

		hue *= 60.0;							// to degrees
		if (hue < 0.0)
			hue += 360.0;
	}
}

/**
* Converts from HSV to RGB values. s,v are in [0..1], h in [0..360] and r,g,b in [0..1]
*/
void ColourDialog::HSVtoRGB(double hue, double saturation, double value, double &red, double &green, double &blue)
{
	int sector;
	double f, p, q, t;

	if (saturation == 0.0)
	{
		// achromatic (grey)
		red = green = blue = value;
		return;
	}

	hue /= 60.0;				// sector 0 to 5
	sector = floor(hue);
	f = hue - sector;			// factorial part of hue

	p = value * (1.0 - saturation);
	q = value * (1.0 - saturation * f);
	t = value * (1.0 - saturation * (1.0 - f));

	switch (sector)
	{
		case 0:
			red = value;
			green = t;
			blue = p;
			break;
		case 1:
			red = q;
			green = value;
			blue = p;
			break;
		case 2:
			red = p;
			green = value;
			blue = t;
			break;
		case 3:
			red = p;
			green = q;
			blue = value;
			break;
		case 4:
			red = t;
			green = p;
			blue = value;
			break;
		case 5:
		default:
			red = value;
			green = p;
			blue = q;
			break;
	}
}

#undef MIN3
#undef MAX3

/*
    Normalise RGB components so the most intense (unless all
    are zero) has a value of 1.
    
	source: http://www.fourmilab.ch/documents/specrend/specrend.c    
*/
void ColourDialog::NormRGB(double *r, double *g, double *b)
{
	double greatest = __max(*r, __max(*g, *b));

	if (greatest > 0.0) {
		*r /= greatest;
		*g /= greatest;
		*b /= greatest;
	}
}

/*
    If the requested RGB shade contains a negative weight for
    one of the primaries, it lies outside the colour gamut 
    accessible from the given triple of primaries.  Desaturate
    it by adding white, equal quantities of R, G, and B, enough
    to make RGB all positive.  The function returns 1 if the
    components were modified, zero otherwise.

	source: http://www.fourmilab.ch/documents/specrend/specrend.c    
*/
bool ColourDialog::ConstrainRGB(double *r, double *g, double *b)
{
	double w;

	/* Amount of white needed is w = - min(0, *r, *g, *b) */
	w = (0 < *r) ? 0 : *r;
	w = (w < *g) ? w : *g;
	w = (w < *b) ? w : *b;
	w = -w;

	/* Add just enough white to make r, g, b all positive. */
	if (w > 0) {
		*r += w;
		*g += w;
		*b += w;

		return true;                     /* Colour modified to fit RGB gamut */
	}

	return false;                         /* Colour within RGB gamut */
}

/*
    This is an implementation of Robertson's method of computing the correlated color
    temperature of an XYZ color. It can compute correlated color temperatures in the
    range [1666.7K, infinity].

    Input:
      X, Y and Z color components
    Output:
      correlated color temperature, if successful.
                     = unchanged if unsuccessful.

    Return: true if successful, else false.

	source: http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_T.html
*/
bool ColourDialog::XYZtoCorrelatedColorTemperature(double xt, double yt, double zt, double *colorTemperature)
{
	double us, vs, p, di, dm;

	us = vs = p = di = dm = 0.0;

	if ((xt < 1.0e-20) && (yt < 1.0e-20) && (zt < 1.0e-20))
	{
		/* protect against possible divide-by-zero failure */
		*colorTemperature = 1000.0;
		return false;
	}

	us = (4.0 * xt) / (xt + 15.0 * yt + 3.0 * zt);
	vs = (6.0 * yt) / (xt + 15.0 * yt + 3.0 * zt);

	int i;

	dm = 0.0;
	for (i = 0; i < 31; i++)
	{
		di = (vs - uvt[i].v) - uvt[i].t * (us - uvt[i].u);
		if ((i > 0) && (((di < 0.0) && (dm >= 0.0)) || ((di >= 0.0) && (dm < 0.0))))
			break;  /* found lines bounding (us, vs) : i-1 and i */
		dm = di;
	}

	if (i == 31)
	{
		/* bad XYZ input, colour temp would be less than minimum of 1666.7 degrees, or too far towards blue */
		*colorTemperature = 1000.0;
		return false;
	}

	di = di / sqrt(1.0 + uvt[i    ].t * uvt[i    ].t);
	dm = dm / sqrt(1.0 + uvt[i - 1].t * uvt[i - 1].t);

	p = dm / (dm - di);     /* p = interpolation parameter, 0.0 : i-1, 1.0 : i */
	p = 1.0 / (LERP(rtt[i - 1], rtt[i], p)); // (((rtt[i]) - (rtt[i - 1])) * (p) + (rtt[i - 1]));// 

	*colorTemperature = p;

	return true;
}

void ColourDialog::SetTemperature()
{
	double xt, yt, zt;

	RGBtoXYZ(&colourSystemList[type], r, g, b, &xt, &yt, &zt);

	double colourTemperature;

	if (!XYZtoCorrelatedColorTemperature(xt, yt, zt, &colourTemperature))
		LOGWARNING(wxT("ColourDialog::SetTemperature() - Correlated color temperature outside the valid range [1666.7K, infinity] => setting default value 1000K."));

	temperatureSpinSlider->SetValue(colourTemperature);
}

void ColourDialog::RGBtoXYZ(struct ColourSystem *cs, double red, double green, double blue, double *xc, double *yc, double *zc)
{
	double xr, yr, zr, xg, yg, zg, xb, yb, zb;
	double xw, yw, zw;
	double rx, ry, rz, gx, gy, gz, bx, by, bz;
	double rw, gw, bw;
	Matrix mat;

	xr = cs->xRed;    yr = cs->yRed;    zr = 1.0 - (xr + yr);
	xg = cs->xGreen;  yg = cs->yGreen;  zg = 1.0 - (xg + yg);
	xb = cs->xBlue;   yb = cs->yBlue;   zb = 1.0 - (xb + yb);

	xw = cs->xWhite;  yw = cs->yWhite;  zw = 1.0 - (xw + yw);
	
	/* xyz -> rgb matrix, before scaling to white. */
	rx = (yg * zb) - (yb * zg);  ry = (xb * zg) - (xg * zb);  rz = (xg * yb) - (xb * yg);
	gx = (yb * zr) - (yr * zb);  gy = (xr * zb) - (xb * zr);  gz = (xb * yr) - (xr * yb);
	bx = (yr * zg) - (yg * zr);  by = (xg * zr) - (xr * zg);  bz = (xr * yg) - (xg * yr);

	/* White scaling factors. Dividing by yw scales the white luminance to unity, as conventional. */
	rw = ((rx * xw) + (ry * yw) + (rz * zw)) / yw;
	gw = ((gx * xw) + (gy * yw) + (gz * zw)) / yw;
	bw = ((bx * xw) + (by * yw) + (bz * zw)) / yw;

	/* xyz -> rgb matrix, correctly scaled to white. */
	rx = rx / rw;  ry = ry / rw;  rz = rz / rw;
	gx = gx / gw;  gy = gy / gw;  gz = gz / gw;
	bx = bx / bw;  by = by / bw;  bz = bz / bw;

	/* matrix for inverse matrix */
	mat._m[0]  = rx;   mat._m[1]  = gx;   mat._m[2]  = bx;   mat._m[3]  = 0.0f;
	mat._m[4]  = ry;   mat._m[5]  = gy;   mat._m[6]  = by;   mat._m[7]  = 0.0f;
	mat._m[8]  = rz;   mat._m[9]  = gz;   mat._m[10] = bz;   mat._m[11] = 0.0f;
	mat._m[12] = 0.0f; mat._m[13] = 0.0f; mat._m[14] = 0.0f; mat._m[15] = 1.0f;
	mat.InvertThisPrecise();

	rx = mat._m[0]; gx = mat._m[1]; bx = mat._m[2];
	ry = mat._m[4]; gy = mat._m[5]; by = mat._m[6];
	rz = mat._m[8]; gz = mat._m[9]; bz = mat._m[10];

	/* rgb of the desired point */
	*xc = (rx * red) + (ry * green) + (rz * blue);
	*yc = (gx * red) + (gy * green) + (gz * blue);
	*zc = (bx * red) + (by * green) + (bz * blue);
}

/* 
    Given an additive tricolour system CS, defined by the CIE x
    and y chromaticities of its three primaries (z is derived
    trivially as 1-(x+y)), and a desired chromaticity (XC, YC,
    ZC) in CIE space, determine the contribution of each
    primary in a linear combination which sums to the desired
    chromaticity.  If the  requested chromaticity falls outside
    the Maxwell  triangle (colour gamut) formed by the three
    primaries, one of the r, g, or b weights will be negative. 

    Caller can use constrain_rgb() to desaturate an
    outside-gamut colour to the closest representation within
    the available gamut and/or norm_rgb to normalise the RGB
    components so the largest nonzero component has value 1.

	source: http://www.fourmilab.ch/documents/specrend/specrend.c
*/
void ColourDialog::XYZtoRGB(struct ColourSystem *cs, double xc, double yc, double zc, double *red, double *green, double *blue)
{
	double xr, yr, zr, xg, yg, zg, xb, yb, zb;
	double xw, yw, zw;
	double rx, ry, rz, gx, gy, gz, bx, by, bz;
	double rw, gw, bw;

	xr = cs->xRed;    yr = cs->yRed;    zr = 1.0 - (xr + yr);
	xg = cs->xGreen;  yg = cs->yGreen;  zg = 1.0 - (xg + yg);
	xb = cs->xBlue;   yb = cs->yBlue;   zb = 1.0 - (xb + yb);

	xw = cs->xWhite;  yw = cs->yWhite;  zw = 1.0 - (xw + yw);

	/* xyz -> rgb matrix, before scaling to white. */
	rx = (yg * zb) - (yb * zg);  ry = (xb * zg) - (xg * zb);  rz = (xg * yb) - (xb * yg);
	gx = (yb * zr) - (yr * zb);  gy = (xr * zb) - (xb * zr);  gz = (xb * yr) - (xr * yb);
	bx = (yr * zg) - (yg * zr);  by = (xg * zr) - (xr * zg);  bz = (xr * yg) - (xg * yr);

	/* White scaling factors.
	Dividing by yw scales the white luminance to unity, as conventional. */
	rw = ((rx * xw) + (ry * yw) + (rz * zw)) / yw;
	gw = ((gx * xw) + (gy * yw) + (gz * zw)) / yw;
	bw = ((bx * xw) + (by * yw) + (bz * zw)) / yw;

	/* xyz -> rgb matrix, correctly scaled to white. */
	rx = rx / rw;  ry = ry / rw;  rz = rz / rw;
	gx = gx / gw;  gy = gy / gw;  gz = gz / gw;
	bx = bx / bw;  by = by / bw;  bz = bz / bw;

	/* rgb of the desired point */
	*red   = (rx * xc) + (ry * yc) + (rz * zc);
	*green = (gx * xc) + (gy * yc) + (gz * zc);
	*blue  = (bx * xc) + (by * yc) + (bz * zc);
}

/*
    Calculate the CIE X, Y, and Z coordinates corresponding to
    a light source with spectral distribution given by the
    BlackBodySpectrum() function, which is called with a series of
    wavelengths between 380 and 780 nm (the argument is 
    expressed in meters), which returns emittance at that
    wavelength in arbitrary units. The chromaticity
	coordinates of the spectrum are returned in the x, y, and z
    arguments which respect the identity: x + y + z = 1

	source: http://www.fourmilab.ch/documents/specrend/specrend.c
*/
void ColourDialog::SpectrumToXYZ(double *x, double *y, double *z)
{
	/* CIE colour matching functions xBar, yBar, and zBar for
	wavelengths from 380 through 780 nanometers, every 5
	nanometers.  For a wavelength lambda in this range:

	cieColourMatch[(lambda - 380) / 5][0] = xBar
	cieColourMatch[(lambda - 380) / 5][1] = yBar
	cieColourMatch[(lambda - 380) / 5][2] = zBar

	To save memory, this table can be declared as floats
	rather than doubles; (IEEE) float has enough 
	significant bits to represent the values. It's declared
	as a double here to avoid warnings about "conversion
	between floating-point types" from certain persnickety
	compilers. */

	static double cieColourMatchingFunctions[81][3] = {
		{0.0014,0.0000,0.0065}, {0.0022,0.0001,0.0105}, {0.0042,0.0001,0.0201},
		{0.0076,0.0002,0.0362}, {0.0143,0.0004,0.0679}, {0.0232,0.0006,0.1102},
		{0.0435,0.0012,0.2074}, {0.0776,0.0022,0.3713}, {0.1344,0.0040,0.6456},
		{0.2148,0.0073,1.0391}, {0.2839,0.0116,1.3856}, {0.3285,0.0168,1.6230},
		{0.3483,0.0230,1.7471}, {0.3481,0.0298,1.7826}, {0.3362,0.0380,1.7721},
		{0.3187,0.0480,1.7441}, {0.2908,0.0600,1.6692}, {0.2511,0.0739,1.5281},
		{0.1954,0.0910,1.2876}, {0.1421,0.1126,1.0419}, {0.0956,0.1390,0.8130},
		{0.0580,0.1693,0.6162}, {0.0320,0.2080,0.4652}, {0.0147,0.2586,0.3533},
		{0.0049,0.3230,0.2720}, {0.0024,0.4073,0.2123}, {0.0093,0.5030,0.1582},
		{0.0291,0.6082,0.1117}, {0.0633,0.7100,0.0782}, {0.1096,0.7932,0.0573},
		{0.1655,0.8620,0.0422}, {0.2257,0.9149,0.0298}, {0.2904,0.9540,0.0203},
		{0.3597,0.9803,0.0134}, {0.4334,0.9950,0.0087}, {0.5121,1.0000,0.0057},
		{0.5945,0.9950,0.0039}, {0.6784,0.9786,0.0027}, {0.7621,0.9520,0.0021},
		{0.8425,0.9154,0.0018}, {0.9163,0.8700,0.0017}, {0.9786,0.8163,0.0014},
		{1.0263,0.7570,0.0011}, {1.0567,0.6949,0.0010}, {1.0622,0.6310,0.0008},
		{1.0456,0.5668,0.0006}, {1.0026,0.5030,0.0003}, {0.9384,0.4412,0.0002},
		{0.8544,0.3810,0.0002}, {0.7514,0.3210,0.0001}, {0.6424,0.2650,0.0000},
		{0.5419,0.2170,0.0000}, {0.4479,0.1750,0.0000}, {0.3608,0.1382,0.0000},
		{0.2835,0.1070,0.0000}, {0.2187,0.0816,0.0000}, {0.1649,0.0610,0.0000},
		{0.1212,0.0446,0.0000}, {0.0874,0.0320,0.0000}, {0.0636,0.0232,0.0000},
		{0.0468,0.0170,0.0000}, {0.0329,0.0119,0.0000}, {0.0227,0.0082,0.0000},
		{0.0158,0.0057,0.0000}, {0.0114,0.0041,0.0000}, {0.0081,0.0029,0.0000},
		{0.0058,0.0021,0.0000}, {0.0041,0.0015,0.0000}, {0.0029,0.0010,0.0000},
		{0.0020,0.0007,0.0000}, {0.0014,0.0005,0.0000}, {0.0010,0.0004,0.0000},
		{0.0007,0.0002,0.0000}, {0.0005,0.0002,0.0000}, {0.0003,0.0001,0.0000},
		{0.0002,0.0001,0.0000}, {0.0002,0.0001,0.0000}, {0.0001,0.0000,0.0000},
		{0.0001,0.0000,0.0000}, {0.0001,0.0000,0.0000}, {0.0000,0.0000,0.0000}
	};

	double X = 0, Y = 0, Z = 0;

	double lambda = 380.0;
	for (int i = 0; lambda < 780.1; i++)
	{
		double Me = BlackBodySpectrum(lambda);

		X += Me * cieColourMatchingFunctions[i][0];
		Y += Me * cieColourMatchingFunctions[i][1];
		Z += Me * cieColourMatchingFunctions[i][2];

		lambda += 5.0;
	}

	double XYZ = (X + Y + Z);

	*x = X / XYZ;
	*y = Y / XYZ;
	*z = Z / XYZ;
}

/*
	Calculate, by Planck's radiation law, the emittance of a black body
    of temperature blackBodyTemperature at the given wavelength (in metres).
	source: http://www.fourmilab.ch/documents/specrend/specrend.c
*/
double ColourDialog::BlackBodySpectrum(double wavelength)
{
	// Planck's radiation law:
	//		amount of radiation emitted by black body = (2*PI*h*c^2 / lambda^5)*(1/(exp(h*c/kB*T*lambda) - 1)) [W.m^-2.m^-1]
	// where:
	//  - h			Planck’s constant						6.62608*10^-34		J.s
	//  - c			speed of electromagnetic radiation		2.99792458*10^8		m.s^-1
	//  - kB		Boltzmann constant						1.38066*10^-23		J.K^-1
	//  - T			surface temperature of object								K
	//  - lambda	wavelength of electromagnetic radiation

	double wavelengthM = wavelength * 1e-9;   /* Wavelength in meters */

	return (3.74183e-16 * pow(wavelengthM, -5.0)) / (exp(1.4388e-2 / (wavelengthM * blackBodyTemperature)) - 1.0);
	//return (3.74183e-16 * pow(wavelengthM, -5.0)) / expm1(1.4388e-2 / (wavelengthM * blackBodyTemperature));			// TODO This is better, but expm1 is not supported by VS2012
}

/*
   Input: RGB values [0.0, 1.0]
   Output: 0 <= L <= 100; -127 <= a <= 127; -127 <= b <= 127
*/
void ColourDialog::RGBToCIELab(double red, double green, double blue, double& L, double& a, double& b)
{
	// Inverse sRGB Companding - made channels linear with respect to energy
	double rL = (red   > 0.04045) ? pow((red   + 0.055) / 1.055, 2.4) : red   / 12.92;
	double gL = (green > 0.04045) ? pow((green + 0.055) / 1.055, 2.4) : green / 12.92;
	double bL = (blue  > 0.04045) ? pow((blue  + 0.055) / 1.055, 2.4) : blue  / 12.92;

	double xt, yt, zt;

	RGBtoXYZ(&colourSystemList[type], rL, gL, bL, &xt, &yt, &zt);

	// Conversion CIE XYZ => CIE Lab
	// 
	//   (6.0 / 29.0)^3          => 0.008856 EPSILON
	//   (1.0/3.0)*(29.0/6.0)^2  => 7.787037
	//
	// forward transformation:
	//   L* = 116 * f(Y/Yn) - 16
	//   a* = 500 * ( f(X/Xn) - f(Y/Yn) )
	//   b* = 200 * ( f(Y/Yn) - f(Z/Zn) )
	// where
	//   f(t) = t^(1/3)                  for t > 0.008856
    //   f(t) = 7.787037 * t + 16/116    otherwise
	//
	// Xn, Yn and Zn are the CIE XYZ tristimulus values of the reference white point (normalized Yn = 1.0)
	// for D65 illuminant Xn,Yn,Zn = 0.9505, 1.0000, 1.0890

	//double Xn = colourSystemList[type].xWhite;
	//double Yn = colourSystemList[type].yWhite;
	//double Zn = 1.0 - Xn - Yn;

	double Xn = 0.9505;
	double Yn = 1.0000;
	double Zn = 1.0890;

	double Xr = xt / Xn;
	double Yr = yt / Yn;
	double Zr = zt / Zn;

	double fx = (Xr > CIE_LAB_EPSILON) ? pow(Xr, 1.0/3.0) : (CIE_LAB_K * Xr + 16.0) / 116.0;
	double fy = (Yr > CIE_LAB_EPSILON) ? pow(Yr, 1.0/3.0) : (CIE_LAB_K * Yr + 16.0) / 116.0;
	double fz = (Zr > CIE_LAB_EPSILON) ? pow(Zr, 1.0/3.0) : (CIE_LAB_K * Zr + 16.0) / 116.0;

	L = 116.0 * fy - 16.0;
	a = 500.0 * (fx - fy);
	b = 200.0 * (fy - fz);

	//LOGERROR(wxString::Format(">>> RGBToCIELab(): RGB: [%g, %g, %g]", red, green, blue));
	//LOGERROR(wxString::Format(">>> RGBToCIELab(): linear RGB: [%g, %g, %g]", rL, gL, bL));
	//LOGERROR(wxString::Format(">>> RGBToCIELab(): XYZ: [%g, %g, %g]", xt, yt, zt));
	//LOGERROR(wxString::Format(">>> RGBToCIELab(): Lab: [%g, %g, %g]", L, a, b));
}

void ColourDialog::SetLab()
{
	double Lt, at, bt;

	RGBToCIELab(r, g, b, Lt, at, bt);

	lSpinSlider->SetValue(Lt);
	aSpinSlider->SetValue(at);
	bSpinSlider->SetValue(bt);
}

void ColourDialog::TemperatureToRGB()
{
	double xt, yt, zt, rt, gt, bt;

	blackBodyTemperature = temperatureSpinSlider->GetValue();		

	SpectrumToXYZ(&xt,&yt,&zt);
	XYZtoRGB(&colourSystemList[type], xt, yt, zt, &rt, &gt, &bt);

	if (ConstrainRGB(&rt, &gt, &bt)) 
		NormRGB(&rt, &gt, &bt);            
	else 
		NormRGB(&rt, &gt, &bt);

	r = rt;
	g = gt;
	b = bt;

	redSpinSlider->SetValue(r);
	greenSpinSlider->SetValue(g);
	blueSpinSlider->SetValue(b);
}

/*
   Input: 0 <= L <= 100; -127 <= a <= 127; -127 <= b <= 127
   Output: RGB values [0.0, 1.0]
*/
void ColourDialog::CIELabToRGB(double Lt, double at, double bt, double& red, double& green, double& blue)
{
	// conversion CIELab -> CIE XYZ
	// http://www.brucelindbloom.com/index.html?Eqn_Lab_to_XYZ.html

	double fy = (Lt + 16.0) / 116.0;
	double fx = (at / 500.0) + fy;
	double fz = fy - (bt / 200.0);

	double fx3 = std::pow(fx, 3.0);
	double fy3 = std::pow(fy, 3.0);
	double fz3 = std::pow(fz, 3.0); 

	double Xr = (fx3 > CIE_LAB_EPSILON) ? fx3 : (116.0*fx - 16.0) / CIE_LAB_K;
	double Zr = (fz3 > CIE_LAB_EPSILON) ? fz3 : (116.0*fz - 16.0) / CIE_LAB_K;
	double Yr = (Lt > 8.0 /*CIE_LAB_K * CIE_LAB_EPSILON = (216.0 / 27.0) = 8.0*/) ? fy3 : Lt / CIE_LAB_K;

	//double Xn = colourSystemList[type].xWhite;
	//double Yn = colourSystemList[type].yWhite;
	//double Zn = 1.0 - Xn - Yn;

	double Xn = 0.9505;
	double Yn = 1.0000;
	double Zn = 1.0890;

	double xt = Xr * Xn;
	double yt = Yr * Yn;
	double zt = Zr * Zn;

	double rL, gL, bL;

	/* quick hack */
	if ( (xt < 0.0) || (yt < 0.0) || (zt < 0.0) )
	{
		// these colours can't be represented by RGB colour model
		LOGWARNING(wxString::Format("CIELabToRGB() - conversion failed (negative values of CIE XYZ [%g, %g, %g])", xt, yt, zt));
		xt = (xt < 0.0) ? 0.0 : xt;
		yt = (yt < 0.0) ? 0.0 : yt;
		zt = (zt < 0.0) ? 0.0 : zt;
	}

	XYZtoRGB(&colourSystemList[type], xt, yt, zt, &rL, &gL, &bL);

	//LOGERROR(wxString::Format(">>> CIELabToRGB(): Lab: [%g, %g, %g]", Lt, at, bt));
	//LOGERROR(wxString::Format(">>> CIELabToRGB(): XYZ: [%g, %g, %g]", xt, yt, zt));
	//LOGERROR(wxString::Format(">>> CIELabToRGB(): linear RGB: [%g, %g, %g]", rL, gL, bL));

	/* quick hack */
	if ( (rL < 0.0) || (gL < 0.0) || (bL < 0.0) )
	{
		// these colours can't be represented by RGB colour model
		LOGWARNING(wxString::Format("CIELabToRGB() - conversion failed (negative values of RGB [%g, %g, %g])", rL, gL, bL));

		if (ConstrainRGB(&rL, &gL, &bL)) 
			NormRGB(&rL, &gL, &bL);            
		else 
			NormRGB(&rL, &gL, &bL);
	}

	// sRGB Companding
	red   = rL > 0.0031308 ? (1.055*pow(rL, 1.0 / 2.4) - 0.055) : 12.92*rL;
	green = gL > 0.0031308 ? (1.055*pow(gL, 1.0 / 2.4) - 0.055) : 12.92*gL;
	blue  = bL > 0.0031308 ? (1.055*pow(bL, 1.0 / 2.4) - 0.055) : 12.92*bL;

	//LOGERROR(wxString::Format(">>> CIELabToRGB(): RGB: [%g, %g, %g]", red, green, blue));
}

void ColourDialog::OnSlider(wxCommandEvent &evt)
{
	wxObject *srcObject = evt.GetEventObject();

	if (srcObject == alphaSpinSlider)
		alpha = alphaSpinSlider->GetValue();
	else if ((srcObject == redSpinSlider) || (srcObject == greenSpinSlider) || (srcObject == blueSpinSlider))
	{
		r = redSpinSlider->GetValue();
		g = greenSpinSlider->GetValue();
		b = blueSpinSlider->GetValue();

		SetHSV();
		SetTemperature();
		SetLab();
	}
	else if ((srcObject == hueSpinSlider) || (srcObject == saturationSpinSlider) || (srcObject == valueSpinSlider))
	{
		double ht = hueSpinSlider->GetValue();
		double st = saturationSpinSlider->GetValue();
		double vt = valueSpinSlider->GetValue();

		HSVtoRGB(ht, st, vt, r, g, b);

		redSpinSlider->SetValue(r);
		greenSpinSlider->SetValue(g);
		blueSpinSlider->SetValue(b);

		SetTemperature();
		SetLab();
	}
	else if(srcObject == temperatureSpinSlider) 
	{
		TemperatureToRGB();
		SetHSV();
		SetLab();
	}
	else if( (srcObject == lSpinSlider) || (srcObject == aSpinSlider) || (srcObject == bSpinSlider) )
	{
		double Lt = lSpinSlider->GetValue();
		double at = aSpinSlider->GetValue();
		double bt = bSpinSlider->GetValue();

		CIELabToRGB(Lt, at, bt, r, g, b);

		redSpinSlider->SetValue(r);
		greenSpinSlider->SetValue(g);
		blueSpinSlider->SetValue(b);

		SetHSV();
		SetTemperature();
	}
	actColourPreview->SetBackgroundColour(wxColour(TO_COLOUR_COMPONENT(r), TO_COLOUR_COMPONENT(g), TO_COLOUR_COMPONENT(b)));
	actColourPreview->Refresh();

	SliderUpdated();
}

void ColourDialog::OnClick(wxCommandEvent &evt)
{
	if (evt.GetEventObject() == resetButton)
	{
		// Reset color
		r = r0;
		g = g0;
		b = b0;
		alpha = alpha0;

		UpdateGUI();
		SliderUpdated();
	}
}

void ColourDialog::SliderUpdated()
{
	wxCommandEvent slider_event(wxEVT_COMMAND_SLIDER_UPDATED, this->GetId());
	this->ProcessEvent(slider_event);
}

void ColourDialog::SetHSV()
{
	double fHue, fSaturation, fValue;

	RGBtoHSV(r, g, b, fHue, fSaturation, fValue);

	hueSpinSlider->SetValue(fHue);
	saturationSpinSlider->SetValue(fSaturation);
	valueSpinSlider->SetValue(fValue);
}

void ColourDialog::ComboBoxSelected(wxCommandEvent &evt)
{
	type = evt.GetSelection();

	TemperatureToRGB();
	SetHSV();
	SetLab();

	actColourPreview->SetBackgroundColour(wxColour(TO_COLOUR_COMPONENT(r), TO_COLOUR_COMPONENT(g), TO_COLOUR_COMPONENT(b)));
	actColourPreview->Refresh();

	//SliderUpdated();
}

void ColourDialog::UpdateGUI()
{
		redSpinSlider->SetValue(r);
		greenSpinSlider->SetValue(g);
		blueSpinSlider->SetValue(b);
		alphaSpinSlider->SetValue(alpha);

		SetHSV();
		SetTemperature();
		SetLab();

		origColourPreview->SetBackgroundColour(wxColour(TO_COLOUR_COMPONENT(r0), TO_COLOUR_COMPONENT(g0), TO_COLOUR_COMPONENT(b0)));
		origColourPreview->Refresh();

		actColourPreview->SetBackgroundColour(wxColour(TO_COLOUR_COMPONENT(r), TO_COLOUR_COMPONENT(g), TO_COLOUR_COMPONENT(b)));
		actColourPreview->Refresh();
}

IMPLEMENT_DYNAMIC_CLASS(ColourPicker, wxButton)

ColourDialog *ColourPicker::dialog = nullptr;

void ColourPicker::OnClick(wxMouseEvent &evt)
{
	SAFE_DELETE(dialog);
	wxString name = wxT("Colour selection - ") + GetName();
	dialog = new ColourDialog(this, wxID_ANY, name, r, g, b, a, dialogMode);
	dialog->Connect(wxID_ANY, wxEVT_COMMAND_SLIDER_UPDATED, wxCommandEventHandler(ColourPicker::OnDialog), NULL, this);
	dialog->Show();
}

void ColourPicker::OnDialog(wxCommandEvent &evt)
{
	Vector4 tmpCol(dialog->r, dialog->g, dialog->b, dialog->alpha);
	wxString value = TextWriter::WriteToWxString(tmpCol._v, 4, wxT(", "));

	if (strValue == value)
		return;

	r = tmpCol.x;
	g = tmpCol.y;
	b = tmpCol.z;
	a = tmpCol.w;
	strValue = value;

	this->SetBackgroundColour(wxColour(TO_COLOUR_COMPONENT(r), TO_COLOUR_COMPONENT(g), TO_COLOUR_COMPONENT(b)));

	SendEvent();
}

void ColourPicker::SendEvent()
{
	wxCommandEvent textEvent(wxEVT_COMMAND_TEXT_ENTER, this->GetId());

	textEvent.SetEventObject(this);
	textEvent.SetClientData(&strValue);
	textEvent.SetString(strValue);

	this->ProcessEvent(textEvent);
}

void ColourPicker::SetValue(const wxString &value)
{
	if (value.IsEmpty() || value == strValue)
		return;

	Vector4 tmpCol(0.0f, 0.0f, 0.0f, 0.0f);
	strValue = value;

	TextParser textParser(value.utf8_str());
	textParser.ParseVector(tmpCol._v, 4);

	r = tmpCol.x;
	g = tmpCol.y;
	b = tmpCol.z;
	a = tmpCol.w;

	this->SetBackgroundColour(wxColour(TO_COLOUR_COMPONENT(r), TO_COLOUR_COMPONENT(g), TO_COLOUR_COMPONENT(b)));

	// Update color dialog if it belongs to this object.
	if (dialog && dialog->GetParent() == this)
	{
		dialog->ResetValue(r, g, b, a);
	}
}

#undef TO_COLOUR_COMPONENT

// Register with wxWidgets' dynamic class subsystem.
IMPLEMENT_DYNAMIC_CLASS(ColourPickerXmlHandler, wxXmlResourceHandler)
