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

utils.cpp (C-Munipack project)
Helper functions
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 <glib/gstdio.h>
#include <math.h>

#include "utils.h"
#include "configuration.h"
#include "sound.h"
#include "main.h"

// File access codes 
#ifndef S_IRUSR
#define S_IRUSR _S_IREAD
#endif
#ifndef S_IWUSR
#define S_IWUSR _S_IWRITE
#endif
#ifndef S_IXUSR
#define S_IXUSR _S_IEXEC
#endif

#ifndef S_IRGRP
#define S_IRGRP _S_IREAD
#endif
#ifndef S_IXGRP
#define S_IXGRP _S_IEXEC
#endif
#ifndef S_IROTH
#define S_IROTH _S_IREAD
#endif
#ifndef S_IXOTH
#define S_IXOTH _S_IEXEC
#endif

#ifdef _WIN32
#include <windows.h>
#include <htmlhelp.h>
#else
#include <unistd.h>
#include <fcntl.h>
#endif

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

static gchar *FromLocale(const char *str)
{
	if (str)
		return g_locale_to_utf8(str, -1, NULL, NULL, NULL);
	return NULL;
}

#ifdef _WIN32
static const char *get_app_dir(void)
{
	static char *app_dir = NULL;
	if (!app_dir) {
		char buf[4096], buf2[4096], *filepart;
		GetModuleFileNameA(NULL, buf, 4096);
		GetFullPathNameA(buf, 4096, buf2, &filepart);
		if (filepart && filepart>buf2) {
			*(filepart-1) = '\0';
			app_dir = g_path_get_dirname(buf2);
		}
	}
	return app_dir;
}
#endif

const char *get_user_config_dir(void)
{
	static char *user_config_dir = NULL;
	if (!user_config_dir) {
		const char *dir = g_get_user_config_dir();
		user_config_dir = g_build_filename(dir, "C-Munipack-2.0", NULL);
	}
	return user_config_dir;
}

const char *get_user_data_dir(void)
{
	static char *user_data_dir = NULL;
	if (!user_data_dir) {
		const char *dir = g_get_user_data_dir();
		user_data_dir = g_build_filename(dir, "C-Munipack-2.0", NULL);
	}
	return user_data_dir;
}

const char *get_share_dir(void)
{
#ifdef _WIN32
	static char *share_dir = NULL;
	if (!share_dir)
		share_dir = g_build_filename(get_app_dir(), "share", NULL);
	return share_dir;
#else
	return CMUNIPACK_INSTALL_DATADIR;
#endif
}

static const char *get_help_dir(void)
{
	static char *help_dir = NULL;
	if (!help_dir)
		help_dir = g_build_filename(get_share_dir(), "manual", "en", "html", NULL);
	return help_dir;
}

char *get_icon_file(const gchar *icon)
{
	char fname[512];
	g_snprintf(fname, 512, "%s.png", icon);
	return g_build_filename(get_share_dir(), "icons", fname, NULL);
}

// Make directory tree
bool force_directory(const gchar *path)
{
	if (!path)
		return false;
	else if (!g_file_test(path, G_FILE_TEST_IS_DIR))
		return g_mkdir_with_parents(path, S_IRUSR | S_IXUSR | S_IWUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)==0;
	else
		return true;
}

// Open a file (file name is UTF-8 encoded)
FILE *open_file(const gchar *fpath, const gchar *mode)
{
	FILE *f;
	gchar *lp;
	
	if (fpath) {
		lp = g_locale_from_utf8(fpath, -1, NULL, NULL, NULL);
		f = fopen(lp, mode);
		g_free(lp);
		return f;
	}
	return NULL;
}

// Copy a file
bool copy_file(const gchar *source, const gchar *target, bool fail_if_exists, GError **error)
{
	gchar *contents;
	gsize length;

	if (!fail_if_exists || !g_file_test(source, G_FILE_TEST_EXISTS)) {
		if (g_file_test(source, G_FILE_TEST_IS_REGULAR)) {
			gboolean retval = g_file_get_contents(source, &contents, &length, error) &&
				g_file_set_contents(target, contents, length, error);
			g_free(contents);
			return retval!=FALSE;
		}
	}
	return false;
}

// Copy files
bool copy_all_files(const gchar *source, const gchar *target, bool recursive, GError **error)
{
	// Make target directory
	if (!g_file_test(target, G_FILE_TEST_EXISTS)) {
		if (g_mkdir_with_parents(target, S_IRUSR | S_IXUSR | S_IWUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)!=0)
			return false;
	}

	// List files in source directory
	GDir *dir = g_dir_open(source, 0, error);
	if (!dir) 
		return false;

	bool ok = true;
	const gchar *filename = g_dir_read_name(dir);
	while (ok && filename) {
		gchar *srcpath = g_build_filename(source, filename, NULL);
		gchar *dstpath = g_build_filename(target, filename, NULL);
		if (g_file_test(srcpath, G_FILE_TEST_IS_REGULAR)) 
			ok = copy_file(srcpath, dstpath, false, error);
		else if (recursive && g_file_test(srcpath, G_FILE_TEST_IS_DIR))
			ok = copy_all_files(srcpath, dstpath, true, error);
		g_free(srcpath);
		g_free(dstpath);
		filename = g_dir_read_name(dir);
	}
	g_dir_close(dir);
	return ok;
}

// Returns target for given context id
static gchar* translate_topic(int context_id)
{
	static GKeyFile* topics = NULL;

	if (!topics) {
		// Load topic definitions
		gchar* filepath = g_build_filename(get_help_dir(), "topics.ini", NULL);
		topics = g_key_file_new();
		g_key_file_load_from_file(topics, filepath, G_KEY_FILE_NONE, NULL);
		g_free(filepath);
	}
	if (topics) {
		char buf[64];
		sprintf(buf, "%d", context_id);
		return g_key_file_get_string(topics, "MAP", buf, NULL);
	}
	return NULL;
}

