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

photometry2_dlg.cpp (C-Munipack project)
The 'Photometry' 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 <assert.h>

#include "photometry2_dlg.h"
#include "configuration.h"
#include "main.h"
#include "ctxhelp.h"
#include "project_dlg.h"
#include "editselections_dlg.h"
#include "varcat.h"

//-------------------------   TABLES   ------------------------------------

enum tFrameColumnId
{
	FCOL_ID,
	FCOL_JULDAT,
	FNCOLS
};

struct tTreeViewColumn 
{
	const char *caption;		// Column name
	int column;					// Model column index
	GtkTreeCellDataFunc datafn;	// Data function
	gfloat align;				// Text alignment
	const char *maxtext;		// Maximum text (for width estimation)
};

struct tColData 
{
	GtkTreeViewColumn *col;
	const tTreeViewColumn *data;
};

struct tGetFileInfo
{
	int			id;
	GtkTreePath *path;
	char		*file;
};

struct tFindFirstInfo
{
	int			col;
	bool		valid;
	int			id;
	int			iVal;
	double		dVal;
};

enum tPopupCommand
{
	CMD_VARIABLE,
	CMD_COMPARISON,
	CMD_CHECK,
	CMD_OBJECT,
	CMD_REMOVE,
	CMD_EDIT_TAG,
	CMD_REMOVE_TAG,
	CMD_CLEAR_ALL,
	CMD_COPY_WCS,
	CMD_AUTO_DETECT
};

static const CPopupMenu::tPopupMenuItem SelectMenu[] = {
	{ CPopupMenu::MB_ITEM, CMD_VARIABLE,		"_Variable" },
	{ CPopupMenu::MB_ITEM, CMD_COMPARISON,		"_Comparison" },
	{ CPopupMenu::MB_ITEM, CMD_CHECK,			"Chec_k" },
	{ CPopupMenu::MB_ITEM, CMD_OBJECT,			"_Object" },
	{ CPopupMenu::MB_SEPARATOR },
	{ CPopupMenu::MB_ITEM, CMD_REMOVE,			"_Remove object" },
	{ CPopupMenu::MB_SEPARATOR },
	{ CPopupMenu::MB_ITEM, CMD_EDIT_TAG,		"_Edit tag" },
	{ CPopupMenu::MB_ITEM, CMD_REMOVE_TAG,		"_Remove tag" },
	{ CPopupMenu::MB_SEPARATOR },
	{ CPopupMenu::MB_ITEM, CMD_COPY_WCS,		"Copy _WCS coordinates" },
	{ CPopupMenu::MB_END }
};

static const CPopupMenu::tPopupMenuItem ContextMenu[] = {
	{ CPopupMenu::MB_ITEM, CMD_VARIABLE,		"_Variable" },
	{ CPopupMenu::MB_ITEM, CMD_COMPARISON,		"_Comparison" },
	{ CPopupMenu::MB_ITEM, CMD_CHECK,			"Chec_k" },
	{ CPopupMenu::MB_ITEM, CMD_OBJECT,			"_Object" },
	{ CPopupMenu::MB_SEPARATOR },
	{ CPopupMenu::MB_ITEM, CMD_CLEAR_ALL,		"_Remove all" },
	{ CPopupMenu::MB_SEPARATOR },
	{ CPopupMenu::MB_ITEM, CMD_AUTO_DETECT,		"_Find stars" },
	{ CPopupMenu::MB_SEPARATOR },
	{ CPopupMenu::MB_ITEM, CMD_COPY_WCS,		"Copy _WCS coordinates" },
	{ CPopupMenu::MB_END }
};

//-------------------------   LIST OF CATALOGS   --------------------------------

enum tCatalogId
{
	GCVS,
	NSV,
	NSVS,
	VSX,
	CATALOG_COUNT
};

static const struct tCatalogInfo {
	const gchar *caption;
	const gchar *id;
	int par_use, par_path;
	int defaultColor;
	VarCat::tCatalogFilter filter;
} Catalogs[CATALOG_COUNT] = {
	{ "GCVS", "GCVS", CConfig::SEARCH_GCVS, CConfig::GCVS_PATH, 0xE6BE00, VarCat::GCVS },
	{ "NSV", "NSV", CConfig::SEARCH_NSV,  CConfig::NSV_PATH, 0xE6BE00, VarCat::NSV },
	{ "NSVS", "NSVS", CConfig::SEARCH_NSVS, CConfig::NSVS_PATH, 0xE6BE00, VarCat::NSVS },
	{ "VSX", "VSX", CConfig::SEARCH_VSX, CConfig::VSX_PATH, 0xCC00CC, VarCat::VSX }
};

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

// Make list of row references
static gboolean make_list(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, GList **list)
{
	GtkTreeRowReference *rowref = gtk_tree_row_reference_new(model, path);
	*list = g_list_append(*list, rowref);
	return FALSE;
}

