/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/* Copyright (C) 2002 the Free Software Foundation
 * Copyright (C) 2012-2021 MATE Developers
 *
 * This file is part of MATE Utils.
 *
 * MATE Utils 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.
 *
 * MATE Utils 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 MATE Utils.  If not, see <https://www.gnu.org/licenses/>.
 *
 *  Authors:
 *    Dennis Cranston <dennis_cranston@yahoo.com>
 *    George Lebl
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <string.h>
#include <glib/gi18n.h>
#include <glib.h>
#include <regex.h>
#include <stdlib.h>
#include <gdk/gdkx.h>
#include <gio/gio.h>
#include <gio/gdesktopappinfo.h>

#include "gsearchtool.h"
#include "gsearchtool-callbacks.h"
#include "gsearchtool-support.h"

#define C_STANDARD_STRFTIME_CHARACTERS "aAbBcdHIjmMpSUwWxXyYZ"
#define C_STANDARD_NUMERIC_STRFTIME_CHARACTERS "dHIjmMSUwWyY"
#define SUS_EXTENDED_STRFTIME_MODIFIERS "EO"
#define BINARY_EXEC_MIME_TYPE      "application/x-executable"

GtkTreeViewColumn *
gsearchtool_gtk_tree_view_get_column_with_sort_column_id (GtkTreeView * treeview,
                                                          gint id);

/* START OF GENERIC MATE-SEARCH-TOOL FUNCTIONS */

gboolean
is_path_hidden (const gchar * path)
{
	gint results = FALSE;
	gchar * sub_str;
	gchar * hidden_path_substr = g_strconcat (G_DIR_SEPARATOR_S, ".", NULL);

	sub_str = g_strstr_len (path, strlen (path), hidden_path_substr);

	if (sub_str != NULL) {
		gchar * mate_desktop_str;

		mate_desktop_str = g_strconcat (G_DIR_SEPARATOR_S, ".mate-desktop", G_DIR_SEPARATOR_S, NULL);

		/* exclude the .mate-desktop folder */
		if (strncmp (sub_str, mate_desktop_str, strlen (mate_desktop_str)) == 0) {
			sub_str++;
			results = (g_strstr_len (sub_str, strlen (sub_str), hidden_path_substr) != NULL);
		}
		else {
			results = TRUE;
		}

		g_free (mate_desktop_str);
	}

	g_free (hidden_path_substr);
	return results;
}

gboolean
is_quick_search_excluded_path (const gchar * path)
{
	GSettings  * settings;
	gchar     ** exclude_path_list;
	gchar      * dir;
	gboolean     results = FALSE;
	gint         i;

	dir = g_strdup (path);

	/* Remove trailing G_DIR_SEPARATOR. */
	if ((strlen (dir) > 1) && (g_str_has_suffix (dir, G_DIR_SEPARATOR_S) == TRUE)) {
		dir[strlen (dir) - 1] = '\0';
	}

	/* Always exclude a path that is symbolic link. */
	if (g_file_test (dir, G_FILE_TEST_IS_SYMLINK)) {
		g_free (dir);

		return TRUE;
	}
	g_free (dir);

	settings = g_settings_new ("org.mate.search-tool");

	/* Check path against the Quick-Search-Excluded-Paths list. */
	exclude_path_list = g_settings_get_strv (settings, "quick-search-excluded-paths");

	if (exclude_path_list) {
		for (i = 0; exclude_path_list[i]; i++) {

			/* Skip empty or null values. */
			if (strlen (exclude_path_list[i]) == 0) {
				continue;
			}

			dir = g_strdup (exclude_path_list[i]);

			/* Wild-card comparisons. */
			if (g_strstr_len (dir, strlen (dir), "*") != NULL) {

				if (g_pattern_match_simple (dir, path) == TRUE) {

					results = TRUE;
					g_free (dir);
					break;
				}
			}
			/* Non-wild-card comparisons. */
			else {
				/* Add a trailing G_DIR_SEPARATOR. */
				if (g_str_has_suffix (dir, G_DIR_SEPARATOR_S) == FALSE) {

					gchar *tmp;

					tmp = dir;
					dir = g_strconcat (dir, G_DIR_SEPARATOR_S, NULL);
					g_free (tmp);
				}

				if (strcmp (path, dir) == 0) {

					results = TRUE;
					g_free (dir);
					break;
				}
			}
			g_free (dir);
		}
		g_strfreev (exclude_path_list);
	}

	g_object_unref (settings);
	return results;
}

