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

chart_dlg.cpp (C-Munipack project)
The 'Plot chart' dialog
Copyright (C) 2008 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 <string.h>
#include <math.h>

#include "utils.h"
#include "main.h"
#include "ctxhelp.h"
#include "configuration.h"
#include "export_baavss.h"
#include "config.h"

struct tBAAVSSPageData
{
	CBaavssExportTab *tab;
	bool skip;
	tBAAVSSPageData(CBaavssExportTab *t):tab(t), skip(false) {}
};

static tBAAVSSPageData *MakePage(CBaavssExportTab *tab)
{
	return new tBAAVSSPageData(tab);
}

static void DeletePage(tBAAVSSPageData *data, gpointer *user_data)
{
	delete data->tab;
}

static void PrintLocation(const CLocation &loc, char *buf)
{
	double lon, lat;
	int positiveWest = (g_Project->Profile()->GetBool(CProfile::POSITIVE_WEST) ? 1 : 0);

	buf[0] = '\0';
	if (loc.Valid() && cmpack_strtolon2(loc.Lon(), positiveWest, &lon)==0 && cmpack_strtolat(loc.Lat(), &lat)==0) {
		char buf1[256], buf2[256];
		if (lat>=-90.0 && lat<=90.0) {
			if (lat>=0) {
				int x = (int)(lat*3600.0+0.5);
				sprintf(buf1, "%d %d %dN", (x/3600), ((x/60)%60), (x%60));
			} else {
				int x = (int)(-lat*3600.0+0.5);
				sprintf(buf1, "%d %d %dS", (x/3600), ((x/60)%60), (x%60));
			}
		}
		if (lon>=-180.0 && lon<=180.0) {
			if (lon>=0) {
				int x = (int)(lon*3600.0+0.5);
				sprintf(buf2, "%d %d %dE", (x/3600), ((x/60)%60), (x%60));
			} else {
				int x = (int)(-lon*3600.0+0.5);
				sprintf(buf2, "%d %d %dW", (x/3600), ((x/60)%60), (x%60));
			}
		}
		sprintf(buf, "%s %s", buf1, buf2);
	}
}

static bool indexedfield(const char *str, char type, int *index)
{
	size_t len = strlen(str);
	return (len >= 2) && (*str == type) && (strspn(str+1, "0123456789") == len-1) && (sscanf(str+1, "%9d", index)==1);
}

tBaavssExportParams::tBaavssExportParams():star_id(NULL), obs_code(NULL), obs_type(NULL), 
	chart(NULL), filter(NULL), scope(NULL), camera(NULL), lon(NULL), lat(NULL), notes(NULL)
{
	memset(cname, 0, CNAME_MAX*sizeof(gchar*));
	memset(cmag, 0, CNAME_MAX*sizeof(gchar*));
}

tBaavssExportParams::~tBaavssExportParams()
{
	g_free(star_id);
	g_free(obs_code);
	g_free(obs_type);
	g_free(chart);
	g_free(filter);
	g_free(scope);
	g_free(camera);
	g_free(lon);
	g_free(lat);
	g_free(notes);
	for (int i=0; i<CNAME_MAX; i++) {	
		g_free(cname[i]);
		g_free(cmag[i]);
	}
}

static void UpdateString(gchar *&str, const gchar *value)
{
	if (StrCmp0(str, value)) {
		g_free(str);
		if (value && *value!='\0') 
			str = g_strdup(value);
		else 
			str = NULL;
	}
}

//--------------------------   BAAVSS Export   -------------------------------

//
// Constructor
//
CBaavssExportDlg::CBaavssExportDlg(GtkWindow *pParent):m_Pages(NULL), m_ncomp(0)
{
	m_pDlg = gtk_dialog_new_with_buttons("Export light curve in BAAVSS format", pParent, 
		(GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, 
		GTK_STOCK_GO_BACK, GTK_RESPONSE_NO, GTK_STOCK_GO_FORWARD, GTK_RESPONSE_YES, 
		GTK_STOCK_SAVE, GTK_RESPONSE_OK, GTK_STOCK_HELP, GTK_RESPONSE_HELP, NULL);
	gtk_dialog_widget_standard_tooltips(GTK_DIALOG(m_pDlg));
	gtk_window_set_position(GTK_WINDOW(m_pDlg), GTK_WIN_POS_CENTER);
	m_pSaveBtn = get_dialog_widget_by_response(GTK_DIALOG(m_pDlg), GTK_RESPONSE_OK);
	m_pCancelBtn = get_dialog_widget_by_response(GTK_DIALOG(m_pDlg), GTK_RESPONSE_CANCEL);
	m_pBackBtn = get_dialog_widget_by_response(GTK_DIALOG(m_pDlg), GTK_RESPONSE_NO);
	gtk_widget_set_tooltip_text(m_pBackBtn, "Go back to the previous page");
	m_pNextBtn = get_dialog_widget_by_response(GTK_DIALOG(m_pDlg), GTK_RESPONSE_YES);
	gtk_widget_set_tooltip_text(m_pNextBtn, "Continue to the next page");
	g_signal_connect(G_OBJECT(m_pDlg), "response", G_CALLBACK(response_dialog), this);

	// Dialog icon
	gchar *icon = get_icon_file("lightcurve");
	gtk_window_set_icon(GTK_WINDOW(m_pDlg), gdk_pixbuf_new_from_file(icon, NULL));
	g_free(icon);

	// Page 1: Observation data / Variable star
	m_Pages = g_slist_append(m_Pages, MakePage(new CBaavssExportInitTab(this)));

	// Page 2: Observation data / Variable star
	m_Pages = g_slist_append(m_Pages, MakePage(new CBaavssExportNextTab(this)));

	// Page 3: Comparison star
	m_Pages = g_slist_append(m_Pages, MakePage(new CBaavssExportCompTab(this)));

	// Page 4: Comparison stars (ensemble photometry)
	m_Pages = g_slist_append(m_Pages, MakePage(new CBaavssExportEnsembleTab(this)));

	// Notebook
	m_pTabs = gtk_notebook_new();
	gtk_widget_set_size_request(m_pTabs, 512, 320);
	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(m_pDlg)->vbox), m_pTabs);
	gtk_container_set_border_width(GTK_CONTAINER(m_pTabs), 8);
	gtk_notebook_set_show_border(GTK_NOTEBOOK(m_pTabs), FALSE);
	gtk_notebook_set_show_tabs(GTK_NOTEBOOK(m_pTabs), FALSE);
	for (GSList *ptr=m_Pages; ptr!=NULL; ptr=ptr->next) 
		gtk_notebook_append_page(GTK_NOTEBOOK(m_pTabs), ((tBAAVSSPageData*)ptr->data)->tab->Widget(), NULL);

	gtk_widget_show_all(GTK_DIALOG(m_pDlg)->vbox);
}

CBaavssExportDlg::~CBaavssExportDlg()
{
	gtk_widget_destroy(m_pDlg);
	g_slist_foreach(m_Pages, (GFunc)DeletePage, NULL);
	g_slist_free(m_Pages);
}