static gboolean find_frame(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
{
	int fileid;
	tGetFileInfo *pData = (tGetFileInfo*)data;

	gtk_tree_model_get(model, iter, FCOL_ID, &fileid, -1);
	if (fileid == pData->id) {
		pData->path = gtk_tree_path_copy(path);
		return TRUE;
	}
	return FALSE;
}

static gboolean find_first(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
{
	int fileid;
	double juldat;
	tFindFirstInfo *pData = (tFindFirstInfo*)data;

	gtk_tree_model_get(model, iter, FCOL_ID, &fileid, FCOL_JULDAT, &juldat, -1);
	switch (pData->col)
	{
	case FCOL_ID:
		if (!pData->valid || (pData->iVal > fileid)) {
			pData->id = fileid;
			pData->iVal = fileid;
			pData->valid = 1;
		}
		break;
	case FCOL_JULDAT:
		if (!pData->valid || (pData->dVal > juldat)) {
			pData->id = fileid;
			pData->dVal = juldat;
			pData->valid = 1;
		}
		break;
	}
	return FALSE;
}

static int text_width(GtkWidget *widget, const gchar *buf)
{
	PangoRectangle logical_rect;

	if (buf) {
		PangoLayout *layout = gtk_widget_create_pango_layout(widget, buf);
		pango_layout_get_pixel_extents(layout, NULL, &logical_rect);
		g_object_unref(layout);
		return logical_rect.width;
	}
	return 0;
}

//-------------------------   LIST COLUMNS   --------------------------------

static void GetFrameID(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
	GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
{
	int id;
	char buf[64];

	gtk_tree_model_get(tree_model, iter, FCOL_ID, &id, -1);

	g_snprintf(buf, sizeof(buf), "%d", id);
	g_object_set(cell, "text", buf, NULL);
}

static void GetDateTime(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
	GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
{
	double jd;
	CmpackDateTime dt;
	char buf[256];

	gtk_tree_model_get(tree_model, iter, FCOL_JULDAT, &jd, -1);

	if (cmpack_decodejd(jd, &dt) == 0) {
		sprintf(buf, "%04d-%02d-%02d %02d:%02d:%02d", dt.date.year, dt.date.month, dt.date.day,
			dt.time.hour, dt.time.minute, dt.time.second);
		g_object_set(cell, "text", buf, NULL);
	}
	else {
		g_object_set(cell, "text", "", NULL);
	}
}

const static tTreeViewColumn FrameColumns[] = 
{
	{ "Frame #",		FCOL_ID,		GetFrameID,		1.0 },
	{ "Date and time (UTC)",	FCOL_JULDAT,	GetDateTime,	0.0, "9999-19-99 99:99:99" },
	{ NULL }
};

//-------------------------   MAIN WINDOW   --------------------------------

CPhotometry2Dlg::CPhotometry2Dlg(GtkWindow *pParent) :m_pParent(pParent), m_FileList(NULL),
m_InFiles(0), m_OutFiles(0), m_FrameCols(NULL), m_ChartData(NULL), m_ImageData(NULL), m_Updating(false), 
m_Wcs(NULL), m_SelectMenu(SelectMenu), m_ContextMenu(ContextMenu), m_StatusCtx(-1), m_StatusMsg(-1), m_LastFocus(-1), 
m_LastPosX(-1), m_LastPosY(-1), m_UpdatePos(false), m_ImageWidth(0), m_ImageHeight(0), m_CurrentCatalog(0), m_ShowTags(NULL)
{
	int mon, i, w, width;
	GtkWidget *hbox;
	GdkRectangle rc;

	// Dialog with buttons
	m_pDlg = gtk_dialog_new_with_buttons("Photometry (aligned frames)", pParent,
		(GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
		GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
		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);
	gtk_dialog_set_tooltip_by_response(GTK_DIALOG(m_pDlg), GTK_RESPONSE_ACCEPT, "Start the process");
	g_signal_connect(G_OBJECT(m_pDlg), "response", G_CALLBACK(response_dialog), this);

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

	// Dialog layout
	GtkWidget *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);

	// Dialog size
	GdkScreen *scr = gtk_window_get_screen(pParent);
	mon = gdk_screen_get_monitor_at_window(scr, GTK_WIDGET(pParent)->window);
	gdk_screen_get_monitor_geometry(scr, mon, &rc);
	if (rc.width > 0 && rc.height > 0)
		gtk_window_set_default_size(GTK_WINDOW(m_pDlg), RoundToInt(0.7*rc.width), RoundToInt(0.7*rc.height));

	m_Negative = CConfig::GetBool(CConfig::NEGATIVE_CHARTS);
	m_RowsUpward = CConfig::GetBool(CConfig::ROWS_UPWARD);
	m_VSTags = g_Project->GetBool("PhotometryDlg", "Tags", true);

	hbox = gtk_hbox_new(FALSE, 8);
	gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);

	// List of frames
	m_Frames = gtk_list_store_new(FNCOLS, G_TYPE_INT, G_TYPE_DOUBLE);

	// List of reference frames
	m_FrameView = gtk_tree_view_new();
	width = 8;
	for (i = 0; FrameColumns[i].caption != NULL; i++) {
		GtkTreeViewColumn *col = gtk_tree_view_column_new();
		// Set column name and alignment
		gtk_tree_view_column_set_title(col, FrameColumns[i].caption);
		gtk_tree_view_append_column(GTK_TREE_VIEW(m_FrameView), 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", FrameColumns[i].align, NULL);
		if (FrameColumns[i].datafn)
			gtk_tree_view_column_set_cell_data_func(col, renderer, FrameColumns[i].datafn, NULL, NULL);
		else
			gtk_tree_view_column_add_attribute(col, renderer, "text", FrameColumns[i].column);
		tColData *data = (tColData*)g_malloc(sizeof(tColData));
		data->col = col;
		data->data = &FrameColumns[i];
		m_FrameCols = g_slist_append(m_FrameCols, data);
		if (FrameColumns[i].maxtext)
			w = text_width(m_FrameView, FrameColumns[i].maxtext);
		else
			w = text_width(m_FrameView, FrameColumns[i].caption);
		width += w + 24;
	}
	gtk_tree_view_set_model(GTK_TREE_VIEW(m_FrameView), GTK_TREE_MODEL(m_Frames));
	gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(m_FrameView)), GTK_SELECTION_SINGLE);
	GtkWidget *frameBox = gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(frameBox),
		GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(frameBox),
		GTK_SHADOW_ETCHED_IN);
	gtk_container_add(GTK_CONTAINER(frameBox), m_FrameView);
	gtk_widget_set_size_request(m_FrameView, width, -1);
	gtk_box_pack_start(GTK_BOX(hbox), frameBox, FALSE, TRUE, 0);

	// Register callback for selection change
	GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(m_FrameView));
	gtk_tree_selection_set_mode(selection, GTK_SELECTION_BROWSE);
	g_signal_connect(G_OBJECT(selection), "changed", G_CALLBACK(selection_changed), this);

	GtkWidget *previewBox = gtk_vbox_new(FALSE, 0);
	gtk_box_pack_start(GTK_BOX(hbox), previewBox, TRUE, TRUE, 0);

	// Catalogs
	GtkWidget *frame = gtk_frame_new(NULL);
	gtk_widget_set_size_request(frame, 260, -1);
	gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
	gtk_box_pack_start(GTK_BOX(previewBox), frame, FALSE, TRUE, 0);

	GtkWidget *toolBox = gtk_hbox_new(FALSE, 4);
	gtk_container_set_border_width(GTK_CONTAINER(toolBox), 4);
	gtk_container_add(GTK_CONTAINER(frame), toolBox);

	// Aperture selection
	m_CatalogsLabel = gtk_label_new("Catalogs");
	gtk_misc_set_alignment(GTK_MISC(m_CatalogsLabel), 0, 0.5);
	gtk_box_pack_start(GTK_BOX(toolBox), m_CatalogsLabel, FALSE, FALSE, 0);

	m_CatalogBtns = new tCatalogSet[CATALOG_COUNT];
	memset(m_CatalogBtns, 0, CATALOG_COUNT * sizeof(tCatalogSet));

	CreateCatalogsWidget(GTK_BOX(toolBox));

	// Frame preview
	m_Preview = cmpack_chart_view_new();
	cmpack_chart_view_set_mouse_control(CMPACK_CHART_VIEW(m_Preview), TRUE);
	cmpack_chart_view_set_activation_mode(CMPACK_CHART_VIEW(m_Preview), CMPACK_ACTIVATION_CLICK);
	g_signal_connect(G_OBJECT(m_Preview), "item-activated", G_CALLBACK(chart_item_activated), this);
	g_signal_connect(G_OBJECT(m_Preview), "button_press_event", G_CALLBACK(button_press_event), this);
	g_signal_connect(G_OBJECT(m_Preview), "mouse-moved", G_CALLBACK(chart_mouse_moved), this);
	g_signal_connect(G_OBJECT(m_Preview), "mouse-left", G_CALLBACK(chart_mouse_left), this);
	GtkWidget *scrwnd = gtk_scrolled_window_new(NULL, NULL);
	gtk_widget_set_size_request(scrwnd, 320, 200);
	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_Preview);
	gtk_box_pack_start(GTK_BOX(previewBox), scrwnd, TRUE, TRUE, 0);

	// Status bar
	m_Status = gtk_statusbar_new();
	gtk_statusbar_set_has_resize_grip(GTK_STATUSBAR(m_Status), FALSE);
	gtk_box_pack_end(GTK_BOX(previewBox), m_Status, FALSE, FALSE, 0);
	m_StatusCtx = gtk_statusbar_get_context_id(GTK_STATUSBAR(m_Status), "Main");

	// Bottom toolbox
	GtkWidget *optionsBox = gtk_hbox_new(FALSE, 8);
	m_OptionsBtn = gtk_button_new_with_label("Options");
	gtk_widget_set_tooltip_text(m_OptionsBtn, "Edit project settings");
	gtk_box_pack_start(GTK_BOX(optionsBox), m_OptionsBtn, 0, 0, 0);
	g_signal_connect(G_OBJECT(m_OptionsBtn), "clicked", G_CALLBACK(button_clicked), this);
	gtk_box_pack_end(GTK_BOX(vbox), optionsBox, FALSE, TRUE, 0);

	// Timers
	m_TimerId = g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE, 100, GSourceFunc(timer_cb), this, NULL);

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