gboolean
is_second_scan_excluded_path (const gchar * path)
{
	GSettings  * settings;
	gchar     ** exclude_path_list;
	gchar      * dir;
	gboolean     results = FALSE;
	gint         i;

	dir = g_strdup (path);

	/* Remove trailing G_DIR_SEPARATOR. */
	if ((strlen (dir) > 1) && (g_str_has_suffix (dir, G_DIR_SEPARATOR_S) == TRUE)) {
		dir[strlen (dir) - 1] = '\0';
	}

	/* Always exclude a path that is symbolic link. */
	if (g_file_test (dir, G_FILE_TEST_IS_SYMLINK)) {
		g_free (dir);

		return TRUE;
	}
	g_free (dir);

	settings = g_settings_new ("org.mate.search-tool");

	/* Check path against the Quick-Search-Excluded-Paths list. */
	exclude_path_list = g_settings_get_strv (settings, "quick-search-second-scan-excluded-paths");

	if (exclude_path_list) {
		for (i = 0; exclude_path_list[i]; i++) {

			/* Skip empty or null values. */
			if (strlen (exclude_path_list[i]) == 0) {
				continue;
			}

			dir = g_strdup (exclude_path_list[i]);

			/* Wild-card comparisons. */
			if (g_strstr_len (dir, strlen (dir), "*") != NULL) {

				if (g_pattern_match_simple (dir, path) == TRUE) {

					results = TRUE;
					g_free (dir);
					break;
				}
			}
			/* Non-wild-card comparisons. */
			else {
				/* Add a trailing G_DIR_SEPARATOR. */
				if (g_str_has_suffix (dir, G_DIR_SEPARATOR_S) == FALSE) {

					gchar *tmp;

					tmp = dir;
					dir = g_strconcat (dir, G_DIR_SEPARATOR_S, NULL);
					g_free (tmp);
				}

				if (strcmp (path, dir) == 0) {

					results = TRUE;
					g_free (dir);
					break;
				}
			}
			g_free (dir);
		}
		g_strfreev (exclude_path_list);
	}

	g_object_unref (settings);
	return results;
}

gboolean
compare_regex (const gchar * regex,
	       const gchar * string)
{
	regex_t regexec_pattern;

	if (regex == NULL) {
		return TRUE;
	}

	if (!regcomp (&regexec_pattern, regex, REG_EXTENDED|REG_NOSUB)) {
		if (regexec (&regexec_pattern, string, 0, 0, 0) != REG_NOMATCH) {
			regfree (&regexec_pattern);
			return TRUE;
		}
		regfree (&regexec_pattern);
	}
	return FALSE;
}

gboolean
limit_string_to_x_lines (GString * string,
			 gint x)
{
	int i;
	int count = 0;
	for (i = 0; string->str[i] != '\0'; i++) {
		if (string->str[i] == '\n') {
			count++;
			if (count == x) {
				g_string_truncate (string, i);
				return TRUE;
			}
		}
	}
	return FALSE;
}

static gint
count_of_char_in_string (const gchar * string,
			 const gchar c)
{
	int cnt = 0;
	for(; *string; string++) {
		if (*string == c) cnt++;
	}
	return cnt;
}

gchar *
escape_single_quotes (const gchar * string)
{
	GString * gs;

	if (string == NULL) {
		return NULL;
	}

	if (count_of_char_in_string (string, '\'') == 0) {
		return g_strdup(string);
	}
	gs = g_string_new ("");
	for(; *string; string++) {
		if (*string == '\'') {
			g_string_append(gs, "'\\''");
		}
		else {
			g_string_append_c(gs, *string);
		}
	}
	return g_string_free (gs, FALSE);
}

gchar *
escape_double_quotes (const gchar * string)
{
	GString * gs;

	if (string == NULL) {
		return NULL;
	}

	if (count_of_char_in_string (string, '\"') == 0) {
		return g_strdup(string);
	}
	gs = g_string_new ("");
	for(; *string; string++) {
		if (*string == '\"') {
			g_string_append(gs, "\\\"");
		}
		else {
			g_string_append_c(gs, *string);
		}
	}
	return g_string_free (gs, FALSE);
}

gchar *
backslash_backslash_characters (const gchar * string)
{
	GString * gs;

	if (string == NULL) {
		return NULL;
	}

	if (count_of_char_in_string (string, '\\') == 0){
		return g_strdup(string);
	}
	gs = g_string_new ("");
	for(; *string; string++) {
		if (*string == '\\') {
			g_string_append(gs, "\\\\");
		}
		else {
			g_string_append_c(gs, *string);
		}
	}
	return g_string_free (gs, FALSE);
}

gchar *
backslash_special_characters (const gchar * string)
{
	GString * gs;

	if (string == NULL) {
		return NULL;
	}

	if ((count_of_char_in_string (string, '\\') == 0) &&
	    (count_of_char_in_string (string, '-') == 0)) {
		return g_strdup(string);
	}
	gs = g_string_new ("");
	for(; *string; string++) {
		if (*string == '\\') {
			g_string_append(gs, "\\\\");
		}
		else if (*string == '-') {
			g_string_append(gs, "\\-");
		}
		else {
			g_string_append_c(gs, *string);
		}
	}
	return g_string_free (gs, FALSE);
}

gchar *
remove_mnemonic_character (const gchar * string)
{
	GString * gs;
	gboolean first_mnemonic = TRUE;

	if (string == NULL) {
		return NULL;
	}

	gs = g_string_new ("");
	for(; *string; string++) {
		if ((first_mnemonic) && (*string == '_')) {
			first_mnemonic = FALSE;
			continue;
		}
		g_string_append_c(gs, *string);
	}
	return g_string_free (gs, FALSE);
}

gchar *
get_readable_date (const CajaDateFormat  date_format_enum,
                   GDateTime            *file_date_time)
{
	const gchar *format;
	GDateTime   *now;
	GTimeSpan    file_date_age;

	/* Base format of date column on caja date-format key */
	if (date_format_enum == CAJA_DATE_FORMAT_LOCALE) {
		return g_date_time_format (file_date_time, "%c");
	} else if (date_format_enum == CAJA_DATE_FORMAT_ISO) {
		return g_date_time_format (file_date_time, "%Y-%m-%d %H:%M:%S");
	}

	now = g_date_time_new_now_local ();
	file_date_age = g_date_time_difference (now, file_date_time);
	g_date_time_unref (now);

	if (file_date_age < G_TIME_SPAN_DAY)	{
		format = _("today at %-I:%M %p");
	} else if (file_date_age < 2 * G_TIME_SPAN_DAY) {
		format = _("yesterday at %-I:%M %p");
	} else {
		format = _("%A, %B %-d %Y at %-I:%M:%S %p");
	}

	return g_date_time_format (file_date_time, format);
}