// Open help
void ShowHelp(GtkWindow *pParent, int context_id)
{
#ifdef _WIN32
	gchar *filename, *filepath;
	filename = translate_topic(context_id);
	if (!filename)
		filepath = g_build_filename(get_help_dir(), "index.html", NULL);
	else
		filepath = g_build_filename(get_help_dir(), filename, NULL);
	gchar* lpath = g_locale_from_utf8(filepath, -1, NULL, NULL, NULL);
	ShellExecute(NULL, "open", lpath, NULL, NULL, SW_SHOWMAXIMIZED);
	g_free(lpath);
	g_free(filepath);
	g_free(filename);
#else
	const gchar* cmd[3];
	GError* err = NULL;
	gchar* filename, * filepath;
	filename = translate_topic(context_id);
	if (!filename)
		filepath = g_build_filename(get_help_dir(), "index.html", NULL);
	else
		filepath = g_build_filename(get_help_dir(), filename, NULL);
	cmd[0] = "xdg-open";
	cmd[1] = filepath;
	cmd[2] = NULL;
	if (!gdk_spawn_on_screen(gtk_window_get_screen(pParent), NULL,
		(gchar**)cmd, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &err)) {
		ShowError(pParent, err->message);
		g_error_free(err);
	}
	g_free(filepath);
	g_free(filename);
#endif
}

// Show message box with error message
void ShowError(GtkWindow *pParent, const char *message, bool play_sound)
{
	if (play_sound && CConfig::GetBool(CConfig::EVENT_SOUNDS))
		SoundPlay("error16");

	GtkWidget *pDialog = gtk_message_dialog_new(pParent,
		(GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT), 
		GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, NULL);
	if (pParent)
		gtk_window_set_title(GTK_WINDOW(pDialog), "Error");
	else 
		gtk_window_set_title(GTK_WINDOW(pDialog), "Error (Muniwin)");
	gtk_label_set_text(GTK_LABEL(GTK_MESSAGE_DIALOG(pDialog)->label), message); 
	gtk_window_position(GTK_WINDOW(pDialog), GTK_WIN_POS_CENTER);
	gtk_dialog_run(GTK_DIALOG(pDialog));
	gtk_widget_destroy(pDialog);
}

// Show message box with warning message
void ShowWarning(GtkWindow *pParent, const char *message, bool play_sound)
{
	if (play_sound && CConfig::GetBool(CConfig::EVENT_SOUNDS))
		SoundPlay("warning");

	GtkWidget *pDialog = gtk_message_dialog_new(pParent, 
		(GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT), 
		GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, NULL);
	if (pParent)
		gtk_window_set_title(GTK_WINDOW(pDialog), "Warning");
	else
		gtk_window_set_title(GTK_WINDOW(pDialog), "Warning (Muniwin)");
	gtk_label_set_text(GTK_LABEL(GTK_MESSAGE_DIALOG(pDialog)->label), message); 
	gtk_window_position(GTK_WINDOW(pDialog), GTK_WIN_POS_CENTER);
	gtk_dialog_run(GTK_DIALOG(pDialog));
	gtk_widget_destroy(pDialog);
}

// Show message box with information
void ShowInformation(GtkWindow *pParent, const char *message, bool play_sound)
{
	if (play_sound && CConfig::GetBool(CConfig::EVENT_SOUNDS))
		SoundPlay("finished");

	GtkWidget *pDialog = gtk_message_dialog_new(pParent, 
		(GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT), 
		GTK_MESSAGE_INFO, GTK_BUTTONS_OK, NULL);
	if (pParent) 
		gtk_window_set_title(GTK_WINDOW(pDialog), "Information");
	else
		gtk_window_set_title(GTK_WINDOW(pDialog), "Information (Muniwin)");
	gtk_label_set_text(GTK_LABEL(GTK_MESSAGE_DIALOG(pDialog)->label), message); 
	gtk_window_position(GTK_WINDOW(pDialog), GTK_WIN_POS_CENTER);
	gtk_dialog_run(GTK_DIALOG(pDialog));
	gtk_widget_destroy(pDialog);
}

// Show configuration message box
bool ShowConfirmation(GtkWindow *pParent, const char *message)
{
	GtkWidget *pDialog = gtk_message_dialog_new(pParent, 
		(GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT), 
		GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, NULL);
	if (pParent)
		gtk_window_set_title(GTK_WINDOW(pDialog), "Confirmation");
	else
		gtk_window_set_title(GTK_WINDOW(pDialog), "Confirmation (Muniwin)");
	gtk_label_set_text(GTK_LABEL(GTK_MESSAGE_DIALOG(pDialog)->label), message); 
	gtk_window_position(GTK_WINDOW(pDialog), GTK_WIN_POS_CENTER);
	GtkResponseType res = (GtkResponseType)gtk_dialog_run(GTK_DIALOG(pDialog));
	gtk_widget_destroy(pDialog);
	return (res==GTK_RESPONSE_YES);
}

// Show configuration message box
GtkResponseType ShowYesNoCancel(GtkWindow *pParent, const char *message)
{
	GtkWidget *pDialog = gtk_message_dialog_new(pParent, 
		(GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT), 
		GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, NULL);
	gtk_dialog_add_buttons(GTK_DIALOG(pDialog), GTK_STOCK_YES, GTK_RESPONSE_YES, 
		GTK_STOCK_NO, GTK_RESPONSE_NO, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL);
	if (pParent)
		gtk_window_set_title(GTK_WINDOW(pDialog), "Confirmation");
	else
		gtk_window_set_title(GTK_WINDOW(pDialog), "Confirmation (Muniwin)");
	gtk_label_set_text(GTK_LABEL(GTK_MESSAGE_DIALOG(pDialog)->label), message); 
	gtk_window_position(GTK_WINDOW(pDialog), GTK_WIN_POS_CENTER);
	GtkResponseType res = (GtkResponseType)gtk_dialog_run(GTK_DIALOG(pDialog));
	gtk_widget_destroy(pDialog);
	return res;
}

