/*
 * util.c
 *
 * Copyright (C) 2002 Sun Microsystems, Inc.
 *           (C) 1999, 2000 Red Hat Inc.
 *           (C) 1998 James Henstridge
 *           (C) 1995-2002 Free Software Foundation
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 *
 * Authors: Glynn Foster <glynn.foster@sun.com>
 *          Havoc Pennington <hp@redhat.com>
 *          James Henstridge <james@daa.com.au>
 *          Tom Tromey <tromey@redhat.com>
 */

#include "config.h"

#include "config.h"
#include "util.h"
#include "zenity.h"
#include <errno.h>
#include <locale.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef GDK_WINDOWING_X11
#include <gdk/gdkx.h>
#endif

#define ZENITY_OK_DEFAULT 0
#define ZENITY_CANCEL_DEFAULT 1
#define ZENITY_ESC_DEFAULT 1
#define ZENITY_ERROR_DEFAULT -1
#define ZENITY_EXTRA_DEFAULT 127

/* This exit code number is arbitrary, but since for the entire 3.x release
 * cycle, zenity would essentially exit(ZENITY_TIMEOUT), which happened to be
 * defined as 5 based on where it was placed in the enum sequence. So
 * hardcoding it as 5 now in case any pre-existing scripts relied upon that
 * being the exit status for timeouts.
 */
#define ZENITY_TIMEOUT_DEFAULT 5

GtkBuilder *
zenity_util_load_ui_file (const gchar *root_widget, ...) {
	va_list args;
	gchar *arg = NULL;
	GPtrArray *ptrarray;
	GtkBuilder *builder = gtk_builder_new ();
	GError *error = NULL;
	gchar **objects;
	guint result = 0;

	gtk_builder_set_translation_domain (builder, GETTEXT_PACKAGE);

	/* We have at least the root_widget and a NULL */
	ptrarray = g_ptr_array_sized_new (2);

	g_ptr_array_add (ptrarray, g_strdup (root_widget));

	va_start (args, root_widget);

	arg = va_arg (args, gchar *);

	while (arg) {
		g_ptr_array_add (ptrarray, g_strdup (arg));
		arg = va_arg (args, gchar *);
	}
	va_end (args);

	/* Enforce terminating NULL */
	g_ptr_array_add (ptrarray, NULL);
	objects = (gchar **) g_ptr_array_free (ptrarray, FALSE);

	if (g_file_test (ZENITY_UI_FILE_RELATIVEPATH, G_FILE_TEST_EXISTS)) {
		/* Try current dir, for debugging */
		result = gtk_builder_add_objects_from_file (
			builder, ZENITY_UI_FILE_RELATIVEPATH, objects, NULL);
	}

	if (result == 0)
		result = gtk_builder_add_objects_from_file (
			builder, ZENITY_UI_FILE_FULLPATH, objects, &error);

	g_strfreev (objects);

	if (result == 0) {
		g_warning ("Could not load ui file %s: %s",
			ZENITY_UI_FILE_FULLPATH,
			error->message);
		g_error_free (error);
		g_object_unref (builder);
		return NULL;
	}

	return builder;
}
gchar *
zenity_util_strip_newline (gchar *string) {
	gsize len;

	g_return_val_if_fail (string != NULL, NULL);

	len = strlen (string);
	while (len--) {
		if (string[len] == '\n')
			string[len] = '\0';
		else
			break;
	}

	return string;
}