gchar *
get_file_type_description (const gchar * file,
                           GFileInfo * file_info)
{
	const char * content_type = NULL;
	gchar * desc;

	if (file != NULL) {
		content_type = g_file_info_get_content_type (file_info);
	}

	if (content_type == NULL || g_content_type_is_unknown (content_type) == TRUE) {
		return g_strdup (g_content_type_get_description ("application/octet-stream"));
	}

	desc = g_strdup (g_content_type_get_description (content_type));

	if (g_file_info_get_is_symlink (file_info) == TRUE) {

		const gchar * symlink_target;
		gchar * absolute_symlink = NULL;
		gchar * str = NULL;

		symlink_target = g_file_info_get_symlink_target (file_info);

		if (g_path_is_absolute (symlink_target) != TRUE) {
			gchar *dirname;

			dirname = g_path_get_dirname (file);
			absolute_symlink = g_strconcat (dirname, G_DIR_SEPARATOR_S, symlink_target, NULL);
			g_free (dirname);
		}
		else {
			absolute_symlink = g_strdup (symlink_target);
		}

		if (g_file_test (absolute_symlink, G_FILE_TEST_EXISTS) != TRUE) {
                       if ((g_ascii_strcasecmp (content_type, "x-special/socket") != 0) &&
                           (g_ascii_strcasecmp (content_type, "x-special/fifo") != 0)) {
				g_free (absolute_symlink);
				g_free (desc);
				return g_strdup (_("link (broken)"));
			}
		}

		str = g_strdup_printf (_("link to %s"), (desc != NULL) ? desc : content_type);
		g_free (absolute_symlink);
		g_free (desc);
		return str;
	}
	return desc;
}

static gchar *
gsearchtool_pixmap_file (const gchar * partial_path)
{
	gchar * path;

	path = g_build_filename(DATADIR "/pixmaps/mate-search-tool", partial_path, NULL);
	if (g_file_test(path, G_FILE_TEST_EXISTS)){
		return path;
	}
	g_free (path);
	return NULL;
}

static GdkPixbuf *
gsearchtool_load_thumbnail_frame (void)
{
	GdkPixbuf * pixbuf = NULL;
	gchar * image_path;

	image_path = gsearchtool_pixmap_file("thumbnail_frame.png");

	if (image_path != NULL){
		pixbuf = gdk_pixbuf_new_from_file(image_path, NULL);
	}
	g_free(image_path);
	return pixbuf;
}

static void
gsearchtool_draw_frame_row (GdkPixbuf * frame_image,
                            gint target_width,
                            gint source_width,
                            gint source_v_position,
                            gint dest_v_position,
                            GdkPixbuf * result_pixbuf,
                            gint left_offset,
                            gint height)
{
	gint remaining_width;
	gint h_offset;
	gint slab_width;

	remaining_width = target_width;
	h_offset = 0;
	while (remaining_width > 0) {
		slab_width = remaining_width > source_width ? source_width : remaining_width;
		gdk_pixbuf_copy_area (frame_image, left_offset, source_v_position, slab_width,
		                      height, result_pixbuf, left_offset + h_offset, dest_v_position);
		remaining_width -= slab_width;
		h_offset += slab_width;
	}
}

static void
gsearchtool_draw_frame_column (GdkPixbuf * frame_image,
                               gint target_height,
                               gint source_height,
                               gint source_h_position,
                               gint dest_h_position,
                               GdkPixbuf * result_pixbuf,
                               gint top_offset,
                               gint width)
{
	gint remaining_height;
	gint v_offset;
	gint slab_height;

	remaining_height = target_height;
	v_offset = 0;
	while (remaining_height > 0) {
		slab_height = remaining_height > source_height ? source_height : remaining_height;
		gdk_pixbuf_copy_area (frame_image, source_h_position, top_offset, width, slab_height,
		                      result_pixbuf, dest_h_position, top_offset + v_offset);
		remaining_height -= slab_height;
		v_offset += slab_height;
	}
}