void CBaavssExportDlg::LoadParams(void)
{
	// Variable star designation
	gchar *star_id = g_Project->GetStr("BAAVSS", "STAR_ID");
	if (!star_id || *star_id=='\0') {
		const CObjectCoords *obj = g_Project->ObjectCoords();
		if (obj->Name())
			star_id = g_strdup(obj->Name());
	}
	UpdateString(m_params.star_id, star_id);
	g_free(star_id);

	// Observer code
	gchar *obs_code = g_Project->GetStr("BAAVSS", "OBS_CODE");
	if (!obs_code || *obs_code=='\0') 
		obs_code = CConfig::GetStr("BAAVSS", "OBS_CODE");
	UpdateString(m_params.obs_code, obs_code);
	g_free(obs_code);

	// Observation type
	gchar *obs_type = g_Project->GetStr("BAAVSS", "OBS_TYPE");
	if (!obs_type || *obs_type=='\0') 
		obs_type = CConfig::GetStr("BAAVSS", "OBS_TYPE");
	UpdateString(m_params.obs_type, obs_type);
	g_free(obs_type);

	// Filter
	gchar *filter = g_Project->GetStr("BAAVSS", "FILTER");
	UpdateString(m_params.filter, filter);
	g_free(filter);

	// Chart
	gchar *chart = g_Project->GetStr("BAAVSS", "CHART");
	UpdateString(m_params.chart, chart);
	g_free(chart);

	// Telescope
	gchar *scope = g_Project->GetStr("BAAVSS", "SCOPE");
	if (!scope || *scope=='\0') 
		scope = CConfig::GetStr("BAAVSS", "SCOPE");
	UpdateString(m_params.scope, scope);
	g_free(scope);

	// Camera
	gchar *camera = g_Project->GetStr("BAAVSS", "CAMERA");
	if (!camera || *camera=='\0') 
		camera = CConfig::GetStr("BAAVSS", "CAMERA");
	UpdateString(m_params.camera, camera);
	g_free(camera);

	// Longitude
	UpdateString(m_params.lon, g_Project->Location()->Lon());

	// Latitude
	UpdateString(m_params.lat, g_Project->Location()->Lat());
	
	// Notes
	gchar *notes = g_Project->GetStr("BAAVSS", "NOTES");
	UpdateString(m_params.notes, notes);
	g_free(notes);

	int starId[CNAME_MAX];
	int ncomp = m_selection.GetStarList(CMPACK_SELECT_COMP, starId, CNAME_MAX);
	for (int i=0; i<CNAME_MAX; i++) {
		char key[256];

		// Comparison star designation
		if (i==0) 
			strcpy(key, "CNAME");
		else
			sprintf(key, "CNAME%d", i+1);
		gchar *cname = g_Project->GetStr("BAAVSS", key);
		if ((!cname || cname[0]=='\0') && (i<ncomp)) {
			const gchar *tag = m_tags.caption(starId[i]);
			if (tag)
				cname = g_strdup(tag);
		}
		UpdateString(m_params.cname[i], cname);
		g_free(cname);

		// Comparison star magnitude
		if (i==0)
			strcpy(key, "CMAG");
		else
			sprintf(key, "CMAG%d", i+1);
		gchar *cmag = g_Project->GetStr("BAAVSS", key);
		UpdateString(m_params.cmag[i], cmag);
		g_free(cmag);
	}
}

void CBaavssExportDlg::SaveParams(void)
{
	g_Project->SetStr("BAAVSS", "STAR_ID", m_params.star_id);
	g_Project->SetStr("BAAVSS", "OBS_CODE", m_params.obs_code);
	CConfig::SetStr("BAAVSS", "OBS_CODE", m_params.obs_code);
	g_Project->SetStr("BAAVSS", "OBS_TYPE", m_params.obs_type);
	CConfig::SetStr("BAAVSS", "OBS_TYPE", m_params.obs_type);
	g_Project->SetStr("BAAVSS", "FILTER", m_params.filter);
	g_Project->SetStr("BAAVSS", "CHART", m_params.chart);
	g_Project->SetStr("BAAVSS", "SCOPE", m_params.scope);
	CConfig::SetStr("BAAVSS", "SCOPE", m_params.scope);
	g_Project->SetStr("BAAVSS", "CAMERA", m_params.camera);
	CConfig::SetStr("BAAVSS", "CAMERA", m_params.camera);

	if (StrCmp0(g_Project->Location()->Lat(), m_params.lat) || StrCmp0(g_Project->Location()->Lon(), m_params.lon)) {
		CLocation loc;
		loc.SetLon(m_params.lon);
		loc.SetLat(m_params.lat);
		g_Project->SetLocation(loc);
	}

	g_Project->SetStr("BAAVSS", "NOTES", m_params.notes);
	for (int i=0; i<CNAME_MAX; i++) {
		char key[256];

		if (i==0) 
			strcpy(key, "CNAME");
		else
			sprintf(key, "CNAME%d", i+1);
		g_Project->SetStr("BAAVSS", key, m_params.cname[i]);

		if (i==0)
			strcpy(key, "CMAG");
		else
			sprintf(key, "CMAG%d", i+1);
		g_Project->SetStr("BAAVSS", key, m_params.cmag[i]);
	}
}

bool CBaavssExportDlg::Execute(const gchar *filename, const CTable &table, const CSelection &stars, 
	const CTags &tags, bool heliocentric, GError **error)
{
	m_selection = stars;
	m_tags = tags;
	m_ncomp = stars.CountStars(CMPACK_SELECT_COMP);

	// Check parameters
	if (m_ncomp==0) {
		set_error(error, "Select at least one comparison star");
		return false;
	}
	if (stars.CountStars(CMPACK_SELECT_COMP) > CNAME_MAX) {
		char msg[256];
		sprintf(msg, "Select no more than %d comparison stars", CNAME_MAX);
		set_error(error, msg);
		return false;
	}

	// Read parameters from the project
	LoadParams();

	// Initialize controls
	GoToFirstPage();
	if (gtk_dialog_run(GTK_DIALOG(m_pDlg)) != GTK_RESPONSE_OK) {
		gtk_widget_hide(m_pDlg);
		return false;
	}
	gtk_widget_hide(m_pDlg);

	// Save parameters to the project
	SaveParams();
	return SaveFile(filename, table, error);
}

void CBaavssExportDlg::response_dialog(GtkDialog *pDlg, gint response_id, CBaavssExportDlg *pMe)
{
	if (!pMe->OnResponseDialog(response_id)) 
		g_signal_stop_emission_by_name(pDlg, "response");
}

bool CBaavssExportDlg::OnResponseDialog(int response_id)
{
	switch (response_id)
	{
	case GTK_RESPONSE_OK:
		// Accept the dialog
		return LeavePageQuery(response_id);
	case GTK_RESPONSE_YES:
		// Go to next page
		if (LeavePageQuery(response_id))
			GoForward();
		return false;
	case GTK_RESPONSE_NO:
		// Go to previous page
		if (LeavePageQuery(response_id))
			GoBackward();
		return false;
	case GTK_RESPONSE_HELP:
		// Show context help
		g_MainWnd->ShowHelp(GTK_WINDOW(m_pDlg), IDH_BAAVSS_EXPORT);
		return false;
	}
	return true;
}

void CBaavssExportDlg::GoToFirstPage(void)
{
	GoToPage(0);
}

void CBaavssExportDlg::GoForward(void)
{
	switch (gtk_notebook_get_current_page(GTK_NOTEBOOK(m_pTabs)))
	{
	case 0:
		GoToPage(1);
		break;
	case 1:
		GoToPage(m_ncomp<=1 ? 2 : 3);	// Go to 'comparison star' page or 'ensemble comp. stars' page
		break;
	default:
		break;
	}
}

void CBaavssExportDlg::GoBackward()
{
	switch (gtk_notebook_get_current_page(GTK_NOTEBOOK(m_pTabs)))
	{
	case 1:
		GoToPage(0);
		break;
	case 2:
	case 3:
		GoToPage(1);
		break;
	default:
		break;
	}
}

void CBaavssExportDlg::GoToPage(int page)
{
	switch (page)
	{
	case 0:
		gtk_widget_hide(m_pBackBtn);
		gtk_widget_hide(m_pSaveBtn);
		gtk_widget_show(m_pNextBtn);
		break;
	case 1:
		gtk_widget_show(m_pBackBtn);
		gtk_widget_hide(m_pSaveBtn);
		gtk_widget_show(m_pNextBtn);
		break;
	case 2:
	case 3:
		gtk_widget_show(m_pBackBtn);
		gtk_widget_show(m_pSaveBtn);
		gtk_widget_hide(m_pNextBtn);
		break;
	}
	UpdateTab(page);
	gtk_notebook_set_current_page(GTK_NOTEBOOK(m_pTabs), page);
}

