/**************************************************************

mv_chart.cpp (C-Munipack project)
Display class for photometry files and catalog files
Copyright (C) 2003 David Motl, dmotl@volny.cz

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.

**************************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <windows.h>
#include <math.h>

#include "mv_chart.h"
#include "properties_dlg.h"
#include "ctxhelp.h"

#define SIZE_BOOST_MIN	(-3)
#define SIZE_BOOST_MAX	3

enum {
	CMD_PROPERTIES = 100,
	CMD_COPY,
	CMD_SIZE_FIRST = 200,
	CMD_AP_FIRST = 300
};

#ifndef MAX
#define MAX(a, b) ((a)>(b) ? (a) : (b))
#endif

#ifndef MIN
#define MIN(a, b) ((a)<(b) ? (a) : (b))
#endif

//-------------------------   PRIVATE DATA   ---------------------------

static const struct {
	const char *label;
	UsrColor color;
} Types[CMPACK_SELECT_COUNT] = {
	{ NULL, CMPACK_COLOR_DEFAULT },
	{ "var", CMPACK_COLOR_RED },
	{ "comp", CMPACK_COLOR_GREEN },
	{ "check", CMPACK_COLOR_BLUE }
};

//-------------------------   HELPER FUNCTIONS   ---------------------------

static wchar_t *utf8_to_wcs(const char *str)
{
	if (str) {
		int len = MultiByteToWideChar(CP_UTF8, 0, str, -1, 0, 0);
		wchar_t *buf = (wchar_t*)malloc(len*sizeof(wchar_t));
		if (buf) 
			MultiByteToWideChar(CP_UTF8, 0, str, -1, buf, len);
		return buf;
	}
	return NULL;
}

static char *wcs_to_acp(const wchar_t *str)
{
	if (str) {
		int len = WideCharToMultiByte(CP_ACP, 0, str, -1, 0, 0, 0, 0);
		char *buf = (char*)malloc(len*sizeof(char));
		if (buf) 
			WideCharToMultiByte(CP_ACP, 0, str, -1, buf, len, 0, 0);
		return buf;
	}
	return NULL;
}

static char *utf8_to_acp(const char *str)
{
	wchar_t *wbuf = utf8_to_wcs(str);
	if (wbuf) {
		char *buf = wcs_to_acp(wbuf);
		free(wbuf);
		return buf;
	}
	return NULL;
}

TCCD_Chart::TCCD_Chart(void):m_Pht(NULL), m_Cat(NULL), m_JulianDate(0), m_Exposure(0),
	m_Filter(NULL), m_Object(NULL), m_Observer(NULL), m_Stars(0), m_Data(NULL), 
	m_Count(0), m_Capacity(0), m_Width(0), m_Height(0), m_ApertureIndex(0), 
	m_CenterImage(false), m_Title(NULL), m_SizeBoost(0)
{
	memset(&m_DateTime, 0, sizeof(CmpackDateTime));
	memset(&m_CanvasRc, 0, sizeof(RECT));
	HDC hdc = CreateCompatibleDC(0);
	m_hFont = CreateFont(-MulDiv(10, GetDeviceCaps(hdc, LOGPIXELSY), 72), 
		0, 0, 0, 0, 0, 0, 0, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
		CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, "Lucida Console");
	m_hBold = CreateFont(-MulDiv(10, GetDeviceCaps(hdc, LOGPIXELSY), 72), 
		0, 0, 0, FW_BOLD, 0, 0, 0, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
		CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, "Lucida Console");
	DeleteDC(hdc);
}

TCCD_Chart::~TCCD_Chart(void)
{
	Clear();
	DeleteObject(m_hFont);
	DeleteObject(m_hBold);
	free(m_Title);
	free(m_Filter);
	free(m_Object);
	free(m_Observer);
}

// Check if the buffer contains beginning of an image
bool TCCD_Chart::Test(const char *buf, int bytes)
{
	return cmpack_pht_test_buffer(buf, bytes, -1)!=0 ||
		cmpack_cat_test_buffer(buf, bytes, -1);
}

void TCCD_Chart::Clear(void)
{
	for (int i=0; i<m_Count; i++)
		free(m_Data[i].tag);
	free(m_Data);
	m_Data = NULL;
	m_Count = m_Capacity = 0;
	m_Apertures.Clear();
	m_ApertureIndex = -1;
	m_JulianDate = m_Exposure = 0;
	memset(&m_DateTime, 0, sizeof(CmpackDateTime));
	free(m_Title);
	m_Title = NULL;
	free(m_Filter);
	m_Filter = NULL;
	free(m_Object);
	m_Object = NULL;
	free(m_Observer);
	m_Observer = NULL;
	m_Stars = 0;
	if (m_Pht) {
		cmpack_pht_close(m_Pht);
		m_Pht = NULL;
	}
	if (m_Cat) {
		cmpack_cat_close(m_Cat);
		m_Cat = NULL;
	}
}

bool TCCD_Chart::Load(const char *filename)
{
	bool res = false;

	Clear();
	if (cmpack_pht_open(&m_Pht, filename, CMPACK_OPEN_READONLY, 0)==0) 
		res = true;
	else if (cmpack_cat_open(&m_Cat, filename, CMPACK_OPEN_READONLY, 0)==0)
		res = true;
	if (res) {
		SetFilePath(filename);
		UpdateParams();
		UpdateApertures();
		UpdateTitle();
		UpdateData();
	}
	return res;
}

void TCCD_Chart::SetShowFlags(int ShowFlags)
{
	m_CenterImage = (ShowFlags & lcp_center)!=0;
	Invalidate();
}

void TCCD_Chart::UpdateParams(void)
{
	CmpackPhtInfo info;

	if (m_Pht) {
		memset(&info, 0, sizeof(CmpackPhtInfo));
		cmpack_pht_get_info(m_Pht, CMPACK_PI_FRAME_PARAMS | CMPACK_PI_OBJECT |
			CMPACK_PI_LOCATION, &info);
		m_Width = info.width;
		m_Height = info.height;
		m_JulianDate = info.jd;
		cmpack_decodejd(m_JulianDate, &m_DateTime);
		free(m_Filter);
		m_Filter = utf8_to_acp(info.filter);
		free(m_Object);
		m_Object = utf8_to_acp(info.object.designation);
		free(m_Observer);
		m_Observer = NULL;
		m_Exposure = info.exptime;
		m_Stars = cmpack_pht_object_count(m_Pht);
	}
	if (m_Cat) {
		m_Width = cmpack_cat_get_width(m_Cat);
		m_Height = cmpack_cat_get_height(m_Cat);
		if (cmpack_cat_gkyd(m_Cat, "jd", &m_JulianDate)!=0)
			m_JulianDate = 0;
		cmpack_decodejd(m_JulianDate, &m_DateTime);
		free(m_Filter);
		m_Filter = utf8_to_acp(cmpack_cat_gkys(m_Cat, "filter"));
		free(m_Object);
		m_Object = utf8_to_acp(cmpack_cat_gkys(m_Cat, "object"));
		free(m_Observer);
		m_Observer = utf8_to_acp(cmpack_cat_gkys(m_Cat, "observer"));
		if (cmpack_cat_gkyd(m_Cat, "exptime", &m_Exposure)!=0)
			m_Exposure = 0;
		m_Stars = cmpack_cat_nstar(m_Cat);
	}
}

void TCCD_Chart::UpdateTitle(void)
{
	char buf[512], aux[64];
	CmpackPhtInfo info;

	if (m_Pht) {
		memset(&info, 0, sizeof(CmpackPhtInfo));
		cmpack_pht_get_info(m_Pht, CMPACK_PI_FRAME_PARAMS, &info);
		CmpackDateTime *dt = &m_DateTime;
		sprintf_s(aux, "%04d-%02d-%02d %d:%02d:%02d", dt->date.year, dt->date.month, dt->date.day,
			dt->time.hour, dt->time.minute, dt->time.second);
		const char *format = FormatName();
		sprintf_s(buf, "%s | %s | Filter: %s | Aperture: %d", (format ? format : ""),
			aux, (info.filter ? info.filter : ""), m_Apertures.GetId(m_ApertureIndex));
		SetTitle(buf);
	} else 
	if (m_Cat) {
		const char *format = FormatName();
		const char *objname = cmpack_cat_gkys(m_Cat, "object");
		const char *filter = cmpack_cat_gkys(m_Cat, "filter");
		sprintf_s(buf, "%s | Object: %s | Filter: %s", (format ? format : ""),
			(objname ? objname : ""), (filter ? filter : ""));
		SetTitle(buf);
	} else
		SetTitle(NULL);
}

void TCCD_Chart::UpdateApertures(void)
{
	int i, count;
	CmpackPhtAperture aper;

	m_Apertures.Clear();
	if (m_Pht) {
		count = cmpack_pht_aperture_count(m_Pht);
		for (i=0; i<count; i++) {
			cmpack_pht_get_aperture(m_Pht, i, CMPACK_PA_ID | CMPACK_PA_RADIUS, &aper);
			m_Apertures.Add(CAperture(aper.id, aper.radius));
		}
	} else
	if (m_Cat) {
		m_Apertures.Add(CAperture(1, 0));
	}
	m_ApertureIndex = LimitValue(m_ApertureIndex, 0, m_Apertures.Count()-1);
}

void TCCD_Chart::UpdateData(void)
{
	struct tSelectionData {
		int star_id;
		CmpackSelectionType type;
		int index;
	};

	struct tTagData {
		int star_id;
		const char *tag;
	};

	CmpackPhtObject obj;
	CmpackPhtData mag;
	CmpackCatObject cobj;

	for (int i=0; i<m_Count; i++)
		free(m_Data[i].tag);
	m_Count = 0;

	if (m_Pht) {
		int count = cmpack_pht_object_count(m_Pht);
		if (count > m_Capacity) {
			free(m_Data);
			m_Capacity = count;
			m_Data = (tChartData*)malloc(m_Capacity*sizeof(tChartData));
		}
		memset(m_Data, 0, count*sizeof(tChartData));
		double ref = GetRefMag(m_ApertureIndex);
		for (int i=0; i<count; i++) {
			tChartData *item = m_Data + i;
			cmpack_pht_get_object(m_Pht, i, CMPACK_PO_CENTER, &obj);
			item->xproj = obj.x;
			item->yproj = obj.y;
			mag.mag_valid = 0;
			cmpack_pht_get_data(m_Pht, i, m_ApertureIndex, &mag);
			item->filled = mag.mag_valid!=0;
			item->size = MAX(0, (mag.mag_valid ? 1.0 + 0.75*(ref - mag.magnitude) : 2.0));
			item->size *= pow(1.2, m_SizeBoost);
			m_Count++;
		}
	} else
	if (m_Cat) {
		int count = cmpack_cat_nstar(m_Cat);
		if (count > m_Capacity) {
			free(m_Data);
			m_Capacity = count;
			m_Data = (tChartData*)malloc(m_Capacity*sizeof(tChartData));
		}
		memset(m_Data, 0, count*sizeof(tChartData));
		double ref = GetRefMag(0);
		for (int i=0; i<count; i++) {
			tChartData *item = m_Data + i;
			cmpack_cat_get_star(m_Cat, i, CMPACK_OM_ID | CMPACK_OM_CENTER | CMPACK_OM_MAGNITUDE, &cobj);
			item->id = cobj.id;
			item->xproj = cobj.center_x;
			item->yproj = cobj.center_y;
			item->filled = cobj.refmag_valid!=0;
			item->size = MAX(0, (cobj.refmag_valid ? 1.0 + 0.75*(ref - cobj.refmagnitude) : 2.0));
			item->size *= pow(1.2, m_SizeBoost);
			m_Count++;
		}
		int var_count = 0, comp_count = 0, check_count = 0;
		int sel_count = cmpack_cat_get_selection_count(m_Cat);
		tSelectionData *sdata = (tSelectionData*)malloc(sel_count*sizeof(tSelectionData));
		for (int i=0; i<sel_count; i++) {
			cmpack_cat_get_selection(m_Cat, i, &sdata[i].star_id, &sdata[i].type);
			switch (sdata[i].type)
			{
			case CMPACK_SELECT_VAR:
				sdata[i].index = ++var_count;
				break;
			case CMPACK_SELECT_COMP:
				sdata[i].index = ++comp_count;
				break;
			case CMPACK_SELECT_CHECK:
				sdata[i].index = ++check_count;
				break;
			default:
				break;
			}
		}
		int tag_count = cmpack_cat_get_tag_count(m_Cat);
		tTagData *tdata = (tTagData*)malloc(tag_count*sizeof(tTagData));
		for (int i=0; i<tag_count; i++) {
			cmpack_cat_get_tag(m_Cat, i, &tdata[i].star_id, &tdata[i].tag);
		}
		for (int i=0; i<m_Count; i++) {
			int tag_index = -1;
			for (int j=0; j<tag_count; j++) {
				if (tdata[j].star_id == m_Data[i].id) {
					tag_index = j;
					break;
				}
			}
			int sel_index = -1;
			for (int j=0; j<sel_count; j++) {
				if (sdata[j].star_id == m_Data[i].id) {
					sel_index = j;
					break;
				}
			}
			if (sel_index>=0) {
				int index = sdata[sel_index].index;
				CmpackSelectionType type = sdata[sel_index].type;
				const char *tag = (tag_index>=0 ? tdata[tag_index].tag : NULL);
				size_t len = (256+(tag ? strlen(tag)+1 : 0));
				char *buf = (char*)malloc(len*sizeof(char));
				if (index==1)
					strcpy_s(buf, len, Types[type].label);
				else
					sprintf_s(buf, len, "%s #%d", Types[type].label, index);
				if (tag) {
					strcat_s(buf, len, "\n");
					strcat_s(buf, len, tag);
				}
				m_Data[i].tag = buf;
				m_Data[i].color = Types[type].color;
				m_Data[i].topmost = true;
			} else
			if (tag_index>=0) {
				m_Data[i].tag = StrDup(tdata[tag_index].tag);
				m_Data[i].color = CMPACK_COLOR_YELLOW;
				m_Data[i].topmost = true;
			}
		}
		free(sdata);
		free(tdata);
	}
}

const char *TCCD_Chart::FormatName(void) const
{
	if (m_Pht)
		return "C-Munipack photometry file";
	else if (m_Cat)
		return "C-Munipack catalogue file";
	else
		return NULL;
}

//
// Update reference magnitudes
//
double TCCD_Chart::GetRefMag(int index)
{
	int i, count, nstars;
	double *maglist, mag = 0;
	CmpackPhtData data;
	CmpackCatObject cobj;

	if (m_Pht) {
		count = cmpack_pht_aperture_count(m_Pht);
		if (index>=0 && index<count) {
			nstars = cmpack_pht_object_count(m_Pht);
			if (nstars>0) {
				maglist = (double*)malloc(nstars*sizeof(double));
				count = 0;
				for (i=0; i<nstars; i++) {
					data.mag_valid = 0;
					cmpack_pht_get_data(m_Pht, i, index, &data);
					if (data.mag_valid) 
						maglist[count++] = data.magnitude;
				}
				if (count>0) 
					cmpack_robustmean(count, maglist, &mag, NULL);
				free(maglist);
			}
		}
	} else
	if (m_Cat) {
		nstars = cmpack_cat_nstar(m_Cat);
		if (nstars>0) {
			maglist = (double*)malloc(nstars*sizeof(double));
			count = 0;
			for (i=0; i<nstars; i++) {
				cobj.refmag_valid = 0;
				cmpack_cat_get_star(m_Cat, i, CMPACK_OM_MAGNITUDE, &cobj);
				if (cobj.refmag_valid) 
					maglist[count++] = cobj.refmagnitude;
			}
			if (count>0) 
				cmpack_robustmean(count, maglist, &mag, NULL);
			free(maglist);
		}
	}
	return mag;
}

// Adjust scroll bars
void TCCD_Chart::OnSizeChanged(void)
{
	int left, top, right, bottom;
	RECT rc2;

	if (!m_hWnd)
		return;

	GetWindowRect(m_hWnd, &rc2);
	left = top = 0;
	right	= rc2.right - rc2.left;
    bottom	= rc2.bottom - rc2.top;

	HDC hDC = CreateCompatibleDC(0);
	HFONT oldfont = (HFONT)SelectObject(hDC, m_hFont);
	if (m_Title) {
		int t = text_height(hDC, m_Title) + 4;
		top += t;
	}
	SelectObject(hDC, oldfont);
	DeleteDC(hDC);
	
	SetRect(&m_CanvasRc, left, top, right, bottom);
}

// Keyboard event handler
void TCCD_Chart::OnKeyDown(int vKey)
{
	switch (vKey) 
	{
	case VK_ADD:
		SelectAperture(m_ApertureIndex+1);
		break;
	case VK_SUBTRACT:
		SelectAperture(m_ApertureIndex-1);
		break;
	case VK_MULTIPLY:
		SetSizeBoost(m_SizeBoost+1);
		break;
	case VK_DIVIDE:
		SetSizeBoost(m_SizeBoost-1);
		break;
	default:
		TCCD_Interface::OnKeyDown(vKey);
		break;
	}
}

// Set size boost factor
void TCCD_Chart::SetSizeBoost(int factor)
{
	m_SizeBoost = LimitValue(factor, SIZE_BOOST_MIN, SIZE_BOOST_MAX);
	UpdateData();
	Invalidate();
}

// Change aperture
void TCCD_Chart::SelectAperture(int index)
{
	m_ApertureIndex = LimitValue(index, 0, m_Apertures.Count()-1);
	UpdateTitle();
	UpdateData();
	Invalidate();
}

// Paint single object
void TCCD_Chart::DrawObject(HDC hDC, HBRUSH *brushes, HPEN *pens, tChartData *item, 
	double kx, double ky, int dx, int dy, double zoom, bool preview)
{
	int x, y, d;

	x = dx+RoundToInt(item->xproj*kx);
	y = dy+RoundToInt(item->yproj*ky);
	if (!preview)
		d = RoundToInt(2.0 * item->size * sqrt(zoom) + 1.0);
	else
		d = RoundToInt(2.0 * item->size * sqrt(zoom));
	if (d>=1 &&item->color>=0) {
		if (item->filled) {
			SelectObject(hDC, brushes[item->color]);
			SelectObject(hDC, pens[CMPACK_COLOR_OUTLINE]);
			Ellipse(hDC, x-(d+1)/2,y-(d+1)/2,x+(d/2)+1,y+(d/2)+1);
		} else 
		if (!preview) {
			SelectObject(hDC, pens[item->color]);
			Arc(hDC, x-(d+1)/2,y-(d+1)/2,x+(d/2)+1,y+(d/2)+1, 0, 0, 0, 0);
		}
	}
}

void TCCD_Chart::DrawLabel(HDC hDC, COLORREF *colors, tChartData *item, double kx, double ky, 
	int dx, int dy, double zoom)
{
	int x, y, d;
	RECT rc;

	x = dx+RoundToInt(item->xproj*kx);
	y = dy+RoundToInt(item->yproj*ky);
	d = RoundToInt(2.0 + 0.8 * item->size * sqrt(zoom));

	SetRect(&rc, x+d, y+d, x+d, y+d);
	DrawText(hDC, item->tag, (int)strlen(item->tag), &rc, DT_CALCRECT | DT_NOPREFIX);
	SetTextColor(hDC, colors[item->color]);
	SetBkMode(hDC, TRANSPARENT);
	if (rc.right>=m_CanvasRc.right) 
		rc.left = x-d-(rc.right-rc.left);
	if (rc.bottom>=m_CanvasRc.bottom)
		rc.top = y-d-(rc.bottom-rc.top);
	DrawText(hDC, item->tag, (int)strlen(item->tag), &rc, DT_NOCLIP | DT_NOPREFIX);
}

void TCCD_Chart::OnRender(HDC hDC)
// Renders an image into bitmap
{
	int		w, h, dx, dy;
	double	kx, ky, zoom;
	RECT	rc;

	if (m_Width<=0 || m_Height<=0)
		return;

	dx = dy = w = h = 0;
	zoom = 0.0;

	int cvs_w = m_CanvasRc.right-m_CanvasRc.left, 
		cvs_h = m_CanvasRc.bottom-m_CanvasRc.top;
	if (cvs_w>0 && cvs_h>0) {
		kx = (double)cvs_w / m_Width;
		ky = (double)cvs_h / m_Height;
		zoom = MIN(kx, ky);
		w = RoundToInt(zoom*m_Width);
		h = RoundToInt(zoom*m_Height);
		if (m_CenterImage) {
			dx = (cvs_w - w)/2;
			dy = (cvs_h - h)/2;
		}
	}
	dy += m_CanvasRc.top;
	dx += m_CanvasRc.left;

	HBRUSH bg = GetSysColorBrush(COLOR_WINDOW);
	if (dy>0) {
		SetRect(&rc, 0, 0, dx+w, dy);
		FillRect(hDC, &rc, bg);
	} 
	if (dx>0) {
		SetRect(&rc, 0, dy, dx, m_CanvasRc.bottom);
		FillRect(hDC, &rc, bg);
	}
	if (dx+w < m_CanvasRc.right) {
		SetRect(&rc, dx+w, 0, m_CanvasRc.right, dy+h);
		FillRect(hDC, &rc, bg);
	}
	if (dy+h < m_CanvasRc.bottom) {
		SetRect(&rc, dx, dy+h, m_CanvasRc.right, m_CanvasRc.bottom);
		FillRect(hDC, &rc, bg);
	}

	// Draw title
	if (m_Title) {
		HFONT oldfont = (HFONT)SelectObject(hDC, m_hFont);
		SetBkMode(hDC, TRANSPARENT);
		TextOut(hDC, 4, 2, m_Title, (int)strlen(m_Title));
		SelectObject(hDC, oldfont);
	}
 
	if (w>0 && h>0) 
		RenderChart(hDC, dx, dy, w, h, false, false);
}

//
// Render chart
//
void TCCD_Chart::RenderChart(HDC hDC, int left, int top, int width, int height, 
	bool for_print, bool for_preview)
{
	double		kx, ky, zoom;
	RECT		rc;
	HRGN		rgn;
	HFONT		oldfont;
	HPEN		oldpen, pens[CMPACK_N_COLORS];
	HBRUSH		oldbrush, brushes[CMPACK_N_COLORS];
	COLORREF	colors[CMPACK_N_COLORS];

	for (int i=0; i<CMPACK_N_COLORS; i++) {
		if (!for_print) 
			colors[i] = GetScreenColor((UsrColor)i);
		else
			colors[i] = GetPrintColor((UsrColor)i);
		pens[i] = CreatePen(PS_SOLID, 0, colors[i]);
		brushes[i] = CreateSolidBrush(colors[i]);
	}
		
	kx = (double)width / m_Width;
	ky = (double)height / m_Height;
	zoom = 0.5*(kx+ky);
	// Fill background
	HBRUSH hbr = CreateSolidBrush(for_print ? RGB(255,255,255) : RGB(25,0,50));
	SetRect(&rc, left, top, left+width, top+height);
	FillRect(hDC, &rc, hbr);
	DeleteObject(hbr);
	// Draw stars
	rgn = CreateRectRgn(left, top, left+width, top+height);
	SelectClipRgn(hDC, rgn);
	oldfont = (HFONT)SelectObject(hDC, m_hBold);
	SetBkMode(hDC, TRANSPARENT);
	oldpen = (HPEN) SelectObject(hDC, pens[0]);
	oldbrush = (HBRUSH)SelectObject(hDC, brushes[0]);
	// Draw non-topmost items
	for (int i=0; i<m_Count; i++) {
		if (!m_Data[i].topmost)
			DrawObject(hDC, brushes, pens, m_Data+i, kx, ky, left, top, zoom, for_preview);
	}
	// Draw topmost item
	for (int i=0; i<m_Count; i++) {
		if (m_Data[i].topmost)
			DrawObject(hDC, brushes, pens, m_Data+i, kx, ky, left, top, zoom, for_preview);
	}
	// Draw labels
	if (!for_preview) {
		for (int i=0; i<m_Count; i++) {
			if (m_Data[i].tag)
				DrawLabel(hDC, colors, m_Data+i, kx, ky, left, top, zoom);
		}
	}
	// Restore original pen and brush
	SelectClipRgn(hDC, NULL);
	DeleteObject(rgn);
	SelectObject(hDC, oldpen);
	SelectObject(hDC, oldbrush);
	SelectObject(hDC, oldfont);
	for (int i=0; i<CMPACK_N_COLORS; i++) {
		DeleteObject(brushes[i]);
		DeleteObject(pens[i]);
	}
}

// Set title
void TCCD_Chart::SetTitle(const char *caption)
{
	free(m_Title);
	m_Title = StrDup(caption);
	OnSizeChanged();
	Invalidate();
}

void TCCD_Chart::text_extents(HDC hDC, const char *buf, SIZE *size)
{
	if (buf) 
		GetTextExtentPoint(hDC, buf, (int)strlen(buf), size);
	else
		size->cx = size->cy = 0;
}

int TCCD_Chart::text_height(HDC hDC, const char *buf)
{
	SIZE size;
	text_extents(hDC, buf, &size);
	return size.cy;
}

// Makes preview and returns the HBITMAP
HBITMAP TCCD_Chart::MakePreview(int max_width, int max_height)
{
	int		w, h;

	if (m_Width<=0 || m_Height<=0)
		return NULL;

	// Compute thumbnail size
	if (m_Width>max_width || m_Height>max_height) {
		int stretchy = MulDiv(max_width, m_Height, m_Width);
		if (stretchy <= max_height) {
			w = max_width;
			h = MAX(1, stretchy);
		} else {
			int stretchx = MulDiv(max_height, m_Width, m_Height);
			w = MAX(1, stretchx);
			h = max_height;
		}
	} else {
		w = m_Width;
		h = m_Height;
	}
	
	HDC dc_main = GetDC(GetDesktopWindow());
	HDC hDC = CreateCompatibleDC(dc_main);
	HBITMAP bmp_thumbnail = CreateCompatibleBitmap(dc_main, w, h);
	ReleaseDC(GetDesktopWindow(), dc_main);
	HBITMAP old_bitmap = (HBITMAP)SelectObject(hDC, bmp_thumbnail);
	RenderChart(hDC, 0, 0, w, h, false, true);
	SelectObject(hDC, old_bitmap);
	DeleteDC(hDC);
	return bmp_thumbnail;
}

// Create base menu
HMENU TCCD_Chart::CreateCtxMenu(void)
{
	int pos = 0;
	HMENU hMenu, hPopup, hSubMenu;
	char buf[256];

	hMenu = TCCD_Interface::CreateCtxMenu();
	hPopup = GetSubMenu(hMenu, 0);

	InsertMenu(hPopup, pos++, MF_BYPOSITION | MF_STRING, CMD_PROPERTIES, "Properties...");
	InsertMenu(hPopup, pos++, MF_BYPOSITION | MF_SEPARATOR, 0, NULL);

	if (m_Apertures.Count()>1) {
		hSubMenu = CreateMenu();
		for (int i=0; i<m_Apertures.Count(); i++) {
			const CAperture *a = m_Apertures.Get(i);
			if (a->Radius()>0)
				sprintf_s(buf, "#%d (%.2f pxl)", a->Id(), a->Radius());
			else
				sprintf_s(buf, "#%d", a->Id());
			InsertMenu(hSubMenu, i, MF_BYPOSITION | MF_STRING, CMD_AP_FIRST+i, buf);
		}
		CheckMenuRadioItem(hSubMenu, 0, m_Apertures.Count()-1, m_ApertureIndex, MF_BYPOSITION);
		InsertMenu(hPopup, pos++, MF_BYPOSITION | MF_POPUP | MF_STRING, (UINT_PTR)hSubMenu, "Aperture");
	}

	hSubMenu = CreateMenu();
	InsertMenu(hSubMenu, 0, MF_BYPOSITION | MF_STRING, CMD_SIZE_FIRST, "Extra large");
	InsertMenu(hSubMenu, 1, MF_BYPOSITION | MF_STRING, CMD_SIZE_FIRST+1, "Large");
	InsertMenu(hSubMenu, 2, MF_BYPOSITION | MF_STRING, CMD_SIZE_FIRST+2, "Larger");
	InsertMenu(hSubMenu, 3, MF_BYPOSITION | MF_STRING, CMD_SIZE_FIRST+3, "Default");
	InsertMenu(hSubMenu, 4, MF_BYPOSITION | MF_STRING, CMD_SIZE_FIRST+4, "Smaller");
	InsertMenu(hSubMenu, 5, MF_BYPOSITION | MF_STRING, CMD_SIZE_FIRST+5, "Small");
	InsertMenu(hSubMenu, 6, MF_BYPOSITION | MF_STRING, CMD_SIZE_FIRST+6, "Extra small");
	CheckMenuRadioItem(hSubMenu, 0, 6, SIZE_BOOST_MAX-m_SizeBoost, MF_BYPOSITION);
	InsertMenu(hPopup, pos++, MF_BYPOSITION | MF_POPUP | MF_STRING, (UINT_PTR)hSubMenu, "Symbol size");

	InsertMenu(hPopup, pos++, MF_BYPOSITION | MF_SEPARATOR, 0, NULL);
	
	InsertMenu(hPopup, pos++, MF_BYPOSITION | MF_STRING, CMD_COPY, "Copy to clipboard");
	InsertMenu(hPopup, pos++, MF_BYPOSITION | MF_SEPARATOR, 0, NULL);

	return hMenu;
}

// Context menu commands
void TCCD_Chart::OnMenuCommand(int iCmd)
{
	if (iCmd>=CMD_SIZE_FIRST && iCmd<=CMD_SIZE_FIRST+6) {
		SetSizeBoost(SIZE_BOOST_MAX-(iCmd-CMD_SIZE_FIRST));
		return;
	}
	if (iCmd>=CMD_AP_FIRST && iCmd<CMD_AP_FIRST+m_Apertures.Count()) {
		SelectAperture(iCmd-CMD_AP_FIRST);
		return;
	}
	if (iCmd==CMD_PROPERTIES) {
		ShowProperties();
		return;
	}
	if (iCmd==CMD_COPY) {
		CopyToClipboard();
		return;
	}
	TCCD_Interface::OnMenuCommand(iCmd);
}

// Context menu commands
void TCCD_Chart::ShowProperties(void)
{
	CChartProperties dlg(this);
	dlg.ShowModal(m_hWnd);
}

// Copy image to clipboard
void TCCD_Chart::CopyToClipboard(void)
{
	int cvs_w, cvs_h, img_w, img_h;
	double kx, ky, zoom;

	cvs_w = m_CanvasRc.right-m_CanvasRc.left;
	cvs_h = m_CanvasRc.bottom-m_CanvasRc.top;
	if (cvs_w>0 && cvs_h>0 && m_Width>0 && m_Height>0) {
		kx = (double)cvs_w / m_Width;
		ky = (double)cvs_h / m_Height;
		zoom = MIN(kx, ky);
		img_w = RoundToInt(zoom*m_Width);
		img_h = RoundToInt(zoom*m_Height);
		if (OpenClipboard(GetParent(m_hWnd))) {
			EmptyClipboard();
			HDC wnd_dc = GetDC(m_hWnd);
			HDC hDC = CreateCompatibleDC(wnd_dc);
			HBITMAP hBmp = CreateCompatibleBitmap(wnd_dc, img_w, img_h);
			ReleaseDC(m_hWnd, wnd_dc);
			HBITMAP oldbmp = (HBITMAP)SelectObject(hDC, hBmp);
			RenderChart(hDC, 0, 0, img_w, img_h, true, false);
			SelectObject(hDC, oldbmp);
			DeleteDC(hDC);
			SetClipboardData(CF_BITMAP, hBmp);
			CloseClipboard();
		}
	}
}

// Get topic id (help page opened)
int TCCD_Chart::GetTopicId(void)
{
	return IDH_CHART_VIEW;
}