static GdkPixbuf *
gsearchtool_stretch_frame_image (GdkPixbuf *frame_image,
                                 gint left_offset,
                                 gint top_offset,
                                 gint right_offset,
                                 gint bottom_offset,
                                 gint dest_width,
                                 gint dest_height,
                                 gboolean fill_flag)
{
	GdkPixbuf * result_pixbuf;
	gint frame_width, frame_height;
	gint target_width, target_frame_width;
	gint target_height, target_frame_height;

	frame_width = gdk_pixbuf_get_width (frame_image);
	frame_height = gdk_pixbuf_get_height (frame_image);

	if (fill_flag) {
		result_pixbuf = gdk_pixbuf_scale_simple (frame_image, dest_width, dest_height, GDK_INTERP_NEAREST);
	} else {
		result_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, dest_width, dest_height);
	}

	/* clear the new pixbuf */
	if (fill_flag == FALSE) {
		gdk_pixbuf_fill (result_pixbuf, 0xffffffff);
	}

	target_width  = dest_width - left_offset - right_offset;
	target_frame_width = frame_width - left_offset - right_offset;

	target_height  = dest_height - top_offset - bottom_offset;
	target_frame_height = frame_height - top_offset - bottom_offset;

	/* Draw the left top corner  and top row */
	gdk_pixbuf_copy_area (frame_image, 0, 0, left_offset, top_offset, result_pixbuf, 0,  0);
	gsearchtool_draw_frame_row (frame_image, target_width, target_frame_width, 0, 0,
	                            result_pixbuf, left_offset, top_offset);

	/* Draw the right top corner and left column */
	gdk_pixbuf_copy_area (frame_image, frame_width - right_offset, 0, right_offset, top_offset,
	                      result_pixbuf, dest_width - right_offset,  0);
	gsearchtool_draw_frame_column (frame_image, target_height, target_frame_height, 0, 0,
	                               result_pixbuf, top_offset, left_offset);

	/* Draw the bottom right corner and bottom row */
	gdk_pixbuf_copy_area (frame_image, frame_width - right_offset, frame_height - bottom_offset,
	                      right_offset, bottom_offset, result_pixbuf, dest_width - right_offset,
			      dest_height - bottom_offset);
	gsearchtool_draw_frame_row (frame_image, target_width, target_frame_width,
	                            frame_height - bottom_offset, dest_height - bottom_offset,
				    result_pixbuf, left_offset, bottom_offset);

	/* Draw the bottom left corner and the right column */
	gdk_pixbuf_copy_area (frame_image, 0, frame_height - bottom_offset, left_offset, bottom_offset,
	                      result_pixbuf, 0,  dest_height - bottom_offset);
	gsearchtool_draw_frame_column (frame_image, target_height, target_frame_height,
	                               frame_width - right_offset, dest_width - right_offset,
				       result_pixbuf, top_offset, right_offset);
	return result_pixbuf;
}

static GdkPixbuf *
gsearchtool_embed_image_in_frame (GdkPixbuf * source_image,
                                  GdkPixbuf * frame_image,
                                  gint left_offset,
                                  gint top_offset,
                                  gint right_offset,
                                  gint bottom_offset)
{
	GdkPixbuf * result_pixbuf;
	gint source_width, source_height;
	gint dest_width, dest_height;

	source_width = gdk_pixbuf_get_width (source_image);
	source_height = gdk_pixbuf_get_height (source_image);

	dest_width = source_width + left_offset + right_offset;
	dest_height = source_height + top_offset + bottom_offset;

	result_pixbuf = gsearchtool_stretch_frame_image (frame_image, left_offset, top_offset, right_offset, bottom_offset,
						         dest_width, dest_height, FALSE);

	gdk_pixbuf_copy_area (source_image, 0, 0, source_width, source_height, result_pixbuf, left_offset, top_offset);

	return result_pixbuf;
}

static void
gsearchtool_thumbnail_frame_image (GdkPixbuf ** pixbuf)
{
	GdkPixbuf * pixbuf_with_frame;
	GdkPixbuf * frame;

	frame = gsearchtool_load_thumbnail_frame ();
	if (frame == NULL) {
		return;
	}

	pixbuf_with_frame = gsearchtool_embed_image_in_frame (*pixbuf, frame, 3, 3, 6, 6);
	g_object_unref (*pixbuf);
	g_object_unref (frame);

	*pixbuf = pixbuf_with_frame;
}

static GdkPixbuf *
gsearchtool_get_thumbnail_image (const gchar * thumbnail)
{
	GdkPixbuf * pixbuf = NULL;

	if (thumbnail != NULL) {
		if (g_file_test (thumbnail, G_FILE_TEST_EXISTS)) {

			GdkPixbuf * thumbnail_pixbuf = NULL;
			gfloat scale_factor_x = 1.0;
			gfloat scale_factor_y = 1.0;
			gint scale_x;
			gint scale_y;
			gint width;
			gint height;

			thumbnail_pixbuf = gdk_pixbuf_new_from_file (thumbnail, NULL);
			gsearchtool_thumbnail_frame_image (&thumbnail_pixbuf);

			width = gdk_pixbuf_get_width (thumbnail_pixbuf);
			if (width > ICON_SIZE) {
				scale_factor_x = (gfloat) ICON_SIZE / (gfloat) width;
			}
			height = gdk_pixbuf_get_height (thumbnail_pixbuf);
			if (height > ICON_SIZE) {
				scale_factor_y = (gfloat) ICON_SIZE / (gfloat) height;
			}

			if (width > height) {
				scale_x = ICON_SIZE;
				scale_y = (gint) (height * scale_factor_x);
			}
			else {
				scale_x = (gint) (width * scale_factor_y);
				scale_y = ICON_SIZE;
			}

			pixbuf = gdk_pixbuf_scale_simple (thumbnail_pixbuf, scale_x, scale_y, GDK_INTERP_BILINEAR);
			g_object_unref (thumbnail_pixbuf);
		}
	}
	return pixbuf;
}

static GdkPixbuf *
get_themed_icon_pixbuf (GThemedIcon * icon,
                        int size,
                        GtkIconTheme * icon_theme)
{
	char ** icon_names;
	GtkIconInfo * icon_info;
	GdkPixbuf * pixbuf;
	GError * error = NULL;

	g_object_get (icon, "names", &icon_names, NULL);

	icon_info = gtk_icon_theme_choose_icon (icon_theme, (const char **)icon_names, size, 0);
	if (icon_info == NULL) {
		icon_info = gtk_icon_theme_lookup_icon (icon_theme, "text-x-generic", size, GTK_ICON_LOOKUP_USE_BUILTIN);
	}
	pixbuf = gtk_icon_info_load_icon (icon_info, &error);
	if (pixbuf == NULL) {
		g_warning ("Could not load icon pixbuf: %s\n", error->message);
		g_clear_error (&error);
	}

	g_object_unref (icon_info);
	g_strfreev (icon_names);

	return pixbuf;
}



