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

objproperties_dlg.cpp (C-Munipack project)
The 'Plot object properties' 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 "project.h"
#include "configuration.h"
#include "progress_dlg.h"
#include "objproperties_dlg.h"
#include "editselections_dlg.h"
#include "main.h"
#include "utils.h"
#include "proc_classes.h"
#include "ctxhelp.h"
#include "object_dlg.h"
#include "observer_dlg.h"
#include "profile.h"
#include "export_dlgs.h"

//-------------------------   LOCAL MENU   --------------------------------

enum tMenuId
{
	MENU_FILE = 1,
	MENU_EDIT,
	MENU_VIEW,
	MENU_TOOLS,
	MENU_HELP
};

enum tCommandId
{
	CMD_CLOSE = 100,
	CMD_EXPORT,
	CMD_REBUILD,
	CMD_PRINT,
	CMD_SAVE,
	CMD_HELP,
	CMD_HIDE,
	CMD_DELETE,
	CMD_PREVIEW,
	CMD_FRAMEINFO,
	CMD_GRAPH,
	CMD_TABLE,
	CMD_ERRORS,
	CMD_GRID,
	CMD_STATISTICS,
	CMD_MEASUREMENT
};

static const CMenuBar::tMenuItem FileMenu[] = {
	{ CMenuBar::MB_ITEM,		CMD_SAVE,		"_Save" },
	{ CMenuBar::MB_SEPARATOR },
	{ CMenuBar::MB_ITEM,		CMD_EXPORT,		"Export _image" },
	{ CMenuBar::MB_SEPARATOR },
	{ CMenuBar::MB_ITEM,		CMD_REBUILD,	"_Rebuild" },
	{ CMenuBar::MB_SEPARATOR },
	{ CMenuBar::MB_ITEM,		CMD_CLOSE,		"_Close" },
	{ CMenuBar::MB_END }
};

static const CMenuBar::tMenuItem ViewMenu[] = {
	{ CMenuBar::MB_RADIOBTN,	CMD_GRAPH,		"_Graph" },
	{ CMenuBar::MB_RADIOBTN,	CMD_TABLE,		"_Table" },
	{ CMenuBar::MB_SEPARATOR },
	{ CMenuBar::MB_CHECKBTN,	CMD_ERRORS,		"_Error bars" },
	{ CMenuBar::MB_CHECKBTN,	CMD_GRID,		"_Grid" },
	{ CMenuBar::MB_END }
};

static const CMenuBar::tMenuItem ToolsMenu[] = {
	{ CMenuBar::MB_CHECKBTN,	CMD_STATISTICS,	"_Statistics" },
	{ CMenuBar::MB_CHECKBTN,	CMD_MEASUREMENT, "_Measurement" },
	{ CMenuBar::MB_END }
};

static const CMenuBar::tMenuItem HelpMenu[] = {
	{ CMenuBar::MB_ITEM,		CMD_HELP,		"_Show help", "help" },
	{ CMenuBar::MB_END }
};

static const CMenuBar::tMenu MainMenu[] = {
	{ "_File",	MENU_FILE,	FileMenu },
	{ "_View",	MENU_VIEW,	ViewMenu },
	{ "_Tools", MENU_TOOLS,	ToolsMenu },
	{ "_Help",	MENU_HELP,	HelpMenu },
	{ NULL }
};

static const CPopupMenu::tPopupMenuItem GraphContextMenu[] = {
	{ CPopupMenu::MB_ITEM, CMD_PREVIEW,		"_Show frame" },
	{ CPopupMenu::MB_ITEM, CMD_FRAMEINFO,	"_Show properties" },
	{ CPopupMenu::MB_SEPARATOR },
	{ CPopupMenu::MB_ITEM, CMD_HIDE,		"_Delete from data set" },
	{ CPopupMenu::MB_ITEM, CMD_DELETE,		"_Remove from project" },
	{ CPopupMenu::MB_END }
};

//-------------------------   OBJECT PROPERTIES DIALOG   --------------------------------

CObjPropertiesDlg::CObjPropertiesDlg(void):m_UpdatePos(true), m_LastPosValid(false), 
	m_LastPosX(0), m_LastPosY(0), m_InFiles(0), m_OutFiles(0), 
	m_ApertureIndex(-1), m_ObjectId(0), m_ChannelX(-1), m_ChannelY(-1), m_MovingTarget(0),
	m_Table(NULL), m_InfoMode(INFO_NONE), m_GraphData(NULL), m_TableData(NULL), 
	m_ShowToolBox(false)
{
	gchar buf[512];
	GtkWidget *tbox;
	GtkCellRenderer *renderer;
	GtkTreeIter iter;

	m_ShowErrors = g_Project->GetBool("ObjProperties", "Errors", true);
	m_ShowGrid   = g_Project->GetBool("ObjProperties", "Grid", false);
	m_DateFormat = (tDateFormat)g_Project->GetInt("Display", "DateFormat", JULIAN_DATE, 0, GREGORIAN_DATE);
	m_DispMode   = (tDisplayMode)g_Project->GetInt("ObjProperties", "Display", DISPLAY_GRAPH, 0, DISPLAY_TABLE);

	// Dialog caption
	sprintf(buf, "%s - %s", "Object properties", g_AppTitle);
	gtk_window_set_title(GTK_WINDOW(m_pDlg), buf);

	// Menu bar
	m_Menu.Create(MainMenu, false);
	m_Menu.RegisterCallback(MenuCallback, this);
	gtk_box_pack_start(GTK_BOX(m_MainBox), m_Menu.Handle(), FALSE, FALSE, 0);

	// Toolbar
	tbox = gtk_toolbar_new();
	gtk_toolbar_set_style(GTK_TOOLBAR(tbox), GTK_TOOLBAR_ICONS);
	gtk_toolbar_set_orientation(GTK_TOOLBAR(tbox), GTK_ORIENTATION_HORIZONTAL);
	gtk_box_pack_start(GTK_BOX(m_MainBox), tbox, false, false, 0);

	m_XLabel = toolbar_new_label(tbox, "X axis");
	m_DateFormats = gtk_list_store_new(2, GTK_TYPE_INT, GTK_TYPE_STRING);
	gtk_list_store_append(m_DateFormats, &iter);
	gtk_list_store_set(m_DateFormats, &iter, 0, JULIAN_DATE, 1, "JD", -1);
	gtk_list_store_append(m_DateFormats, &iter);
	gtk_list_store_set(m_DateFormats, &iter, 0, GREGORIAN_DATE, 1, "UTC", -1);
	m_DCombo = toolbar_new_combo(tbox, "Column shown on the horizontal axis of the graph");
	gtk_combo_box_set_model(GTK_COMBO_BOX(m_DCombo), GTK_TREE_MODEL(m_DateFormats));
	g_signal_connect(G_OBJECT(m_DCombo), "changed", G_CALLBACK(entry_changed), this);
	renderer = gtk_cell_renderer_text_new();
	gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(m_DCombo), renderer, TRUE);
	gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(m_DCombo), renderer, "text", 1);
	SelectItem(GTK_COMBO_BOX(m_DCombo), m_DateFormat);
	if (gtk_combo_box_get_active(GTK_COMBO_BOX(m_DCombo))<0) {
		gtk_combo_box_set_active(GTK_COMBO_BOX(m_DCombo), 0);
		m_DateFormat = (tDateFormat)SelectedItem(GTK_COMBO_BOX(m_DCombo));
	}

	m_YLabel = toolbar_new_label(tbox, "Y axis");
	m_YChannels = gtk_list_store_new(2, GTK_TYPE_INT, GTK_TYPE_STRING);
	m_YCombo = toolbar_new_combo(tbox, "Column shown on the vertical axis of the graph");
	g_signal_connect(G_OBJECT(m_YCombo), "changed", G_CALLBACK(entry_changed), this);
	renderer = gtk_cell_renderer_text_new();
	gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(m_YCombo), renderer, TRUE);
	gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(m_YCombo), renderer, "text", 1);

	toolbar_new_separator(tbox);

	m_OLabel = toolbar_new_label(tbox, "Object");
	m_OEntry = toolbar_new_entry(tbox, "Object # (reference ID)", FALSE);
	if (g_Project->GetTargetType() == MOVING_TARGET)
		gtk_entry_set_width_chars(GTK_ENTRY(m_OEntry), 15);
	m_SelectObject = toolbar_new_button(tbox, "...", "Show chart to change selected object");
	g_signal_connect(G_OBJECT(m_SelectObject), "clicked", G_CALLBACK(button_clicked), this);

	toolbar_new_separator(tbox);

	m_ALabel = toolbar_new_label(tbox, "Aperture");
	m_Apertures = gtk_list_store_new(2, GTK_TYPE_INT, GTK_TYPE_STRING);
	m_ACombo = toolbar_new_combo(tbox, "Aperture used to get brightness of the objects");
	g_signal_connect(G_OBJECT(m_ACombo), "changed", G_CALLBACK(entry_changed), this);
	renderer = gtk_cell_renderer_text_new();
	gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(m_ACombo), renderer, TRUE);
	gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(m_ACombo), renderer, "text", 1);
	
	toolbar_new_separator(tbox);

	m_ZoomLabel = toolbar_new_label(tbox, "Zoom");
	m_ZoomFit = toolbar_new_button_from_stock(tbox, GTK_STOCK_ZOOM_FIT, "Zoom to all points");
	g_signal_connect(G_OBJECT(m_ZoomFit), "clicked", G_CALLBACK(button_clicked), this);
	m_ZoomOut = toolbar_new_button_from_stock(tbox, GTK_STOCK_ZOOM_OUT, "Zoom out");
	g_signal_connect(G_OBJECT(m_ZoomOut), "clicked", G_CALLBACK(button_clicked), this);
	m_ZoomIn = toolbar_new_button_from_stock(tbox, GTK_STOCK_ZOOM_IN, "Zoom in");
	g_signal_connect(G_OBJECT(m_ZoomIn), "clicked", G_CALLBACK(button_clicked), this);

	GtkWidget *hbox = gtk_hbox_new(FALSE, 0);
	gtk_box_pack_start(GTK_BOX(m_MainBox), hbox, TRUE, TRUE, 0);

	// Graph tool box
	m_ToolBox.RegisterCallback(ToolBoxCallback, this);
	gtk_box_pack_start(GTK_BOX(hbox), m_ToolBox.Handle(), FALSE, FALSE, 0);

	// Graph data
	m_GraphView = cmpack_graph_view_new_with_model(NULL);
	cmpack_graph_view_set_mouse_control(CMPACK_GRAPH_VIEW(m_GraphView), TRUE);
	cmpack_graph_view_set_selection_mode(CMPACK_GRAPH_VIEW(m_GraphView), GTK_SELECTION_MULTIPLE);
	cmpack_graph_view_set_scales(CMPACK_GRAPH_VIEW(m_GraphView), TRUE, TRUE);
	g_signal_connect(G_OBJECT(m_GraphView), "mouse-moved", G_CALLBACK(mouse_moved), this);
	g_signal_connect(G_OBJECT(m_GraphView), "mouse-left", G_CALLBACK(mouse_left), this);
	g_signal_connect(G_OBJECT(m_GraphView), "button_press_event", G_CALLBACK(button_press_event), this);
	g_signal_connect(G_OBJECT(m_GraphView), "selection-changed", G_CALLBACK(selection_changed), this);
	gtk_widget_set_size_request(m_GraphView, 300, 200);
	m_GraphScrWnd = gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(m_GraphScrWnd), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(m_GraphScrWnd), GTK_SHADOW_ETCHED_IN);
	gtk_container_add(GTK_CONTAINER(m_GraphScrWnd), m_GraphView);
	gtk_box_pack_start(GTK_BOX(hbox), m_GraphScrWnd, TRUE, TRUE, 0);

	// Table
	m_TableView = gtk_tree_view_new_with_model(NULL);
	m_TableScrWnd = gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(m_TableScrWnd), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(m_TableScrWnd), GTK_SHADOW_ETCHED_IN);
	g_signal_connect(G_OBJECT(m_TableView), "button_press_event", G_CALLBACK(button_press_event), this);
	gtk_container_add(GTK_CONTAINER(m_TableScrWnd), m_TableView);
	gtk_box_pack_start(GTK_BOX(hbox), m_TableScrWnd, TRUE, TRUE, 0);
	GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(m_TableView));
	gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
	gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(m_TableView), TRUE);
	g_signal_connect(G_OBJECT(selection), "changed", G_CALLBACK(selection_changed), this);

	// Info box
	m_InfoBox.RegisterCallback(InfoBoxCallback, this);
	gtk_box_pack_start(GTK_BOX(hbox), m_InfoBox.Handle(), FALSE, FALSE, 0);
	m_InfoBox.SetCaption("Statistics");

	// Measurement tool
	m_MeasBox.RegisterCallback(InfoBoxCallback, this);
	gtk_box_pack_start(GTK_BOX(hbox), m_MeasBox.Handle(), FALSE, FALSE, 0);
	m_MeasBox.SetGraphView(CMPACK_GRAPH_VIEW(m_GraphView));

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

	// Popup menu
	m_GraphMenu.Create(GraphContextMenu);

	// Show the dialog
	gtk_widget_show_all(m_MainBox);
	SetDisplayMode(m_DispMode);
	SetInfoMode(INFO_NONE);
	ShowToolBoxMode(false);
}