gboolean
zenity_util_fill_file_buffer (GtkTextBuffer *buffer, const gchar *filename) {
	GtkTextIter iter, end;
	FILE *f;
	gchar buf[2048];
	gint remaining = 0;

	if (filename == NULL)
		return FALSE;

	f = fopen (filename, "r");

	if (f == NULL) {
		g_warning ("Cannot open file '%s': %s", filename, g_strerror (errno));
		return FALSE;
	}

	gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0);

	while (!feof (f)) {
		gint count;
		const char *leftover;
		int to_read = 2047 - remaining;

		count = fread (buf + remaining, 1, to_read, f);
		buf[count + remaining] = '\0';

		g_utf8_validate (buf, count + remaining, &leftover);

		g_assert (g_utf8_validate (buf, leftover - buf, NULL));
		gtk_text_buffer_insert (buffer, &iter, buf, leftover - buf);

		remaining = (buf + remaining + count) - leftover;
		memmove (buf, leftover, remaining);

		if (remaining > 6 || count < to_read)
			break;
	}

	if (remaining) {
		g_warning (
			"Invalid UTF-8 data encountered reading file '%s'", filename);
		return FALSE;
	}

	/* We had a newline in the buffer to begin with. (The buffer always contains
	 * a newline, so we delete to the end of the buffer to clean up.
	 */

	gtk_text_buffer_get_end_iter (buffer, &end);
	gtk_text_buffer_delete (buffer, &iter, &end);

	gtk_text_buffer_set_modified (buffer, FALSE);

	return TRUE;
}

const gchar *
zenity_util_icon_name_from_filename (const gchar *filename) {
	if (!filename || !filename[0])
		return "dialog-warning"; /* default */

	if (!g_ascii_strcasecmp (filename, "warning"))
		return "dialog-warning";
	if (!g_ascii_strcasecmp (filename, "info"))
		return "dialog-information";
	if (!g_ascii_strcasecmp (filename, "question"))
		return "dialog-question";
	if (!g_ascii_strcasecmp (filename, "error"))
		return "dialog-error";
	return NULL;
}

void
zenity_util_set_window_icon_from_file (
	GtkWidget *widget, const gchar *filename) {
	GdkPixbuf *pixbuf;
	const gchar *icon_name;

	icon_name = zenity_util_icon_name_from_filename (filename);
	if (icon_name) {
		gtk_window_set_icon_name (GTK_WINDOW (widget), icon_name);
	} else {
		pixbuf = gdk_pixbuf_new_from_file (filename, NULL);
		gtk_window_set_icon (GTK_WINDOW (widget), pixbuf);
		g_object_unref (pixbuf);
	}
}

void
zenity_util_set_window_icon (
	GtkWidget *widget, const gchar *filename, const gchar *default_file) {
	GdkPixbuf *pixbuf;

	if (filename != NULL) {
		zenity_util_set_window_icon_from_file (widget, filename);
	} else {
		pixbuf = gdk_pixbuf_new_from_file (default_file, NULL);
		if (pixbuf != NULL) {
			gtk_window_set_icon (GTK_WINDOW (widget), pixbuf);
			g_object_unref (pixbuf);
		}
	}
}

void
zenity_util_set_window_icon_from_icon_name (
	GtkWidget *widget, const gchar *filename, const gchar *default_icon_name) {
	if (filename != NULL)
		zenity_util_set_window_icon_from_file (widget, filename);
	else
		gtk_window_set_icon_name (GTK_WINDOW (widget), default_icon_name);
}

void
zenity_util_show_help (GError **error) {
	gchar *tmp;
	tmp = g_find_program_in_path ("yelp");

	if (tmp) {
		g_free (tmp);
		g_spawn_command_line_async ("yelp help:zenity", error);
	}
}

gint
zenity_util_return_exit_code (ZenityExitCode value) {

	const gchar *env_var = NULL;
	gint retval;

	switch (value) {

		case ZENITY_OK:
			env_var = g_getenv ("ZENITY_OK");
			if (!env_var)
				env_var = g_getenv ("DIALOG_OK");
			if (!env_var)
				retval = ZENITY_OK_DEFAULT;
			break;

		case ZENITY_CANCEL:
			env_var = g_getenv ("ZENITY_CANCEL");
			if (!env_var)
				env_var = g_getenv ("DIALOG_CANCEL");
			if (!env_var)
				retval = ZENITY_CANCEL_DEFAULT;
			break;

		case ZENITY_ESC:
			env_var = g_getenv ("ZENITY_ESC");
			if (!env_var)
				env_var = g_getenv ("DIALOG_ESC");
			if (!env_var)
				retval = ZENITY_ESC_DEFAULT;
			break;

		case ZENITY_EXTRA:
			env_var = g_getenv ("ZENITY_EXTRA");
			if (!env_var)
				env_var = g_getenv ("DIALOG_EXTRA");
			if (!env_var)
				retval = ZENITY_EXTRA_DEFAULT;
			break;

		case ZENITY_ERROR:
			env_var = g_getenv ("ZENITY_ERROR");
			if (!env_var)
				env_var = g_getenv ("DIALOG_ERROR");
			if (!env_var)
				retval = ZENITY_ERROR_DEFAULT;
			break;

		case ZENITY_TIMEOUT:
			env_var = g_getenv ("ZENITY_TIMEOUT");
			if (!env_var)
				env_var = g_getenv ("DIALOG_TIMEOUT");
			if (!env_var)
				retval = ZENITY_TIMEOUT_DEFAULT;
			break;

		default:
			retval = 1;
	}

	if (env_var)
		retval = atoi (env_var);
	return retval;
}