bool CBaavssExportDlg::LeavePageQuery(int response_id)
{
	int page = gtk_notebook_get_current_page(GTK_NOTEBOOK(m_pTabs));
	if (page>=0) {
		CBaavssExportTab *tab = ((tBAAVSSPageData*)g_slist_nth_data(m_Pages, page))->tab;
		if (response_id==GTK_RESPONSE_OK || response_id==GTK_RESPONSE_YES) {
			if (!tab->Check())
				return false;
		}
		tab->Apply();
		SaveParams();
	}
	return true;
}

void CBaavssExportDlg::UpdateTab(int page)
{
	((tBAAVSSPageData*)g_slist_nth_data(m_Pages, page))->tab->Update();
}

bool CBaavssExportDlg::SaveFile(const gchar *filepath, const CTable &table, GError **error)
{
	g_assert(filepath != NULL);

	FILE *f = open_file(filepath, "w");
	if (!f) {
		set_error(error, "Error when creating the file", filepath, CMPACK_ERR_OPEN_ERROR);
		return false;
	}

	CTable tmp;
	tmp.MakeCopy(table);

	CCSVWriter *writer = new CCSVWriter(f, '\t');
	ExportTable(*writer, tmp);
	delete writer;
	fclose(f);
	return true;
}

void CBaavssExportDlg::ExportTable(CCSVWriter &f, CTable &table)
{
	static const int jd_prec = 6, exp_prec = 2, mag_prec = 4;

	char buf[256];
	int index, jd_column = -1, exp_column = -1, dmag_column = -1, derr_column = -1, fname_column = -1;
	int vmag_column = -1, verr_column = -1, cmag_column[CNAME_MAX+1], cerr_column[CNAME_MAX+1];

	memset(cmag_column, -1, (CNAME_MAX+1)*sizeof(int));
	memset(cerr_column, -1, (CNAME_MAX+1)*sizeof(int));

	// Meta data
	f.SetMetaFormat(0, '\t');
	f.AddMeta("File Format", "CCD/DSLR v2.01");
	f.AddMeta("Observation Method", m_params.obs_type);
	f.AddMeta("Variable", m_params.star_id);
	f.AddMeta("Chart ID", m_params.chart);
	f.AddMeta("Observer Code", m_params.obs_code);
	PrintLocation(*g_Project->Location(), buf);
	f.AddMeta("Location", buf);
	if (m_params.scope && *m_params.scope!='\0')
		f.AddMeta("Telescope", m_params.scope);
	if (m_params.camera && *m_params.camera!='\0')
		f.AddMeta("Camera", m_params.camera);
	f.AddMeta("Magnitude type", "Instrumental");
	sprintf(buf, "%s %s", CMAKE_PROJECT_NAME, VERSION);
	f.AddMeta("Photometry software", buf);
	f.AddMeta("Analysis software", buf);
	if (m_params.notes && *m_params.notes!='\0')
		f.AddMeta("Comments", m_params.notes);
	f.AddLine();
	
	// Table header
	f.AddColumn("JulianDate");
	if (m_params.filter && *m_params.filter!='\0')
		f.AddColumn("Filter",m_params.filter);
	f.AddColumn("VarAbsMag");
	f.AddColumn("VarAbsErr");
	f.AddColumn("VarMag");
	f.AddColumn("VarErr");
	f.AddColumn("ExpLen");
	f.AddColumn("FileName");
	for (int i=0; i<m_ncomp; i++) {
		f.AddColumn("CmpStar");
		f.AddColumn("RefMag");
		f.AddColumn("CMMag");
		f.AddColumn("CmpErr");
	}

	CChannels *cx = table.ChannelsX();
	for (int i=0; i<cx->Count(); i++) {
		CChannel *ch = table.ChannelsX()->Get(i);
		if (ch->Info()==CChannel::DATA_JD) 
			jd_column = ch->Column();
	}

	CChannels *cy = table.ChannelsY();
	for (int i=0; i<cy->Count(); i++) {
		CChannel *ch = table.ChannelsY()->Get(i);
		switch (ch->Info())
		{
		case CChannel::DATA_VCMAG:
			if (strcmp(ch->Name(), "V-C")==0 || strcmp(ch->Name(), "V1-C")==0) {
				dmag_column = ch->Column();
				derr_column = ch->ColumnU();
			}
			break;
		case CChannel::DATA_VMAG:
			if (strcmp(ch->Name(), "V")==0 || strcmp(ch->Name(), "V1")==0) {
				vmag_column = ch->Column();
				verr_column = ch->ColumnU();
			}
			break;
		case CChannel::DATA_CMAG:
			if (strcmp(ch->Name(), "C")==0) {
				cmag_column[0] = ch->Column();
				cerr_column[0] = ch->ColumnU();
			}
			if (indexedfield(ch->Name(), 'C', &index) && index>=1 && index<=m_ncomp && index <= CNAME_MAX) {
				cmag_column[index] = ch->Column();
				cerr_column[index] = ch->ColumnU();
			}
			break;
		case CChannel::DATA_DURATION:
			if (strcmp(ch->Name(), "EXPOSURE")==0)
				exp_column = ch->Column();
			break;
		case CChannel::DATA_FILENAME:
			if (strcmp(ch->Name(), "FILENAME")==0)
				fname_column = ch->Column();
			break;
		default:
			break;
		}
	}

	// Table data
	if (m_ncomp==1) {
		// Observation with single comparison star
		// Standard magnitude of a comparison star (mag)
		double mag_offset = atof(m_params.cmag[0]);
		bool ok = table.Rewind();
		while (ok) {
			double date = 0, exposure = 0, dmag_value = 0, dmag_error = 0;
			double vmag_value = 0, verr_value = 0, cmag_value = 0, cerr_value = 0;
			gchar *fpath = NULL;
			bool show = table.GetDbl(jd_column, &date) && table.GetDbl(exp_column, &exposure) && table.GetStr(fname_column, &fpath);
			show = show && table.GetDbl(dmag_column, &dmag_value) && table.GetDbl(derr_column, &dmag_error);
			show = show && table.GetDbl(vmag_column, &vmag_value) && table.GetDbl(verr_column, &verr_value);
			show = show && table.GetDbl(cmag_column[0], &cmag_value) && table.GetDbl(cerr_column[0], &cerr_value);
			if (show) {
				int col = 0;
				f.Append();
				f.SetDbl(col++, date, jd_prec);
				if (m_params.filter && *m_params.filter!='\0')
					f.SetStr(col++, m_params.filter);
				f.SetDbl(col++, dmag_value + mag_offset, mag_prec);	// Calculated (standardized) magnitude
				f.SetDbl(col++, dmag_error, mag_prec);
				f.SetDbl(col++, vmag_value, mag_prec);				// Instrumental magnitude of the variable
				f.SetDbl(col++, verr_value, mag_prec);
				f.SetDbl(col++, exposure, exp_prec);
				gchar *fname = g_path_get_basename(fpath);
				f.SetStr(col++, fname);
				g_free(fname);
				f.SetStr(col++, m_params.cname[0]);
				f.SetDbl(col++, mag_offset, mag_prec);				// Reference magnitude of the comparison
				f.SetDbl(col++, cmag_value, mag_prec);				// Instrumental magnitude of the comparison
				f.SetDbl(col++, cerr_value, mag_prec);			
				g_free(fpath);
			}
			ok = table.Next();
		}
	} else 
	if (m_ncomp>1) {
		// Ensemble photometry with multiple comparison stars
		// Standard magnitude of an ensemble comparison
		double mag_offset = 0;
		for (int i=0; i<m_ncomp; i++) 
			mag_offset += pow(10.0, -0.4*atof(m_params.cmag[i]));
		mag_offset = -2.5*log10(mag_offset/m_ncomp);
		// Check star is mandatory
		bool ok = table.Rewind();
		while (ok) {
			gchar *fpath = NULL;
			double date = 0, exposure = 0;
			double dmag_value = 0, dmag_error = 0, vmag_value = 0, vmag_error = 0;
			bool show = table.GetDbl(jd_column, &date) && table.GetDbl(exp_column, &exposure) && table.GetStr(fname_column, &fpath);
			show = show && table.GetDbl(dmag_column, &dmag_value) && table.GetDbl(derr_column, &dmag_error);
			show = show && table.GetDbl(vmag_column, &vmag_value) && table.GetDbl(verr_column, &vmag_error);
			if (show) {
				int col = 0;
				f.Append();
				f.SetDbl(col++, date, jd_prec);
				if (m_params.filter && *m_params.filter!='\0')
					f.SetStr(col++, m_params.filter);
				f.SetDbl(col++, dmag_value + mag_offset, mag_prec);
				f.SetDbl(col++, dmag_error, mag_prec);
				f.SetDbl(col++, vmag_value, mag_prec);
				f.SetDbl(col++, vmag_error, mag_prec);
				f.SetDbl(col++, exposure, exp_prec);
				gchar *fname = g_path_get_basename(fpath);
				f.SetStr(col++, fname);
				g_free(fname);
				for (int i=0; i<m_ncomp; i++) {
					// Identification and reference magnitude of the comparison stars
					f.SetStr(col, m_params.cname[i]);
					f.SetDbl(col+1, atof(m_params.cmag[i]), mag_prec);
					double cmag_value = 0, cerr_value = 0;
					if (table.GetDbl(cmag_column[i+1], &cmag_value) & table.GetDbl(cerr_column[i+1], &cerr_value)) {
						// Instrumental magnitudes of comparison stars
						f.SetDbl(col+2, cmag_value, mag_prec);
						f.SetDbl(col+3, cerr_value, mag_prec);
					}
					col+=4;
				}
			}
			ok = table.Next();
		}
	}
}