CObjPropertiesDlg::~CObjPropertiesDlg()
{
	// Disconnect graph signals
	g_signal_handlers_disconnect_by_func(G_OBJECT(m_GraphView), (gpointer)mouse_moved, this);
	g_signal_handlers_disconnect_by_func(G_OBJECT(m_GraphView), (gpointer)mouse_left, this);
	g_signal_handlers_disconnect_by_func(G_OBJECT(m_GraphView), (gpointer)button_press_event, this);

	// Release objects, free allocated memory
	g_source_remove(m_TimerId);
	g_object_unref(m_DateFormats);
	g_object_unref(m_YChannels);
	g_object_unref(m_Apertures);
	if (m_GraphData)
		g_object_unref(m_GraphData);
	if (m_TableData)
		g_object_unref(m_TableData);
	delete m_Table;
}

bool CObjPropertiesDlg::Make(GtkWindow *parent, bool selected_files)
{
	m_MeasBox.SetTable(NULL);
	delete m_Table;
	m_Table = NULL;
	m_FrameSet.Clear();
	m_AperList.Clear();
	m_ChannelX = m_ChannelY = -1;
	m_ApertureIndex = m_MovingTarget = -1;
	m_DataSaved = false;
	if (g_Project->GetTargetType() == MOVING_TARGET)
		m_MovingTarget = g_Project->GetReferenceTarget();

	// Create list of files
	if (!InitFileList(parent, selected_files))
		return false;

	// Apertures
	m_AperList = *g_Project->Apertures();
	if (m_AperList.Count()==0) {
		ShowError(parent, "No aperture available.\nCheck the configuration and execute the photometry and the matching.");
		return false;
	}

	// Choose stars
	CSelectObjectDlg dlg(parent);
	m_ObjectId = dlg.Execute(g_Project->GetInt("ObjProperties", "ObjectId", 0));
	if (m_ObjectId<=0)
		return false;
	g_Project->SetInt("ObjProperties", "ObjectId", m_ObjectId);
	if (!RebuildData(parent))
		return false;

	// Choose aperture
	m_ApertureIndex = m_AperList.Find(g_Project->GetInt("LightCurve", "Aperture", 0));
	g_Project->SetInt("LightCurve", "Aperture", m_AperList.GetId(m_ApertureIndex));

	UpdateApertures();
	UpdateObject();
	UpdateCurve(parent);
	UpdateChannels();
	UpdateGraphTable(TRUE, TRUE);
	UpdateTools();
	SetInfoMode(INFO_NONE);
	ShowToolBoxMode(false);
	SetStatus(NULL);
	UpdateControls();
	return true;
}

void CObjPropertiesDlg::OnFrameSetChanged(void)
{
	UpdateCurve(GTK_WINDOW(m_pDlg));
	UpdateGraphTable(TRUE, TRUE);
	UpdateStatus();
	UpdateTools();
	UpdateControls();
}

//
// Rebuild frame set
//
bool CObjPropertiesDlg::RebuildData(GtkWindow *parent)
{
	m_Updating = true;
	m_DataSaved = false;
	CSelection sel;
	if (m_ObjectId>0)
		sel.Select(m_ObjectId, CMPACK_SELECT_VAR);
	m_FrameSet.Init(m_AperList, sel);
	GError *error = NULL;
	bool ok = ProcessFiles(parent, &error);
	if (!ok) {
		if (error) {
			ShowError(parent, error->message, true);
			g_error_free(error);
		}
	}
	m_Updating = false;
	return ok;
}

void CObjPropertiesDlg::UpdateApertures(void)
{
	char txt[256];
	GtkTreeIter iter;

	m_Updating = true;

	// Update list of independent channels
	gtk_combo_box_set_model(GTK_COMBO_BOX(m_ACombo), NULL);
	gtk_list_store_clear(m_Apertures);
	for (int i=0; i<m_AperList.Count(); i++) {
		const CAperture *aper = m_AperList.Get(i);
		sprintf(txt, "#%d (%.2f)", aper->Id(), aper->Radius());
		gtk_list_store_append(m_Apertures, &iter);
		gtk_list_store_set(m_Apertures, &iter, 0, i, 1, txt, -1);
	}
	gtk_combo_box_set_model(GTK_COMBO_BOX(m_ACombo), GTK_TREE_MODEL(m_Apertures));
	if (gtk_tree_model_iter_n_children(GTK_TREE_MODEL(m_Apertures), NULL)>0) {
		SelectItem(GTK_COMBO_BOX(m_ACombo), m_ApertureIndex);
		if (gtk_combo_box_get_active(GTK_COMBO_BOX(m_ACombo))<0) {
			gtk_combo_box_set_active(GTK_COMBO_BOX(m_ACombo), 0);
			m_ApertureIndex = SelectedItem(GTK_COMBO_BOX(m_ACombo));
		}
	} else {
		gtk_combo_box_set_active(GTK_COMBO_BOX(m_ACombo), -1);
		m_ApertureIndex = -1;
	}

	m_Updating = false;
}

void CObjPropertiesDlg::UpdateObject(void)
{
	if (m_ObjectId>0) {
		char buf[256];
		if (m_ObjectId == m_MovingTarget)
			sprintf(buf, "Moving target");
		else
			sprintf(buf, "%d", m_ObjectId);
		gtk_entry_set_text(GTK_ENTRY(m_OEntry), buf);
	} else
		gtk_entry_set_text(GTK_ENTRY(m_OEntry), "");
}

bool CObjPropertiesDlg::UpdateCurve(GtkWindow *parent)
{
	GError *error = NULL;

	m_MeasBox.SetTable(NULL);
	delete m_Table;

	m_Table = CmpackObjectProperties(NULL, m_FrameSet, m_ObjectId, m_AperList.GetId(m_ApertureIndex), &error);
	if (!m_Table) {
		if (error) {
			ShowError(parent, error->message, true);
			g_error_free(error);
		}
		return false;
	}

	m_MeasBox.SetTable(m_Table);
	return true;
}

void CObjPropertiesDlg::UpdateControls()
{
	gtk_widget_set_sensitive(m_XLabel, gtk_tree_model_iter_n_children(GTK_TREE_MODEL(m_DateFormats), NULL)>1 &&
		m_DispMode==DISPLAY_GRAPH);
	gtk_widget_set_sensitive(m_DCombo, gtk_tree_model_iter_n_children(GTK_TREE_MODEL(m_DateFormats), NULL)>1 &&
		m_DispMode==DISPLAY_GRAPH);
	gtk_widget_set_sensitive(m_YLabel, gtk_tree_model_iter_n_children(GTK_TREE_MODEL(m_YChannels), NULL)>1 && 
		m_DispMode==DISPLAY_GRAPH);
	gtk_widget_set_sensitive(m_YCombo, gtk_tree_model_iter_n_children(GTK_TREE_MODEL(m_YChannels), NULL)>1 && 
		m_DispMode==DISPLAY_GRAPH);
	gtk_widget_set_sensitive(m_ZoomLabel, m_DispMode==DISPLAY_GRAPH);
	gtk_widget_set_sensitive(GTK_WIDGET(m_ZoomIn), m_DispMode==DISPLAY_GRAPH);
	gtk_widget_set_sensitive(GTK_WIDGET(m_ZoomOut), m_DispMode==DISPLAY_GRAPH);
	gtk_widget_set_sensitive(GTK_WIDGET(m_ZoomFit), m_DispMode==DISPLAY_GRAPH);
	m_Menu.Check(CMD_GRAPH, m_DispMode==DISPLAY_GRAPH);
	m_Menu.Check(CMD_TABLE, m_DispMode==DISPLAY_TABLE);
	m_Menu.Enable(CMD_ERRORS, m_DispMode==DISPLAY_GRAPH);
	m_Menu.Check(CMD_ERRORS, m_ShowErrors);
	m_Menu.Enable(CMD_GRID, m_DispMode==DISPLAY_GRAPH);
	m_Menu.Check(CMD_GRID, m_ShowGrid);
	m_Menu.Enable(CMD_STATISTICS, m_DispMode==DISPLAY_GRAPH);
	m_Menu.Check(CMD_STATISTICS, m_InfoMode == INFO_STATISTICS);
	m_Menu.Enable(CMD_MEASUREMENT, m_DispMode==DISPLAY_GRAPH);
	m_Menu.Check(CMD_MEASUREMENT, m_InfoMode == INFO_MEASUREMENT);
	m_Menu.Enable(CMD_EXPORT, m_DispMode==DISPLAY_GRAPH);
}

void CObjPropertiesDlg::UpdateGraphTable(gboolean autozoom_x, gboolean autozoom_y)
{
	cmpack_graph_view_set_model(CMPACK_GRAPH_VIEW(m_GraphView), NULL);
	if (m_GraphData) {
		g_object_unref(m_GraphData);
		m_GraphData = NULL;
	}
	if (m_Table && m_ChannelX>=0 && m_ChannelY>=0) {
		m_GraphData = m_Table->ToGraphData(m_ChannelX, m_ChannelY);
		m_Table->SetView(CMPACK_GRAPH_VIEW(m_GraphView), m_ChannelX, m_ChannelY, !m_ShowErrors, 
			NULL, NULL, m_DateFormat);
		cmpack_graph_view_set_grid(CMPACK_GRAPH_VIEW(m_GraphView), m_ShowGrid, m_ShowGrid);
		cmpack_graph_view_set_model(CMPACK_GRAPH_VIEW(m_GraphView), m_GraphData);
		cmpack_graph_view_reset_zoom(CMPACK_GRAPH_VIEW(m_GraphView), autozoom_x, autozoom_y);
	}

	gtk_tree_view_set_model(GTK_TREE_VIEW(m_TableView), NULL);
	if (m_TableData) {
		g_object_unref(m_TableData);
		m_TableData = NULL;
	}
	if (m_Table && m_ChannelX>=0 && m_ChannelY>=0) {
		m_TableData = m_Table->ToTreeModel();
		m_Table->SetView(GTK_TREE_VIEW(m_TableView));
		gtk_tree_view_set_model(GTK_TREE_VIEW(m_TableView), m_TableData);
	}
}