CPhotometry2Dlg::~CPhotometry2Dlg()
{
	g_source_remove(m_TimerId);
	g_signal_handlers_disconnect_by_func(G_OBJECT(m_Preview), (gpointer)chart_mouse_moved, this);
	g_signal_handlers_disconnect_by_func(G_OBJECT(m_Preview), (gpointer)chart_mouse_left, this);

	for (GSList *ptr = m_FrameCols; ptr != NULL; ptr = ptr->next)
		g_free(ptr->data);
	g_slist_free(m_FrameCols);

	if (m_ChartData)
		g_object_unref(m_ChartData);
	if (m_ImageData)
		g_object_unref(m_ImageData);
	if (m_Frames)
		g_object_unref(m_Frames);

	gtk_widget_destroy(m_pDlg);
	g_list_free(m_FileList);
	delete m_Wcs;
}

bool CPhotometry2Dlg::OnInitDialog(GtkWindow *parent, GError **error)
{
	m_FrameID = g_Project->GetInt("Photometry2", "Frame", 0);
	m_LastFocus = -1;
	m_LastPosX = m_LastPosY = -1;
	m_UpdatePos = true;

	ReadObjects();
	ReadFrames(false);
	if (gtk_tree_model_iter_n_children(GTK_TREE_MODEL(m_Frames), NULL) == 0) {
		set_error(error, "There are no frames usable as a reference frame.");
		return false;
	}
	UpdatePreview(parent, true);
	UpdateControls();
	return true;
}

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

bool CPhotometry2Dlg::OnResponseDialog(gint response_id)
{
	switch (response_id)
	{
	case GTK_RESPONSE_ACCEPT:
	{
		GError *error = NULL;
		if (!OnResponseDialog(response_id, &error)) {
			if (error) {
				ShowError(m_pParent, error->message, true);
				g_error_free(error);
			}
			return false;
		}
	}
	break;

	case GTK_RESPONSE_HELP:
		// Show context help
		g_MainWnd->ShowHelp(GTK_WINDOW(m_pDlg), IDH_PHOTOMETRY_2);
		return false;
	}
	return true;
}

bool CPhotometry2Dlg::OnResponseDialog(gint response_id, GError **error)
{
	if (m_Objects.isEmpty()) {
		ShowError(m_pParent, "Use the mouse click to define objects on the chart.", true);
		return false;
	}
	return true;
}

void CPhotometry2Dlg::Execute(void)
{
	char msg[256];

	g_list_foreach(m_FileList, (GFunc)gtk_tree_row_reference_free, NULL);
	g_list_free(m_FileList);
	m_FileList = NULL;

	GError *error = NULL;
	if (!OnInitDialog(m_pParent, &error)) {
		if (error) {
			ShowError(m_pParent, error->message);
			g_error_free(error);
		}
		return;
	}

	if (gtk_dialog_run(GTK_DIALOG(m_pDlg)) != GTK_RESPONSE_ACCEPT) {
		gtk_widget_hide(m_pDlg);
		return;
	}
	gtk_widget_hide(m_pDlg);

	g_Project->ClearReference();

	// Always all files
	gtk_tree_model_foreach(g_Project->FileList(), GtkTreeModelForeachFunc(make_list), &m_FileList);
	if (m_FileList) {
		CProgressDlg pDlg(m_pParent, "Processing PHOTOMETRY");
		pDlg.SetMinMax(0, g_list_length(m_FileList));
		bool retval = pDlg.Execute(ExecuteProc, this, &error) != 0;
		g_Project->applyPendingUpdates();
		if (!retval) {
			if (error) {
				ShowError(m_pParent, error->message, true);
				g_error_free(error);
			}
		}
		else if (m_OutFiles == 0) {
			ShowError(m_pParent, "No file was successfully processed.", true);
		}
		else if (m_OutFiles != m_InFiles) {
			sprintf(msg, "%d file(s) were successfully processed, %d file(s) failed.",
				m_OutFiles, m_InFiles - m_OutFiles);
			ShowWarning(m_pParent, msg, true);
		}
		else {
			sprintf(msg, "All %d file(s) were successfully processed.", m_OutFiles);
			ShowInformation(m_pParent, msg, true);
		}
		g_list_foreach(m_FileList, (GFunc)gtk_tree_row_reference_free, NULL);
		g_list_free(m_FileList);
		m_FileList = NULL;
	}
	g_Project->Save();
}

int CPhotometry2Dlg::ExecuteProc(CProgressDlg *sender, void *userdata)
{
	return ((CPhotometry2Dlg*)userdata)->ProcessFiles(sender);
}

int CPhotometry2Dlg::ProcessFiles(CProgressDlg *sender)
{
	int frameid;
	gchar *tpath, msg[128];
	CPhotometry2Proc pht;

	g_Project->SetInt("Photometry2", "Frame", m_FrameID);
	g_Project->ClearReference();

	m_InFiles = m_OutFiles = 0;
	sender->Print("------ Photometry & matching (aligned frames) ------");

	int count = m_Objects.count();
	CmpackPhotObject *objs = (CmpackPhotObject*)g_malloc(count * sizeof(CmpackPhotObject));
	for (int i = 0; i < count; i++) {
		const CObjectList::tObject &obj = m_Objects[i];
		objs[i].center_x = obj.x;
		objs[i].center_y = obj.y;
	}
	pht.InitWithObjects(sender, count, objs);
	g_free(objs);

	// Set reference frame
	GtkTreePath *refpath = g_Project->GetPath(m_FrameID);
	g_Project->SetReferenceFrame(refpath);

	// Object coordinates, location and observer's name
	CCCDFile ccd;
	gchar *fpath = g_Project->GetImageFile(refpath);
	if (ccd.Open(fpath, CMPACK_OPEN_READONLY)) {
		if (ccd.Object()->Valid())
			g_Project->SetObjectCoords(*ccd.Object());
		else
			g_Project->SetObjectCoords(CObjectCoords());
		if (ccd.Location()->Valid())
			g_Project->SetLocation(*ccd.Location());
		else if (g_Project->Profile()->DefaultLocation().Valid())
			g_Project->SetLocation(g_Project->Profile()->DefaultLocation());
		else
			g_Project->SetLocation(CLocation());
		g_Project->SetObserver(ccd.Observer());
		g_Project->SetTelescope(ccd.Telescope());
		g_Project->SetInstrument(ccd.Instrument());
	}
	g_free(fpath);
	gtk_tree_path_free(refpath);

	g_Project->SetTargetType(STATIONARY_TARGET);
	g_Project->SelectionList()->Clear();
	g_Project->SetLastSelection(m_Selection);
	g_Project->SetObjectList(m_Objects);
	g_Project->SetTags(m_Tags);

	for (GList *node = m_FileList; node != NULL && !sender->Cancelled(); node = node->next) {
		GtkTreePath *path = gtk_tree_row_reference_get_path((GtkTreeRowReference*)node->data);
		if (path) {
			frameid = g_Project->GetFrameID(path);
			tpath = g_Project->GetImageFileName(path);
			sender->SetFileName(tpath);
			g_free(tpath);
			sender->SetProgress(m_InFiles++);
			sprintf(msg, "Frame #%d:", frameid);
			sender->Print(msg);
			GError *error = NULL;
			if (pht.Execute(path, &error)) {
				m_OutFiles++;
			}
			else {
				sender->Print(error->message);
				g_error_free(error);
			}
			gtk_tree_path_free(path);
		}
	}
	if (sender->Cancelled()) {
		sender->Print("Cancelled at the user's request");
		return false;
	}

	sprintf(msg, "====== %d succeeded, %d failed ======", m_OutFiles, m_InFiles - m_OutFiles);
	sender->Print(msg);
	return true;
}