void
zenity_util_exit_code_with_data (ZenityExitCode value, ZenityData *zen_data) {
	zen_data->exit_code = zenity_util_return_exit_code (value);
}

#ifdef GDK_WINDOWING_X11

static Window
transient_get_xterm (void) {
	const char *wid_str = g_getenv ("WINDOWID");
	if (wid_str) {
		char *wid_str_end;
		int ret;
		Window wid = strtoul (wid_str, &wid_str_end, 10);
		if (*wid_str != '\0' && *wid_str_end == '\0' && wid != 0) {
			XWindowAttributes attrs;
			GdkDisplay *display;
			display = gdk_display_get_default ();
			gdk_x11_display_error_trap_push (display);
			ret = XGetWindowAttributes (
				GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), wid, &attrs);
			if (gdk_x11_display_error_trap_pop (display) != 0 || ret == 0) {
				return None;
			}
			return wid;
		}
	}
	return None;
}

static void
transient_x_free (void *ptr) {
	if (ptr)
		XFree (ptr);
}

static gboolean
transient_is_toplevel (Window wid) {
	XTextProperty prop;
	Display *dpy = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
	if (!XGetWMName (dpy, wid, &prop))
		return FALSE;
	transient_x_free (prop.value);
	return !!prop.value;
}

/*
 * GNOME Terminal doesn't give us its toplevel window, but the WM needs a
 * toplevel XID for proper stacking.  Other terminals work fine without this
 * magic.  We can't use GDK here since "xterm" is a foreign window.
 */

static Window
transient_get_xterm_toplevel (void) {
	Window xterm = transient_get_xterm ();
	Display *dpy = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
	while (xterm != None && !transient_is_toplevel (xterm)) {
		Window root, parent, *children;
		unsigned nchildren;
		XQueryTree (dpy, xterm, &root, &parent, &children, &nchildren);
		transient_x_free (children);
		if (parent == root)
			xterm = None;
		else
			xterm = parent;
	}
	return xterm;
}

static void
zenity_util_make_transient (GdkWindow *window, Window parent) {
	Window parent_window = parent;
	if (parent_window == 0)
		parent_window = transient_get_xterm_toplevel ();
	if (parent_window != None) {
		XSetTransientForHint (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()),
			GDK_WINDOW_XID (window),
			parent_window);
	}
}

#endif /* GDK_WINDOWING_X11 */

void
zenity_util_show_dialog (GtkWidget *dialog, guintptr parent) {
	gtk_widget_realize (dialog);
#ifdef GDK_WINDOWING_X11
	if (GDK_IS_X11_DISPLAY (gdk_display_get_default ())) {
		g_assert (gtk_widget_get_window (dialog));
		zenity_util_make_transient (gtk_widget_get_window (dialog), parent);
	}
#endif
	gtk_widget_show (dialog);
}

gboolean
zenity_util_timeout_handle (gpointer data) {
	GtkDialog *dialog = GTK_DIALOG (data);
	if (dialog != NULL)
		gtk_dialog_response (dialog, ZENITY_TIMEOUT);
	else {
		gtk_main_quit ();
		exit (ZENITY_TIMEOUT_DEFAULT);
	}
	return FALSE;
}