void CObjPropertiesDlg::UpdateChannels(void)
{
	// First JD column is on horizontal axis
	if (m_Table) {
		CChannels *cx = m_Table->ChannelsX();
		for (int i=0; i<cx->Count(); i++) {
			if (cx->GetInfo(i) == CChannel::DATA_JD) {
				m_ChannelX = i;
				break;
			}
		}
	}

	// Update list of dependent channels
	gtk_combo_box_set_model(GTK_COMBO_BOX(m_YCombo), NULL);
	gtk_list_store_clear(m_YChannels);
	if (m_Table) {
		CChannels *cy = m_Table->ChannelsY();
		for (int i=0; i<cy->Count(); i++) {
			if (cy->GetInfo(i) == CChannel::DATA_OFFSET || cy->GetInfo(i) == CChannel::DATA_SIZE || cy->GetInfo(i) == CChannel::DATA_ADU || cy->GetInfo(i) == CChannel::DATA_MAG_OTHER) {
				GtkTreeIter iter;
				gtk_list_store_append(m_YChannels, &iter);
				gtk_list_store_set(m_YChannels, &iter, 0, i, 1, cy->GetName(i), -1);
			}
		}
	}
	/*if (gtk_tree_model_iter_n_children(GTK_TREE_MODEL(m_YChannels), NULL)>1) {
		GtkTreeIter iter;
		gtk_list_store_append(m_YChannels, &iter);
		gtk_list_store_set(m_YChannels, &iter, 0, -1, 1, "More...", -1);
	}*/
	gtk_combo_box_set_model(GTK_COMBO_BOX(m_YCombo), GTK_TREE_MODEL(m_YChannels));
	if (gtk_tree_model_iter_n_children(GTK_TREE_MODEL(m_YChannels), NULL)>0) {
		if (m_ShowToolBox || m_ChannelY>=0)
			SelectItem(GTK_COMBO_BOX(m_YCombo), (!m_ShowToolBox ? m_ChannelY : -1));
		if (gtk_combo_box_get_active(GTK_COMBO_BOX(m_YCombo))<0) {
			gtk_combo_box_set_active(GTK_COMBO_BOX(m_YCombo), 0);
			m_ChannelY = SelectedItem(GTK_COMBO_BOX(m_YCombo));
		}
	} else {
		gtk_combo_box_set_active(GTK_COMBO_BOX(m_YCombo), -1);
		m_ChannelY = -1;
	}

	// Update graph tool box
	m_ToolBox.BeginReset();
	if (m_Table) {
		CChannels *cy = m_Table->ChannelsY();
		for (int i=0; i<cy->Count(); i++) {
			if (cy->GetInfo(i) == CChannel::DATA_OFFSET || cy->GetInfo(i) == CChannel::DATA_SIZE || cy->GetInfo(i) == CChannel::DATA_ADU || cy->GetInfo(i) == CChannel::DATA_MAG_OTHER)
				m_ToolBox.AddChannel(i, cy->GetName(i), cy->GetColor(i));
		}
	}
	m_ToolBox.EndReset();
}

void CObjPropertiesDlg::MenuCallback(CCBObject *sender, int message, int wparam, void *lparam, void *cb_data)
{
	CObjPropertiesDlg *pMe = (CObjPropertiesDlg*)cb_data;

	switch (message)
	{
	case CMenuBar::CB_ACTIVATE:
		pMe->OnCommand(wparam);
		break;
	}
}

void CObjPropertiesDlg::OnCommand(int cmd_id)
{
	switch (cmd_id)
	{
	// File menu
	case CMD_CLOSE:
		Close();
		break;
	case CMD_SAVE:
		SaveData();
		break;
	case CMD_EXPORT:
		Export();
		break;
	case CMD_REBUILD:
		RebuildData(GTK_WINDOW(m_pDlg));
		UpdateApertures();
		UpdateObject();
		UpdateCurve(GTK_WINDOW(m_pDlg));
		UpdateChannels();
		UpdateGraphTable(TRUE, TRUE);
		UpdateTools();
		SetInfoMode(INFO_NONE);
		ShowToolBoxMode(false);
		SetStatus(NULL);
		UpdateControls();
		break;

	// View menu
	case CMD_GRAPH:
		SetDisplayMode(DISPLAY_GRAPH);
		break;
	case CMD_TABLE:
		SetDisplayMode(DISPLAY_TABLE);
		break;
	case CMD_ERRORS:
		m_ShowErrors = m_Menu.IsChecked(CMD_ERRORS);
		m_Table->SetView(CMPACK_GRAPH_VIEW(m_GraphView), m_ChannelX, m_ChannelY, !m_ShowErrors);
		g_Project->SetBool("ObjProperties", "Errors", m_ShowErrors);
		break;
	case CMD_GRID:
		m_ShowGrid = m_Menu.IsChecked(CMD_GRID);
		cmpack_graph_view_set_grid(CMPACK_GRAPH_VIEW(m_GraphView), m_ShowGrid, m_ShowGrid);
		g_Project->SetBool("ObjProperties", "Grid", m_ShowGrid);
		break;

	// Tools menu
	case CMD_STATISTICS:
		if (m_Menu.IsChecked(CMD_STATISTICS)) 
			SetInfoMode(INFO_STATISTICS);
		else if (m_InfoMode == INFO_STATISTICS)
			SetInfoMode(INFO_NONE);
		break;
	case CMD_MEASUREMENT:
		if (m_Menu.IsChecked(CMD_MEASUREMENT)) 
			SetInfoMode(INFO_MEASUREMENT);
		else if (m_InfoMode == INFO_MEASUREMENT)
			SetInfoMode(INFO_NONE);
		break;

	// Help menu
	case CMD_HELP:
		g_MainWnd->ShowHelp(GTK_WINDOW(m_pDlg), IDH_PLOT_OBJ_PROPS);
		break;
	}
}

void CObjPropertiesDlg::entry_changed(GtkWidget *pBtn, CObjPropertiesDlg *pMe)
{
	pMe->OnEntryChanged(pBtn);
}

void CObjPropertiesDlg::OnEntryChanged(GtkWidget *pBtn)
{
	if (pBtn==m_DCombo) {
		int format = SelectedItem(GTK_COMBO_BOX(m_DCombo));
		if (format>=0 && format!=m_DateFormat) {
			m_DateFormat = (tDateFormat)format;
			g_Project->SetInt("Display", "DateFormat", m_DateFormat);
			UpdateGraphTable(FALSE, FALSE);
			UpdateTools();
			UpdateControls();
		}
	} else if (pBtn == m_YCombo) {
		int channel = SelectedItem(GTK_COMBO_BOX(m_YCombo));
		if (channel>=0) {
			if (m_ShowToolBox || channel!=m_ChannelY) {
				if (m_ShowToolBox)
					ShowToolBoxMode(FALSE);
				m_ChannelY = channel;
				UpdateGraphTable(FALSE, TRUE);
				UpdateTools();
				UpdateControls();
			}
		} else {
			if (!m_ShowToolBox) {
				ShowToolBoxMode(TRUE);
				UpdateGraphTable(FALSE, TRUE);
				UpdateTools();
				UpdateControls();
			}
		}
	} else if (pBtn==m_ACombo) {
		int index = SelectedItem(GTK_COMBO_BOX(m_ACombo));
		if (index>=0 && index!=m_ApertureIndex) {
			m_ApertureIndex = index;
			g_Project->SetInt("ObjProperties", "Aperture", m_AperList.GetId(m_ApertureIndex));
			m_DataSaved = false;
			UpdateCurve(GTK_WINDOW(m_pDlg));
			UpdateGraphTable(FALSE, FALSE);
			UpdateTools();
			UpdateControls();
		}
	}
}

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

void CObjPropertiesDlg::mouse_left(GtkWidget *button, CObjPropertiesDlg *pDlg)
{
	pDlg->UpdateStatus();
}

//
// Mouse button handler
//
gint CObjPropertiesDlg::button_press_event(GtkWidget *widget, GdkEventButton *event, CObjPropertiesDlg *pMe)
{
	if (event->type==GDK_BUTTON_PRESS && event->button==3) {
		gtk_widget_grab_focus(widget);
		if (widget==pMe->m_GraphView) {
			int col, row;
			if (cmpack_graph_view_get_focused(CMPACK_GRAPH_VIEW(widget), &col, &row) && !cmpack_graph_view_is_selected(CMPACK_GRAPH_VIEW(widget), col, row)) {
				cmpack_graph_view_unselect_all(CMPACK_GRAPH_VIEW(widget));
				cmpack_graph_view_select(CMPACK_GRAPH_VIEW(widget), col, row);
			}
		} else
		if (widget==pMe->m_TableView) {
			GtkTreePath *path;
			if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), (int)event->x, (int)event->y, &path, NULL, NULL, NULL)) {
				gtk_widget_grab_focus(widget);
				GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
				if (gtk_tree_selection_count_selected_rows(sel)<=1)
					gtk_tree_view_set_cursor(GTK_TREE_VIEW(widget), path, NULL, FALSE);
				gtk_tree_path_free(path);
			}
		}
		pMe->OnContextMenu(widget, event);
		return TRUE;
	}
	return FALSE;
}

//
// Show context menu
//
void CObjPropertiesDlg::OnContextMenu(GtkWidget *widget, GdkEventButton *event)
{
	int res, selected;

	if (m_DispMode == DISPLAY_GRAPH) {
		selected = cmpack_graph_view_get_selected_count(CMPACK_GRAPH_VIEW(m_GraphView));
	} else {
		GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(m_TableView));
		selected = gtk_tree_selection_count_selected_rows(selection);
	}
	if (selected>0) {
		m_GraphMenu.Enable(CMD_DELETE, true);
		m_GraphMenu.Enable(CMD_HIDE, true);
		m_GraphMenu.Enable(CMD_FRAMEINFO, selected==1);
		m_GraphMenu.Enable(CMD_PREVIEW, selected==1);
		res = m_GraphMenu.Execute(event);
		switch (res) 
		{
		case CMD_PREVIEW:
			// Show frame preview
			ShowFramePreview();
			break;
		case CMD_FRAMEINFO:
			// Show frame properties
			ShowFrameInfo();
			break;
		case CMD_HIDE:
			// Remove selected points from data set
			RemoveFromDataSet();
			break;
		case CMD_DELETE:
			// Delete selected points from project
			DeleteFromProject();
			break;
		}
	}
}

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

void CObjPropertiesDlg::selection_changed(GtkWidget *pChart, CObjPropertiesDlg *pDlg)
{
	pDlg->OnSelectionChanged();
}

void CObjPropertiesDlg::OnSelectionChanged(void)
{
	UpdateTools();
	UpdateStatus();
}

void CObjPropertiesDlg::UpdateStatus(void)
{
	char	msg[1024], buf1[512], buf2[512];
	gdouble	dx, dy;
	int		frame_id;

	if (m_DispMode == DISPLAY_GRAPH) {
		int col, row;
		if (cmpack_graph_view_get_focused(CMPACK_GRAPH_VIEW(m_GraphView), &col, &row)) {
			if (m_LastFocus!=tIndex(col, row) && m_GraphData && m_Table) {
				m_LastFocus = tIndex(col, row);
				int frame_id = (int)cmpack_graph_data_get_param(m_GraphData, col, row),
					xcol = m_Table->ChannelsX()->GetColumn(m_ChannelX),
					ycol = m_Table->ChannelsY()->GetColumn(m_ChannelY);
				if (m_Table->Find(frame_id) && m_Table->GetDbl(xcol, &dx) && m_Table->GetDbl(ycol, &dy)) {
					PrintKeyValue(buf1, dx, m_Table->ChannelsX()->Get(m_ChannelX));
					PrintKeyValue(buf2, dy, m_Table->ChannelsY()->Get(m_ChannelY));
					sprintf(msg, "Frame #%d: %s, %s", frame_id, buf1, buf2);
				} else {
					sprintf(msg, "Frame #%d", frame_id);
				}
				SetStatus(msg);
			}
		} else {
			m_LastFocus = tIndex();
			if (m_Table && m_ChannelY>=0 && m_ChannelY<m_Table->ChannelsY()->Count() && 
				cmpack_graph_view_mouse_pos(CMPACK_GRAPH_VIEW(m_GraphView), &dx, &dy)) {
					if (!m_LastPosValid || dx!=m_LastPosX || dy!=m_LastPosY) {
						m_LastPosValid = true;
						m_LastPosX = dx;
						m_LastPosY = dy;
						PrintKeyValue(buf1, dx, m_Table->ChannelsX()->Get(m_ChannelX));
						PrintKeyValue(buf2, dy, m_Table->ChannelsY()->Get(m_ChannelY));
						sprintf(msg, "Cursor: %s, %s", buf1, buf2);
						SetStatus(msg);
					}
			} else {
				if (m_LastPosValid) {
					m_LastPosValid = false;
					SetStatus(NULL);
				}
			}
		}
	} else {
		GList *list = gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(GTK_TREE_VIEW(m_TableView)), NULL);
		if (list && m_TableData) {
			int count = g_list_length(list);
			if (count>1) {
				sprintf(msg, "%d rows selected", count);
				SetStatus(msg);
			} else {
				GtkTreeIter iter;
				if (gtk_tree_model_get_iter(m_TableData, &iter, (GtkTreePath*)list->data)) {
					gtk_tree_model_get(m_TableData, &iter, 0, &frame_id, -1);
					sprintf(msg, "Frame #%d", frame_id);
					SetStatus(msg);
				} else
					SetStatus(NULL);
			}
		} else 
			SetStatus(NULL);
		g_list_foreach (list, (GFunc)gtk_tree_path_free, NULL);
		g_list_free (list);
	}
}