void CPhotometry2Dlg::button_clicked(GtkWidget *button, CPhotometry2Dlg *pDlg)
{
	pDlg->OnButtonClicked(button);
}

void CPhotometry2Dlg::OnButtonClicked(GtkWidget *pBtn)
{
	if (!m_Updating) {
		m_Updating = true;
		if (pBtn == m_OptionsBtn) {
			CEditProjectDlg pDlg(GTK_WINDOW(m_pDlg));
			pDlg.Execute(PAGE_PHOTOMETRY);
			UpdatePreview(GTK_WINDOW(m_pDlg), true);
			UpdateChart();
			UpdateCatalogs(GTK_WINDOW(m_pDlg));
			UpdateStatus();
			UpdateControls();
		}
		m_Updating = false;
	}

	if (pBtn == m_ShowTags) {
		m_VSTags = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_ShowTags)) != FALSE;
		for (int i = 0; i < CATALOG_COUNT; i++) {
			if (m_CatalogBtns[i].layerId > 0)
				cmpack_chart_view_show_tags(CMPACK_CHART_VIEW(m_Preview), m_CatalogBtns[i].layerId, m_VSTags);
		}
	}

	for (int i = 0; i < CATALOG_COUNT; i++) {
		if (m_CatalogBtns[i].chkButton == pBtn) {
			ShowCatalog(GTK_WINDOW(m_pDlg), i, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_CatalogBtns[i].chkButton)) != 0);
			break;
		}
		else if (GTK_WIDGET(m_CatalogBtns[i].setButton) == pBtn) {
			EditCatalog(i);
			break;
		}
	}
}

void CPhotometry2Dlg::selection_changed(GtkTreeSelection *widget, CPhotometry2Dlg *pMe)
{
	if (!pMe->m_Updating) {
		pMe->m_Updating = true;
		pMe->UpdatePreview(GTK_WINDOW(pMe->m_pDlg));
		pMe->UpdateControls();
		pMe->m_Updating = false;
	}
}

void CPhotometry2Dlg::UpdatePreview(GtkWindow *parent, bool force_update)
{
	GtkTreeIter iter;
	GtkTreeModel *model;

	GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(m_FrameView));
	if (gtk_tree_selection_get_selected(sel, &model, &iter)) {
		int frame_id;
		gtk_tree_model_get(model, &iter, FCOL_ID, &frame_id, -1);
		if (force_update || m_FrameID != frame_id) {
			cmpack_chart_view_set_image(CMPACK_CHART_VIEW(m_Preview), NULL);
			if (m_ImageData) {
				g_object_unref(m_ImageData);
				m_ImageData = NULL;
			}
			if (m_Wcs) {
				delete m_Wcs;
				m_Wcs = NULL;
			}
			m_FrameID = frame_id;
			GtkTreePath *path = g_Project->GetPath(m_FrameID);
			gchar *fts_file = g_Project->GetImageFile(path);
			CCCDFile file;
			if (file.Open(fts_file, CMPACK_OPEN_READONLY)) {
				CImage *img = file.GetImageData();
				if (img) {
					UpdateImage(img);
					UpdateChart();
					delete img;
				}
				m_Phot.Init(NULL);
				m_Phot.Read(file, NULL);
				if (file.Wcs())
					m_Wcs = new CWcs(*file.Wcs());
			}
			g_free(fts_file);
			gtk_tree_path_free(path);
			UpdateCatalogs(parent);
		}
	}
	else {
		if (force_update || m_FrameID >= 0) {
			m_FrameID = -1;
			cmpack_chart_view_set_image(CMPACK_CHART_VIEW(m_Preview), NULL);
			if (m_ImageData) {
				g_object_unref(m_ImageData);
				m_ImageData = NULL;
			}
			if (m_Wcs) {
				delete m_Wcs;
				m_Wcs = NULL;
			}
		}
	}
}

void CPhotometry2Dlg::ReadObjects(void)
{
	ClearObjects();
	m_Objects = *g_Project->objectList();

	const CSelection &selection = *g_Project->LastSelection();
	for (int i = 0; i < selection.Count(); i++) {
		int obj_id = selection.GetId(i);
		if (m_Objects.contains(obj_id))
			m_Selection.Select(obj_id, selection.Type(obj_id));
	}

	const CTags &tags = *g_Project->Tags();
	for (int i = 0; i < tags.count(); i++) {
		int obj_id = tags[i].id;
		if (m_Objects.contains(obj_id))
			m_Tags.insert(obj_id, tags.caption(obj_id));
	}
}

void CPhotometry2Dlg::ReadFrames(bool all_frames)
{
	gboolean ok;
	GtkTreeModel *pList = g_Project->FileList();
	GtkTreeIter iter, iter2;
	tGetFileInfo info;
	tFindFirstInfo info2;

	m_Updating = true;

	gtk_tree_view_set_model(GTK_TREE_VIEW(m_FrameView), NULL);
	gtk_list_store_clear(m_Frames);
	ok = gtk_tree_model_get_iter_first(pList, &iter);
	while (ok) {
		GtkTreePath *pPath = gtk_tree_model_get_path(GTK_TREE_MODEL(pList), &iter);
		if (pPath) {
			if (all_frames || (g_Project->GetState(pPath) & CFILE_CONVERSION) != 0) {
				int frameId = g_Project->GetFrameID(pPath);
				double jd = g_Project->GetJulDate(pPath);
				gtk_list_store_append(m_Frames, &iter2);
				gtk_list_store_set(m_Frames, &iter2, FCOL_ID, frameId, FCOL_JULDAT, jd, -1);
			}
			gtk_tree_path_free(pPath);
		}
		ok = gtk_tree_model_iter_next(pList, &iter);
	}
	gtk_tree_view_set_model(GTK_TREE_VIEW(m_FrameView), GTK_TREE_MODEL(m_Frames));

	info.path = NULL;
	if (m_FrameID > 0) {
		info.id = m_FrameID;
		gtk_tree_model_foreach(GTK_TREE_MODEL(m_Frames), GtkTreeModelForeachFunc(find_frame), &info);
	}
	if (!info.path) {
		// First first frame
		info2.col = 0;
		info2.valid = 0;
		info2.id = -1;
		gtk_tree_model_foreach(GTK_TREE_MODEL(m_Frames), GtkTreeModelForeachFunc(find_first), &info2);
		if (info2.id > 0) {
			info.id = info2.id;
			gtk_tree_model_foreach(GTK_TREE_MODEL(m_Frames), GtkTreeModelForeachFunc(find_frame), &info);
		}
	}
	if (info.path) {
		GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(m_FrameView));
		gtk_tree_selection_select_path(sel, info.path);
		gtk_tree_path_free(info.path);
	}

	m_Updating = false;
}

void CPhotometry2Dlg::UpdateControls(void)
{
}