//---------------------   TABS (COMMON) ------------------------

//
// Constructor - common controls for all pages
//
CBaavssExportTab::CBaavssExportTab(CBaavssExportDlg *parent, const gchar *caption):m_pParent(parent) 
{
	m_tab = gtk_vbox_new(FALSE, 8);

	// Title
	m_title = gtk_label_new(NULL);

	char buf[256];
	sprintf(buf, "<span size=\"xx-large\" weight=\"bold\">%s</span>", caption);
	gtk_label_set_markup(GTK_LABEL(m_title), buf);

	gtk_misc_set_alignment(GTK_MISC(m_title), 0.5, 0.5);
	gtk_box_pack_start(GTK_BOX(m_tab), m_title, FALSE, TRUE, 0);
}

//---------------------   TAB - OBSERVATION DATA ------------------------

//
// Constructor
//
CBaavssExportInitTab::CBaavssExportInitTab(CBaavssExportDlg *parent):CBaavssExportTab(parent, "Observation")
{
	GtkWidget *label;

	GtkWidget *box = gtk_table_new(7, 5, FALSE);
	gtk_box_pack_start(GTK_BOX(Widget()), box, TRUE, TRUE, 0);
	gtk_table_set_row_spacings(GTK_TABLE(box), 8);
	gtk_table_set_col_spacings(GTK_TABLE(box), 8);
	gtk_table_attach(GTK_TABLE(box), gtk_label_new(NULL), 0, 1, 0, 6, (GtkAttachOptions)(GTK_FILL | GTK_EXPAND), GTK_FILL, 0, 0);
	gtk_table_attach(GTK_TABLE(box), gtk_label_new(NULL), 4, 5, 0, 6, (GtkAttachOptions)(GTK_FILL | GTK_EXPAND), GTK_FILL, 0, 0);

	label = gtk_label_new("All fields are mandatory");
	gtk_misc_set_alignment(GTK_MISC(label), 0.5, 0.5);
	gtk_table_attach(GTK_TABLE(box), label, 1, 4, 0, 1, GTK_FILL, GTK_FILL, 0, 0);

	label = gtk_label_new("Star designation");
	gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
	gtk_table_attach(GTK_TABLE(box), label, 1, 2, 1, 2, GTK_FILL, GTK_FILL, 0, 0);
	m_StarId = gtk_entry_new_with_max_length(255);
	gtk_widget_set_tooltip_text(m_StarId, "A valid designation for the variable star.");
	gtk_table_attach(GTK_TABLE(box), m_StarId, 2, 3, 1, 2, (GtkAttachOptions)(GTK_FILL | GTK_EXPAND), GTK_FILL, 0, 0);

	label = gtk_label_new("Observer code");
	gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
	gtk_table_attach(GTK_TABLE(box), label, 1, 2, 2, 3, GTK_FILL, GTK_FILL, 0, 0);
	m_ObsCode = gtk_entry_new_with_max_length(5);
	gtk_widget_set_tooltip_text(m_ObsCode, "Your observer code, normally 3 characters.");
	gtk_table_attach(GTK_TABLE(box), m_ObsCode, 2, 3, 2, 3, (GtkAttachOptions)(GTK_FILL | GTK_EXPAND), GTK_FILL, 0, 0);

	label = gtk_label_new("Observation type");
	gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
	gtk_table_attach(GTK_TABLE(box), label, 1, 2, 3, 4, GTK_FILL, GTK_FILL, 0, 0);
	m_ObsType = gtk_combo_box_entry_new_text();
	gtk_combo_box_append_text(GTK_COMBO_BOX(m_ObsType), "CCD");
	gtk_combo_box_append_text(GTK_COMBO_BOX(m_ObsType), "DSLR");
	gtk_widget_set_tooltip_text(m_ObsType, "Type of the observation");
	gtk_table_attach(GTK_TABLE(box), m_ObsType, 2, 3, 3, 4, (GtkAttachOptions)(GTK_FILL | GTK_EXPAND), GTK_FILL, 0, 0);

	label = gtk_label_new("Filter");
	gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
	gtk_table_attach(GTK_TABLE(box), label, 1, 2, 4, 5, GTK_FILL, GTK_FILL, 0, 0);
	m_Filter = gtk_entry_new();
	gtk_widget_set_tooltip_text(m_Filter, "The short code of the filter.");
	gtk_table_attach(GTK_TABLE(box), m_Filter, 2, 3, 4, 5, (GtkAttachOptions)(GTK_FILL | GTK_EXPAND), GTK_FILL, 0, 0);
	m_SelectFilterBtn = gtk_button_new_with_label("Browse");
	gtk_widget_set_tooltip_text(m_SelectFilterBtn, "Click the \"Browse\" button to show a list of filters");
	g_signal_connect(G_OBJECT(m_SelectFilterBtn), "clicked", G_CALLBACK(button_clicked), this);
	gtk_table_attach(GTK_TABLE(box), m_SelectFilterBtn, 3, 4, 4, 5, (GtkAttachOptions)(GTK_FILL | GTK_SHRINK), GTK_FILL, 0, 0);

	label = gtk_label_new("Chart ID");
	gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
	gtk_table_attach(GTK_TABLE(box), label, 1, 2, 5, 6, GTK_FILL, GTK_FILL, 0, 0);
	m_Chart = gtk_entry_new_with_max_length(50);
	gtk_widget_set_tooltip_text(m_Chart, "Where used the BAA VSS chart id, or AAVSO chart id. Otherwise a short description of the chart, which must include the source of the comparison magnitudes.");
	gtk_table_attach(GTK_TABLE(box), m_Chart, 2, 3, 5, 6, (GtkAttachOptions)(GTK_FILL | GTK_EXPAND), GTK_FILL, 0, 0);
}

//
// Update contents of the controls
//
void CBaavssExportInitTab::Update()
{
	tBaavssExportParams *params = m_pParent->Params();

	if (params->star_id)
		gtk_entry_set_text(GTK_ENTRY(m_StarId), params->star_id);
	else
		gtk_entry_set_text(GTK_ENTRY(m_StarId), "");
	if (params->obs_code)
		gtk_entry_set_text(GTK_ENTRY(m_ObsCode), params->obs_code);
	else
		gtk_entry_set_text(GTK_ENTRY(m_ObsCode), "");
	if (params->obs_type)
		gtk_entry_set_text(GTK_ENTRY(GTK_BIN(m_ObsType)->child), params->obs_type);
	else
		gtk_entry_set_text(GTK_ENTRY(GTK_BIN(m_ObsType)->child), "");
	if (params->filter)
		gtk_entry_set_text(GTK_ENTRY(m_Filter), params->filter);
	else
		gtk_entry_set_text(GTK_ENTRY(m_Filter), "");
	if (params->chart)
		gtk_entry_set_text(GTK_ENTRY(m_Chart), params->chart);
	else
		gtk_entry_set_text(GTK_ENTRY(m_Chart), "");
}