void CObjPropertiesDlg::SetDisplayMode(tDisplayMode mode)
{
	switch (mode) 
	{
	case DISPLAY_TABLE:
		gtk_widget_hide(m_GraphScrWnd);
		gtk_widget_show(m_TableScrWnd);
		SetInfoMode(INFO_NONE);
		ShowToolBoxMode(false);
		break;

	case DISPLAY_GRAPH:
		gtk_widget_show(m_GraphScrWnd);
		gtk_widget_hide(m_TableScrWnd);
		m_LastFocus = tIndex();
		m_LastPosValid = false;
		break;
	}
	m_DispMode = mode;
	g_Project->SetInt("ObjProperties", "Display", m_DispMode);
	UpdateControls();
}

void CObjPropertiesDlg::PrintValue(char *buf, double value, const CChannel *channel)
{
	CChannel::tChannelInfo info = channel->Info();
	if ((info == CChannel::DATA_JD || (info == CChannel::DATA_JD_HEL)) && m_DateFormat != JULIAN_DATE) {
		CmpackDateTime dt;
		cmpack_decodejd(value, &dt);
		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);
	} else {
		const gchar *unit = channel->Unit();
		if (unit)
			sprintf(buf, "%.*f %s", channel->Precision(), value, unit);
		else
			sprintf(buf, "%.*f", channel->Precision(), value);
	}
}

void CObjPropertiesDlg::PrintKeyValue(char *buf, double value, const CChannel *channel)
{
	CChannel::tChannelInfo info = channel->Info();
	if ((info == CChannel::DATA_JD || (info == CChannel::DATA_JD_HEL)) && m_DateFormat != JULIAN_DATE) {
		CmpackDateTime dt;
		cmpack_decodejd(value, &dt);
		sprintf(buf, "%s = %04d-%02d-%02d %02d:%02d:%02d", "UTC", dt.date.year, dt.date.month, dt.date.day,
			dt.time.hour, dt.time.minute, dt.time.second);
	} else {
		const gchar *unit = channel->Unit();
		if (unit)
			sprintf(buf, "%s = %.*f %s", channel->Name(), channel->Precision(), value, unit);
		else
			sprintf(buf, "%s = %.*f", channel->Name(), channel->Precision(), value);
	}
}

void CObjPropertiesDlg::UpdateTools(void)
{
	char	buf[256], msg[512];
	double	dx, dy, dmin, dmax;
	int		xcol, ycol;

	if (m_InfoMode == INFO_MEASUREMENT) {
		// Update measurement tool
		m_MeasBox.SetDateFormat(m_DateFormat);
		m_MeasBox.SetChannel(CMPACK_AXIS_X, m_ChannelX);
		m_MeasBox.SetChannel(CMPACK_AXIS_Y, m_ChannelY);
	} else 
	if (m_InfoMode == INFO_STATISTICS && m_GraphData && m_Table) {
		m_InfoBox.BeginUpdate();
		m_InfoBox.Clear();
		// Show information about selected star
		xcol = m_Table->ChannelsX()->GetColumn(m_ChannelX);
		ycol = m_Table->ChannelsY()->GetColumn(m_ChannelY);
		int selected_count = cmpack_graph_view_get_selected_count(CMPACK_GRAPH_VIEW(m_GraphView));
		if (selected_count==1) {
			int col, row;
			if (cmpack_graph_view_get_selected(CMPACK_GRAPH_VIEW(m_GraphView), &col, &row)) {
				int frame_id = (int)cmpack_graph_data_get_param(m_GraphData, 0, row);
				if (frame_id>=0) {
					sprintf(buf, "Frame #%d", frame_id);
					m_InfoBox.AddTitle(1, buf);
					if (m_Table->Find(frame_id) && m_Table->GetDbl(xcol, &dx) && m_Table->GetDbl(ycol, &dy)) {
						PrintKeyValue(buf, dx, m_Table->ChannelsX()->Get(m_ChannelX));
						m_InfoBox.AddText(buf);
						PrintKeyValue(buf, dy, m_Table->ChannelsY()->Get(m_ChannelY));
						m_InfoBox.AddText(buf);
					}
				}
			}
		} else {
			// Compute statistics
			int count = 0, length = (selected_count>0 ? selected_count : m_Table->Rows());
			double *x = new double[length], *y = new double[length];
			if (selected_count>0) {
				GList *list = cmpack_graph_view_get_selected_rows(CMPACK_GRAPH_VIEW(m_GraphView));
				for (GList *ptr=list; ptr!=NULL; ptr=ptr->next) {
					int frame_id = (int)cmpack_graph_data_get_param(m_GraphData, 0, GPOINTER_TO_INT(ptr->data));
					if (m_Table->Find(frame_id) && m_Table->GetDbl(xcol, x+count) && m_Table->GetDbl(ycol, y+count))
						count++;
				}
				g_list_free(list);
			} else {
				bool ok = m_Table->Rewind();
				while (ok) {
					if (m_Table->GetDbl(xcol, x+count) && m_Table->GetDbl(ycol, y+count))
						count++;
					ok = m_Table->Next();
				}
			}
			if (selected_count>0)
				sprintf(buf, "There are %d selected points.", count);
			else
				sprintf(buf, "There are %d points in total.", count);
			m_InfoBox.AddText(buf);
			if (ComputeMinMax(count, x, &dmin, &dmax)) {
				m_InfoBox.AddText("");
				sprintf(msg, "Data %s:", (m_DateFormat==JULIAN_DATE ? "JD" : "UTC"));
				m_InfoBox.AddTitle(1, msg);
				PrintValue(buf, dmin, m_Table->ChannelsX()->Get(m_ChannelX));
				sprintf(msg, "%s: %s", "Min.", buf);
				m_InfoBox.AddText(msg);
				PrintValue(buf, dmax, m_Table->ChannelsX()->Get(m_ChannelX));
				sprintf(msg, "%s: %s", "Max.", buf);
				m_InfoBox.AddText(msg);
			}
			if (ComputeMinMax(count, y, &dmin, &dmax)) {
				m_InfoBox.AddText("");
				sprintf(msg, "Data %s:", m_Table->ChannelsY()->GetName(m_ChannelY));
				m_InfoBox.AddTitle(1, msg);
				PrintValue(buf, dmin, m_Table->ChannelsY()->Get(m_ChannelY));
				sprintf(msg, "%s: %s", "Min.", buf);
				m_InfoBox.AddText(msg);
				PrintValue(buf, dmax, m_Table->ChannelsY()->Get(m_ChannelY));
				sprintf(msg, "%s: %s", "Max.", buf);
				m_InfoBox.AddText(msg);
			}
			delete[] x;
			delete[] y;
		}
		m_InfoBox.EndUpdate();
	} else {
		// No tool is open
		cmpack_graph_view_set_cursors(CMPACK_GRAPH_VIEW(m_GraphView), CMPACK_AXIS_X, 0);
		cmpack_graph_view_set_cursors(CMPACK_GRAPH_VIEW(m_GraphView), CMPACK_AXIS_Y, 0);
	}
}

void CObjPropertiesDlg::SetInfoMode(tInfoMode mode)
{
	m_InfoMode = mode;
	switch (m_InfoMode) 
	{
	case INFO_STATISTICS:
		m_MeasBox.Show(false);
		m_InfoBox.Show(true);
		break;
	case INFO_MEASUREMENT:
		m_InfoBox.Show(false);
		m_MeasBox.Show(true);
		break;
	default:
		m_MeasBox.Show(false);
		m_InfoBox.Show(false);
		break;
	}
	UpdateTools();
	UpdateControls();
}

void CObjPropertiesDlg::ShowToolBoxMode(bool show)
{
	m_ShowToolBox = show;
	m_ToolBox.Show(show);
	UpdateControls();
}

void CObjPropertiesDlg::InfoBoxCallback(CCBObject *sender, int message, int wparam, void* lparam, void* cb_data)
{
	CObjPropertiesDlg *pMe = (CObjPropertiesDlg*)cb_data;

	switch (message)
	{
	case CInfoBox::CB_CLOSED:
		pMe->OnInfoBoxClosed();
		break;
	}
}

void CObjPropertiesDlg::OnInfoBoxClosed(void)
{
	m_InfoMode = INFO_NONE;
	UpdateControls();
}

void CObjPropertiesDlg::ToolBoxCallback(CCBObject *sender, int message, int wparam, void* lparam, void* cb_data)
{
	CObjPropertiesDlg *pMe = (CObjPropertiesDlg*)cb_data;

	switch (message)
	{
	case CInfoBox::CB_CLOSED:
		pMe->OnToolBoxClosed();
		break;
	}
}

void CObjPropertiesDlg::OnToolBoxClosed(void)
{
	m_ShowToolBox = false;
	SelectItem(GTK_COMBO_BOX(m_YCombo), m_ChannelY);
	UpdateGraphTable(FALSE, TRUE);
	UpdateTools();
	UpdateControls();
}

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

void CObjPropertiesDlg::OnButtonClicked(GtkWidget *pBtn)
{
	double zoom;

	if (pBtn==GTK_WIDGET(m_ZoomIn)) {
		zoom = cmpack_graph_view_get_zoom(CMPACK_GRAPH_VIEW(m_GraphView), CMPACK_AXIS_X);
		cmpack_graph_view_set_zoom(CMPACK_GRAPH_VIEW(m_GraphView), CMPACK_AXIS_X, zoom + 5.0);
		zoom = cmpack_graph_view_get_zoom(CMPACK_GRAPH_VIEW(m_GraphView), CMPACK_AXIS_Y);
		cmpack_graph_view_set_zoom(CMPACK_GRAPH_VIEW(m_GraphView), CMPACK_AXIS_Y, zoom + 5.0);
	} else 
	if (pBtn==GTK_WIDGET(m_ZoomOut)) {
		zoom = cmpack_graph_view_get_zoom(CMPACK_GRAPH_VIEW(m_GraphView), CMPACK_AXIS_X);
		cmpack_graph_view_set_zoom(CMPACK_GRAPH_VIEW(m_GraphView), CMPACK_AXIS_X, zoom - 5.0);
		zoom = cmpack_graph_view_get_zoom(CMPACK_GRAPH_VIEW(m_GraphView), CMPACK_AXIS_Y);
		cmpack_graph_view_set_zoom(CMPACK_GRAPH_VIEW(m_GraphView), CMPACK_AXIS_Y, zoom - 5.0);
	} else 
	if (pBtn==GTK_WIDGET(m_ZoomFit)) {
		cmpack_graph_view_reset_zoom(CMPACK_GRAPH_VIEW(m_GraphView), TRUE, TRUE);
	} else
	if (pBtn==GTK_WIDGET(m_SelectObject)) {
		SelectObject();
	}
}

void CObjPropertiesDlg::SelectObject(void)
{
	CSelectObjectDlg dlg(GTK_WINDOW(m_pDlg));
	int newId = dlg.Execute(m_ObjectId);
	if (newId > 0 && newId != m_ObjectId) {
		m_ObjectId = newId;
		RebuildData(GTK_WINDOW(m_pDlg));
		UpdateApertures();
		UpdateObject();
		UpdateCurve(GTK_WINDOW(m_pDlg));
		UpdateChannels();
		UpdateGraphTable(TRUE, TRUE);
		UpdateTools();
		SetInfoMode(INFO_NONE);
		ShowToolBoxMode(false);
		SetStatus(NULL);
		UpdateControls();
	}
}

void CObjPropertiesDlg::SaveData(void)
{
	CSaveObjPropertiesDlg pDlg(GTK_WINDOW(m_pDlg));
	if (pDlg.Execute(*m_Table, m_ChannelY, m_ObjectId)) {
		m_DataSaved = true;
		UpdateControls();
	}
}