void CPhotometry2Dlg::UpdateImage(CImage *img)
{
	cmpack_chart_view_set_image(CMPACK_CHART_VIEW(m_Preview), NULL);
	if (m_ImageData) {
		g_object_unref(m_ImageData);
		m_ImageData = NULL;
	}
	if (img) {
		m_ImageData = img->ToImageData(m_Negative, false, true, m_RowsUpward);
		cmpack_chart_view_set_image(CMPACK_CHART_VIEW(m_Preview), m_ImageData);
		cmpack_chart_view_set_negative(CMPACK_CHART_VIEW(m_Preview), m_Negative);
		cmpack_chart_view_set_orientation(CMPACK_CHART_VIEW(m_Preview), (m_RowsUpward ? CMPACK_ROWS_UPWARDS : CMPACK_ROWS_DOWNWARDS));
		cmpack_chart_view_set_auto_zoom(CMPACK_CHART_VIEW(m_Preview), TRUE);
	}
}

void CPhotometry2Dlg::UpdateChart()
{
	cmpack_chart_view_set_model(CMPACK_CHART_VIEW(m_Preview), NULL);
	if (m_ChartData) {
		g_object_unref(m_ChartData);
		m_ChartData = NULL;
	}
	m_ChartData = MakeChartData();
	if (m_ChartData) {
		int count = cmpack_chart_data_count(m_ChartData);
		for (int row = 0; row < count; row++)
			UpdateObject(row, (int)cmpack_chart_data_get_param(m_ChartData, row));
	}
	cmpack_chart_view_set_model(CMPACK_CHART_VIEW(m_Preview), m_ChartData);
}

void CPhotometry2Dlg::ClearObjects()
{
	m_Objects.clear();
	m_Selection.Clear();
	m_Tags.clear();
}

CmpackChartData *CPhotometry2Dlg::MakeChartData()
{
	if (!m_Objects.isEmpty() && m_ImageData) {
		CmpackChartItem item;
		memset(&item, 0, sizeof(CmpackChartItem));
		item.d = 3.0;

		int count = m_Objects.count();
		CmpackChartData *data = cmpack_chart_data_new_with_alloc(count);
		cmpack_chart_data_set_dimensions(data, cmpack_image_data_width(m_ImageData), cmpack_image_data_height(m_ImageData));
		for (int i = 0; i < count; i++) {
			const CObjectList::tObject &obj = m_Objects[i];
			item.x = obj.x;
			item.y = obj.y;
			item.param = obj.id;
			cmpack_chart_data_add(data, &item, sizeof(item));
		}
		return data;
	}
	return NULL;
}


//
// Left button click
//
void CPhotometry2Dlg::chart_item_activated(GtkWidget *widget, gint item, CPhotometry2Dlg *pMe)
{
	GdkEventButton ev;
	ev.button = 1;
	ev.time = gtk_get_current_event_time();
	pMe->OnSelectMenu(&ev, item);
}

//
// Right mouse click
//
gint CPhotometry2Dlg::button_press_event(GtkWidget *widget, GdkEventButton *event, CPhotometry2Dlg *pMe)
{
	int focused;

	if (event->type == GDK_BUTTON_PRESS && event->button == 3) {
		gtk_widget_grab_focus(widget);
		if (widget == pMe->m_Preview) {
			focused = cmpack_chart_view_get_focused(CMPACK_CHART_VIEW(widget));
			if (focused >= 0)
				pMe->OnSelectMenu(event, focused);
			else
				pMe->OnContextMenu(event);
		}
		return TRUE;
	}
	return FALSE;
}


//
// Object's context menu
//
void CPhotometry2Dlg::OnSelectMenu(GdkEventButton *event, gint row)
{
	if (!m_ChartData)
		return;

	int star_id = (int)cmpack_chart_data_get_param(m_ChartData, row);
	CmpackSelectionType type = m_Selection.Type(star_id);
	m_SelectMenu.Enable(CMD_VARIABLE, type != CMPACK_SELECT_VAR);
	m_SelectMenu.Enable(CMD_COMPARISON, type != CMPACK_SELECT_COMP);
	m_SelectMenu.Enable(CMD_CHECK, type != CMPACK_SELECT_CHECK);
	m_SelectMenu.Enable(CMD_OBJECT, type != CMPACK_SELECT_NONE);
	m_SelectMenu.Enable(CMD_CLEAR_ALL, !m_Objects.isEmpty());
	m_SelectMenu.Enable(CMD_REMOVE_TAG, m_Tags.contains(star_id));
	m_SelectMenu.Enable(CMD_COPY_WCS, m_ChartData && m_Wcs);
	switch (m_SelectMenu.Execute(event))
	{
	case CMD_VARIABLE:
		Update(star_id, CMPACK_SELECT_VAR);
		break;
	case CMD_COMPARISON:
		Update(star_id, CMPACK_SELECT_COMP);
		break;
	case CMD_CHECK:
		Update(star_id, CMPACK_SELECT_CHECK);
		break;
	case CMD_OBJECT:
		Update(star_id, CMPACK_SELECT_NONE);
		break;
	case CMD_REMOVE:
		Remove(star_id);
		break;
	case CMD_CLEAR_ALL:
		RemoveAll();
		break;
	case CMD_EDIT_TAG:
		EditTag(row, star_id);
		break;
	case CMD_REMOVE_TAG:
		RemoveTag(row, star_id);
		break;
	case CMD_COPY_WCS:
		CopyWcsCoordinatesFromChart(row, star_id);
		break;
	}
	UpdateControls();
}

//
// Context menu (no object focused)
//
void CPhotometry2Dlg::OnContextMenu(GdkEventButton *event)
{
	double x, y;
	if (cmpack_chart_view_from_viewport(CMPACK_CHART_VIEW(m_Preview), event->x, event->y, &x, &y)) {
		m_SelectMenu.Enable(CMD_CLEAR_ALL, !m_Objects.isEmpty());
		m_SelectMenu.Enable(CMD_COPY_WCS, m_ChartData && m_Wcs);
		switch (m_ContextMenu.Execute(event))
		{
		case CMD_VARIABLE:
			Create(x, y, CMPACK_SELECT_VAR);
			break;
		case CMD_COMPARISON:
			Create(x, y, CMPACK_SELECT_COMP);
			break;
		case CMD_CHECK:
			Create(x, y, CMPACK_SELECT_CHECK);
			break;
		case CMD_OBJECT:
			Create(x, y, CMPACK_SELECT_NONE);
			break;
		case CMD_CLEAR_ALL:
			RemoveAll();
			break;
		case CMD_COPY_WCS:
			CopyWcsCoordinatesFromChart(x, y);
			break;
		case CMD_AUTO_DETECT:
			AutoDetect();
			break;
		}
		UpdateControls();
	}
}

void CPhotometry2Dlg::CopyWcsCoordinatesFromChart(int star_id)
{
	int index = m_Objects.indexOf(star_id);
	if (index >= 0) {
		const CObjectList::tObject &obj = m_Objects[index];
		CopyWcsCoordinatesFromChart(obj.x, obj.y);
	}
}

void CPhotometry2Dlg::CopyWcsCoordinatesFromChart(double x, double y)
{
	if (m_Wcs) {
		double lng, lat;
		if (m_Wcs->pixelToWorld(x, y, lng, lat))
			CopyWcsCoordinates(m_Wcs, lng, lat);
	}
}