GdkPixbuf *
get_file_pixbuf (GSearchWindow * gsearch,
                 GFileInfo * file_info)
{
	GdkPixbuf * pixbuf;
	GIcon * icon = NULL;
	const gchar * thumbnail_path = NULL;

	if (file_info == NULL) {
		return NULL;
	}

	icon = g_file_info_get_icon (file_info);

	if (gsearch->show_thumbnails == TRUE) {
		thumbnail_path = g_file_info_get_attribute_byte_string (file_info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH);
	}

	if (thumbnail_path != NULL) {
		pixbuf = gsearchtool_get_thumbnail_image (thumbnail_path);
	}
	else {
		gchar * icon_string;

		icon_string = g_icon_to_string (icon);
		pixbuf = (GdkPixbuf *) g_hash_table_lookup (gsearch->search_results_filename_hash_table, icon_string);

		if (pixbuf == NULL) {
			pixbuf = get_themed_icon_pixbuf (G_THEMED_ICON (icon), ICON_SIZE, gtk_icon_theme_get_default ());
			g_hash_table_insert (gsearch->search_results_filename_hash_table, g_strdup (icon_string), pixbuf);
		}
		g_free (icon_string);
	}
	return pixbuf;
}

gboolean
open_file_with_filemanager (GtkWidget * window,
                            const gchar * file)
{
	GDesktopAppInfo * d_app_info;
	GKeyFile * key_file;
	GdkAppLaunchContext * ctx = NULL;
	GdkScreen *screen;
	GdkDisplay *display;
	GList * list = NULL;
	GAppInfo * g_app_info;
	GFile * g_file;
	gchar * command;
	gchar * contents;
	gchar * uri;
	gboolean result = TRUE;

	uri = g_filename_to_uri (file, NULL, NULL);
	list = g_list_prepend (list, uri);

	g_file = g_file_new_for_path (file);
	g_app_info = g_file_query_default_handler (g_file, NULL, NULL);

	if (strcmp (g_app_info_get_executable (g_app_info), "caja") == 0) {
		command = g_strconcat ("caja ",
		                       "--sm-disable ",
		                       "--no-desktop ",
		                       "--no-default-window ",
		                       NULL);
	}
	else {
		command = g_strconcat (g_app_info_get_executable (g_app_info),
		                       " ", NULL);
	}

	contents = g_strdup_printf ("[Desktop Entry]\n"
				    "Name=Caja\n"
				    "Icon=file-manager\n"
				    "Exec=%s\n"
				    "Terminal=false\n"
				    "StartupNotify=true\n"
				    "Type=Application\n",
				    command);
	key_file = g_key_file_new ();
	g_key_file_load_from_data (key_file, contents, strlen(contents), G_KEY_FILE_NONE, NULL);
	d_app_info = g_desktop_app_info_new_from_keyfile (key_file);

	if (d_app_info != NULL) {
		screen = gtk_widget_get_screen (GTK_WIDGET (window));
		display = gdk_screen_get_display (screen);
		ctx = gdk_display_get_app_launch_context (display);
		gdk_app_launch_context_set_screen (ctx, gtk_widget_get_screen (window));

		result = g_app_info_launch_uris (G_APP_INFO (d_app_info), list,  G_APP_LAUNCH_CONTEXT (ctx), NULL);
	}
	else {
		result = FALSE;
	}

	g_object_unref (g_app_info);
	g_object_unref (d_app_info);
	g_object_unref (g_file);
	g_object_unref (ctx);
	g_key_file_free (key_file);
	g_list_free (list);
	g_free (contents);
	g_free (command);
	g_free (uri);

	return result;
}

gboolean
open_file_with_application (GtkWidget * window,
                            const gchar * file,
                            GAppInfo * app)
{
	GdkAppLaunchContext * context;
	GdkDisplay *display;
	GdkScreen * screen;
	gboolean result;

	if (g_file_test (file, G_FILE_TEST_IS_DIR) == TRUE) {
		return FALSE;
	}

	screen = gtk_widget_get_screen (window);
	display = gdk_screen_get_display (screen);
	context = gdk_display_get_app_launch_context (display);
	gdk_app_launch_context_set_screen (context, screen);

	if (app == NULL) {
		gchar * uri;

		uri = g_filename_to_uri (file, NULL, NULL);
		result = g_app_info_launch_default_for_uri (uri, (GAppLaunchContext *) context, NULL);
		g_free (uri);
	}
	else {
		GList * g_file_list = NULL;
		GFile * g_file = NULL;

		g_file = g_file_new_for_path (file);

		if (g_file == NULL) {
			result = FALSE;
		}
		else {
			g_file_list = g_list_prepend (g_file_list, g_file);

			result = g_app_info_launch (app, g_file_list, (GAppLaunchContext *) context, NULL);
			g_list_free (g_file_list);
			g_object_unref (g_file);
		}
	}
	return result;
}

gboolean
launch_file (const gchar * file)
{
	const char * content_type = g_content_type_guess (file, NULL, 0, NULL);
	gboolean result = FALSE;

	if ((g_file_test (file, G_FILE_TEST_IS_EXECUTABLE)) &&
	    (g_ascii_strcasecmp (content_type, BINARY_EXEC_MIME_TYPE) == 0)) {
		result = g_spawn_command_line_async (file, NULL);
	}

	return result;
}