void CObjPropertiesDlg::Export(void)
{
	CGraphExportDlg dlg(GTK_WINDOW(m_pDlg));

	char name[64];
	sprintf(name, "object_%d", m_ObjectId);
	dlg.Execute(CMPACK_GRAPH_VIEW(m_GraphView), name);
}

GList *CObjPropertiesDlg::GetSelectedFrames(void)
{
	GList *frames = NULL;

	if (m_DispMode == DISPLAY_GRAPH) {
		GList *rows = cmpack_graph_view_get_selected_rows(CMPACK_GRAPH_VIEW(m_GraphView));
		if (rows && m_GraphData) {
			for (GList *ptr=rows; ptr!=NULL; ptr=ptr->next) {
				int frame = (gint)cmpack_graph_data_get_param(m_GraphData, 0, GPOINTER_TO_INT(ptr->data));
				frames = g_list_prepend(frames, g_Project->GetPath(frame));
			}
			g_list_free(rows);
		}
	} else {
		GList *rows = gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(GTK_TREE_VIEW(m_TableView)), NULL);
		if (rows && m_TableData) {
			GtkTreeIter iter;
			if (gtk_tree_model_get_iter(m_TableData, &iter, (GtkTreePath*)rows->data)) {
				int frame_id;
				gtk_tree_model_get(m_TableData, &iter, 0, &frame_id, -1);
				frames = g_list_prepend(frames, g_Project->GetPath(frame_id));
			}
		}
		g_list_foreach (rows, (GFunc)gtk_tree_path_free, NULL);
		g_list_free (rows);
	}
	return g_list_reverse(frames);
}

GtkTreePath *CObjPropertiesDlg::GetSelectedFrame(void)
{
	GtkTreePath *retval = NULL;
	if (m_DispMode == DISPLAY_GRAPH) {
		GList *rows = cmpack_graph_view_get_selected_rows(CMPACK_GRAPH_VIEW(m_GraphView));
		if (rows && m_GraphData) {
			int frame = (gint)cmpack_graph_data_get_param(m_GraphData, 0, GPOINTER_TO_INT(rows->data));
			retval = g_Project->GetPath(frame);
		}
		g_list_free(rows);
	} else {
		GList *rows = gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(GTK_TREE_VIEW(m_TableView)), NULL);
		if (rows && m_TableData) {
			GtkTreeIter iter;
			if (gtk_tree_model_get_iter(m_TableData, &iter, (GtkTreePath*)rows->data)) {
				int frame_id;
				gtk_tree_model_get(m_TableData, &iter, 0, &frame_id, -1);
				retval = g_Project->GetPath(frame_id);
			}
		}
		g_list_foreach (rows, (GFunc)gtk_tree_path_free, NULL);
		g_list_free (rows);
	}
	return retval;
}

//-------------------------   MAKE TRACK CURVE DIALOG   --------------------------------

CMakeObjPropertiesDlg::CMakeObjPropertiesDlg(GtkWindow *pParent):m_pParent(pParent)
{
	GtkWidget *vbox;
	GSList *group;

	// Dialog with buttons
	m_pDlg = gtk_dialog_new_with_buttons("Plot object properties", pParent, 
		(GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, 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, "Use the entered values and continue");
	g_signal_connect(G_OBJECT(m_pDlg), "response", G_CALLBACK(response_dialog), this);

	// Dialog icon
	gchar *icon = get_icon_file("objproperties");
	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);

	// Frame selection
	GtkWidget *label = gtk_label_new(NULL);
	gtk_label_set_markup(GTK_LABEL(label), "<b>Process</b>");
	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, TRUE, 0);
	m_AllBtn = gtk_radio_button_new_with_label(NULL, "all files in current project");
	gtk_widget_set_tooltip_text(m_AllBtn, "Process all frames in the current project");
	gtk_box_pack_start(GTK_BOX(vbox), m_AllBtn, TRUE, TRUE, 0);
	group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(m_AllBtn));
	m_SelBtn = gtk_radio_button_new_with_label(group, "selected files only");
	gtk_widget_set_tooltip_text(m_SelBtn, "Process frames that are selected in the main window");
	gtk_box_pack_start(GTK_BOX(vbox), m_SelBtn, TRUE, TRUE, 0);

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

CMakeObjPropertiesDlg::~CMakeObjPropertiesDlg()
{
	gtk_widget_destroy(m_pDlg);
}

void CMakeObjPropertiesDlg::Execute(void)
{
	// Frames
	GtkTreeSelection *pSel = g_MainWnd->GetSelection();
	gtk_widget_set_sensitive(m_AllBtn, TRUE);
	gtk_widget_set_sensitive(m_SelBtn, 
		gtk_tree_selection_count_selected_rows(pSel)>0);
	if (gtk_tree_selection_count_selected_rows(pSel)>1)
		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_SelBtn), TRUE);
	else
		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_AllBtn), TRUE);

	// Show the dialog
	if (gtk_dialog_run(GTK_DIALOG(m_pDlg))!=GTK_RESPONSE_ACCEPT) {
		gtk_widget_hide(m_pDlg);
		return;
	}
	gtk_widget_hide(m_pDlg);

	bool selected_files = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_SelBtn))!=0;
		
	CObjPropertiesDlg *dlg = new CObjPropertiesDlg();
	if (dlg->Make(m_pParent, selected_files))
		dlg->Show();
	else
		dlg->Close();
}

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

bool CMakeObjPropertiesDlg::OnResponseDialog(gint response_id)
{
	switch (response_id)
	{
	case GTK_RESPONSE_ACCEPT:
		// Commit changes
		return OnCloseQuery();

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

bool CMakeObjPropertiesDlg::OnCloseQuery()
{
	return true;
}

//------------------------   SAVE OBJECT PROPERTIES   ---------------------------------

static const struct {
	const gchar *Id, *Caption, *Extension, *MimeType;
	const gchar *FilterName, *FilterPattern;
} FileFormats[] = {
	{ "CMPACK",	"C-Munipack (default)",				"txt",	NULL,			"C-Munipack files", "*.txt" },
	{ "TEXT",	"Text (space separated values)",	"txt",	"text/plain",	"Text files",		"*.txt" },
	{ "CSV",	"CSV (comma separated values)",		"csv",	"text/csv",		"CSV files",		"*.csv" },
	{ NULL }
};

static const gchar *FileExtension(gint type)
{
	if (type>=0)
		return FileFormats[type].Extension;
	return "";
}

static const gchar *FileMimeType(gint type)
{
	if (type>=0)
		return FileFormats[type].MimeType;
	return NULL;
}

static GtkFileFilter *FileFilter(gint type)
{
	if (type>=0 && FileFormats[type].FilterName && FileFormats[type].FilterPattern) {
		GtkFileFilter *filter = gtk_file_filter_new();
		gtk_file_filter_add_pattern(filter, FileFormats[type].FilterPattern);
		gtk_file_filter_set_name(filter, FileFormats[type].FilterName);
		return filter;
	}
	return NULL;
}

//
// Constructor
//
CSaveObjPropertiesDlg::CSaveObjPropertiesDlg(GtkWindow *pParent):m_pParent(pParent),
	m_Updating(false), m_FileType(TYPE_MUNIPACK), m_SelectedY(-1)
{
	memset(m_Options, 0, TYPE_N_ITEMS*sizeof(tOptions));
	m_Options[TYPE_MUNIPACK].header = m_Options[TYPE_MUNIPACK].all_values = true;
	
	// Dialog with buttons
	m_pDlg = gtk_file_chooser_dialog_new("Save object properties", pParent,
		GTK_FILE_CHOOSER_ACTION_SAVE, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, 
		GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, GTK_STOCK_HELP, GTK_RESPONSE_HELP, NULL);
	gtk_file_chooser_standard_tooltips(GTK_FILE_CHOOSER(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);
	gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(m_pDlg), true);

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

	// Options
	GtkWidget *frame = gtk_frame_new("Export options");
	GtkWidget *hbox = gtk_hbox_new(TRUE, 8);
	gtk_container_add(GTK_CONTAINER(frame), hbox);
	gtk_container_set_border_width(GTK_CONTAINER(hbox), 8);

	// File format
	GtkWidget *lbox = gtk_vbox_new(FALSE, 4);
	gtk_box_pack_start(GTK_BOX(hbox), lbox, TRUE, TRUE, 0);
	m_FileTypes = gtk_list_store_new(2, G_TYPE_INT, G_TYPE_STRING);
	GtkWidget *label = gtk_label_new("File type");
	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
	gtk_box_pack_start(GTK_BOX(lbox), label, FALSE, TRUE, 0);
	m_TypeCombo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(m_FileTypes));
	GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
	gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(m_TypeCombo), renderer, TRUE);
	gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(m_TypeCombo), renderer, "text", 1);
	gtk_box_pack_start(GTK_BOX(lbox), m_TypeCombo, FALSE, TRUE, 0);
	g_signal_connect(G_OBJECT(m_TypeCombo), "changed", G_CALLBACK(selection_changed), this);
	m_Header = gtk_check_button_new_with_label("Include column names");
	gtk_box_pack_start(GTK_BOX(lbox), m_Header, FALSE, TRUE, 0);
	g_signal_connect(G_OBJECT(m_Header), "toggled", G_CALLBACK(button_toggled), this);
	m_FrameIds = gtk_check_button_new_with_label("Include frame #");
	gtk_box_pack_start(GTK_BOX(lbox), m_FrameIds, FALSE, TRUE, 0);
	g_signal_connect(G_OBJECT(m_FrameIds), "toggled", G_CALLBACK(button_toggled), this);
	m_SkipInvalid = gtk_check_button_new_with_label("Discard rows with invalid values");
	gtk_box_pack_start(GTK_BOX(lbox), m_SkipInvalid, FALSE, TRUE, 0);
	g_signal_connect(G_OBJECT(m_SkipInvalid), "toggled", G_CALLBACK(button_toggled), this);
	
	// Column selection
	GtkWidget *rbox = gtk_vbox_new(FALSE, 4);
	gtk_box_pack_start(GTK_BOX(hbox), rbox, TRUE, TRUE, 0);
	m_Channels = gtk_list_store_new(2, G_TYPE_INT, G_TYPE_STRING);
	label = gtk_label_new("Values");
	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
	gtk_box_pack_start(GTK_BOX(rbox), label, FALSE, TRUE, 0);
	m_VCCombo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(m_Channels));
	renderer = gtk_cell_renderer_text_new();
	gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(m_VCCombo), renderer, TRUE);
	gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(m_VCCombo), renderer, "text", 1);
	gtk_box_pack_start(GTK_BOX(rbox), m_VCCombo, FALSE, TRUE, 0);
	g_signal_connect(G_OBJECT(m_VCCombo), "changed", G_CALLBACK(selection_changed), this);
	m_AllValues = gtk_check_button_new_with_label("Export all values");
	gtk_box_pack_start(GTK_BOX(rbox), m_AllValues, FALSE, TRUE, 0);
	g_signal_connect(G_OBJECT(m_AllValues), "toggled", G_CALLBACK(button_toggled), this);
	
	gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(m_pDlg), frame);
	gtk_widget_show_all(frame);
}

CSaveObjPropertiesDlg::~CSaveObjPropertiesDlg()
{
	gtk_widget_destroy(m_pDlg);
	g_object_unref(m_FileTypes);
	g_object_unref(m_Channels);
}

CSaveObjPropertiesDlg::tFileType CSaveObjPropertiesDlg::StrToFileType(const gchar *str)
{
	if (str) {
		for (gint i=0; FileFormats[i].Id!=NULL; i++) {
			if (strcmp(FileFormats[i].Id, str)==0)
				return (tFileType)i;
		}
	}
	return TYPE_MUNIPACK;
}

const gchar *CSaveObjPropertiesDlg::FileTypeToStr(tFileType type)
{
	if (type>=0 && type<TYPE_N_ITEMS)
		return FileFormats[type].Id;
	return "";
}