//
// Validate the fields
//
bool CBaavssExportInitTab::Check()
{
	const gchar *star_id = gtk_entry_get_text(GTK_ENTRY(m_StarId));
	if (strlen(star_id)<=0) {
		ShowError(Window(), "Please, fill in the star identifier");
		gtk_widget_grab_focus(m_StarId);
		return false;
	}
	
	const gchar *obs_code = gtk_entry_get_text(GTK_ENTRY(m_ObsCode));
	if (strlen(obs_code)<=0) {
		ShowError(Window(), "Please, fill in the observer code");
		gtk_widget_grab_focus(m_ObsCode);
		return false;
	}
	if (strspn(obs_code, "ABCDEFGHIJKLMNOPQRSTUVWXYZ") != strlen(obs_code)) {
		ShowError(Window(), "The value is not a valid VSS observer code");
		gtk_widget_grab_focus(m_ObsCode);
		return false;
	}		

	const gchar *obs_type = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(m_ObsType)->child));
	if (strlen(obs_type)<=0) {
		ShowError(Window(), "Please, choose the observation type");
		gtk_widget_grab_focus(m_ObsType);
		return false;
	}
	if (strspn(obs_type, "ABCDEFGHIJKLMNOPQRSTUVWXYZ") != strlen(obs_type)) {
		ShowError(Window(), "The value is not a valid observation type");
		gtk_widget_grab_focus(m_ObsType);
		return false;
	}		

	const gchar *filter = gtk_entry_get_text(GTK_ENTRY(m_Filter));
	if (strlen(filter)<=0) {
		ShowError(Window(), "Please, fill in the filter");
		gtk_widget_grab_focus(m_Filter);
		return false;
	}
	if (strspn(filter, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz") != strlen(filter)) {
		ShowError(Window(), "The value is not a valid filter");
		gtk_widget_grab_focus(m_Filter);
		return false;
	}		

	const gchar *chart = gtk_entry_get_text(GTK_ENTRY(m_Chart));
	if (strlen(chart)<=0) {
		ShowError(Window(), "Please, fill in the chart - sequence ID");
		gtk_widget_grab_focus(m_Chart);
		return false;
	}

	return true;
}


//
// Save values from controls
//
void CBaavssExportInitTab::Apply()
{
	tBaavssExportParams *params = m_pParent->Params();

	UpdateString(params->star_id, gtk_entry_get_text(GTK_ENTRY(m_StarId)));
	UpdateString(params->obs_code, gtk_entry_get_text(GTK_ENTRY(m_ObsCode)));
	UpdateString(params->obs_type, gtk_entry_get_text(GTK_ENTRY(GTK_BIN(m_ObsType)->child)));
	UpdateString(params->filter, gtk_entry_get_text(GTK_ENTRY(m_Filter)));
	UpdateString(params->chart, gtk_entry_get_text(GTK_ENTRY(m_Chart)));
}


//
// Button click handler
//
void CBaavssExportInitTab::button_clicked(GtkWidget *button, CBaavssExportInitTab *pDlg)
{
	pDlg->OnButtonClicked(button);
}

void CBaavssExportInitTab::OnButtonClicked(GtkWidget *pBtn)
{
	if (pBtn == m_SelectFilterBtn) {
		const gchar *obs_type = gtk_entry_get_text(GTK_ENTRY(GTK_BIN(m_ObsType)->child));
		bool ccd = strcmp(obs_type, "CCD")==0, dslr = strcmp(obs_type, "DSLR")==0;
		if (!ccd && !dslr) {
			ShowError(Window(), "Please, select the observation type first.");
			return;
		}
		BaavssExportSelectFilterDlg dlg(Window(), ccd, dslr);
		gchar *str = dlg.Execute(gtk_entry_get_text(GTK_ENTRY(m_Filter)));
		if (str) {
			gtk_entry_set_text(GTK_ENTRY(m_Filter), str);
			g_free(str);
		}
	}
}

//---------------------   TAB - OBSERVATION DATA ------------------------

//
// Constructor
//
CBaavssExportNextTab::CBaavssExportNextTab(CBaavssExportDlg *parent):CBaavssExportTab(parent, "Location / equipment")
{
	GtkWidget *label;

	GtkWidget *box = gtk_table_new(8, 5, FALSE);
	gtk_box_pack_start(GTK_BOX(Widget()), box, TRUE, TRUE, 0);
	gtk_table_set_row_spacings(GTK_TABLE(box), 8);
	gtk_table_set_col_spacings(GTK_TABLE(box), 8);
	gtk_table_attach(GTK_TABLE(box), gtk_label_new(NULL), 0, 1, 0, 8, (GtkAttachOptions)(GTK_FILL | GTK_EXPAND), GTK_FILL, 0, 0);
	gtk_table_attach(GTK_TABLE(box), gtk_label_new(NULL), 4, 5, 0, 8, (GtkAttachOptions)(GTK_FILL | GTK_EXPAND), GTK_FILL, 0, 0);

	label = gtk_label_new("These fields are mandatory");
	gtk_misc_set_alignment(GTK_MISC(label), 0.5, 0.5);
	gtk_table_attach(GTK_TABLE(box), label, 1, 4, 0, 1, GTK_FILL, GTK_FILL, 0, 0);

	label = gtk_label_new("Longitude");
	gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
	gtk_table_attach(GTK_TABLE(box), label, 1, 2, 1, 2, GTK_FILL, GTK_FILL, 0, 0);
	m_Lon = gtk_entry_new_with_max_length(32);
	gtk_widget_set_tooltip_text(m_Lon, "Your longitude.");
	gtk_table_attach(GTK_TABLE(box), m_Lon, 2, 3, 1, 2, (GtkAttachOptions)(GTK_FILL | GTK_EXPAND), GTK_FILL, 0, 0);

	label = gtk_label_new("Latitude");
	gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
	gtk_table_attach(GTK_TABLE(box), label, 1, 2, 2, 3, GTK_FILL, GTK_FILL, 0, 0);
	m_Lat = gtk_entry_new_with_max_length(32);
	gtk_widget_set_tooltip_text(m_Lat, "Your latitude");
	gtk_table_attach(GTK_TABLE(box), m_Lat, 2, 3, 2, 3, (GtkAttachOptions)(GTK_FILL | GTK_EXPAND), GTK_FILL, 0, 0);

	label = gtk_label_new("These fields are optional");
	gtk_misc_set_alignment(GTK_MISC(label), 0.5, 0.5);
	gtk_table_attach(GTK_TABLE(box), label, 1, 4, 4, 5, GTK_FILL, GTK_FILL, 0, 0);

	label = gtk_label_new("Telescope");
	gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
	gtk_table_attach(GTK_TABLE(box), label, 1, 2, 5, 6, GTK_FILL, GTK_FILL, 0, 0);
	m_Scope = gtk_entry_new_with_max_length(255);
	gtk_widget_set_tooltip_text(m_Scope, "A short description of the telescope used.");
	gtk_table_attach(GTK_TABLE(box), m_Scope, 2, 3, 5, 6, (GtkAttachOptions)(GTK_FILL | GTK_EXPAND), GTK_FILL, 0, 0);

	label = gtk_label_new("Camera");
	gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
	gtk_table_attach(GTK_TABLE(box), label, 1, 2, 6, 7, GTK_FILL, GTK_FILL, 0, 0);
	m_Camera = gtk_entry_new_with_max_length(255);
	gtk_widget_set_tooltip_text(m_Camera, "Your observer code, normally 3 characters.");
	gtk_table_attach(GTK_TABLE(box), m_Camera, 2, 3, 6, 7, (GtkAttachOptions)(GTK_FILL | GTK_EXPAND), GTK_FILL, 0, 0);

	label = gtk_label_new("Comments");
	gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
	gtk_table_attach(GTK_TABLE(box), label, 1, 2, 7, 8, GTK_FILL, GTK_FILL, 0, 0);
	m_Notes = gtk_entry_new_with_max_length(255);
	gtk_widget_set_tooltip_text(m_Notes, "Any information which you think would be useful such as the observing conditions should be included in the comment field.");
	gtk_table_attach(GTK_TABLE(box), m_Notes, 2, 3, 7, 8, (GtkAttachOptions)(GTK_FILL | GTK_EXPAND), GTK_FILL, 0, 0);
}