void CPhotometry2Dlg::CopyWcsCoordinates(CWcs *wcs, double lng, double lat)
{
	char buf[256];
	wcs->print(lng, lat, buf, 256, false);
	GtkClipboard *cb = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
	gtk_clipboard_set_text(cb, buf, -1);
	gtk_clipboard_store(cb);
}

void CPhotometry2Dlg::EditTag(int row, int star_id)
{
	if (row >= 0 && star_id >= 0) {
		char buf[256];
		const gchar *cap = m_Selection.Description(star_id);
		if (cap)
			sprintf(buf, "Enter tag caption for %s:", cap);
		else
			sprintf(buf, "Enter tag caption for object #%d:", star_id);
		CTextQueryDlg dlg(GTK_WINDOW(m_pDlg), "Edit tag");
		gchar *value = dlg.Execute(buf, MAX_TAG_SIZE, m_Tags.caption(star_id),
			(CTextQueryDlg::tValidator*)tag_validator, this);
		if (value) {
			SetTag(row, star_id, value);
			g_free(value);
		}
	}
}

bool CPhotometry2Dlg::tag_validator(const gchar *name, GtkWindow *parent, CPhotometry2Dlg *pMe)
{
	return pMe->OnTagValidator(name, parent);
}

bool CPhotometry2Dlg::OnTagValidator(const gchar *name, GtkWindow *parent)
{
	if (!name || name[0] == '\0') {
		ShowError(parent, "Please, specify caption for the new tag.");
		return false;
	}
	return true;
}


void CPhotometry2Dlg::SetTag(int row, int star_id, const gchar *value)
{
	if (row >= 0 && star_id >= 0) {
		if (value && value[0] != '\0')
			m_Tags.insert(star_id, value);
		else
			m_Tags.remove(star_id);
		UpdateObject(row, star_id);
		UpdateStatus();
	}
}

void CPhotometry2Dlg::RemoveTag(int row, int star_id)
{
	if (row >= 0 && m_Tags.contains(star_id)) {
		m_Tags.remove(star_id);
		UpdateObject(row, star_id);
		UpdateStatus();
	}
}

void CPhotometry2Dlg::UpdateStatus(void)
{
	gchar		buf[1024];

	int item = cmpack_chart_view_get_focused(CMPACK_CHART_VIEW(m_Preview));
	if (item >= 0 && m_ChartData) {
		if (m_LastFocus != item) {
			m_LastFocus = item;
			int obj_id = (int)cmpack_chart_data_get_param(m_ChartData, item);
			gdouble pos_x = 0, pos_y = 0;
			int i = m_Objects.indexOf(obj_id);
			if (i >= 0) {
				const CObjectList::tObject &obj = m_Objects[i];
				pos_x = obj.x;
				pos_y = obj.y;
			}
			sprintf(buf, "Object #%d: X = %.1f, Y = %.1f", obj_id, pos_x, pos_y);
			// World coordinates
			double r, d;
			if (m_Wcs && m_Wcs->pixelToWorld(pos_x, pos_y, r, d)) {
				char cel[256];
				m_Wcs->print(r, d, cel, 256);
				strcat(buf, ", ");
				strcat(buf, cel);
			}
			// Selection
			const gchar *cap = m_Selection.Description(obj_id);
			if (cap) {
				strcat(buf, ", ");
				strcat(buf, cap);
			}
			// Tag
			const gchar *tag = m_Tags.caption(obj_id);
			if (tag) {
				strcat(buf, ", ");
				strcat(buf, tag);
			}
			SetStatus(buf);
		}
	}
	else {
		m_LastFocus = -1;
		double dx, dy;
		if (cmpack_chart_view_mouse_pos(CMPACK_CHART_VIEW(m_Preview), &dx, &dy)) {
			int x = (int)dx, y = (int)dy;
			if (x != m_LastPosX || y != m_LastPosY) {
				m_LastPosX = x;
				m_LastPosY = y;
				double r, d;
				if (m_Wcs && m_Wcs->pixelToWorld(x, y, r, d)) {
					char cel[256];
					m_Wcs->print(r, d, cel, 256);
					sprintf(buf, "Cursor: X = %d, Y = %d, %s", x, y, cel);
				}
				else {
					sprintf(buf, "Cursor: X = %d, Y = %d", x, y);
				}
				SetStatus(buf);
			}
		}
		else {
			if (m_LastPosX != -1 || m_LastPosY != -1) {
				m_LastPosX = m_LastPosY = -1;
				SetStatus(NULL);
			}
		}
	}
}

//
// Update displayed object
//
void CPhotometry2Dlg::UpdateObject(int row, int star_id)
{
	if (m_ChartData) {
		const gchar *tag = m_Tags.caption(star_id);
		const gchar *cap = m_Selection.Caption(star_id);
		if (cap) {
			if (tag) {
				// Selected object, with tag
				gchar *buf = (gchar*)g_malloc((strlen(cap) + strlen(tag) + 2) * sizeof(gchar));
				sprintf(buf, "%s\n%s", cap, tag);
				cmpack_chart_data_set_tag(m_ChartData, row, buf);
				g_free(buf);
			}
			else {
				// Selected object, no tag
				cmpack_chart_data_set_tag(m_ChartData, row, cap);
			}
			cmpack_chart_data_set_color(m_ChartData, row, m_Selection.Color(star_id));
		}
		else {
			if (tag) {
				// Not selected, with tag
				cmpack_chart_data_set_tag(m_ChartData, row, tag);
				cmpack_chart_data_set_color(m_ChartData, row, CMPACK_COLOR_YELLOW);
			}
			else {
				// Not selected, no tag
				cmpack_chart_data_set_tag(m_ChartData, row, NULL);
				cmpack_chart_data_set_color(m_ChartData, row, CMPACK_COLOR_DEFAULT);
			}
		}
	}
}

// 
// Update selection and tags for all object
//
void CPhotometry2Dlg::Create(double x, double y, CmpackSelectionType type)
{
	if (m_Phot.Pos(&x, &y)) {
		// Check for duplicates
		bool found = false;
		for (int i = 0; i < m_Objects.count(); i++) {
			const CObjectList::tObject &obj = m_Objects[i];
			if ((x - obj.x) * (x - obj.x) + (y - obj.y) * (y - obj.y) < 3) {
				Update(obj, type);
				found = true;
				break;
			}
		}

		if (!found) {
			// Find maximum object identifier
			int maxId = 0;
			for (int i = 0; i < m_Objects.count(); i++) {
				if (m_Objects[i].id > maxId)
					maxId = m_Objects[i].id;
			}

			// Create a new object
			int obj_id = maxId + 1;
			m_Objects.insert(x, y, obj_id);
			m_Selection.Select(obj_id, type);
		}

		UpdateChart();
		UpdateStatus();
		UpdateControls();
	}
}

void CPhotometry2Dlg::Update(int star_id, CmpackSelectionType type)
{
	int i = m_Objects.indexOf(star_id);
	if (i >= 0)
		Update(m_Objects[i], type);
}

void CPhotometry2Dlg::Update(const CObjectList::tObject &obj, CmpackSelectionType type)
{
	if (m_Selection.Type(obj.id) != type && m_ChartData) {
		m_Selection.Select(obj.id, type);
		UpdateObject(cmpack_chart_data_find_item(m_ChartData, obj.id), obj.id);
		UpdateStatus();
	}
}