bool CSaveObjPropertiesDlg::Execute(const CTable &table, int ychannel, int objectId)
{
	m_Table.MakeCopy(table);
	m_SelectedY = ychannel;

	gchar *aux = g_Project->GetStr("ObjProperties", "FileType");
	m_FileType = StrToFileType(aux);
	g_free(aux);

	m_Updating = true;

	// Restore last folder and file name
	gchar *dirpath = g_Project->GetStr("Output", "Folder", NULL);
	if (!dirpath)
		dirpath = g_path_get_dirname(g_Project->Path());
	if (dirpath && g_file_test(dirpath, G_FILE_TEST_IS_DIR)) 
		gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(m_pDlg), dirpath);
	g_free(dirpath);

	gchar defname[64];
	sprintf(defname, "object_%d.txt", objectId);
	gchar *filename = g_Project->GetStr("ObjProperties", "File", defname);
	const gchar *defext = FileExtension(m_FileType);
	if (defext && filename) {
		gchar *newname = SetFileExtension(filename, defext);
		g_free(filename);
		filename = newname;
	}
	gchar *basename = g_path_get_basename(filename);
	gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(m_pDlg), basename);
	g_free(basename);
	g_free(filename);

	// File types
	gtk_combo_box_set_model(GTK_COMBO_BOX(m_TypeCombo), NULL);
	gtk_list_store_clear(m_FileTypes);
	for (int i=0; FileFormats[i].Caption; i++) {
		GtkTreeIter iter;
		gtk_list_store_append(m_FileTypes, &iter);
		gtk_list_store_set(m_FileTypes, &iter, 0, i, 1, FileFormats[i].Caption, -1);
	}
	gtk_combo_box_set_model(GTK_COMBO_BOX(m_TypeCombo), GTK_TREE_MODEL(m_FileTypes));
	SelectItem(GTK_COMBO_BOX(m_TypeCombo), m_FileType);
	if (gtk_combo_box_get_active(GTK_COMBO_BOX(m_TypeCombo))<0) {
		gtk_combo_box_set_active(GTK_COMBO_BOX(m_TypeCombo), 0);
		m_FileType = (tFileType)SelectedItem(GTK_COMBO_BOX(m_TypeCombo));
	}
	gtk_widget_set_sensitive(m_TypeCombo, 
		gtk_tree_model_iter_n_children(GTK_TREE_MODEL(m_FileTypes), NULL)>1);

	// Restore options (format dependent customizable options)
	m_Options[TYPE_CSV].frame_id = g_Project->GetBool("ObjProperties", "CSV_FRAME_ID");
	m_Options[TYPE_CSV].skip_invalid = g_Project->GetBool("ObjProperties", "CSV_SKIP_INVALID", true);
	m_Options[TYPE_CSV].header = g_Project->GetBool("ObjProperties", "CSV_HEADER", true);
	m_Options[TYPE_CSV].all_values = g_Project->GetBool("ObjProperties", "CSV_ALL_VALUES");
	m_Options[TYPE_TEXT].frame_id = g_Project->GetBool("ObjProperties", "TEXT_FRAME_ID");
	m_Options[TYPE_TEXT].skip_invalid = g_Project->GetBool("ObjProperties", "TEXT_SKIP_INVALID", true);
	m_Options[TYPE_TEXT].header = g_Project->GetBool("ObjProperties", "TEXT_HEADER", true);
	m_Options[TYPE_TEXT].all_values = g_Project->GetBool("ObjProperties", "TEXT_ALL_VALUES");

	m_Updating = false;

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

	// Output file path
	filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(m_pDlg));

	// Save last folder and file name
	dirpath = g_path_get_dirname(filename);
	g_Project->SetStr("Output", "Folder", dirpath);
	g_free(dirpath);
	basename = g_path_get_basename(filename);
	g_Project->SetStr("ObjProperties", "File", basename);
	g_free(basename);

	// Save settings
	g_Project->SetStr("ObjProperties", "FileType", FileTypeToStr(m_FileType));
	g_Project->SetBool("ObjProperties", "CSV_FRAME_ID", m_Options[TYPE_CSV].frame_id);
	g_Project->SetBool("ObjProperties", "CSV_SKIP_INVALID", m_Options[TYPE_CSV].skip_invalid);
	g_Project->SetBool("ObjProperties", "CSV_HEADER", m_Options[TYPE_CSV].header);
	g_Project->SetBool("ObjProperties", "CSV_ALL_VALUES", m_Options[TYPE_CSV].all_values);
	g_Project->SetBool("ObjProperties", "TEXT_FRAME_ID", m_Options[TYPE_TEXT].frame_id);
	g_Project->SetBool("ObjProperties", "TEXT_SKIP_INVALID", m_Options[TYPE_TEXT].skip_invalid);
	g_Project->SetBool("ObjProperties", "TEXT_HEADER", m_Options[TYPE_TEXT].header);
	g_Project->SetBool("ObjProperties", "TEXT_ALL_VALUES", m_Options[TYPE_TEXT].all_values);

	const tOptions *opt = m_Options+m_FileType;

	CChannels *cx = m_Table.ChannelsX();
	for (int i=0; i<cx->Count(); i++) {
		switch (cx->GetInfo(i))
		{
		case CChannel::DATA_FRAME_ID:
			if (opt->frame_id)
				cx->SetExportFlags(i, 0);
			else
				cx->SetExportFlags(i, CChannel::EXPORT_SKIP);
			break;
		case CChannel::DATA_JD:
			cx->SetExportFlags(i, 0);
			break;
		default:
			cx->SetExportFlags(i, CChannel::EXPORT_SKIP);
		}
	}

	CChannels *cy = m_Table.ChannelsY();
	for (int i=0; i<cy->Count(); i++) {
		if (opt->all_values || i==m_SelectedY) 
			cy->SetExportFlags(i, 0);
		else 
			cy->SetExportFlags(i, CChannel::EXPORT_SKIP);
	}

	m_Table.SetParam("JD", "geocentric");

	int res = 0;
	if (m_FileType == TYPE_MUNIPACK) {
		GError *error = NULL;
		if (!m_Table.Save(filename, &error)) {
			if (error) {
				ShowError(m_pParent, error->message, true);
				g_error_free(error);
			}
			res = -1;
		}
	} else {
		unsigned flags = 0;
		if (!opt->header)
			flags |= CTable::EXPORT_NO_HEADER;
		if (opt->skip_invalid)
			flags |= CTable::EXPORT_NULVAL_SKIP_ROW;
		else
			flags |= CTable::EXPORT_NULVAL_ZERO;
		GError *error = NULL;
		if (!m_Table.ExportTable(filename, FileMimeType(m_FileType), flags, &error)) {
			if (error) {
				ShowError(m_pParent, error->message);
				g_error_free(error);
			}
			res = -1;
		} else
			ShowInformation(m_pParent, "The file was generated successfully.");
	}
	g_free(filename);
	return res==0;
}

void CSaveObjPropertiesDlg::selection_changed(GtkComboBox *pWidget, CSaveObjPropertiesDlg *pMe)
{
	pMe->OnSelectionChanged(pWidget);
}

void CSaveObjPropertiesDlg::OnSelectionChanged(GtkComboBox *pWidget)
{
	if (!m_Updating) {
		if (GTK_WIDGET(pWidget) == m_TypeCombo) {
			int ft = SelectedItem(pWidget);
			if (ft!=m_FileType) {
				m_FileType = (tFileType)ft;
				OnTypeChanged();
			}
		} else
		if (GTK_WIDGET(pWidget) == m_VCCombo) {
			int ch = SelectedItem(pWidget);
			if (m_SelectedY!=ch) {
				m_SelectedY = ch;
				UpdateControls();
			}
		}
	}
}

void CSaveObjPropertiesDlg::UpdateControls(void)
{
	gtk_widget_set_sensitive(m_VCCombo, 
		gtk_tree_model_iter_n_children(GTK_TREE_MODEL(m_Channels), NULL)>1 &&
		!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_AllValues)));
	gtk_widget_set_sensitive(m_AllValues, (m_FileType==TYPE_CSV || m_FileType==TYPE_TEXT) &&
		gtk_tree_model_iter_n_children(GTK_TREE_MODEL(m_Channels), NULL)>1);
	gtk_widget_set_sensitive(m_FrameIds, (m_FileType==TYPE_CSV || m_FileType==TYPE_TEXT));
	gtk_widget_set_sensitive(m_Header, (m_FileType==TYPE_CSV || m_FileType==TYPE_TEXT));
	gtk_widget_set_sensitive(m_SkipInvalid, (m_FileType==TYPE_CSV || m_FileType==TYPE_TEXT));
}

void CSaveObjPropertiesDlg::OnTypeChanged(void)
{
	// Change file filters
	GSList *list = gtk_file_chooser_list_filters(GTK_FILE_CHOOSER(m_pDlg));
	for (GSList *ptr=list; ptr!=NULL; ptr=ptr->next) 
		gtk_file_chooser_remove_filter(GTK_FILE_CHOOSER(m_pDlg), (GtkFileFilter*)ptr->data);
	g_slist_free(list);
	GtkFileFilter *type_filter = FileFilter(m_FileType);
	if (type_filter)
		gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(m_pDlg), type_filter);
	GtkFileFilter *all_files = gtk_file_filter_new();
	gtk_file_filter_add_pattern(all_files, "*");
	gtk_file_filter_set_name(all_files, "All files");
	gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(m_pDlg), all_files);
	gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(m_pDlg), (type_filter ? type_filter : all_files));

	// Change file's extension
	const gchar *ext = FileExtension(m_FileType);
	if (ext) {
		gchar *oldname = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(m_pDlg));
		if (oldname) {
			gchar *newname = SetFileExtension(oldname, ext);
			gchar *basename = g_path_get_basename(newname);
			gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(m_pDlg), basename);
			g_free(basename);
			g_free(newname);
			g_free(oldname);
		}
	}

	m_Updating = true;

	// Y columns
	gtk_combo_box_set_model(GTK_COMBO_BOX(m_VCCombo), NULL);
	gtk_list_store_clear(m_Channels);
	if (m_FileType == TYPE_MUNIPACK) {
		// Show "All values" only
		for (int i=0; i<m_Table.ChannelsY()->Count(); i++) {
			CChannel *ch = m_Table.ChannelsY()->Get(i);
			if (ch) {
				GtkTreeIter iter;
				gtk_list_store_append(m_Channels, &iter);
				gtk_list_store_set(m_Channels, &iter, 0, -1, 1, "All values", -1);
				break;
			}
		}
	} else {
		// Show list of available channels
		for (int i=0; i<m_Table.ChannelsY()->Count(); i++) {
			CChannel *ch = m_Table.ChannelsY()->Get(i);
			if (ch) {
				GtkTreeIter iter;
				gtk_list_store_append(m_Channels, &iter);
				gtk_list_store_set(m_Channels, &iter, 0, i, 1, ch->Name(), -1);
			}
		}
	}
	gtk_combo_box_set_model(GTK_COMBO_BOX(m_VCCombo), GTK_TREE_MODEL(m_Channels));
	if (gtk_tree_model_iter_n_children(GTK_TREE_MODEL(m_Channels), NULL)>0) {
		SelectItem(GTK_COMBO_BOX(m_VCCombo), m_SelectedY);
		if (gtk_combo_box_get_active(GTK_COMBO_BOX(m_VCCombo))<0) {
			gtk_combo_box_set_active(GTK_COMBO_BOX(m_VCCombo), 0);
			if (m_FileType != TYPE_MUNIPACK)
				m_SelectedY = SelectedItem(GTK_COMBO_BOX(m_VCCombo));
		}
	} else {
		gtk_combo_box_set_active(GTK_COMBO_BOX(m_VCCombo), -1);
		if (m_FileType != TYPE_MUNIPACK)
			m_SelectedY = -1;
	}
	OnSelectionChanged(GTK_COMBO_BOX(m_VCCombo));

	const tOptions *opt = m_Options+m_FileType;
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_FrameIds), opt->frame_id);
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_AllValues), opt->all_values);
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_Header), opt->header);
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_SkipInvalid), opt->skip_invalid);

	m_Updating = false;

	UpdateControls();
}

void CSaveObjPropertiesDlg::button_toggled(GtkToggleButton *pWidget, CSaveObjPropertiesDlg *pMe)
{
	pMe->OnButtonToggled(pWidget);
}