//
// Update contents of the controls
//
void CBaavssExportNextTab::Update()
{
	tBaavssExportParams *params = m_pParent->Params();

	if (params->scope)
		gtk_entry_set_text(GTK_ENTRY(m_Scope), params->scope);
	else
		gtk_entry_set_text(GTK_ENTRY(m_Scope), "");
	if (params->camera)
		gtk_entry_set_text(GTK_ENTRY(m_Camera), params->camera);
	else
		gtk_entry_set_text(GTK_ENTRY(m_Camera), "");
	if (params->notes)
		gtk_entry_set_text(GTK_ENTRY(m_Notes), params->notes);
	else
		gtk_entry_set_text(GTK_ENTRY(m_Notes), "");
	if (params->lon)
		gtk_entry_set_text(GTK_ENTRY(m_Lon), params->lon);
	else
		gtk_entry_set_text(GTK_ENTRY(m_Lon), "");
	if (params->lat)
		gtk_entry_set_text(GTK_ENTRY(m_Lat), params->lat);
	else
		gtk_entry_set_text(GTK_ENTRY(m_Lat), "");
}


//
// Validate the fields
//
bool CBaavssExportNextTab::Check()
{
	double x, y;
	char buf[512];
	int positiveWest = (g_Project->Profile()->GetBool(CProfile::POSITIVE_WEST) ? 1 : 0);

	const gchar *lon = gtk_entry_get_text(GTK_ENTRY(m_Lon));
	if (strlen(lon)<=0 || cmpack_strtolon2(lon, positiveWest, &x)!=0) {
		ShowError(Window(), "Please, fill in the longitude.");
		gtk_widget_grab_focus(m_Lon);
		return false;
	}
	cmpack_lontostr(x, buf, 512);
	gtk_entry_set_text(GTK_ENTRY(m_Lon), buf);
	
	const gchar *lat = gtk_entry_get_text(GTK_ENTRY(m_Lat));
	if (strlen(lat)<=0 || cmpack_strtolat(lat, &y)!=0) {
		ShowError(Window(), "Please, fill in the latitude.");
		gtk_widget_grab_focus(m_Lat);
		return false;
	}
	cmpack_lattostr(y, buf, 512);
	gtk_entry_set_text(GTK_ENTRY(m_Lat), buf);

	return true;
}


//
// Save values from controls
//
void CBaavssExportNextTab::Apply()
{
	tBaavssExportParams *params = m_pParent->Params();

	UpdateString(params->scope, gtk_entry_get_text(GTK_ENTRY(m_Scope)));
	UpdateString(params->camera, gtk_entry_get_text(GTK_ENTRY(m_Camera)));
	UpdateString(params->notes, gtk_entry_get_text(GTK_ENTRY(m_Notes)));
	UpdateString(params->lon, gtk_entry_get_text(GTK_ENTRY(m_Lon)));
	UpdateString(params->lat, gtk_entry_get_text(GTK_ENTRY(m_Lat)));
}

CBaavssExportCompTab::CBaavssExportCompTab(CBaavssExportDlg *parent):CBaavssExportTab(parent, "Comparison star")
{
	GtkWidget *box = gtk_table_new(2, 5, FALSE);
	gtk_box_pack_start(GTK_BOX(Widget()), box, TRUE, TRUE, 0);
	gtk_table_set_row_spacings(GTK_TABLE(box), 8);
	gtk_table_set_col_spacings(GTK_TABLE(box), 8);
	gtk_table_attach(GTK_TABLE(box), gtk_label_new(NULL), 0, 1, 0, 3, (GtkAttachOptions)(GTK_FILL | GTK_EXPAND), GTK_FILL, 0, 0);
	gtk_table_attach(GTK_TABLE(box), gtk_label_new(NULL), 4, 5, 0, 3, (GtkAttachOptions)(GTK_FILL | GTK_EXPAND), GTK_FILL, 0, 0);

	m_label1 = gtk_label_new("Star designation");
	gtk_misc_set_alignment(GTK_MISC(m_label1), 1.0, 0.5);
	gtk_table_attach(GTK_TABLE(box), m_label1, 1, 2, 0, 1, GTK_FILL, GTK_FILL, 0, 0);
	m_StarId = gtk_entry_new_with_max_length(32);
	gtk_widget_set_tooltip_text(m_StarId, "Comparison star name or label");
	gtk_table_attach(GTK_TABLE(box), m_StarId, 2, 3, 0, 1, (GtkAttachOptions)(GTK_FILL | GTK_EXPAND), GTK_FILL, 0, 0);
	g_signal_connect(G_OBJECT(m_StarId), "changed", G_CALLBACK(entry_changed), this);

	m_label2 = gtk_label_new("Magnitude");
	gtk_misc_set_alignment(GTK_MISC(m_label2), 1.0, 0.5);
	gtk_table_attach(GTK_TABLE(box), m_label2, 1, 2, 1, 2, GTK_FILL, GTK_FILL, 0, 0);
	m_Mag = gtk_entry_new_with_max_length(32);
	gtk_widget_set_tooltip_text(m_Mag, "Reference magnitude of the comparison star");
	gtk_table_attach(GTK_TABLE(box), m_Mag, 2, 3, 1, 2, (GtkAttachOptions)(GTK_FILL | GTK_EXPAND), GTK_FILL, 0, 0);
	g_signal_connect(G_OBJECT(m_Mag), "changed", G_CALLBACK(entry_changed), this);
}

//
// Update contents of the controls
//
void CBaavssExportCompTab::Update()
{
	tBaavssExportParams *params = m_pParent->Params();

	if (params->cname[0])
		gtk_entry_set_text(GTK_ENTRY(m_StarId), params->cname[0]);
	else
		gtk_entry_set_text(GTK_ENTRY(m_StarId), "");
	gtk_widget_set_sensitive(GTK_WIDGET(m_label1), TRUE);
	gtk_widget_set_sensitive(GTK_WIDGET(m_StarId), TRUE);
	if (params->cmag[0])
		gtk_entry_set_text(GTK_ENTRY(m_Mag), params->cmag[0]);
	else
		gtk_entry_set_text(GTK_ENTRY(m_Mag), "");
	gtk_widget_set_sensitive(GTK_WIDGET(m_label2), TRUE);
	gtk_widget_set_sensitive(GTK_WIDGET(m_Mag), TRUE);
}


//
// Validate the fields
//
bool CBaavssExportCompTab::Check()
{
	const gchar *star_id = gtk_entry_get_text(GTK_ENTRY(m_StarId));
	if (strlen(star_id)<=0) {
		ShowError(Window(), "Please, fill in the star identifier");
		gtk_widget_grab_focus(m_StarId);
		return false;
	}
	
	const gchar *mag = gtk_entry_get_text(GTK_ENTRY(m_Mag));
	if (strlen(mag)<=0) {
		ShowError(Window(), "Please, fill in the magnitude");
		gtk_widget_grab_focus(m_Mag);
		return false;
	}

	return true;
}


//
// Save values from controls
//
void CBaavssExportCompTab::Apply()
{
	tBaavssExportParams *params = m_pParent->Params();

	UpdateString(params->cname[0], gtk_entry_get_text(GTK_ENTRY(m_StarId)));
	UpdateString(params->cmag[0], gtk_entry_get_text(GTK_ENTRY(m_Mag)));
}