void CPhotometry2Dlg::Remove(int star_id)
{
	int index = m_Objects.indexOf(star_id);
	if (index >= 0) {
		m_Objects.removeAt(index);
		m_Selection.Select(star_id, CMPACK_SELECT_NONE);
		m_Tags.remove(star_id);

		// Reassign object identifiers
		// They have to form a continuous sequence of ordinal numbers
		CTags new_tags;
		CSelection new_selection;
		for (int i = 0; i < m_Objects.count(); i++) {
			int old_id = m_Objects[i].id, new_id = i + 1;
			m_Objects[i].id = new_id;

			const gchar *caption = m_Tags.caption(old_id);
			if (caption)
				new_tags.insert(new_id, caption);

			CmpackSelectionType type = m_Selection.Type(old_id);
			if (type != CMPACK_SELECT_NONE)
				new_selection.Select(new_id, type);
		}
		m_Selection = new_selection;
		m_Tags = new_tags;
			   
		UpdateChart();
		UpdateStatus();
		UpdateControls();
	}
}

void CPhotometry2Dlg::RemoveAll(void)
{
	ClearObjects();
	UpdateChart();
	UpdateStatus();
	UpdateControls();
}

void CPhotometry2Dlg::AutoDetect(void)
{
	bool ok = m_Phot.FindFirst();
	if (ok) {

		// Find maximum object identifier
		int maxId = -1;
		for (int i = 0; i < m_Objects.count(); i++) {
			if (m_Objects[i].id > maxId)
				maxId = m_Objects[i].id;
		}

		while (ok) {
			// Get object position
			CmpackPhotObject data;
			m_Phot.GetData(CMPACK_PHI_XY, data);

			// Check for duplicates
			bool found = false;
			for (int i = 0; i < m_Objects.count(); i++) {
				const CObjectList::tObject &obj = m_Objects[i];
				if ((data.center_x - obj.x) * (data.center_x - obj.x) + (data.center_y - obj.y) * (data.center_y - obj.y) < 3) {
					found = true;
					break;
				}
			}
			if (!found) 
				m_Objects.insert(data.center_x, data.center_y, ++maxId);

			// Next item
			ok = m_Phot.FindNext();
		}
		m_Phot.FindClose();

		UpdateChart();
		UpdateStatus();
		UpdateControls();
	}
}

//
// Set status text
//
void CPhotometry2Dlg::SetStatus(const char *text)
{
	if (m_StatusMsg >= 0) {
		gtk_statusbar_pop(GTK_STATUSBAR(m_Status), m_StatusCtx);
		m_StatusMsg = -1;
	}
	if (text && strlen(text) > 0)
		m_StatusMsg = gtk_statusbar_push(GTK_STATUSBAR(m_Status), m_StatusCtx, text);
}

void CPhotometry2Dlg::chart_mouse_moved(GtkWidget *button, CPhotometry2Dlg *pDlg)
{
	pDlg->m_UpdatePos = true;
}

void CPhotometry2Dlg::chart_mouse_left(GtkWidget *button, CPhotometry2Dlg *pDlg)
{
	pDlg->UpdateStatus();
}

gboolean CPhotometry2Dlg::timer_cb(CPhotometry2Dlg *pDlg)
{
	if (pDlg->m_UpdatePos) {
		pDlg->m_UpdatePos = false;
		pDlg->UpdateStatus();
	}
	return TRUE;
}

void CPhotometry2Dlg::CreateCatalogsWidget(GtkBox *parent)
{
	char key[512], caption[512], tooltip[512];

	int count = 0;
	for (int i = 0; i < CATALOG_COUNT; i++) {
		m_CatalogBtns[i].enabled = CConfig::GetBool((CConfig::tParameter)Catalogs[i].par_use);
		if (m_CatalogBtns[i].enabled)
			count++;
	}

	int row = 0;
	for (int i = 0; i < CATALOG_COUNT; i++) {
		if (m_CatalogBtns[i].enabled) {
			sprintf(key, "Show%s", Catalogs[i].id);
			m_CatalogBtns[i].show = CConfig::GetBool("Catalogs", key);
			sprintf(caption, "Show/hide %s", Catalogs[i].caption);
			m_CatalogBtns[i].chkButton = gtk_check_button_new_with_label(Catalogs[i].caption);
			gtk_widget_set_tooltip_text(m_CatalogBtns[i].chkButton, caption);
			gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_CatalogBtns[i].chkButton), m_CatalogBtns[i].show);
			g_signal_connect(G_OBJECT(m_CatalogBtns[i].chkButton), "clicked", G_CALLBACK(button_clicked), this);
			gtk_box_pack_start(parent, m_CatalogBtns[i].chkButton, FALSE, TRUE, 0);

			m_CatalogBtns[i].icon = gtk_image_new();

			m_CatalogBtns[i].setButton = gtk_button_new();
			gtk_button_set_image(GTK_BUTTON(m_CatalogBtns[i].setButton), m_CatalogBtns[i].icon);
			sprintf(tooltip, "Select color for %s", Catalogs[i].caption);
			gtk_widget_set_tooltip_text(m_CatalogBtns[i].setButton, tooltip);
			g_signal_connect(G_OBJECT(m_CatalogBtns[i].setButton), "clicked", G_CALLBACK(button_clicked), this);
			gtk_box_pack_start(parent, m_CatalogBtns[i].setButton, FALSE, TRUE, 0);

			row++;
		}
	}

	m_ShowTags = gtk_check_button_new_with_label("Tags");
	gtk_widget_set_tooltip_text(m_ShowTags, "Display tags for known variables");
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_ShowTags), m_VSTags);
	g_signal_connect(G_OBJECT(m_ShowTags), "toggled", G_CALLBACK(button_clicked), this);
	gtk_box_pack_start(parent, m_ShowTags, FALSE, TRUE, 0);
}

void CPhotometry2Dlg::ShowCatalog(GtkWindow *parent, int index, bool show)
{
	char msg[512];

	m_CatalogBtns[index].show = show;
	sprintf(msg, "Show%s", Catalogs[index].id);
	CConfig::SetBool("Catalogs", msg, show);

	if (show) {
		UpdateCatalog(parent, index);
		if (m_CatalogBtns[index].layerId > 0) {
			cmpack_chart_view_show_layer(CMPACK_CHART_VIEW(m_Preview), m_CatalogBtns[index].layerId, TRUE);
			cmpack_chart_view_show_tags(CMPACK_CHART_VIEW(m_Preview), m_CatalogBtns[index].layerId, m_VSTags);
		}
	}
	else {
		if (m_CatalogBtns[index].layerId > 0)
			cmpack_chart_view_show_layer(CMPACK_CHART_VIEW(m_Preview), m_CatalogBtns[index].layerId, FALSE);
	}
}