void CSaveObjPropertiesDlg::OnButtonToggled(GtkToggleButton *pWidget)
{
	if (GTK_WIDGET(pWidget) == m_SkipInvalid) {
		if (m_FileType==TYPE_CSV || m_FileType==TYPE_TEXT)
			m_Options[m_FileType].skip_invalid = gtk_toggle_button_get_active(pWidget)!=0;
	} else
	if (GTK_WIDGET(pWidget) == m_Header) {
		if (m_FileType==TYPE_CSV || m_FileType==TYPE_TEXT)
			m_Options[m_FileType].header = gtk_toggle_button_get_active(pWidget)!=0;
	} else
	if (GTK_WIDGET(pWidget) == m_FrameIds) {
		if (m_FileType==TYPE_CSV || m_FileType==TYPE_TEXT)
			m_Options[m_FileType].frame_id = gtk_toggle_button_get_active(pWidget)!=0;
	} else
	if (GTK_WIDGET(pWidget) == m_AllValues) {
		if (m_FileType==TYPE_CSV || m_FileType==TYPE_TEXT)
			m_Options[m_FileType].all_values = gtk_toggle_button_get_active(pWidget)!=0;
	}

	UpdateControls();
}

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

bool CSaveObjPropertiesDlg::OnResponseDialog(gint response_id)
{
	switch (response_id)
	{
	case GTK_RESPONSE_HELP:
		// Show context help
		g_MainWnd->ShowHelp(GTK_WINDOW(m_pDlg), IDH_SAVE_TABLE);
		return false;
	}
	return true;
}

//------------------------   SELECT OBJECT DIALOG   ---------------------------------

enum tPopupCommand
{
	CMD_SELECT,
	CMD_SET_TARGET,
	CMD_EDIT_TAG,
	CMD_REMOVE_TAG,
	CMD_CLEAR_TAGS
};

static const CPopupMenu::tPopupMenuItem SelectMenuST[] = {
	{ CPopupMenu::MB_ITEM, CMD_SELECT,			"_Select" },
	{ CPopupMenu::MB_SEPARATOR },
	{ CPopupMenu::MB_ITEM, CMD_EDIT_TAG,		"_Edit tag" },
	{ CPopupMenu::MB_ITEM, CMD_REMOVE_TAG,		"_Remove tag" },
	{ CPopupMenu::MB_ITEM, CMD_CLEAR_TAGS,		"Clear _all tags" },
	{ CPopupMenu::MB_END }
};

static const CPopupMenu::tPopupMenuItem SelectMenuMT[] = {
	{ CPopupMenu::MB_ITEM, CMD_SELECT,			"_Select" },
	{ CPopupMenu::MB_ITEM, CMD_SET_TARGET,		"Set moving _target as a variable" },
	{ CPopupMenu::MB_SEPARATOR },
	{ CPopupMenu::MB_ITEM, CMD_EDIT_TAG,		"_Edit tag" },
	{ CPopupMenu::MB_ITEM, CMD_REMOVE_TAG,		"_Remove tag" },
	{ CPopupMenu::MB_ITEM, CMD_CLEAR_TAGS,		"Clear _all tags" },
	{ CPopupMenu::MB_END }
};

static const CPopupMenu::tPopupMenuItem ContextMenuST[] = {
	{ CPopupMenu::MB_ITEM, CMD_CLEAR_TAGS,		"Clear _all tags" },
	{ CPopupMenu::MB_END }
};

static const CPopupMenu::tPopupMenuItem ContextMenuMT[] = {
	{ CPopupMenu::MB_ITEM, CMD_SET_TARGET,		"Set moving _target as a variable" },
	{ CPopupMenu::MB_SEPARATOR },
	{ CPopupMenu::MB_ITEM, CMD_CLEAR_TAGS,		"Clear _all tags" },
	{ CPopupMenu::MB_END }
};

//
// Constructor
//
CSelectObjectDlg::CSelectObjectDlg(GtkWindow *pParent):m_pParent(pParent), m_Image(NULL), m_Wcs(NULL), m_ImageData(NULL), 
	m_ChartData(NULL), m_ObjectId(-1), m_MovingTarget(0), m_StatusCtx(-1), m_StatusMsg(-1), m_LastFocus(-1), 
	m_LastPosX(-1), m_LastPosY(-1), m_UpdatePos(true), m_Updating(false)
{
	GtkWidget *tbox, *scrwnd;
	GdkRectangle rc;

	m_DisplayMode = (tDisplayMode)g_Project->GetInt("ChooseStarsDlg", "Mode", DISPLAY_IMAGE, 0, DISPLAY_FULL);
	m_Negative = CConfig::GetBool(CConfig::NEGATIVE_CHARTS);
	m_RowsUpward = CConfig::GetBool(CConfig::ROWS_UPWARD);
	m_Tags = g_Project->Tags();

	// Dialog with buttons
	m_pDlg = gtk_dialog_new_with_buttons("Select object", pParent, 
		(GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR),
		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));
	g_signal_connect(G_OBJECT(m_pDlg), "response", G_CALLBACK(response_dialog), this);

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

	// Dialog size
	GdkScreen *scr = gdk_screen_get_default();
	gdk_screen_get_monitor_geometry(scr, 0, &rc);
	if (rc.width>0 && rc.height>0)
		gtk_window_set_default_size(GTK_WINDOW(m_pDlg), RoundToInt(0.9*rc.width), RoundToInt(0.8*rc.height));
	gtk_window_set_position(GTK_WINDOW(m_pDlg), GTK_WIN_POS_CENTER);

	// Toolbar
	tbox = gtk_toolbar_new();
	gtk_toolbar_set_style(GTK_TOOLBAR(tbox), GTK_TOOLBAR_ICONS);
	gtk_toolbar_set_orientation(GTK_TOOLBAR(tbox), GTK_ORIENTATION_HORIZONTAL);
	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(m_pDlg)->vbox), tbox, FALSE, FALSE, 0);

	// View mode
	toolbar_new_label(tbox, "View");
	m_ShowImage = toolbar_new_radio_button(tbox, NULL, "Image", "Display an image only");
	g_signal_connect(G_OBJECT(m_ShowImage), "toggled", G_CALLBACK(button_clicked), this);
	m_ShowChart = toolbar_new_radio_button(tbox, m_ShowImage, "Chart", "Display objects on a flat background");
	g_signal_connect(G_OBJECT(m_ShowChart), "toggled", G_CALLBACK(button_clicked), this);
	m_ShowMixed = toolbar_new_radio_button(tbox, m_ShowImage, "Mixed", "Display objects over an image");
	g_signal_connect(G_OBJECT(m_ShowMixed), "toggled", G_CALLBACK(button_clicked), this);

	toolbar_new_separator(tbox);

	// Zoom
	toolbar_new_label(tbox, "Zoom");
	m_ZoomFit = toolbar_new_button_from_stock(tbox, GTK_STOCK_ZOOM_FIT, "Fit the frame to the window");
	g_signal_connect(G_OBJECT(m_ZoomFit), "clicked", G_CALLBACK(button_clicked), this);
	m_ZoomOut = toolbar_new_button_from_stock(tbox, GTK_STOCK_ZOOM_OUT, "Zoom out");
	g_signal_connect(G_OBJECT(m_ZoomOut), "clicked", G_CALLBACK(button_clicked), this);
	m_ZoomIn = toolbar_new_button_from_stock(tbox, GTK_STOCK_ZOOM_IN, "Zoom in");
	g_signal_connect(G_OBJECT(m_ZoomIn), "clicked", G_CALLBACK(button_clicked), this);

	// Chart
	m_Chart = cmpack_chart_view_new();
	cmpack_chart_view_set_mouse_control(CMPACK_CHART_VIEW(m_Chart), TRUE);
	cmpack_chart_view_set_activation_mode(CMPACK_CHART_VIEW(m_Chart), CMPACK_ACTIVATION_CLICK);
	g_signal_connect(G_OBJECT(m_Chart), "item-activated", G_CALLBACK(chart_item_activated), this);
	g_signal_connect(G_OBJECT(m_Chart), "mouse-moved", G_CALLBACK(chart_mouse_moved), this);
	g_signal_connect(G_OBJECT(m_Chart), "mouse-left", G_CALLBACK(chart_mouse_left), this);
	g_signal_connect(G_OBJECT(m_Chart), "button_press_event", G_CALLBACK(button_press_event), this);
	gtk_widget_set_size_request(m_Chart, 320, 200);
	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_Chart);
	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(m_pDlg)->vbox), 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(GTK_DIALOG(m_pDlg)->vbox), m_Status, FALSE, FALSE, 0);
	m_StatusCtx = gtk_statusbar_get_context_id(GTK_STATUSBAR(m_Status), "Main");

	// Make popup menus
	if (g_Project->GetTargetType() != MOVING_TARGET) {
		m_SelectMenu.Create(SelectMenuST);
		m_ContextMenu.Create(ContextMenuST);
	} else {
		m_SelectMenu.Create(SelectMenuMT);
		m_ContextMenu.Create(ContextMenuMT);
	}

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

	// Show widgets
	gtk_widget_show_all(GTK_DIALOG(m_pDlg)->vbox);
}

//
// Destructor
//
CSelectObjectDlg::~CSelectObjectDlg()
{
	g_source_remove(m_TimerId);
	g_signal_handlers_disconnect_by_func(G_OBJECT(m_Chart), (gpointer)chart_mouse_moved, this);
	g_signal_handlers_disconnect_by_func(G_OBJECT(m_Chart), (gpointer)chart_mouse_left, this);

	if (m_ChartData)
		g_object_unref(m_ChartData);
	if (m_ImageData)
		g_object_unref(m_ImageData);
	gtk_widget_destroy(m_pDlg);
	delete m_Image;
	delete m_Wcs;
}