//
// Text entry changed
//
void CBaavssExportCompTab::entry_changed(GtkWidget *widget, CBaavssExportCompTab *pDlg)
{
	pDlg->OnEntryChanged(widget);
}

void CBaavssExportCompTab::OnEntryChanged(GtkWidget *pEntry)
{
}

CBaavssExportEnsembleTab::CBaavssExportEnsembleTab(CBaavssExportDlg *parent):CBaavssExportTab(parent, "Comparison stars")
{
	GtkWidget *scr_wnd = gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scr_wnd), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
	gtk_box_pack_start(GTK_BOX(Widget()), scr_wnd, TRUE, TRUE, 0);

	GtkWidget *box = gtk_table_new(CNAME_MAX+2, 5, FALSE);
	gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scr_wnd), box);
	gtk_table_set_row_spacings(GTK_TABLE(box), 8);
	gtk_table_set_col_spacings(GTK_TABLE(box), 8);
	gtk_table_attach(GTK_TABLE(box), gtk_label_new(NULL), 0, 1, 0, CNAME_MAX+1, (GtkAttachOptions)(GTK_FILL | GTK_EXPAND), GTK_FILL, 0, 0);
	gtk_table_attach(GTK_TABLE(box), gtk_label_new(NULL), 4, 5, 0, CNAME_MAX+1, (GtkAttachOptions)(GTK_FILL | GTK_EXPAND), GTK_FILL, 0, 0);
	gtk_table_attach(GTK_TABLE(box), gtk_label_new(NULL), 0, 5, CNAME_MAX+1, CNAME_MAX+2, GTK_FILL, (GtkAttachOptions)(GTK_FILL | GTK_EXPAND), 0, 0);

	m_Header[0] = gtk_label_new("Star designation");
	gtk_misc_set_alignment(GTK_MISC(m_Header[0]), 0.0, 0.5);
	gtk_table_attach(GTK_TABLE(box), m_Header[0], 2, 3, 0, 1, GTK_FILL, GTK_FILL, 0, 0);

	m_Header[1] = gtk_label_new("Magnitude");
	gtk_misc_set_alignment(GTK_MISC(m_Header[1]), 0.0, 0.5);
	gtk_table_attach(GTK_TABLE(box), m_Header[1], 3, 4, 0, 1, GTK_FILL, GTK_FILL, 0, 0);

	for (int i=0; i<CNAME_MAX; i++) {
		m_RowLabel[i] = gtk_label_new("comp #XX");
		gtk_misc_set_alignment(GTK_MISC(m_RowLabel[i]), 1.0, 0.5);
		gtk_table_attach(GTK_TABLE(box), m_RowLabel[i], 1, 2, i+1, i+2, (GtkAttachOptions)(GTK_FILL | GTK_EXPAND), GTK_FILL, 0, 0);

		m_StarId[i] = gtk_entry_new_with_max_length(32);
		gtk_widget_set_tooltip_text(m_StarId[i], "Comparison star name or label");
		gtk_table_attach(GTK_TABLE(box), m_StarId[i], 2, 3, i+1, i+2, (GtkAttachOptions)(GTK_FILL | GTK_EXPAND), GTK_FILL, 0, 0);
		g_signal_connect(G_OBJECT(m_StarId[i]), "changed", G_CALLBACK(entry_changed), this);

		m_Mag[i] = gtk_entry_new_with_max_length(32);
		gtk_widget_set_tooltip_text(m_Mag[i], "Reference magnitude of the comparison star");
		gtk_table_attach(GTK_TABLE(box), m_Mag[i], 3, 4, i+1, i+2, (GtkAttachOptions)(GTK_FILL | GTK_EXPAND), GTK_FILL, 0, 0);
		g_signal_connect(G_OBJECT(m_Mag[i]), "changed", G_CALLBACK(entry_changed), this);
	}
}

//
// Update contents of the controls
//
void CBaavssExportEnsembleTab::Update()
{
	tBaavssExportParams *params = m_pParent->Params();
	CSelection *stars = m_pParent->Selection();

	int starId[CNAME_MAX];
	stars->GetStarList(CMPACK_SELECT_COMP, starId, CNAME_MAX);

	for (int i=0; i<CNAME_MAX; i++) {
		if (i < m_pParent->NComp()) {
			gtk_label_set_text(GTK_LABEL(m_RowLabel[i]), stars->Caption(starId[i]));
			gtk_entry_set_text(GTK_ENTRY(m_StarId[i]), params->cname[i]);
			gtk_entry_set_text(GTK_ENTRY(m_Mag[i]), params->cmag[i]);

			gtk_widget_show(GTK_WIDGET(m_RowLabel[i]));
			gtk_widget_show(GTK_WIDGET(m_StarId[i]));
			gtk_widget_show(GTK_WIDGET(m_Mag[i]));
		} else {
			gtk_widget_hide(GTK_WIDGET(m_RowLabel[i]));
			gtk_widget_hide(GTK_WIDGET(m_StarId[i]));
			gtk_widget_hide(GTK_WIDGET(m_Mag[i]));
		}
	}
}


//
// Validate the fields
//
bool CBaavssExportEnsembleTab::Check()
{
	for (int i=0; i<m_pParent->NComp(); i++) {
		const gchar *star_id = gtk_entry_get_text(GTK_ENTRY(m_StarId[i]));
		if (strlen(star_id)<=0) {
			ShowError(Window(), "Please, fill in the star identifier");
			gtk_widget_grab_focus(m_StarId[i]);
			return false;
		}
	
		const gchar *mag = gtk_entry_get_text(GTK_ENTRY(m_Mag[i]));
		if (strlen(mag)<=0) {
			ShowError(Window(), "Please, fill in the magnitude");
			gtk_widget_grab_focus(m_Mag[i]);
			return false;
		}
	}

	return true;
}


//
// Save values from controls
//
void CBaavssExportEnsembleTab::Apply()
{
	tBaavssExportParams *params = m_pParent->Params();

	for (int i=0; i<m_pParent->NComp(); i++) {
		UpdateString(params->cname[i], gtk_entry_get_text(GTK_ENTRY(m_StarId[i])));
		UpdateString(params->cmag[i], gtk_entry_get_text(GTK_ENTRY(m_Mag[i])));
	}
}


//
// Text entry changed
//
void CBaavssExportEnsembleTab::entry_changed(GtkWidget *widget, CBaavssExportEnsembleTab *pDlg)
{
	pDlg->OnEntryChanged(widget);
}

void CBaavssExportEnsembleTab::OnEntryChanged(GtkWidget *pEntry)
{
}

//----------------------------   SELECT FILTER DIALOG   ----------------------------

enum tColumnId
{
	COL_NAME,
	COL_DESCRIPTION,
	NCOLS
};

const static struct tTreeViewColumn {
	const char *caption;		// Column name
	int column;					// Model column index
	double align;
} ListColumns[] = {
	{ "Filter",			COL_NAME },
	{ "Designation",	COL_DESCRIPTION },
	{ NULL }
};

const static struct tTreeViewRow {
	const char *name, *description;
	bool ccd, dslr;
} ListRows[] = {
	{ "B",		"Johnson B", true },
	{ "U",		"Johnson U", true },
	{ "V",		"Johnson V", true },
	{ "I",		"Cousins I", true },
	{ "R",		"Cousins R", true },
	{ "SG",		"Sloan G", true },
	{ "SI",		"Sloan I", true },
	{ "SR",		"Sloan R", true },
	{ "SU",		"Sloan U", true },
	{ "SZ",		"Sloan Z", true },
	{ "BesB",	"Bessell-B", true },
	{ "BesV",	"Bessell-V", true },
	{ "H",		"NIR 1.6 micron", true },
	{ "J",		"NIR 1.2 micron", true },
	{ "K",		"NIR 2.2 micron", true },
	{ "TB",		"DSLR or color CCD blue channel", false, true },
	{ "TG",		"DSLR or color CCD green channel", false, true },
	{ "TR",		"DSLR or color CCD red channel", false, true },
	{ "TY",		"Yellow filter", false, true },
	{ "VG",		"Corrected Green Channel to Johnson V", false, true },
	{ "C",		"Clear", true },
	{ "CR",		"Clear (unfiltered) using R-band comp star magnitudes", true },
	{ "CV",		"Clear (unfiltered) using V-band comp star magnitudes", true },
	{ "IRB",	"Infrared Blocking", true },
	{ "N",		"No filter", true },
	{ NULL }
};