gchar *
gsearchtool_get_unique_filename (const gchar * path,
                                 const gchar * suffix)
{
	const gint num_of_words = 12;
	gchar     * words[] = {
		    "foo",
		    "bar",
		    "blah",
	   	    "cranston",
		    "frobate",
		    "hadjaha",
		    "greasy",
		    "hammer",
		    "eek",
		    "larry",
		    "curly",
		    "moe",
		    NULL};
	gchar * retval = NULL;
	gboolean exists = TRUE;

	while (exists) {
		gchar * file;
		gint rnd;
		gint word;

		rnd = rand ();
		word = rand () % num_of_words;

		file = g_strdup_printf ("%s-%010x%s",
					    words [word],
					    (guint) rnd,
					    suffix);

		g_free (retval);
		retval = g_strconcat (path, G_DIR_SEPARATOR_S, file, NULL);
		exists = g_file_test (retval, G_FILE_TEST_EXISTS);
		g_free (file);
	}
	return retval;
}

GtkWidget *
gsearchtool_button_new_with_icon (const gchar * string,
                                  const gchar * icon_name)
{
	GtkWidget * button;
	GtkWidget * hbox;
	GtkWidget * image;
	GtkWidget * label;

	button = gtk_button_new ();
	label = gtk_label_new_with_mnemonic (string);
	gtk_label_set_mnemonic_widget (GTK_LABEL (label), GTK_WIDGET (button));
	image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_BUTTON);
	hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
	gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
	gtk_box_pack_end (GTK_BOX (hbox), label, FALSE, FALSE, 0);
	gtk_container_add (GTK_CONTAINER (button), hbox);
	gtk_widget_show_all (hbox);

	return button;
}

GSList *
gsearchtool_get_columns_order (GtkTreeView * treeview)
{
	GSList *order = NULL;
	GList * columns;
	GList * col;

	columns = gtk_tree_view_get_columns (treeview);

	for (col = columns; col; col = col->next) {
		gint id;

		id = gtk_tree_view_column_get_sort_column_id (col->data);
		order = g_slist_prepend (order, GINT_TO_POINTER (id));
	}
	g_list_free (columns);

	order = g_slist_reverse (order);
	return order;
}

GtkTreeViewColumn *
gsearchtool_gtk_tree_view_get_column_with_sort_column_id (GtkTreeView * treeview,
                                                          gint id)
{
	GtkTreeViewColumn * col = NULL;
	GList * columns;
	GList * it;

	columns = gtk_tree_view_get_columns (treeview);

	for (it = columns; it; it = it->next) {
		if (gtk_tree_view_column_get_sort_column_id (it->data) == id) {
			col = it->data;
			break;
		}
	}
	g_list_free (columns);
	return col;
}

void
gsearchtool_set_columns_order (GtkTreeView * treeview)
{
	GtkTreeViewColumn * last = NULL;
	GSettings * settings;
	GVariant * value;

	settings = g_settings_new ("org.mate.search-tool");

	value = g_settings_get_value (settings, "columns-order");

	if (value) {
		GVariantIter *iter;
		GVariant     *item;

		g_variant_get (value, "ai", &iter);

		while ((item = g_variant_iter_next_value (iter))) {
			GtkTreeViewColumn * cur;
			gint id;

			g_variant_get (item, "i", &id);

			if (id >= 0 && id < NUM_COLUMNS) {

				cur = gsearchtool_gtk_tree_view_get_column_with_sort_column_id (treeview, id);

				if (cur && cur != last) {
					gtk_tree_view_move_column_after (treeview, cur, last);
					last = cur;
				}
			}
			g_variant_unref (item);
		}
		g_variant_iter_free (iter);
		g_variant_unref (value);
	}
	g_object_unref (settings);
}

void
gsearchtool_get_stored_window_geometry (gint * width,
                                        gint * height)
{
	GSettings * settings;
	gint saved_width;
	gint saved_height;

	if (width == NULL || height == NULL) {
		return;
	}

	settings = g_settings_new ("org.mate.search-tool");

	saved_width = g_settings_get_int (settings, "default-window-width");
	saved_height = g_settings_get_int (settings, "default-window-height");

	if (saved_width == -1) {
		saved_width = DEFAULT_WINDOW_WIDTH;
	}

	if (saved_height == -1) {
		saved_height = DEFAULT_WINDOW_HEIGHT;
	}

	*width = MAX (saved_width, MINIMUM_WINDOW_WIDTH);
	*height = MAX (saved_height, MINIMUM_WINDOW_HEIGHT);
	g_object_unref (settings);
}

/* START OF CAJA/EEL FUNCTIONS: USED FOR HANDLING OF DUPLICATE FILENAMES */

/* Localizers:
 * Feel free to leave out the st, nd, rd and th suffix or
 * make some or all of them match.
 */

/* localizers: tag used to detect the first copy of a file */
static const char untranslated_copy_duplicate_tag[] = N_(" (copy)");
/* localizers: tag used to detect the second copy of a file */
static const char untranslated_another_copy_duplicate_tag[] = N_(" (another copy)");

/* localizers: tag used to detect the x11th copy of a file */
static const char untranslated_x11th_copy_duplicate_tag[] = N_("th copy)");
/* localizers: tag used to detect the x12th copy of a file */
static const char untranslated_x12th_copy_duplicate_tag[] = N_("th copy)");
/* localizers: tag used to detect the x13th copy of a file */
static const char untranslated_x13th_copy_duplicate_tag[] = N_("th copy)");