void CPhotometry2Dlg::UpdateCatalogs(GtkWindow *parent)
{
	char key[512];

	if (m_Wcs) {
		for (int i = 0; i < CATALOG_COUNT; i++) {
			if (m_CatalogBtns[i].enabled) {
				gtk_widget_set_visible(m_CatalogBtns[i].chkButton, TRUE);
				gtk_widget_set_visible(m_CatalogBtns[i].setButton, TRUE);
				if (m_CatalogBtns[i].show) {
					UpdateCatalog(parent, i);
					if (m_CatalogBtns[i].layerId > 0) {
						cmpack_chart_view_show_layer(CMPACK_CHART_VIEW(m_Preview), m_CatalogBtns[i].layerId, TRUE);
						cmpack_chart_view_show_tags(CMPACK_CHART_VIEW(m_Preview), m_CatalogBtns[i].layerId, m_VSTags);
					}
				}
				sprintf(key, "Color%s", Catalogs[i].id);
				unsigned ucolor = CConfig::GetInt("Catalogs", key, Catalogs[i].defaultColor);
				GdkPixmap *pixmap = createPixmap(16, 16, ucolor);
				gtk_image_set_from_pixmap(GTK_IMAGE(m_CatalogBtns[i].icon), pixmap, NULL);
				gdk_pixmap_unref(pixmap);
			}
			else {
				if (m_CatalogBtns[i].chkButton)
					gtk_widget_set_visible(m_CatalogBtns[i].chkButton, FALSE);
				if (m_CatalogBtns[i].setButton)
					gtk_widget_set_visible(m_CatalogBtns[i].setButton, FALSE);
			}
		}
	}
	else {
		for (int i = 0; i < CATALOG_COUNT; i++) {
			if (m_CatalogBtns[i].chkButton)
				gtk_widget_set_visible(m_CatalogBtns[i].chkButton, FALSE);
			if (m_CatalogBtns[i].setButton)
				gtk_widget_set_visible(m_CatalogBtns[i].setButton, FALSE);
		}
	}
}

void CPhotometry2Dlg::UpdateCatalog(GtkWindow *parent, int index)
{
	char key[512];

	if (m_CatalogBtns[index].layerId == 0) {
		if (m_ImageData && m_Wcs) {
			int width = cmpack_image_data_width(m_ImageData);
			int height = cmpack_image_data_height(m_ImageData);

			m_CatalogBtns[index].layerId = cmpack_chart_view_add_layer(CMPACK_CHART_VIEW(m_Preview));
			cmpack_chart_view_show_tags(CMPACK_CHART_VIEW(m_Preview), m_CatalogBtns[index].layerId, m_VSTags);

			sprintf(key, "Color%s", Catalogs[index].id);
			unsigned ucolor = CConfig::GetInt("Catalogs", key, Catalogs[index].defaultColor);
			m_CatalogBtns[index].color.red = (double)((ucolor >> 16) & 0xFF) / 255.0;
			m_CatalogBtns[index].color.green = (double)((ucolor >> 8) & 0xFF) / 255.0;
			m_CatalogBtns[index].color.blue = (double)(ucolor & 0xFF) / 255.0;

			double r, rmax = 0, lon, lat, center_lon, center_lat;
			m_Wcs->pixelToWorld(width / 2, height / 2, center_lon, center_lat);
			center_lon = center_lon / 180.0 * M_PI;
			center_lat = center_lat / 180.0 * M_PI;
			m_Wcs->pixelToWorld(0, 0, lon, lat);
			r = angular_distance(center_lon, center_lat, lon / 180.0 * M_PI, lat / 180.0 * M_PI);
			if (r > rmax)
				rmax = r;
			m_Wcs->pixelToWorld(width - 1, 0, lon, lat);
			r = angular_distance(center_lon, center_lat, lon / 180.0 * M_PI, lat / 180.0 * M_PI);
			if (r > rmax)
				rmax = r;
			m_Wcs->pixelToWorld(0, height - 1, lon, lat);
			r = angular_distance(center_lon, center_lat, lon / 180.0 * M_PI, lat / 180.0 * M_PI);
			if (r > rmax)
				rmax = r;
			m_Wcs->pixelToWorld(width - 1, height - 1, lon, lat);
			r = angular_distance(center_lon, center_lat, lon / 180.0 * M_PI, lat / 180.0 * M_PI);
			if (r > rmax)
				rmax = r;

			VarCat::tPosFilter filter;
			filter.ra = center_lon * 180.0 / M_PI;
			filter.dec = center_lat * 180.0 / M_PI;
			filter.radius = 1.1 * rmax * 180.0 / M_PI;		// To degrees, plus 10 percent
			m_CurrentCatalog = index;
			m_ImageWidth = width;
			m_ImageHeight = height;
			VarCat::SearchEx(parent, Catalogs[index].filter, NULL, &filter, AddToLayer, this);
		}
	}
}

void CPhotometry2Dlg::AddToLayer(const char *objname, double ra, double dec, const char *catalog, const char *comment, void *data)
{
	CPhotometry2Dlg *pMe = reinterpret_cast<CPhotometry2Dlg*>(data);

	double x, y;
	if (pMe->m_Wcs->worldToPixel(ra, dec, x, y) && x >= 0 && y >= 0 && x < pMe->m_ImageWidth && y < pMe->m_ImageHeight) {
		int layerId = pMe->m_CatalogBtns[pMe->m_CurrentCatalog].layerId;
		int objectId = cmpack_chart_view_add_object(CMPACK_CHART_VIEW(pMe->m_Preview), layerId, x, y, CMPACK_COLOR_CUSTOM, objname);
		const tCustomColor *color = &pMe->m_CatalogBtns[pMe->m_CurrentCatalog].color;
		cmpack_chart_view_set_object_custom_color(CMPACK_CHART_VIEW(pMe->m_Preview), objectId, color->red, color->green, color->blue);
		pMe->m_CatalogBtns[pMe->m_CurrentCatalog].obj_list = g_slist_append(pMe->m_CatalogBtns[pMe->m_CurrentCatalog].obj_list, GINT_TO_POINTER(objectId));
	}
}

void CPhotometry2Dlg::EditCatalog(int index)
{
	char key[512];
	GdkColor gcolor;

	sprintf(key, "Color%s", Catalogs[index].id);
	unsigned old_rgb = CConfig::GetInt("Catalogs", key, Catalogs[index].defaultColor);
	gcolor.red = ((old_rgb >> 16) & 0xFF) * 257;
	gcolor.green = ((old_rgb >> 8) & 0xFF) * 257;
	gcolor.blue = (old_rgb & 0xFF) * 257;

	GtkWidget *dialog = gtk_color_selection_dialog_new("Select a color");
	GtkColorSelection *colorsel = GTK_COLOR_SELECTION(GTK_COLOR_SELECTION_DIALOG(dialog)->colorsel);
	gtk_color_selection_set_has_opacity_control(colorsel, FALSE);
	gtk_color_selection_set_current_color(colorsel, &gcolor);

	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_OK) {
		gtk_color_selection_get_current_color(colorsel, &gcolor);
		unsigned new_rgb = (((gcolor.red >> 8) & 0xFF) << 16) | (((gcolor.green >> 8) & 0xFF) << 8) | ((gcolor.blue >> 8) & 0xFF);
		if (new_rgb != old_rgb) {
			CConfig::SetInt("Catalogs", key, new_rgb);
			GdkPixmap *pixmap = createPixmap(16, 16, new_rgb);
			gtk_image_set_from_pixmap(GTK_IMAGE(m_CatalogBtns[index].icon), pixmap, NULL);
			gdk_pixmap_unref(pixmap);
			tCustomColor *ccolor = &m_CatalogBtns[index].color;
			ccolor->red = (double)((new_rgb >> 16) & 0xFF) / 257.0;
			ccolor->green = (double)((new_rgb >> 8) & 0xFF) / 257.0;
			ccolor->blue = (double)(new_rgb & 0xFF) / 257.0;

			for (GSList *ptr = m_CatalogBtns[index].obj_list; ptr != NULL; ptr = ptr->next)
				cmpack_chart_view_set_object_custom_color(CMPACK_CHART_VIEW(m_Preview), GPOINTER_TO_INT(ptr->data), ccolor->red, ccolor->green, ccolor->blue);
		}
	}
	gtk_widget_destroy(dialog);
}