//
// Execute the dialog. Returns id of the selected object
// or zero if the dialog was cancelled.
//
int CSelectObjectDlg::Execute(int defaultId)
{
	int	res = 0;
	gchar *fts_file;
	GtkTreePath *refpath;
	const char *tmp_file;

	cmpack_chart_view_set_model(CMPACK_CHART_VIEW(m_Chart), NULL);
	cmpack_chart_view_set_image(CMPACK_CHART_VIEW(m_Chart), NULL);
	if (m_ChartData) {
		g_object_unref(m_ChartData);
		m_ChartData = NULL;
	}
	if (m_ImageData) {
		g_object_unref(m_ImageData);
		m_ImageData = NULL;
	}
	m_Phot.Clear();
	m_Catalog.Clear();
	delete m_Image;
	m_Image = NULL;
	delete m_Wcs;
	m_Wcs = NULL;
	m_LastFocus = m_ObjectId = m_MovingTarget = -1;
	m_LastPosX = m_LastPosY = -1;
	m_UpdatePos = true;
	if (g_Project->GetTargetType() == MOVING_TARGET) {
		m_MovingTarget = g_Project->GetReferenceTarget();
		if (defaultId<=0)
			defaultId = m_MovingTarget;
	}

	switch(g_Project->GetReferenceType())
	{
	case REF_FRAME:
		refpath = g_Project->GetReferencePath();
		if (refpath) {
			gchar *pht_file = g_Project->GetPhotFile(refpath);
			if (pht_file) {
				GError *error = NULL;
				if (m_Phot.Load(pht_file, &error)) {
					m_Phot.SelectAperture(0);
					if (m_Phot.FindObjectRefID(defaultId)>=0)
						m_ObjectId = defaultId;
					UpdateChart();
					gchar *fts_file = g_Project->GetImageFile(refpath);
					if (fts_file) {
						m_Image = CImage::fromFile(fts_file, g_Project->Profile(), CMPACK_BITPIX_AUTO, &error);
						UpdateImage();
						g_free(fts_file);
					}
					if (m_Phot.Wcs())
						m_Wcs = new CWcs(*m_Phot.Wcs());
				} else {
					if (error) {
						ShowError(m_pParent, error->message);
						g_error_free(error);
					}
					res = -1;
				}
				g_free(pht_file);
			}
			gtk_tree_path_free(refpath);
		}
		break;

	case REF_CATALOG_FILE:
		// Load catalog file
		tmp_file = g_Project->GetTempCatFile()->FullPath();
		if (tmp_file) {
			GError *error = NULL;
			if (m_Catalog.Load(tmp_file, &error)) {
				if (m_Catalog.FindObject(defaultId))
					m_ObjectId = defaultId;
				UpdateChart();
				fts_file = SetFileExtension(tmp_file, FILE_EXTENSION_FITS);
				if (fts_file) {
					m_Image = CImage::fromFile(fts_file, g_Project->Profile(), CMPACK_BITPIX_AUTO, &error);
					UpdateImage();
					g_free(fts_file);
				}
				if (m_Catalog.Wcs())
					m_Wcs = new CWcs(*m_Catalog.Wcs());
			} else {
				if (error) {
					ShowError(m_pParent, error->message);
					g_error_free(error);
				}
				res = -1;
			}
		}
		break;

	default:
		ShowError(m_pParent, "No reference file");
		res = -1;
	}
	if (res!=0)
		return false;

	gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(m_ShowChart),
		m_DisplayMode == DISPLAY_CHART);
	gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(m_ShowImage),
		m_DisplayMode == DISPLAY_IMAGE);
	gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(m_ShowMixed),
		m_DisplayMode == DISPLAY_FULL);
	gtk_widget_set_sensitive(GTK_WIDGET(m_ShowImage), 
		m_Image && m_Image->Width()>0 && m_Image->Height()>0);
	gtk_widget_set_sensitive(GTK_WIDGET(m_ShowMixed), 
		m_Image && m_Image->Width()>0 && m_Image->Height()>0);

	UpdateStatus();
	UpdateControls();
	bool retval = gtk_dialog_run(GTK_DIALOG(m_pDlg)) == GTK_RESPONSE_ACCEPT;
	gtk_widget_hide(m_pDlg);
	return (retval && m_ObjectId>0 ? m_ObjectId : 0);
}

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

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

	if (event->type==GDK_BUTTON_PRESS && event->button==3) {
		gtk_widget_grab_focus(widget);
		if (widget==pMe->m_Chart) {
			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;
}

//
// Set status text
//
void CSelectObjectDlg::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 CSelectObjectDlg::chart_mouse_moved(GtkWidget *button, CSelectObjectDlg *pDlg)
{
	pDlg->m_UpdatePos = true;
}

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

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

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

	int star_id = (int)cmpack_chart_data_get_param(m_ChartData, row);
	m_SelectMenu.Enable(CMD_SELECT, m_ObjectId != star_id);
	m_SelectMenu.Enable(CMD_SET_TARGET, m_MovingTarget > 0);
	m_SelectMenu.Enable(CMD_REMOVE_TAG, m_Tags && m_Tags->contains(star_id));
	m_SelectMenu.Enable(CMD_CLEAR_TAGS, m_Tags && m_Tags->count() > 0);
	switch (m_SelectMenu.Execute(event))
	{
	case CMD_SELECT:
		Select(star_id);
		break;
	case CMD_SET_TARGET:
		Select(m_MovingTarget);
		break;
	case CMD_EDIT_TAG:
		EditTag(row, star_id);
		break;
	case CMD_REMOVE_TAG:
		RemoveTag(row, star_id);
		break;
	case CMD_CLEAR_TAGS:
		ClearTags();
		break;
	}
	UpdateControls();
}

//
// Context menu (no object focused)
//
void CSelectObjectDlg::OnContextMenu(GdkEventButton *event)
{
	m_ContextMenu.Enable(CMD_CLEAR_TAGS, m_Tags && m_Tags->count() > 0);
	switch (m_ContextMenu.Execute(event))
	{
	case CMD_SET_TARGET:
		Select(m_MovingTarget);
		break;
	case CMD_CLEAR_TAGS:
		ClearTags();
		break;
	}
	UpdateControls();
}

void CSelectObjectDlg::Select(int star_id)
{
	if (star_id>0) {
		m_ObjectId = star_id;
		UpdateStatus();
		UpdateAll();
	}
}

void CSelectObjectDlg::EditTag(int row, int star_id)
{
	if (row>=0 && star_id>=0 && m_Tags) {
		char buf[256];
		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 CSelectObjectDlg::tag_validator(const gchar *name, GtkWindow *parent, CSelectObjectDlg *pMe)
{
	return pMe->OnTagValidator(name, parent);
}

bool CSelectObjectDlg::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 CSelectObjectDlg::SetTag(int row, int star_id, const gchar *value)
{
	if (row>=0 && star_id>=0 && m_Tags) {
		if (value && value[0]!='\0') 
			m_Tags->insert(star_id, value);
		else 
			m_Tags->remove(star_id);
		UpdateObject(row, star_id);
		UpdateStatus();
	}
}

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

void CSelectObjectDlg::ClearTags(void)
{
	if (m_Tags) {
		m_Tags->clear();
		UpdateStatus();
		UpdateAll();
		UpdateControls();
	}
}

void CSelectObjectDlg::button_clicked(GtkWidget *pButton, CSelectObjectDlg *pMe)
{
	pMe->OnButtonClicked(pButton);
}

void CSelectObjectDlg::OnButtonClicked(GtkWidget *pBtn)
{
	double zoom;

	if (pBtn==GTK_WIDGET(m_ShowChart)) {
		m_DisplayMode = DISPLAY_CHART;
		g_Project->SetInt("ChooseStarsDlg", "Mode", m_DisplayMode);
		cmpack_chart_view_set_image(CMPACK_CHART_VIEW(m_Chart), NULL);
		UpdateImage();
		UpdateChart();
		UpdateStatus();
	} else
	if (pBtn==GTK_WIDGET(m_ShowImage)) {
		m_DisplayMode = DISPLAY_IMAGE;
		g_Project->SetInt("ChooseStarsDlg", "Mode", m_DisplayMode);
		cmpack_chart_view_set_image(CMPACK_CHART_VIEW(m_Chart), m_ImageData);
		UpdateImage();
		UpdateChart();
		UpdateStatus();
	} else
	if (pBtn==GTK_WIDGET(m_ShowMixed)) {
		m_DisplayMode = DISPLAY_FULL;
		g_Project->SetInt("ChooseStarsDlg", "Mode", m_DisplayMode);
		cmpack_chart_view_set_image(CMPACK_CHART_VIEW(m_Chart), m_ImageData);
		UpdateImage();
		UpdateChart();
		UpdateStatus();
	} else
	if (pBtn==GTK_WIDGET(m_ZoomIn)) {
		zoom = cmpack_chart_view_get_zoom(CMPACK_CHART_VIEW(m_Chart));
		cmpack_chart_view_set_zoom(CMPACK_CHART_VIEW(m_Chart), zoom + 5.0);
	} else 
	if (pBtn==GTK_WIDGET(m_ZoomOut)) {
		zoom = cmpack_chart_view_get_zoom(CMPACK_CHART_VIEW(m_Chart));
		cmpack_chart_view_set_zoom(CMPACK_CHART_VIEW(m_Chart), zoom - 5.0);
	} else 
	if (pBtn==GTK_WIDGET(m_ZoomFit)) {
		cmpack_chart_view_set_auto_zoom(CMPACK_CHART_VIEW(m_Chart), TRUE);
	}
}

void CSelectObjectDlg::UpdateImage(void)
{
	cmpack_chart_view_set_image(CMPACK_CHART_VIEW(m_Chart), NULL);
	if (m_ImageData) {
		g_object_unref(m_ImageData);
		m_ImageData = NULL;
	}
	if (m_DisplayMode != DISPLAY_CHART && m_Image) {
		m_ImageData = m_Image->ToImageData(m_Negative, false, false, m_RowsUpward);
		cmpack_chart_view_set_image(CMPACK_CHART_VIEW(m_Chart), m_ImageData);
	}
}

//
// Update chart
//
void CSelectObjectDlg::UpdateChart(void)
{
	cmpack_chart_view_set_model(CMPACK_CHART_VIEW(m_Chart), NULL);
	if (m_ChartData) {
		g_object_unref(m_ChartData);
		m_ChartData = NULL;
	}
	if (m_Phot.Valid())
		m_ChartData = m_Phot.ToChartData(false, m_DisplayMode==DISPLAY_IMAGE);
	else if (m_Catalog.Valid())
		m_ChartData = m_Catalog.ToChartData(false, false, m_DisplayMode==DISPLAY_IMAGE);
	cmpack_chart_view_set_orientation(CMPACK_CHART_VIEW(m_Chart), m_RowsUpward ? CMPACK_ROWS_UPWARDS : CMPACK_ROWS_DOWNWARDS);
	cmpack_chart_view_set_negative(CMPACK_CHART_VIEW(m_Chart), m_Negative);
	cmpack_chart_view_set_model(CMPACK_CHART_VIEW(m_Chart), m_ChartData);
	UpdateStatus();
	UpdateAll();
}


//
// Enable/disable controls
//
void CSelectObjectDlg::UpdateControls(void)
{
}

//
// Update displayed object
//
void CSelectObjectDlg::UpdateObject(int row, int star_id) 
{
	if (m_ChartData) {
		const gchar *tag = (m_Tags ? m_Tags->caption(star_id) : NULL);
		if (star_id == m_ObjectId) {
			if (star_id == m_MovingTarget) {
				if (tag) {
					// Moving target, with tag
					gchar *buf = (gchar*)g_malloc((strlen(tag)+64)*sizeof(gchar));
					sprintf(buf, "Moving target\n%s", tag);
					cmpack_chart_data_set_tag(m_ChartData, row, buf);
					g_free(buf);
				} else {
					// Moving target, no tag
					cmpack_chart_data_set_tag(m_ChartData, row, "Moving target");
				}
			} else {
				if (tag) {
					// Selected object, with tag
					gchar *buf = (gchar*)g_malloc((strlen(tag)+64)*sizeof(gchar));
					sprintf(buf, "Object #%d\n%s", m_ObjectId, tag);
					cmpack_chart_data_set_tag(m_ChartData, row, buf);
					g_free(buf);
				} else {
					// Selected object, no tag
					gchar buf[64];
					sprintf(buf, "Object #%d", m_ObjectId);
					cmpack_chart_data_set_tag(m_ChartData, row, buf);
				}
			}
			cmpack_chart_data_set_color(m_ChartData, row, CMPACK_COLOR_RED);
			cmpack_chart_data_set_topmost(m_ChartData, row, TRUE);
			if (m_DisplayMode==DISPLAY_IMAGE)
				cmpack_chart_data_set_diameter(m_ChartData, row, 4.0);
		} 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);
				cmpack_chart_data_set_topmost(m_ChartData, row, TRUE);
				if (m_DisplayMode==DISPLAY_IMAGE)
					cmpack_chart_data_set_diameter(m_ChartData, row, 4.0);
			} 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);
				cmpack_chart_data_set_topmost(m_ChartData, row, FALSE);
				if (m_DisplayMode==DISPLAY_IMAGE)
					cmpack_chart_data_set_diameter(m_ChartData, row, 0.0);
			}
		}
	}
}

// 
// Update selection and tags for all object
//
void CSelectObjectDlg::UpdateAll(void)
{
	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));
	}
}

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

bool CSelectObjectDlg::OnResponseDialog(gint response_id)
{
	switch (response_id)
	{
	case GTK_RESPONSE_ACCEPT:
		// Check input
		if (!OnCloseQuery())
			return false;
		break;

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

void CSelectObjectDlg::UpdateStatus(void)
{
	gchar		buf[1024];
	gint		obj_id, ref_id, index;

	int item = cmpack_chart_view_get_focused(CMPACK_CHART_VIEW(m_Chart));
	if (item>=0 && m_ChartData) {
		if (m_LastFocus!=item) {
			m_LastFocus = item;
			obj_id = (int)cmpack_chart_data_get_param(m_ChartData, item);
			index = m_Phot.FindObject(obj_id);
			ref_id = (index>=0 ? m_Phot.GetObjectRefID(index) : -1);
			gdouble pos_x, pos_y;
			m_Phot.GetObjectPos(index, &pos_x, &pos_y);
			sprintf(buf, "Object #%d: X = %.1f, Y = %.1f", ref_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);
			}
			// Moving target
			if (obj_id == m_MovingTarget) {
				strcat(buf, ", ");
				strcat(buf, "moving target");
			}
			// Tag
			const gchar *tag = (m_Tags ? m_Tags->caption(obj_id) : NULL);
			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_Chart), &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);
			}
		}
	}
}

bool CSelectObjectDlg::OnCloseQuery()
{
	if (m_ObjectId<=0) {
		ShowError(GTK_WINDOW(m_pDlg), "Please, select an object.");
		return false;
	}
	return true;
}