/* localizers: tag used to detect the x1st copy of a file */
static const char untranslated_st_copy_duplicate_tag[] = N_("st copy)");
/* localizers: tag used to detect the x2nd copy of a file */
static const char untranslated_nd_copy_duplicate_tag[] = N_("nd copy)");
/* localizers: tag used to detect the x3rd copy of a file */
static const char untranslated_rd_copy_duplicate_tag[] = N_("rd copy)");

/* localizers: tag used to detect the xxth copy of a file */
static const char untranslated_th_copy_duplicate_tag[] = N_("th copy)");

#define COPY_DUPLICATE_TAG _(untranslated_copy_duplicate_tag)
#define ANOTHER_COPY_DUPLICATE_TAG _(untranslated_another_copy_duplicate_tag)
#define X11TH_COPY_DUPLICATE_TAG _(untranslated_x11th_copy_duplicate_tag)
#define X12TH_COPY_DUPLICATE_TAG _(untranslated_x12th_copy_duplicate_tag)
#define X13TH_COPY_DUPLICATE_TAG _(untranslated_x13th_copy_duplicate_tag)

#define ST_COPY_DUPLICATE_TAG _(untranslated_st_copy_duplicate_tag)
#define ND_COPY_DUPLICATE_TAG _(untranslated_nd_copy_duplicate_tag)
#define RD_COPY_DUPLICATE_TAG _(untranslated_rd_copy_duplicate_tag)
#define TH_COPY_DUPLICATE_TAG _(untranslated_th_copy_duplicate_tag)

/* localizers: appended to first file copy */
static const char untranslated_first_copy_duplicate_format[] = N_("%s (copy)%s");
/* localizers: appended to second file copy */
static const char untranslated_second_copy_duplicate_format[] = N_("%s (another copy)%s");

/* localizers: appended to x11th file copy */
static const char untranslated_x11th_copy_duplicate_format[] = N_("%s (%dth copy)%s");
/* localizers: appended to x12th file copy */
static const char untranslated_x12th_copy_duplicate_format[] = N_("%s (%dth copy)%s");
/* localizers: appended to x13th file copy */
static const char untranslated_x13th_copy_duplicate_format[] = N_("%s (%dth copy)%s");

/* localizers: appended to x1st file copy */
static const char untranslated_st_copy_duplicate_format[] = N_("%s (%dst copy)%s");
/* localizers: appended to x2nd file copy */
static const char untranslated_nd_copy_duplicate_format[] = N_("%s (%dnd copy)%s");
/* localizers: appended to x3rd file copy */
static const char untranslated_rd_copy_duplicate_format[] = N_("%s (%drd copy)%s");
/* localizers: appended to xxth file copy */
static const char untranslated_th_copy_duplicate_format[] = N_("%s (%dth copy)%s");

#define FIRST_COPY_DUPLICATE_FORMAT _(untranslated_first_copy_duplicate_format)
#define SECOND_COPY_DUPLICATE_FORMAT _(untranslated_second_copy_duplicate_format)
#define X11TH_COPY_DUPLICATE_FORMAT _(untranslated_x11th_copy_duplicate_format)
#define X12TH_COPY_DUPLICATE_FORMAT _(untranslated_x12th_copy_duplicate_format)
#define X13TH_COPY_DUPLICATE_FORMAT _(untranslated_x13th_copy_duplicate_format)

#define ST_COPY_DUPLICATE_FORMAT _(untranslated_st_copy_duplicate_format)
#define ND_COPY_DUPLICATE_FORMAT _(untranslated_nd_copy_duplicate_format)
#define RD_COPY_DUPLICATE_FORMAT _(untranslated_rd_copy_duplicate_format)
#define TH_COPY_DUPLICATE_FORMAT _(untranslated_th_copy_duplicate_format)

static gchar *
make_valid_utf8 (const gchar * name)
{
	GString *string;
	const char *remainder, *invalid;
	int remaining_bytes, valid_bytes;

	string = NULL;
	remainder = name;
	remaining_bytes = strlen (name);

	while (remaining_bytes != 0) {
		if (g_utf8_validate (remainder, remaining_bytes, &invalid)) {
			break;
		}
		valid_bytes = invalid - remainder;

		if (string == NULL) {
			string = g_string_sized_new (remaining_bytes);
		}
		g_string_append_len (string, remainder, valid_bytes);
		g_string_append_c (string, '?');

		remaining_bytes -= valid_bytes + 1;
		remainder = invalid + 1;
	}

	if (string == NULL) {
		return g_strdup (name);
	}

	g_string_append (string, remainder);
	g_string_append (string, _(" (invalid Unicode)"));
	g_assert (g_utf8_validate (string->str, -1, NULL));

	return g_string_free (string, FALSE);
}

static gchar *
extract_string_until (const gchar * original,
                      const gchar * until_substring)
{
	gchar * result;

	g_assert ((gint) strlen (original) >= until_substring - original);
	g_assert (until_substring - original >= 0);

	result = g_malloc (until_substring - original + 1);
	strncpy (result, original, until_substring - original);
	result[until_substring - original] = '\0';

	return result;
}

/* Dismantle a file name, separating the base name, the file suffix and removing any
 * (xxxcopy), etc. string. Figure out the count that corresponds to the given
 * (xxxcopy) substring.
 */