bool ConfirmOverwrite(GtkWindow *pParent, const gchar *fpath)
{
	int res;

	if (!g_file_test(fpath, G_FILE_TEST_EXISTS))
		return true;

	gchar *dirpath = g_path_get_dirname(fpath);
	gchar *basename = g_path_get_basename(fpath);
	GtkWidget *dialog = gtk_message_dialog_new(pParent, 
		(GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
		GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "A file named \"%s\" already exists.  Do you want to replace it?",
		basename);
	gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
		"The file already exists in \"%s\".  Replacing it will overwrite its contents.", dirpath);
	gtk_dialog_add_button (GTK_DIALOG (dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
	gtk_dialog_add_button (GTK_DIALOG (dialog), "_Replace", GTK_RESPONSE_ACCEPT);
	gtk_dialog_set_alternative_button_order(GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT, GTK_RESPONSE_CANCEL, -1);
	gtk_dialog_set_default_response(GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
	gtk_window_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
	res = gtk_dialog_run(GTK_DIALOG (dialog));
	gtk_widget_destroy (dialog);
	g_free(basename);
	g_free(dirpath);
	return (res == GTK_RESPONSE_ACCEPT);
}

bool ConfirmOverwriteDir(GtkWindow *pParent, const gchar *fpath)
{
	int res;

	if (!g_file_test(fpath, G_FILE_TEST_EXISTS))
		return true;

	gchar *dirpath = g_path_get_dirname(fpath);
	gchar *basename = g_path_get_basename(fpath);
	GtkWidget *dialog = gtk_message_dialog_new(pParent, 
		(GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
		GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "A directory named \"%s\" already exists.  Do you want to replace it?",
		basename);
	gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
		"The directory already exists in \"%s\".  Replacing it will overwrite its contents.", dirpath);
	gtk_dialog_add_button (GTK_DIALOG (dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
	gtk_dialog_add_button (GTK_DIALOG (dialog), "_Replace", GTK_RESPONSE_ACCEPT);
	gtk_dialog_set_alternative_button_order(GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT, GTK_RESPONSE_CANCEL, -1);
	gtk_dialog_set_default_response(GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
	gtk_window_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
	res = gtk_dialog_run(GTK_DIALOG (dialog));
	gtk_widget_destroy (dialog);
	g_free(basename);
	g_free(dirpath);
	return (res == GTK_RESPONSE_ACCEPT);
}

// Add file extension
gchar *AddFileExtension(const gchar *path, const gchar *ext)
{
	g_assert(ext != NULL);

	if (path) {
		gchar *basename = g_path_get_basename(path);
		gchar *newname = (gchar*)g_malloc((strlen(basename)+strlen(ext)+2)*sizeof(gchar));
		strcpy(newname, basename);
		// Trim old extension (if any)
		gchar *ptr = strrchr(newname, '.');
		if (ptr)
			*ptr = '\0';
		// Append new extension
		if (*ext!='\0') {
			strcat(newname, ".");
			strcat(newname, ext);
		} 
		// Prepend path (if available)
		if (strcmp(path, basename)!=0) {
			gchar *dir = g_path_get_dirname(path);
			gchar *res = g_build_filename(dir, newname, NULL);
			g_free(dir);
			g_free(newname);
			newname = res;
		}
		g_free(basename);
		return newname;
	}
	return NULL;
}

// Set/change file extension
gchar *SetFileExtension(const gchar *path, const gchar *ext)
{
	g_assert(ext != NULL);

	if (path) {
		gchar *basename = g_path_get_basename(path);
		gchar *newname = (gchar*)g_malloc((strlen(basename)+strlen(ext)+2)*sizeof(gchar));
		strcpy(newname, basename);
		// Trim old extension (if any)
		gchar *ptr = strrchr(newname, '.');
		if (ptr)
			*ptr = '\0';
		// Append new extension
		if (*ext!='\0') {
			strcat(newname, ".");
			strcat(newname, ext);
		} 
		// Prepend path (if available)
		if (strcmp(path, basename)!=0) {
			gchar *dir = g_path_get_dirname(path);
			gchar *res = g_build_filename(dir, newname, NULL);
			g_free(dir);
			g_free(newname);
			newname = res;
		}
		g_free(basename);
		return newname;
	}
	return NULL;
}

// Strip file extension
gchar *StripFileExtension(const gchar *path)
{
	if (path) {
		gchar *basename = g_path_get_basename(path);
		gchar *newname = (gchar*)g_malloc((strlen(basename)+1)*sizeof(gchar));
		strcpy(newname, basename);
		// Trim old extension (if any)
		gchar *ptr = strrchr(newname, '.');
		if (ptr)
			*ptr = '\0';
		// Prepend path (if available)
		if (strcmp(path, basename)!=0) {
			gchar *dir = g_path_get_dirname(path);
			gchar *res = g_build_filename(dir, newname, NULL);
			g_free(dir);
			g_free(newname);
			newname = res;
		}
		g_free(basename);
		return newname;
	}
	return NULL;
}

// Set/change file extension
bool CheckFileExtension(const gchar *path, const gchar *ext)
{
	bool retval = false;
	gchar *basename = g_path_get_basename(path);
	if (basename && ext) {
		gchar *ptr = strrchr(basename, '.');
		if (ptr)
			retval = (strcmp(ptr+1, ext)==0);
		else
			retval = (*ext=='\0');
	}
	g_free(basename);
	return retval;
}

gchar *GetFileExtension(const gchar *path)
{
	gchar *retval = NULL;
	if (path) {
		gchar *basename = g_path_get_basename(path);
		const gchar *period = strrchr(basename, '.');
		if (period) 
			retval = g_strdup(period+1);
		g_free(basename);
	}
	return retval;
}

// Check given string if it is valid file base name 
bool CheckFileBaseName(const char *filename)
{
	if (filename) 
		return filename[strcspn(filename, "/\\?%*:|\"<>")]=='\0';
	return false;
}

// Get a button from dialog's action area by a response id
GtkWidget *get_dialog_widget_by_response(GtkDialog *pDlg, gint response_id)
{
	GtkWidget *retval = NULL;

	GList *children = gtk_container_get_children(GTK_CONTAINER((pDlg->action_area)));
	for (GList *l=children; l!=NULL; l=l->next) {
		if (gtk_dialog_get_response_for_widget(pDlg, GTK_WIDGET(l->data)) == response_id) {
			retval = GTK_WIDGET(l->data);
			break;
		}
	}
	g_list_free(children);
	return retval;
}

// Set tooltip text to a button from dialog's action area 
void gtk_dialog_set_tooltip_by_response(GtkDialog *pDlg, gint response_id, const gchar *tooltip)
{
	GtkWidget *widget = get_dialog_widget_by_response(pDlg, response_id);
	if (widget)
		gtk_widget_set_tooltip_text(widget, tooltip);
}

// Set standard tooltips for standard dialog buttons
void gtk_dialog_widget_standard_tooltips(GtkDialog *pDlg)
{
	GList *children = gtk_container_get_children(GTK_CONTAINER((pDlg->action_area)));
	for (GList *l=children; l!=NULL; l=l->next) {
		const gchar *text;
		switch (gtk_dialog_get_response_for_widget(pDlg, GTK_WIDGET(l->data)))
		{
		case GTK_RESPONSE_CLOSE:
			text = "Close the dialog";
			break;
		case GTK_RESPONSE_REJECT:
		case GTK_RESPONSE_CANCEL:
			text = "Cancel the action";
			break;
		case GTK_RESPONSE_ACCEPT:
		case GTK_RESPONSE_APPLY:
			text = "Save changes and continue";
			break;
		case GTK_RESPONSE_HELP:
			text = "Show help";
			break;
		default:
			text = NULL;
			break;
		}
		if (text)
			gtk_widget_set_tooltip_text(GTK_WIDGET(l->data), text);
	}
	g_list_free(children);
}

// Set standard tooltips for file chooser dialog
void gtk_file_chooser_standard_tooltips(GtkFileChooser *pDlg)
{
	GList *children = gtk_container_get_children(GTK_CONTAINER((GTK_DIALOG(pDlg)->action_area)));
	for (GList *l=children; l!=NULL; l=l->next) {
		const gchar *text;
		switch (gtk_dialog_get_response_for_widget(GTK_DIALOG(pDlg), GTK_WIDGET(l->data)))
		{
		case GTK_RESPONSE_REJECT:
		case GTK_RESPONSE_CANCEL:
			text = "Close the dialog and cancel the action";
			break;
		case GTK_RESPONSE_ACCEPT:
			switch (gtk_file_chooser_get_action(pDlg))
			{
			case GTK_FILE_CHOOSER_ACTION_OPEN:
				text = "Load data from selected file";
				break;
			case GTK_FILE_CHOOSER_ACTION_SAVE:
				text = "Save data to selected file";
				break;
			case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
				text = "Use selected folder";
				break;
			default:
				text = NULL;
				break;
			}
			break;
		case GTK_RESPONSE_HELP:
			text = "Show help";
			break;
		default:
			text = NULL;
			break;
		}
		if (text)
			gtk_widget_set_tooltip_text(GTK_WIDGET(l->data), text);
	}
	g_list_free(children);
}


// Round number to the integer with limit checking
int RoundToInt(double x)
{
	if (x>INT_MAX)
		return INT_MAX;
	else if (x<INT_MIN)
		return INT_MIN;
	else if (x<0)
		return (int)(x-0.5);
	else
		return (int)(x+0.5);
}

double LimitValue(double value, double min, double max)
{
	if (value > max)
		return max;
	else if (value < min)
		return min;
	else
		return value;
}

int LimitValue(int value, int min, int max)
{
	if (value > max)
		return max;
	else if (value < min)
		return min;
	else
		return value;
}

// Compute minimum and maximum
bool ComputeMinMax(int count, const double *data, double *ymin, double *ymax)
{

	if (count>0) {
		double dmin = data[0], dmax = dmin;
		for (int k=1; k<count; k++) {
			double value = data[k];
			if (value > dmax)
				dmax = value;
			if (value < dmin)
				dmin = value;
		}
		if (ymin)
			*ymin = dmin;
		if (ymax)
			*ymax = dmax;
		return true;
	} else {
		if (ymin)
			*ymin = 0.0;
		if (ymax)
			*ymax = 0.0;
		return false;
	}
}

// Case sensitive comparison or two strings, they can be NULL
int StrCmp0(const gchar *str1, const gchar *str2)
{
	int res;
	gchar *s1, *s2;

	if (str1==NULL && str2==NULL)
		return 0;
	else if (str1==NULL)
		return -1;
	else if (str2==NULL)
		return 1;
	
	s1 = g_utf8_normalize(str1, -1, G_NORMALIZE_DEFAULT), 
	s2 = g_utf8_normalize(str2, -1, G_NORMALIZE_DEFAULT);
	res = strcmp(s1, s2);
	g_free(s1);
	g_free(s2);
	return res;
}

// Case sensitive substring search or two strings, they can be NULL
const gchar *StrStr0(const gchar *str1, const gchar *str2)
{
	glong offset;
	const gchar *res;
	gchar *s1, *s2;

	if (!str1 || !str2)
		return NULL;
	
	s1 = g_utf8_normalize(str1, -1, G_NORMALIZE_DEFAULT), 
	s2 = g_utf8_normalize(str2, -1, G_NORMALIZE_DEFAULT);
	res = strstr(s1, s2);
	if (res)
		offset = g_utf8_pointer_to_offset(s1, res);
	else
		offset = -1;
	g_free(s1);
	g_free(s2);
	if (offset>=0)
		return g_utf8_offset_to_pointer(str1, offset);
	else
		return NULL;
}

// Case insensitive comparison of two strings, they can be NULL
int StrCaseCmp0(const gchar *str1, const gchar *str2)
{
	int res;
	gchar *s1, *s2;

	if (str1==NULL && str2==NULL)
		return 0;
	else if (str1==NULL)
		return -1;
	else if (str2==NULL)
		return 1;

	s1 = g_utf8_casefold(str1, -1);
	s2 = g_utf8_casefold(str2, -1);
	res = strcmp(s1, s2);
	g_free(s1);
	g_free(s2);
	return res;
}

// Case insensitive comparison of two strings, they can be NULL
const gchar *StrCaseStr0(const gchar *str1, const gchar *str2)
{
	glong offset;
	const gchar *res;
	gchar *s1, *s2;

	if (!str1 || !str2)
		return NULL;

	s1 = g_utf8_casefold(str1, -1);
	s2 = g_utf8_casefold(str2, -1);
	res = strstr(s1, s2);
	if (res)
		offset = g_utf8_pointer_to_offset(s1, res);
	else
		offset = -1;
	g_free(s1);
	g_free(s2);
	if (offset>=0)
		return g_utf8_offset_to_pointer(str1, offset);
	else
		return NULL;
}

// Find a string in a memory buffer
const gchar *MemStr(const gchar *c, const gchar *s, gsize n)
{
	const char *p;
	size_t needlesize = strlen(s);
	for (p = c; p <= (c-needlesize+n); p++) {
		if (memcmp(p, s, needlesize) == 0)
			return p;
	}
	return NULL;
}

// Compare two file UTF-8 paths
gint ComparePaths(const gchar *str1, const gchar *str2)
{
#ifdef _WIN32
	return StrCaseCmp0(str1, str2);
#else
	return StrCmp0(str1, str2);
#endif
}

// Compare two file UTF-8 paths
gboolean SamePath(const gchar *str1, const gchar *str2)
{
#ifdef _WIN32
	return StrCaseCmp0(str1, str2) == 0;
#else
	return StrCmp0(str1, str2) == 0;
#endif
}

#ifdef _WIN32

// Constructor
FileLock::FileLock():m_File(INVALID_HANDLE_VALUE)
{
}

// Destructor
FileLock::~FileLock()
{
	if (m_File!=INVALID_HANDLE_VALUE) 
		CloseHandle(m_File);
}

// Acquire lock
bool FileLock::Acquire(const gchar *fpath)
{
	assert(fpath!=NULL);
	
	if (m_File == INVALID_HANDLE_VALUE) {
		gchar *dirpath = g_path_get_dirname(fpath);
		gchar *basename = g_path_get_basename(fpath);
		gchar *lockname = g_strdup_printf(".#%s-lock", basename);
		gchar *lockpath = g_build_filename(dirpath, lockname, NULL);
		gchar *p = g_locale_from_utf8(lockpath, -1, NULL, NULL, NULL);
		m_File = CreateFile(p, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, 
			CREATE_ALWAYS, FILE_ATTRIBUTE_HIDDEN | FILE_FLAG_DELETE_ON_CLOSE, NULL);
		g_free(p);
		g_free(lockpath);
		g_free(lockname);
		g_free(basename);
		g_free(dirpath);
	}
	return (m_File != INVALID_HANDLE_VALUE);
}

// Release lock
void FileLock::Release(void)
{
	if (m_File!=INVALID_HANDLE_VALUE) {
		CloseHandle(m_File);
		m_File = INVALID_HANDLE_VALUE;
	}
}

#else

// Constructor
FileLock::FileLock():m_File(-1)
{
}

// Destructor
FileLock::~FileLock()
{
	if (m_File != -1)
		close(m_File);
}

// Acquire lock
bool FileLock::Acquire(const gchar *fpath)
{
	struct flock fl;

	assert(fpath!=NULL);
	
	if (m_File == -1) {
		gchar *dirpath = g_path_get_dirname(fpath);
		gchar *basename = g_path_get_basename(fpath);
		gchar *lockname = g_strdup_printf(".#%s-lock", basename);
		gchar *lockpath = g_build_filename(dirpath, lockname, NULL);
		gchar *p = g_locale_from_utf8(lockpath, -1, NULL, NULL, NULL);
		m_File = open(p,  O_CREAT | O_RDWR, S_IWRITE | S_IREAD);
		if (m_File != -1) {
			fl.l_type = F_WRLCK;
			fl.l_whence = SEEK_SET;
			fl.l_start = 0;
			fl.l_len = 0; 
			if (fcntl(m_File, F_SETLK, &fl) == -1) {
				// Access denied 
				close(m_File);
				m_File = -1;
			} else {
				// Access granted 
				ftruncate(m_File, 0);
			}
		}
		g_free(p);
		g_free(lockpath);
		g_free(lockname);
		g_free(basename);
		g_free(dirpath);
	}
	return (m_File != -1);
}

// Release lock
void FileLock::Release(void)
{
	if (m_File!=-1) {
		close(m_File);
		m_File = -1;
	}
}

#endif

// Convert Julian date to struct tm
bool jdtime(gdouble jd, struct tm *tm)
{
	CmpackDateTime dt;

	memset(tm, 0, sizeof(struct tm));
	if (cmpack_decodejd(jd, &dt)!=0)
		return false;

	tm->tm_year = dt.date.year-1900;
	tm->tm_mon  = dt.date.month-1;
	tm->tm_mday = dt.date.day;
	tm->tm_hour = dt.time.hour;
	tm->tm_min  = dt.time.minute;
	tm->tm_sec  = dt.time.second;
	return true;
}

// Convert struct tm to Julian date
double timejd(const struct tm *tm)
{
	CmpackDateTime dt;

	memset(&dt, 0, sizeof(CmpackDateTime));
	dt.date.year   = tm->tm_year + 1900;
	dt.date.month  = tm->tm_mon + 1;
	dt.date.day    = tm->tm_mday;
	dt.time.hour   = tm->tm_hour;
	dt.time.minute = tm->tm_min;
	dt.time.second = tm->tm_sec;
	return cmpack_encodejd(&dt);
}

static int compare_int(const void *a, const void *b)
{
	int ma = ((tSortItem*)a)->value.i, mb = ((tSortItem*)b)->value.i;
	if (ma < mb)
		return -1;
	if (ma > mb)
		return 1;
	// If equal, sort by row index
	int sa = ((tSortItem*)a)->row, sb = ((tSortItem*)b)->row;
	return (sa < sb ? -1 : (sa > sb ? 1 : 0));
}

static int compare_ulong(const void *a, const void *b)
{
	unsigned long ma = ((tSortItem*)a)->value.ul, mb = ((tSortItem*)b)->value.ul;
	if (ma < mb)
		return -1;
	if (ma > mb)
		return 1;
	// If equal, sort by row index
	int sa = ((tSortItem*)a)->row, sb = ((tSortItem*)b)->row;
	return (sa < sb ? -1 : (sa > sb ? 1 : 0));
}

static int compare_int_reverse(const void *a, const void *b)
{
	int ma = ((tSortItem*)a)->value.i, mb = ((tSortItem*)b)->value.i;
	if (ma < mb)
		return 1;
	if (ma > mb)
		return -1;
	// If equal, sort by row index
	int sa = ((tSortItem*)a)->row, sb = ((tSortItem*)b)->row;
	return (sa < sb ? 1 : (sa > sb ? -1 : 0));
}

static int compare_ulong_reverse(const void *a, const void *b)
{
	unsigned long ma = ((tSortItem*)a)->value.ul, mb = ((tSortItem*)b)->value.ul;
	if (ma < mb)
		return 1;
	if (ma > mb)
		return -1;
	// If equal, sort by row index
	int sa = ((tSortItem*)a)->row, sb = ((tSortItem*)b)->row;
	return (sa < sb ? 1 : (sa > sb ? -1 : 0));
}

static int compare_dbl(const void *a, const void *b)
{
	double ma = ((tSortItem*)a)->value.d, mb = ((tSortItem*)b)->value.d;
	if (ma < mb)
		return -1;
	if (ma > mb)
		return 1;
	// If equal, sort by row index
	int sa = ((tSortItem*)a)->row, sb = ((tSortItem*)b)->row;
	return (sa < sb ? -1 : (sa > sb ? 1 : 0));
}

static int compare_dbl_reverse(const void *a, const void *b)
{
	double ma = ((tSortItem*)a)->value.d, mb = ((tSortItem*)b)->value.d;
	if (ma < mb)
		return 1;
	if (ma > mb)
		return -1;
	// If equal, sort by row index
	int sa = ((tSortItem*)a)->row, sb = ((tSortItem*)b)->row;
	return (sa < sb ? 1 : (sa > sb ? -1 : 0));
}

static int compare_str(const void *a, const void *b)
{
	int cmp = StrCmp0(((tSortItem*)a)->value.s, ((tSortItem*)b)->value.s);
	if (cmp!=0) 
		return cmp;
	// If equal, sort by row index
	int sa = ((tSortItem*)a)->row, sb = ((tSortItem*)b)->row;
	return (sa < sb ? -1 : (sa > sb ? 1 : 0));
}

static int compare_str_reverse(const void *a, const void *b)
{
	int cmp = -StrCmp0(((tSortItem*)a)->value.s, ((tSortItem*)b)->value.s);
	if (cmp!=0)
		return cmp;
	// If equal, sort by row index
	int sa = ((tSortItem*)a)->row, sb = ((tSortItem*)b)->row;
	return (sa < sb ? 1 : (sa > sb ? -1 : 0));
}

void SortItems(tSortItem *items, int length, tSortType type, GtkSortType sort_order)
{
	if (sort_order == GTK_SORT_DESCENDING) {
		switch (type)
		{
		case SORT_TYPE_INT:
			qsort(items, length, sizeof(tSortItem),compare_int_reverse);
			break;
		case SORT_TYPE_DOUBLE:
			qsort(items, length, sizeof(tSortItem),compare_dbl_reverse);
			break;
		case SORT_TYPE_STRING:
			qsort(items, length, sizeof(tSortItem),compare_str_reverse);
			break;
		case SORT_TYPE_ULONG:
			qsort(items, length, sizeof(tSortItem),compare_ulong_reverse);
			break;
		}
	} else {
		switch (type)
		{
		case SORT_TYPE_INT:
			qsort(items, length, sizeof(tSortItem),compare_int);
			break;
		case SORT_TYPE_DOUBLE:
			qsort(items, length, sizeof(tSortItem),compare_dbl);
			break;
		case SORT_TYPE_STRING:
			qsort(items, length, sizeof(tSortItem),compare_str);
			break;
		case SORT_TYPE_ULONG:
			qsort(items, length, sizeof(tSortItem),compare_ulong);
			break;
		}
	}
}

//
// Quote a string if necessary
//
gchar *StrQuote(const gchar *str, gchar separator, gchar quotechar, bool quote_always)
{
	bool quote;

	if (!str)
		return NULL;

	if (quote_always) {
		quote = false;
		for (const gchar *ptr=str; *ptr!='\0'; ptr++) {
			if (*ptr == separator || *ptr == quotechar) {
				quote = true;
				break;
			}
		}
	} else {
		quote = true;
	}

	// Compute length required for a quoted string
	size_t length;
	if (quote) {
		length = 2;
		for (const gchar *ptr=str; *ptr!='\0'; ptr++) {
			if (*ptr == quotechar) 
				length++;
			length++;
		}
	} else {
		length = strlen(str);
	}

	gchar *buf = (gchar*)g_malloc((length+1)*sizeof(gchar)), *dptr = buf;
	if (quote) {
		for (const gchar *ptr=str; *ptr!='\0'; ptr++) {
			if (*ptr == quotechar) 
				*dptr++ = quotechar;
			*dptr++ = *ptr;
		}
		*dptr = '\0';
	} else {
		strcpy(buf, str);
	}
	return buf;
}

gchar *StrUnquote(const gchar *str, gchar quotechar)
{
	if (!str)
		return NULL;

	// Find first and last non-whitespace characters
	const gchar *startptr = str;
	while (*startptr!='\0') {
		if (*startptr<0 || *startptr>' ')
			break;
		startptr++;
	}
	const gchar *endptr = startptr + strlen(startptr) - 1;
	while (endptr>=startptr) {
		if (*endptr<0 || *endptr>' ')
			break;
		endptr--;
	}

	if (startptr>endptr) {
		// The input consists of whitespace characters only
		return g_strdup("");
	}
	
	if (startptr==endptr || *startptr != quotechar || *endptr != quotechar) {
		// Unquoted string
		int len = (int)(endptr - startptr + 1);
		gchar *buf = (gchar*)g_malloc((len+1)*sizeof(gchar));
		memcpy(buf, startptr, len*sizeof(gchar));
		buf[len] = '\0';
		return buf;
	}

	// Quoted string
	startptr++;
	endptr--;
	if (startptr>endptr) {
		// The input consists of single quote or two subsequent quotes
		return g_strdup("");
	}
	
	// Compute length required for an unquoted string
	int length = 0, state = 0;
	for (const gchar *ptr=startptr; ptr<=endptr; ptr++) {
		gchar ch = *ptr;
		if (state==0) {
			if (ch==quotechar)
				state = 1;
			else
				length++;
		} else {
			if (ch==quotechar) {
				length++;
				state = 0;
			}
		}
	}
	
	// Unquote string
	gchar *buf = (gchar*)g_malloc((length+1)*sizeof(gchar)), *dptr = buf;
	for (const gchar *ptr=startptr; ptr<=endptr; ptr++) {
		gchar ch = (gchar)(*ptr);
		if (state==0) {
			if (ch==quotechar)
				state = 1;
			else 
				*dptr++ = ch;
			break;
		} else {
			if (ch==quotechar) {
				*dptr++ = quotechar;
				state = 0;
			}
		}
	}
	*dptr = '\0';
	return buf;
}

//------------------------  COMBO BOX HELPER FUNCTIONS   ---------------------------------

void SelectItem(GtkComboBox *cb, int id)
{
	gboolean ok;
	int t;
	GtkTreeIter iter;

	GtkTreeModel *model = gtk_combo_box_get_model(cb);
	ok = gtk_tree_model_get_iter_first(model, &iter);
	while (ok) {
		gtk_tree_model_get(model, &iter, 0, &t, -1);
		if (t == id) {
			gtk_combo_box_set_active_iter(cb, &iter);
			return;
		}	
		ok = gtk_tree_model_iter_next(model, &iter);
	}
	gtk_combo_box_set_active(cb, -1);
}

int SelectedItem(GtkComboBox *cb, int defValue)
{
	int t;
	GtkTreeIter iter;

	if (gtk_combo_box_get_active_iter(cb, &iter)) {
		GtkTreeModel *model = gtk_combo_box_get_model(cb);
		gtk_tree_model_get(model, &iter, 0, &t, -1);
		return t;
	}
	return defValue;
}

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

GtkToolItem *toolbar_new_button_from_stock(GtkWidget *toolbar, const gchar *stock_id,
	const gchar *tooltip)
{
	GtkToolItem *tbtn = gtk_tool_button_new_from_stock(stock_id);
	gtk_widget_set_tooltip_text(GTK_WIDGET(tbtn), tooltip);
	gtk_toolbar_insert(GTK_TOOLBAR(toolbar), tbtn, -1);
	return tbtn;
}

GtkToolItem *toolbar_new_button(GtkWidget *toolbar, const gchar *label,
	const gchar *tooltip)
{
	GtkToolItem *tbtn = gtk_tool_button_new(NULL, label);
	gtk_widget_set_tooltip_text(GTK_WIDGET(tbtn), tooltip);
	gtk_toolbar_insert(GTK_TOOLBAR(toolbar), tbtn, -1);
	return tbtn;
}

GtkToolItem *toolbar_new_button(GtkWidget *toolbar, GtkWidget *icon_widget, const gchar *label,
	const gchar *tooltip)
{
	GtkToolItem *tbtn = gtk_tool_button_new(icon_widget, label);
	gtk_widget_set_tooltip_text(GTK_WIDGET(tbtn), tooltip);
	gtk_toolbar_insert(GTK_TOOLBAR(toolbar), tbtn, -1);
	return tbtn;
}

static GtkToolItem *toolbar_new_widget(GtkWidget *toolbar, GtkWidget *widget)
{
	GtkToolItem *item = gtk_tool_item_new();
	gtk_container_add(GTK_CONTAINER(item), widget);
	gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
	return item;
}

GtkWidget *toolbar_new_check_button(GtkWidget *toolbar, const char *label,
	const gchar *tooltip)
{
	GtkWidget *entry = gtk_check_button_new_with_label(label);
	toolbar_new_widget(toolbar, entry);
	return entry;
}

GtkWidget *toolbar_new_label(GtkWidget *toolbar, const char *caption)
{
	GtkWidget *entry = gtk_label_new(caption);
	gtk_misc_set_padding(GTK_MISC(entry), 8, 0);
	gtk_misc_set_alignment(GTK_MISC(entry), 0, 0.5);
	toolbar_new_widget(toolbar, entry);
	return entry;
}

GtkToolItem *toolbar_new_separator(GtkWidget *toolbar)
{
	GtkToolItem *tbtn = gtk_separator_tool_item_new();
	gtk_toolbar_insert(GTK_TOOLBAR(toolbar), tbtn, -1);
	return tbtn;
}

GtkWidget *toolbar_new_combo(GtkWidget *toolbar, const gchar *tooltip, int width)
{
	GtkWidget *entry = gtk_combo_box_new();
	gtk_widget_set_size_request(entry, (width > 0 ? width : 120), -1);
	gtk_widget_set_tooltip_text(entry, tooltip);
	toolbar_new_widget(toolbar, entry);
	return entry;
}

GtkToolItem *toolbar_new_toggle_button(GtkWidget *toolbar, const gchar *label, const gchar *tooltip)
{
	GtkToolItem *tbtn = gtk_toggle_tool_button_new();
	gtk_tool_button_set_label(GTK_TOOL_BUTTON(tbtn), label);
	gtk_widget_set_tooltip_text(GTK_WIDGET(tbtn), tooltip);
	gtk_toolbar_insert(GTK_TOOLBAR(toolbar), tbtn, -1);
	return tbtn;
}

GtkToolItem *toolbar_new_toggle_button(GtkWidget *toolbar, GtkWidget *icon, const gchar *tooltip)
{
	GtkToolItem *tbtn = gtk_toggle_tool_button_new();
	gtk_tool_button_set_icon_widget(GTK_TOOL_BUTTON(tbtn), icon);
	gtk_widget_set_tooltip_text(GTK_WIDGET(tbtn), tooltip);
	gtk_toolbar_insert(GTK_TOOLBAR(toolbar), tbtn, -1);
	return tbtn;
}

GtkToolItem *toolbar_new_radio_button(GtkWidget *toolbar, GtkToolItem *first_in_group, 
	const gchar *label, const gchar *tooltip)
{
	GSList *group = NULL;
	if (first_in_group)
		group = gtk_radio_tool_button_get_group(GTK_RADIO_TOOL_BUTTON(first_in_group));

	GtkToolItem *tbtn = gtk_radio_tool_button_new(group);
	gtk_tool_button_set_label(GTK_TOOL_BUTTON(tbtn), label);
	gtk_widget_set_tooltip_text(GTK_WIDGET(tbtn), tooltip);
	gtk_toolbar_insert(GTK_TOOLBAR(toolbar), tbtn, -1);
	return tbtn;
}

GtkWidget *toolbar_new_entry(GtkWidget *toolbar, const gchar *tooltip, bool editable, double align)
{
	GtkWidget *entry = gtk_entry_new();
	gtk_widget_set_tooltip_text(entry, tooltip);
	gtk_entry_set_width_chars(GTK_ENTRY(entry), 10);
	gtk_entry_set_alignment(GTK_ENTRY(entry), (gfloat)align);
	gtk_editable_set_editable(GTK_EDITABLE(entry), editable ? TRUE : FALSE);
	toolbar_new_widget(toolbar, entry);
	return entry;
}

GtkWidget *toolbar_new_combo_box_entry(GtkWidget *toolbar, const gchar *tooltip)
{
	GtkWidget *entry = gtk_combo_box_entry_new_text();
	gtk_widget_set_size_request(entry, 120, -1);
	gtk_widget_set_tooltip_text(entry, tooltip);
	GtkToolItem *item = toolbar_new_widget(toolbar, entry);
	gtk_tool_item_set_expand(item, FALSE);
	return entry;
}

GtkWidget *toolbar_new_toolbar(GtkWidget *toolbar)
{
	GtkWidget *bbox = gtk_toolbar_new();
	gtk_toolbar_set_style(GTK_TOOLBAR(bbox), GTK_TOOLBAR_ICONS);
	gtk_toolbar_set_icon_size(GTK_TOOLBAR(bbox), GTK_ICON_SIZE_SMALL_TOOLBAR);
	GtkToolItem *item = toolbar_new_widget(toolbar, bbox);
	gtk_tool_item_set_expand(item, FALSE);
	return bbox;
}

// Transform given path 
gchar *TransformPath(const gchar *fpath, const gchar *pattern)
{
	if (!fpath || !pattern)
		return NULL;

	gchar *dirpath = g_path_get_dirname(fpath);
	gchar *basename = g_path_get_basename(fpath);
	const gchar *fileext = NULL;

	// Trim old extension (if any)
	gchar *ptr = strrchr(basename, '.');
	if (ptr) {
		*ptr = '\0';
		fileext = ptr+1;
	}
	int bname_len = (int)strlen(basename), dpath_len = (int)strlen(dirpath),
		fext_len = (fileext ? (int)strlen(fileext) : 0), dsep_len = (int)strlen(G_DIR_SEPARATOR_S);
	
	// Parse the pattern, find out length of the output string
	int state = 0, len = 0;
	for (const gchar *sptr = pattern; *sptr!='\0'; sptr++) {
		gchar ch = *sptr;
		if (state==0) {
			if (ch=='%')
				state = 1;
			else
				len++;
		} else {
			if (ch=='%')
				len++;
			else if (ch=='d')
				len += dpath_len+dsep_len;
			else if (ch=='n')
				len += bname_len;
			else if (ch=='e')
				len += fext_len;
			state = 0;
		}
	}

	gchar *out = (gchar*)g_malloc((len+1)*sizeof(gchar));
	if (!out) {
		g_free(dirpath);
		g_free(basename);
		return NULL;
	}

	gchar *dptr = out;
	for (const gchar *sptr = pattern; *sptr!='\0'; sptr++) {
		gchar ch = *sptr;
		if (state==0) {
			if (ch=='%')
				state = 1;
			else
				*dptr++ = ch;
		} else {
			if (ch=='%')
				*dptr++ = '%';
			else if (ch=='d') {
				strcpy(dptr, dirpath);
				dptr += dpath_len;
				if (dptr>out && *(dptr-1)!='/' && *(dptr-1)!='\\') {
					strcpy(dptr, G_DIR_SEPARATOR_S);
					dptr += dsep_len;
				}
			} else if (ch=='n') {
				strcpy(dptr, basename);
				dptr += bname_len;
			} else if (ch=='e' && fileext) {
				strcpy(dptr, fileext);
				dptr += fext_len;
			}
			state = 0;
		}
	}
	*dptr = '\0';
	g_free(dirpath);
	g_free(basename);
	return out;
}

//-----------------------  Error reporting  --------------------------------------

void set_error(GError **error, int errcode)
{
	if (errcode!=0) {
		char *buf = cmpack_formaterror(errcode);
		if (buf) {
			gchar *msg = FromLocale(buf);
			g_set_error(error, g_AppError, 0, "%s", msg);
			g_free(msg);
			cmpack_free(buf);
		} else
			g_set_error(error, g_AppError, 0, "Unknown error (code: %d)", errcode);
	}
}

void set_error(GError **error, const char *caption, int errcode)
{
	if (errcode==0) {
		g_set_error(error, g_AppError, 0, "%s", caption);
	} else {
		char *buf = cmpack_formaterror(errcode);
		if (buf) {
			gchar *msg = FromLocale(buf);
			g_set_error(error, g_AppError, 0, "%s\n%s", caption, msg);
			g_free(msg);
			cmpack_free(buf);
		} else
			g_set_error(error, g_AppError, 0, "%s\nUnknown error (code: %d)", caption, errcode);
	}
}

void set_error(GError **error, const char *caption, const char *filename, int errcode)
{
	if (errcode==0) {
		g_set_error(error, g_AppError, 0, "%s\n%s", caption, filename);
	} else {
		char *buf = cmpack_formaterror(errcode);
		if (buf) {
			gchar *msg = FromLocale(buf);
			g_set_error(error, g_AppError, 0, "%s\n%s\n%s", caption, filename, msg);
			g_free(msg);
			cmpack_free(buf);
		} else
			g_set_error(error, g_AppError, 0, "%s\n%s\nUnknown error (code: %d)", caption, filename, errcode);
	}
}

double angular_distance(double lambda1, double phi1, double lambda2, double phi2)
{
	double dlambda = (lambda2 - lambda1);
	double y1 = cos(phi2) * sin(dlambda), y2 = cos(phi1) * sin(phi2) - sin(phi1) * cos(phi2) * cos(dlambda);
	double x = sin(phi1) * sin(phi2) + cos(phi1) * cos(phi2) * cos(dlambda);
	return atan2(sqrt(y1 * y1 + y2 * y2), x) * 180.0 / M_PI;
}

GdkPixmap *createPixmap(int width, int height, unsigned rgb)
{
	GdkPixmap *pixmap = gdk_pixmap_new(NULL, width, height, 24);

	double r = (double)((rgb >> 16) & 0xFF) / 255, g = (double)((rgb >> 8) & 0xFF) / 255, b = (double)(rgb & 0xFF) / 255;

	cairo_t *cr = gdk_cairo_create(pixmap);
	cairo_set_source_rgb(cr, 1, 1, 1);
	cairo_paint(cr);
	cairo_set_source_rgb(cr, r, g, b);
	cairo_move_to(cr, 8, 4);
	cairo_line_to(cr, 8, 12);
	cairo_move_to(cr, 4, 8);
	cairo_line_to(cr, 12, 8);
	cairo_stroke(cr);
	cairo_destroy(cr);

	return pixmap;
}