//
// Constructor
//
BaavssExportSelectFilterDlg::BaavssExportSelectFilterDlg(GtkWindow *pParent, bool ccd, bool dslr):m_SelPath(NULL)
{
	GtkWidget *vbox, *scrwnd;
	GtkTreeSelection *selection;

	// Dialog with buttons
	m_pDlg = gtk_dialog_new_with_buttons("Select filter", pParent, 
		(GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
		GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL);
	gtk_dialog_widget_standard_tooltips(GTK_DIALOG(m_pDlg));
	gtk_window_set_position(GTK_WINDOW(m_pDlg), GTK_WIN_POS_CENTER);
	g_signal_connect(G_OBJECT(m_pDlg), "response", G_CALLBACK(response_dialog), this);

	// Dialog icon
	gchar *icon = get_icon_file("muniwin");
	gtk_window_set_icon(GTK_WINDOW(m_pDlg), gdk_pixbuf_new_from_file(icon, NULL));
	g_free(icon);

	// Dialog layout
	vbox = gtk_vbox_new(FALSE, 4);
	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(m_pDlg)->vbox), vbox, TRUE, TRUE, 0);
	gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
	gtk_widget_set_size_request(vbox, 520, 560);

	// Search result
	m_FilterList = gtk_list_store_new(NCOLS, G_TYPE_STRING, G_TYPE_STRING);
	for (int i=0; ListRows[i].name!=NULL; i++) {
		if ((ListRows[i].ccd && ccd) || (ListRows[i].dslr && dslr)) {
			GtkTreeIter iter;
			gtk_list_store_append(m_FilterList, &iter);
			gtk_list_store_set(m_FilterList, &iter, COL_NAME, ListRows[i].name, COL_DESCRIPTION, ListRows[i].description, -1);
		}
	}

	// User catalog
	m_ListView = gtk_tree_view_new();
	gtk_widget_set_tooltip_text(m_ListView, "List of the standard BAAVSS filters");
	for (int i=0; ListColumns[i].caption!=NULL; i++) {
		GtkTreeViewColumn *col = gtk_tree_view_column_new();
		// Set column name and alignment
		gtk_tree_view_column_set_title(col, ListColumns[i].caption);
		gtk_tree_view_append_column(GTK_TREE_VIEW(m_ListView), col);
		// Add text renderer
		GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
		gtk_tree_view_column_pack_start(col, renderer, TRUE);
		g_object_set(renderer, "xalign", ListColumns[i].align, NULL);
		gtk_tree_view_column_add_attribute(col, renderer, "text", ListColumns[i].column);
	}
	gtk_tree_view_set_model(GTK_TREE_VIEW(m_ListView), GTK_TREE_MODEL(m_FilterList));
	gtk_tree_view_set_headers_clickable(GTK_TREE_VIEW(m_ListView), true);
	gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(m_ListView), TRUE);
	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(m_ListView));
	gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
	g_signal_connect(G_OBJECT(selection), "changed", G_CALLBACK(selection_changed), this);
	scrwnd = gtk_scrolled_window_new (NULL, NULL);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrwnd),
		GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrwnd), 
		GTK_SHADOW_ETCHED_IN);
	gtk_container_add(GTK_CONTAINER(scrwnd), m_ListView);
	gtk_box_pack_start(GTK_BOX(vbox), scrwnd, TRUE, TRUE, 0);

	gtk_widget_show_all(GTK_DIALOG(m_pDlg)->vbox);
}

//
// Destructor
//
BaavssExportSelectFilterDlg::~BaavssExportSelectFilterDlg()
{
	gtk_tree_path_free(m_SelPath);
	g_object_unref(m_FilterList);
	gtk_widget_destroy(m_pDlg);
}


//
// Execute the dialog
//
gchar *BaavssExportSelectFilterDlg::Execute(const gchar *defaultValue)
{
	Select(defaultValue);
	if (gtk_dialog_run(GTK_DIALOG(m_pDlg)) != GTK_RESPONSE_ACCEPT) {
		gtk_widget_hide(m_pDlg);
		return NULL;
	}
	gtk_widget_hide(m_pDlg);

	GtkTreeIter iter;
	GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(m_ListView));
	if (!gtk_tree_selection_get_selected(selection, NULL, &iter)) 
		return NULL;

	gchar *name = NULL;
	gtk_tree_model_get(GTK_TREE_MODEL(m_FilterList), &iter, COL_NAME, &name, -1);
	return name;
}


//
// Select item by name
//
void BaavssExportSelectFilterDlg::Select(const gchar *name)
{
	GtkTreeIter iter;
	int ok = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(m_FilterList), &iter);
	while (ok) {
		gchar *n;
		gtk_tree_model_get(GTK_TREE_MODEL(m_FilterList), &iter, COL_NAME, &n, -1);
		if (StrCmp0(n, name)==0) {
			GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(m_ListView));
			gtk_tree_selection_unselect_all(selection);
			gtk_tree_selection_select_iter(selection, &iter);
			g_free(n);
			return;
		}
		g_free(n);
		ok = gtk_tree_model_iter_next(GTK_TREE_MODEL(m_FilterList), &iter);
	}
	UpdateControls();
}

//
// TreeView selection changed
//
void BaavssExportSelectFilterDlg::selection_changed(GtkTreeSelection *selection, BaavssExportSelectFilterDlg *pMe)
{
	pMe->OnSelectionChanged(selection);
}

void BaavssExportSelectFilterDlg::OnSelectionChanged(GtkTreeSelection *selection)
{
	GtkTreeModel *model;

	GList *list = gtk_tree_selection_get_selected_rows(selection, &model);
	if (list) {
		if (model == GTK_TREE_MODEL(m_FilterList)) {
			gtk_tree_path_free(m_SelPath);
			m_SelPath = gtk_tree_path_copy((GtkTreePath*)(list->data));
		}
		g_list_foreach(list, (GFunc)gtk_tree_path_free, NULL);
		g_list_free(list);
	} else {
		if (model == GTK_TREE_MODEL(m_FilterList)) {
			gtk_tree_path_free(m_SelPath);
			m_SelPath = NULL;
		}
	}
	UpdateControls();
}


//
// Enable and disable controls
//
void BaavssExportSelectFilterDlg::UpdateControls(void)
{
	GtkTreeSelection *pSel = gtk_tree_view_get_selection(GTK_TREE_VIEW(m_ListView));
	GtkWidget *ok_btn = get_dialog_widget_by_response(GTK_DIALOG(m_pDlg), GTK_RESPONSE_ACCEPT);
	gtk_widget_set_sensitive(ok_btn, gtk_tree_selection_count_selected_rows(pSel)>0);
}


//
// Handle dialog response
//
void BaavssExportSelectFilterDlg::response_dialog(GtkDialog *pDlg, gint response_id, BaavssExportSelectFilterDlg *pMe)
{
	if (!pMe->OnResponseDialog(response_id))
		g_signal_stop_emission_by_name(pDlg, "response");
}

bool BaavssExportSelectFilterDlg::OnResponseDialog(gint response_id)
{
	switch (response_id)
	{
	case GTK_RESPONSE_ACCEPT:
		{
			GtkTreeSelection *pSel = gtk_tree_view_get_selection(GTK_TREE_VIEW(m_ListView));
			return gtk_tree_selection_count_selected_rows(pSel)>0;
		}
		break;
	}
	return true;
}