static void
parse_previous_duplicate_name (const gchar * name,
                               gchar ** name_base,
                               const gchar ** suffix,
                               gint * count)
{
	const gchar * tag;

	g_assert (name[0] != '\0');

	*suffix = strchr (name + 1, '.');
	if (*suffix == NULL || (*suffix)[1] == '\0') {
		/* no suffix */
		*suffix = "";
	}

	tag = strstr (name, COPY_DUPLICATE_TAG);
	if (tag != NULL) {
		if (tag > *suffix) {
			/* handle case "foo. (copy)" */
			*suffix = "";
		}
		*name_base = extract_string_until (name, tag);
		*count = 1;
		return;
	}

	tag = strstr (name, ANOTHER_COPY_DUPLICATE_TAG);
	if (tag != NULL) {
		if (tag > *suffix) {
			/* handle case "foo. (another copy)" */
			*suffix = "";
		}
		*name_base = extract_string_until (name, tag);
		*count = 2;
		return;
	}

	/* Check to see if we got one of st, nd, rd, th. */
	tag = strstr (name, X11TH_COPY_DUPLICATE_TAG);

	if (tag == NULL) {
		tag = strstr (name, X12TH_COPY_DUPLICATE_TAG);
	}
	if (tag == NULL) {
		tag = strstr (name, X13TH_COPY_DUPLICATE_TAG);
	}
	if (tag == NULL) {
		tag = strstr (name, ST_COPY_DUPLICATE_TAG);
	}
	if (tag == NULL) {
		tag = strstr (name, ND_COPY_DUPLICATE_TAG);
	}
	if (tag == NULL) {
		tag = strstr (name, RD_COPY_DUPLICATE_TAG);
	}
	if (tag == NULL) {
		tag = strstr (name, TH_COPY_DUPLICATE_TAG);
	}

	/* If we got one of st, nd, rd, th, fish out the duplicate number. */
	if (tag != NULL) {
		/* localizers: opening parentheses to match the "th copy)" string */
		tag = strstr (name, _(" ("));
		if (tag != NULL) {
			if (tag > *suffix) {
				/* handle case "foo. (22nd copy)" */
				*suffix = "";
			}
			*name_base = extract_string_until (name, tag);
			/* localizers: opening parentheses of the "th copy)" string */
			if (sscanf (tag, _(" (%d"), count) == 1) {
				if (*count < 1 || *count > 1000000) {
					/* keep the count within a reasonable range */
					*count = 0;
				}
				return;
			}
			*count = 0;
			return;
		}
	}

	*count = 0;
	if (**suffix != '\0') {
		*name_base = extract_string_until (name, *suffix);
	} else {
		*name_base = g_strdup (name);
	}
}

static gchar *
make_next_duplicate_name (const gchar *base,
                          const gchar *suffix,
                          gint count)
{
	const gchar * format;
	gchar * result;

	if (count < 1) {
		g_warning ("bad count %d in make_next_duplicate_name()", count);
		count = 1;
	}

	if (count <= 2) {

		/* Handle special cases for low numbers.
		 * Perhaps for some locales we will need to add more.
		 */
		switch (count) {
		default:
			g_assert_not_reached ();
			/* fall through */
		case 1:
			format = FIRST_COPY_DUPLICATE_FORMAT;
			break;
		case 2:
			format = SECOND_COPY_DUPLICATE_FORMAT;
			break;

		}
		result = g_strdup_printf (format, base, suffix);
	} else {

		/* Handle special cases for the first few numbers of each ten.
		 * For locales where getting this exactly right is difficult,
		 * these can just be made all the same as the general case below.
		 */

		/* Handle special cases for x11th - x20th.
		 */
		switch (count % 100) {
		case 11:
			format = X11TH_COPY_DUPLICATE_FORMAT;
			break;
		case 12:
			format = X12TH_COPY_DUPLICATE_FORMAT;
			break;
		case 13:
			format = X13TH_COPY_DUPLICATE_FORMAT;
			break;
		default:
			format = NULL;
			break;
		}

		if (format == NULL) {
			switch (count % 10) {
			case 1:
				format = ST_COPY_DUPLICATE_FORMAT;
				break;
			case 2:
				format = ND_COPY_DUPLICATE_FORMAT;
				break;
			case 3:
				format = RD_COPY_DUPLICATE_FORMAT;
				break;
			default:
				/* The general case. */
				format = TH_COPY_DUPLICATE_FORMAT;
				break;
			}
		}
		result = g_strdup_printf (format, base, count, suffix);
	}
	return result;
}

static gchar *
get_duplicate_name (const gchar *name)
{
	const gchar * suffix;
	gchar * name_base;
	gchar * result;
	gint count;

	parse_previous_duplicate_name (name, &name_base, &suffix, &count);
	result = make_next_duplicate_name (name_base, suffix, count + 1);
	g_free (name_base);

	return result;
}

gchar *
gsearchtool_get_next_duplicate_name (const gchar * basename)
{
	gchar * utf8_name;
	gchar * utf8_result;
	gchar * result;

	utf8_name = g_filename_to_utf8 (basename, -1, NULL, NULL, NULL);

	if (utf8_name == NULL) {
		/* Couldn't convert to utf8 - probably
		 * G_BROKEN_FILENAMES not set when it should be.
		 * Try converting from the locale */
		utf8_name = g_locale_to_utf8 (basename, -1, NULL, NULL, NULL);

		if (utf8_name == NULL) {
			utf8_name = make_valid_utf8 (basename);
		}
	}

	utf8_result = get_duplicate_name (utf8_name);
	g_free (utf8_name);

	result = g_filename_from_utf8 (utf8_result, -1, NULL, NULL, NULL);
	g_free (utf8_result);
	return result;
}
