/*
 * Copyright © 2001, 2002 Havoc Pennington
 * Copyright © 2002 Red Hat, Inc.
 * Copyright © 2002 Sun Microsystems
 * Copyright © 2003 Mariano Suarez-Alvarez
 * Copyright © 2008 Christian Persch
 *
 * Mate-terminal 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 3 of the License, or
 * (at your option) any later version.
 *
 * Mate-terminal 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, see <http://www.gnu.org/licenses/>.
 */

#include <config.h>

#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>

#include <glib.h>

#include <gio/gio.h>
#include <gtk/gtk.h>

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

#include "terminal-accels.h"
#include "terminal-app.h"
#include "terminal-gsettings.h"
#include "terminal-intl.h"
#include "terminal-util.h"
#include "terminal-window.h"

#if GTK_CHECK_VERSION(3, 0, 0)
 	#define GDK_WINDOW_XWINDOW GDK_WINDOW_XID
 	#define GDK_DRAWABLE_XID GDK_WINDOW_XID
#endif

void
terminal_util_set_unique_role (GtkWindow *window, const char *prefix)
{
	char *role;

	role = g_strdup_printf ("%s-%d-%d-%d", prefix, getpid (), g_random_int (), (int) time (NULL));
	gtk_window_set_role (window, role);
	g_free (role);
}

/**
 * terminal_util_show_error_dialog:
 * @transient_parent: parent of the future dialog window;
 * @weap_ptr: pointer to a #Widget pointer, to control the population.
 * @error: a #GError, or %NULL
 * @message_format: printf() style format string
 *
 * Create a #GtkMessageDialog window with the message, and present it, handling its buttons.
 * If @weap_ptr is not #NULL, only create the dialog if <literal>*weap_ptr</literal> is #NULL
 * (and in that * case, set @weap_ptr to be a weak pointer to the new dialog), otherwise just
 * present <literal>*weak_ptr</literal>. Note that in this last case, the message <emph>will</emph>
 * be changed.
 */
void
terminal_util_show_error_dialog (GtkWindow *transient_parent,
                                 GtkWidget **weak_ptr,
                                 GError *error,
                                 const char *message_format,
                                 ...)
{
	char *message;
	va_list args;

	if (message_format)
	{
		va_start (args, message_format);
		message = g_strdup_vprintf (message_format, args);
		va_end (args);
	}
	else message = NULL;

	if (weak_ptr == NULL || *weak_ptr == NULL)
	{
		GtkWidget *dialog;
		dialog = gtk_message_dialog_new (transient_parent,
		                                 GTK_DIALOG_DESTROY_WITH_PARENT,
		                                 GTK_MESSAGE_ERROR,
		                                 GTK_BUTTONS_OK,
		                                 message ? "%s" : NULL,
		                                 message);

		if (error != NULL)
			gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
			        "%s", error->message);

		g_signal_connect (G_OBJECT (dialog), "response", G_CALLBACK (gtk_widget_destroy), NULL);

		if (weak_ptr != NULL)
		{
			*weak_ptr = dialog;
			g_object_add_weak_pointer (G_OBJECT (dialog), (void**)weak_ptr);
		}

		gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);

		gtk_widget_show_all (dialog);
	}
	else
	{
		g_return_if_fail (GTK_IS_MESSAGE_DIALOG (*weak_ptr));

		/* Sucks that there's no direct accessor for "text" property */
		g_object_set (G_OBJECT (*weak_ptr), "text", message, NULL);

		gtk_window_present (GTK_WINDOW (*weak_ptr));
	}

	g_free (message);
}

static gboolean
open_url (GtkWindow *parent,
          const char *uri,
          guint32 user_time,
          GError **error)
{
	GdkScreen *screen;

	if (parent)
		screen = gtk_widget_get_screen (GTK_WIDGET (parent));
	else
		screen = gdk_screen_get_default ();

	return gtk_show_uri (screen, uri, user_time, error);
}

void
terminal_util_show_help (const char *topic,
                         GtkWindow  *parent)
{
	GError *error = NULL;
	char *url;

	if (topic)
	{
		url = g_strdup_printf ("help:mate-terminal/%s", topic);
	}
	else
	{
		url = g_strdup ("help:mate-terminal");
	}

	if (!open_url (GTK_WINDOW (parent), url, gtk_get_current_event_time (), &error))
	{
		terminal_util_show_error_dialog (GTK_WINDOW (parent), NULL, error,
		                                 _("There was an error displaying help"));
		g_error_free (error);
	}

	g_free (url);
}

/* sets accessible name and description for the widget */

void
terminal_util_set_atk_name_description (GtkWidget  *widget,
                                        const char *name,
                                        const char *desc)
{
	AtkObject *obj;

	obj = gtk_widget_get_accessible (widget);

	if (obj == NULL)
	{
		g_warning ("%s: for some reason widget has no GtkAccessible",
		           G_STRFUNC);
		return;
	}


	if (!GTK_IS_ACCESSIBLE (obj))
		return; /* This means GAIL is not loaded so we have the NoOp accessible */

	g_return_if_fail (GTK_IS_ACCESSIBLE (obj));
	if (desc)
		atk_object_set_description (obj, desc);
	if (name)
		atk_object_set_name (obj, name);
}

void
terminal_util_open_url (GtkWidget *parent,
                        const char *orig_url,
                        TerminalURLFlavour flavor,
                        guint32 user_time)
{
	GError *error = NULL;
	char *uri;

	g_return_if_fail (orig_url != NULL);

	switch (flavor)
	{
	case FLAVOR_DEFAULT_TO_HTTP:
		uri = g_strdup_printf ("http:%s", orig_url);
		break;
	case FLAVOR_EMAIL:
		if (g_ascii_strncasecmp ("mailto:", orig_url, 7) != 0)
			uri = g_strdup_printf ("mailto:%s", orig_url);
		else
			uri = g_strdup (orig_url);
		break;
	case FLAVOR_VOIP_CALL:
	case FLAVOR_AS_IS:
		uri = g_strdup (orig_url);
		break;
	case FLAVOR_SKEY:
		/* shouldn't get this */
	default:
		uri = NULL;
		g_assert_not_reached ();
	}

	if (!open_url (GTK_WINDOW (parent), uri, user_time, &error))
	{
		terminal_util_show_error_dialog (GTK_WINDOW (parent), NULL, error,
		                                 _("Could not open the address “%s”"),
		                                 uri);

		g_error_free (error);
	}

	g_free (uri);
}

/**
 * terminal_util_resolve_relative_path:
 * @path:
 * @relative_path:
 *
 * Returns: a newly allocate string
 */
char *
terminal_util_resolve_relative_path (const char *path,
                                     const char *relative_path)
{
	GFile *file, *resolved_file;
	char *resolved_path = NULL;

	g_return_val_if_fail (relative_path != NULL, NULL);

	if (path == NULL)
		return g_strdup (relative_path);

	file = g_file_new_for_path (path);
	resolved_file = g_file_resolve_relative_path (file, relative_path);
	g_object_unref (file);

	if (resolved_file == NULL)
		return NULL;

	resolved_path = g_file_get_path (resolved_file);
	g_object_unref (resolved_file);

	return resolved_path;
}

/**
 * terminal_util_transform_uris_to_quoted_fuse_paths:
 * @uris:
 *
 * Transforms those URIs in @uris to shell-quoted paths that point to
 * GIO fuse paths.
 */
void
terminal_util_transform_uris_to_quoted_fuse_paths (char **uris)
{
	guint i;

	if (!uris)
		return;

	for (i = 0; uris[i]; ++i)
	{
		GFile *file;
		char *path;

		file = g_file_new_for_uri (uris[i]);

		if ((path = g_file_get_path (file)))
		{
			char *quoted;

			quoted = g_shell_quote (path);
			g_free (uris[i]);
			g_free (path);

			uris[i] = quoted;
		}

		g_object_unref (file);
	}
}

char *
terminal_util_concat_uris (char **uris,
                           gsize *length)
{
	GString *string;
	gsize len;
	guint i;

	len = 0;
	for (i = 0; uris[i]; ++i)
		len += strlen (uris[i]) + 1;

	if (length)
		*length = len;

	string = g_string_sized_new (len + 1);
	for (i = 0; uris[i]; ++i)
	{
		g_string_append (string, uris[i]);
		g_string_append_c (string, ' ');
	}

	return g_string_free (string, FALSE);
}

char *
terminal_util_get_licence_text (void)
{
	const gchar *license[] =
	{
		N_("MATE Terminal 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 3 of the License, or "
		"(at your option) any later version."),
		N_("MATE Terminal 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."),
		N_("You should have received a copy of the GNU General Public License "
		"along with MATE Terminal; if not, write to the Free Software Foundation, "
		"Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA")
	};

	return g_strjoin ("\n\n", _(license[0]), _(license[1]), _(license[2]), NULL);
}

gboolean
terminal_util_load_builder_file (const char *filename,
                                 const char *object_name,
                                 ...)
{
	char *path;
	GtkBuilder *builder;
	GError *error = NULL;
	va_list args;

	path = g_build_filename (TERM_PKGDATADIR, filename, NULL);
	builder = gtk_builder_new ();
	if (!gtk_builder_add_from_file (builder, path, &error))
	{
		g_warning ("Failed to load %s: %s\n", filename, error->message);
		g_error_free (error);
		g_free (path);
		g_object_unref (builder);
		return FALSE;
	}
	g_free (path);

	va_start (args, object_name);

	while (object_name)
	{
		GObject **objectptr;

		objectptr = va_arg (args, GObject**);
		*objectptr = gtk_builder_get_object (builder, object_name);
		if (!*objectptr)
		{
			g_warning ("Failed to fetch object \"%s\"\n", object_name);
			break;
		}

		object_name = va_arg (args, const char*);
	}

	va_end (args);

	g_object_unref (builder);
	return object_name == NULL;
}

gboolean
terminal_util_dialog_response_on_delete (GtkWindow *widget)
{
	gtk_dialog_response (GTK_DIALOG (widget), GTK_RESPONSE_DELETE_EVENT);
	return TRUE;
}

/* Like g_key_file_set_string, but escapes characters so that
 * the stored string is ASCII. Use when the input string may not
 * be UTF-8.
 */
void
terminal_util_key_file_set_string_escape (GKeyFile *key_file,
        const char *group,
        const char *key,
        const char *string)
{
	char *escaped;

	/* FIXMEchpe: be more intelligent and only escape characters that aren't UTF-8 */
	escaped = g_strescape (string, NULL);
	g_key_file_set_string (key_file, group, key, escaped);
	g_free (escaped);
}

char *
terminal_util_key_file_get_string_unescape (GKeyFile *key_file,
        const char *group,
        const char *key,
        GError **error)
{
	char *escaped, *unescaped;

	escaped = g_key_file_get_string (key_file, group, key, error);
	if (!escaped)
		return NULL;

	unescaped = g_strcompress (escaped);
	g_free (escaped);

	return unescaped;
}

void
terminal_util_key_file_set_argv (GKeyFile *key_file,
                                 const char *group,
                                 const char *key,
                                 int argc,
                                 char **argv)
{
	char **quoted_argv;
	char *flat;
	int i;

	if (argc < 0)
		argc = g_strv_length (argv);

	quoted_argv = g_new (char*, argc + 1);
	for (i = 0; i < argc; ++i)
		quoted_argv[i] = g_shell_quote (argv[i]);
	quoted_argv[argc] = NULL;

	flat = g_strjoinv (" ", quoted_argv);
	terminal_util_key_file_set_string_escape (key_file, group, key, flat);

	g_free (flat);
	g_strfreev (quoted_argv);
}

char **
terminal_util_key_file_get_argv (GKeyFile *key_file,
                                 const char *group,
                                 const char *key,
                                 int *argc,
                                 GError **error)
{
	char **argv;
	char *flat;
	gboolean retval;

	flat = terminal_util_key_file_get_string_unescape (key_file, group, key, error);
	if (!flat)
		return NULL;

	retval = g_shell_parse_argv (flat, argc, &argv, error);
	g_free (flat);

	if (retval)
		return argv;

	return NULL;
}

/* Proxy stuff */

static char *
gsettings_get_string (GSettings *settings,
                 const char *key)
{
	char *value;
	value = g_settings_get_string (settings, key);
	if (G_UNLIKELY (value && *value == '\0'))
	{
		g_free (value);
		value = NULL;
	}
	return value;
}

/*
 * set_proxy_env:
 * @env_table: a #GHashTable
 * @key: the env var name
 * @value: the env var value
 *
 * Adds @value for @key to @env_table, taking care to never overwrite an
 * existing value for @key. @value is consumed.
 */
static void
set_proxy_env (GHashTable *env_table,
               const char *key,
               char *value)
{
	char *key1 = NULL, *key2 = NULL;
	char *value1 = NULL, *value2 = NULL;

	if (!value)
		return;

	if (g_hash_table_lookup (env_table, key) == NULL)
		key1 = g_strdup (key);

	key2 = g_ascii_strup (key, -1);
	if (g_hash_table_lookup (env_table, key) != NULL)
	{
		g_free (key2);
		key2 = NULL;
	}

	if (key1 && key2)
	{
		value1 = value;
		value2 = g_strdup (value);
	}
	else if (key1)
		value1 = value;
	else if (key2)
		value2 = value;
	else
		g_free (value);

	if (key1)
		g_hash_table_replace (env_table, key1, value1);
	if (key2)
		g_hash_table_replace (env_table, key2, value2);
}

static void
setup_http_proxy_env (GHashTable *env_table,
                      GSettings *settings_http)
{
	gchar *host;
	gint port;

	host = gsettings_get_string (settings_http, "host");
	port = g_settings_get_int (settings_http, "port");
	if (host && port)
	{

		GString *buf = g_string_sized_new (64);
		g_string_append (buf, "http://");

		if (g_settings_get_boolean (settings_http, "use-authentication"))
		{
			char *user, *password;
			user = gsettings_get_string (settings_http, "authentication-user");
			if (user)
			{
				g_string_append_uri_escaped (buf, user, NULL, TRUE);
				password = gsettings_get_string (settings_http, "authentication-password");
				if (password)
				{
					g_string_append_c (buf, ':');
					g_string_append_uri_escaped (buf, password, NULL, TRUE);
					g_free (password);
				}
				g_free (user);
				g_string_append_c (buf, '@');
			}
		}
		g_string_append_printf (buf, "%s:%d/", host, port);
		set_proxy_env (env_table, "http_proxy", g_string_free (buf, FALSE));
	}
	g_free (host);

}

static void
setup_ignore_host_env (GHashTable *env_table,
                      GSettings *settings)
{
	GSList *ignore;
	gchar **ignore_strv = g_settings_get_strv (settings, "ignore-hosts");

	ignore = terminal_gsettings_strv_to_gslist (ignore_strv);
	if (ignore)
	{
		GString *buf = g_string_sized_new (64);
		while (ignore != NULL)
		{
			GSList *old;

			if (buf->len)
				g_string_append_c (buf, ',');
			g_string_append (buf, ignore->data);

			old = ignore;
			ignore = g_slist_next (ignore);
			g_free (old->data);
			g_slist_free_1 (old);
		}
		set_proxy_env (env_table, "no_proxy", g_string_free (buf, FALSE));
	}
	if (ignore_strv)
		g_strfreev(ignore_strv);
}

static void
setup_https_proxy_env (GHashTable *env_table,
                       GSettings *settings_https)
{
	gchar *host;
	gint port;

	host = gsettings_get_string (settings_https, "host");
	port = g_settings_get_int (settings_https, "port");
	if (host && port)
	{
		char *proxy;
		/* Even though it's https, the proxy scheme is 'http'. See bug #624440. */
		proxy = g_strdup_printf ("http://%s:%d/", host, port);
		set_proxy_env (env_table, "https_proxy", proxy);
	}
	g_free (host);
}

static void
setup_ftp_proxy_env (GHashTable *env_table,
                     GSettings *settings_ftp)
{
	gchar *host;
	gint port;

	host = gsettings_get_string (settings_ftp, "host");
	port = g_settings_get_int (settings_ftp, "port");
	if (host && port)
	{
		char *proxy;
		/* Even though it's ftp, the proxy scheme is 'http'. See bug #624440. */
		proxy = g_strdup_printf ("http://%s:%d/", host, port);
		set_proxy_env (env_table, "ftp_proxy", proxy);
	}
	g_free (host);
}

static void
setup_socks_proxy_env (GHashTable *env_table,
                       GSettings *settings_socks)
{
	gchar *host;
	gint port;

	host = gsettings_get_string (settings_socks, "host");
	port = g_settings_get_int (settings_socks, "port");
	if (host && port)
	{
		char *proxy;
		proxy = g_strdup_printf ("socks://%s:%d/", host, port);
		set_proxy_env (env_table, "all_proxy", proxy);
	}
	g_free (host);
}

static void
setup_autoconfig_proxy_env (GHashTable *env_table,
                            GSettings *settings)
{
	/* XXX  Not sure what to do with this.  See bug #596688.
	gchar *url;

	url = gsettings_get_string (settings, "autoconfig-url");
	if (url)
	  {
	    char *proxy;
	    proxy = g_strdup_printf ("pac+%s", url);
	    set_proxy_env (env_table, "http_proxy", proxy);
	  }
	g_free (url);
	*/
}

/**
 * terminal_util_add_proxy_env:
 * @env_table: a #GHashTable
 *
 * Adds the proxy env variables to @env_table.
 */
void
terminal_util_add_proxy_env (GHashTable *env_table)
{
	char *proxymode;
	GSettings *settings = g_settings_new (CONF_PROXY_SCHEMA);
	GSettings *settings_http = g_settings_new (CONF_HTTP_PROXY_SCHEMA);
	GSettings *settings_https = g_settings_new (CONF_HTTPS_PROXY_SCHEMA);
	GSettings *settings_ftp = g_settings_new (CONF_FTP_PROXY_SCHEMA);
	GSettings *settings_socks = g_settings_new (CONF_SOCKS_PROXY_SCHEMA);

	/* If mode is not manual, nothing to set */
	proxymode = gsettings_get_string (settings, "mode");
	if (proxymode && 0 == strcmp (proxymode, "manual"))
	{
		setup_http_proxy_env (env_table, settings_http);
		setup_ignore_host_env (env_table, settings);
		setup_https_proxy_env (env_table, settings_https);
		setup_ftp_proxy_env (env_table, settings_ftp);
		setup_socks_proxy_env (env_table, settings_socks);
	}
	else if (proxymode && 0 == strcmp (proxymode, "auto"))
	{
		setup_autoconfig_proxy_env (env_table, settings);
	}

	g_free (proxymode);
	g_object_unref (settings);
	g_object_unref (settings_http);
	g_object_unref (settings_https);
	g_object_unref (settings_ftp);
	g_object_unref (settings_socks);
}

/* Bidirectional object/widget binding */

typedef struct
{
	GObject *object;
	const char *object_prop;
	GtkWidget *widget;
	gulong object_notify_id;
	gulong widget_notify_id;
	PropertyChangeFlags flags;
} PropertyChange;

static void
property_change_free (PropertyChange *change)
{
	g_signal_handler_disconnect (change->object, change->object_notify_id);

	g_slice_free (PropertyChange, change);
}

static gboolean
transform_boolean (gboolean input,
                   PropertyChangeFlags flags)
{
	if (flags & FLAG_INVERT_BOOL)
		input = !input;

	return input;
}

static void
object_change_notify_cb (PropertyChange *change)
{
	GObject *object = change->object;
	const char *object_prop = change->object_prop;
	GtkWidget *widget = change->widget;

	g_signal_handler_block (widget, change->widget_notify_id);

	if (GTK_IS_RADIO_BUTTON (widget))
	{
		int ovalue, rvalue;

		g_object_get (object, object_prop, &ovalue, NULL);
		rvalue = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), "enum-value"));
		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), ovalue == rvalue);
	}
	else if (GTK_IS_TOGGLE_BUTTON (widget))
	{
		gboolean enabled;

		g_object_get (object, object_prop, &enabled, NULL);
		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget),
		                              transform_boolean (enabled, change->flags));
	}
	else if (GTK_IS_SPIN_BUTTON (widget))
	{
		int value;

		g_object_get (object, object_prop, &value, NULL);
		gtk_spin_button_set_value (GTK_SPIN_BUTTON (widget), value);
	}
	else if (GTK_IS_ENTRY (widget))
	{
		char *text;

		g_object_get (object, object_prop, &text, NULL);
		gtk_entry_set_text (GTK_ENTRY (widget), text ? text : "");
		g_free (text);
	}
	else if (GTK_IS_COMBO_BOX (widget))
	{
		int value;

		g_object_get (object, object_prop, &value, NULL);
		gtk_combo_box_set_active (GTK_COMBO_BOX (widget), value);
	}
	else if (GTK_IS_RANGE (widget))
	{
		double value;

		g_object_get (object, object_prop, &value, NULL);
		gtk_range_set_value (GTK_RANGE (widget), value);
	}
	else if (GTK_IS_COLOR_BUTTON (widget))
	{
		GdkColor *color;
		GdkColor old_color;

		g_object_get (object, object_prop, &color, NULL);
		gtk_color_button_get_color (GTK_COLOR_BUTTON (widget), &old_color);

		if (color && !gdk_color_equal (color, &old_color))
			gtk_color_button_set_color (GTK_COLOR_BUTTON (widget), color);
		if (color)
			gdk_color_free (color);
	}
	else if (GTK_IS_FONT_BUTTON (widget))
	{
		PangoFontDescription *font_desc;
		char *font;

		g_object_get (object, object_prop, &font_desc, NULL);
		if (!font_desc)
			goto out;

		font = pango_font_description_to_string (font_desc);
		gtk_font_button_set_font_name (GTK_FONT_BUTTON (widget), font);
		g_free (font);
		pango_font_description_free (font_desc);
	}
	else if (GTK_IS_FILE_CHOOSER (widget))
	{
		char *name = NULL, *filename = NULL;

		g_object_get (object, object_prop, &name, NULL);
		if (name)
			filename = g_filename_from_utf8 (name, -1, NULL, NULL, NULL);

		if (filename)
			gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (widget), filename);
		else
			gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (widget));
		g_free (filename);
		g_free (name);
	}

out:
	g_signal_handler_unblock (widget, change->widget_notify_id);
}

static void
widget_change_notify_cb (PropertyChange *change)
{
	GObject *object = change->object;
	const char *object_prop = change->object_prop;
	GtkWidget *widget = change->widget;

	g_signal_handler_block (change->object, change->object_notify_id);

	if (GTK_IS_RADIO_BUTTON (widget))
	{
		gboolean active;
		int value;

		active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget));
		if (!active)
			goto out;

		value = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), "enum-value"));
		g_object_set (object, object_prop, value, NULL);
	}
	else if (GTK_IS_TOGGLE_BUTTON (widget))
	{
		gboolean enabled;

		enabled = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget));
		g_object_set (object, object_prop, transform_boolean (enabled, change->flags), NULL);
	}
	else if (GTK_IS_SPIN_BUTTON (widget))
	{
		int value;

		value = (int) gtk_spin_button_get_value (GTK_SPIN_BUTTON (widget));
		g_object_set (object, object_prop, value, NULL);
	}
	else if (GTK_IS_ENTRY (widget))
	{
		const char *text;

		text = gtk_entry_get_text (GTK_ENTRY (widget));
		g_object_set (object, object_prop, text, NULL);
	}
	else if (GTK_IS_COMBO_BOX (widget))
	{
		int value;

		value = gtk_combo_box_get_active (GTK_COMBO_BOX (widget));
		g_object_set (object, object_prop, value, NULL);
	}
	else if (GTK_IS_COLOR_BUTTON (widget))
	{
		GdkColor color;

		gtk_color_button_get_color (GTK_COLOR_BUTTON (widget), &color);
		g_object_set (object, object_prop, &color, NULL);
	}
	else if (GTK_IS_FONT_BUTTON (widget))
	{
		PangoFontDescription *font_desc;
		const char *font;

		font = gtk_font_button_get_font_name (GTK_FONT_BUTTON (widget));
		font_desc = pango_font_description_from_string (font);
		g_object_set (object, object_prop, font_desc, NULL);
		pango_font_description_free (font_desc);
	}
	else if (GTK_IS_RANGE (widget))
	{
		double value;

		value = gtk_range_get_value (GTK_RANGE (widget));
		g_object_set (object, object_prop, value, NULL);
	}
	else if (GTK_IS_FILE_CHOOSER (widget))
	{
		char *filename, *name = NULL;

		filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (widget));
		if (filename)
			name = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL);

		g_object_set (object, object_prop, name, NULL);
		g_free (filename);
		g_free (name);
	}

out:
	g_signal_handler_unblock (change->object, change->object_notify_id);
}

void
terminal_util_bind_object_property_to_widget (GObject *object,
        const char *object_prop,
        GtkWidget *widget,
        PropertyChangeFlags flags)
{
	PropertyChange *change;
	const char *signal_name;
	char notify_signal_name[64];

	change = g_slice_new0 (PropertyChange);

	change->widget = widget;
	g_assert (g_object_get_data (G_OBJECT (widget), "GT:PCD") == NULL);
	g_object_set_data_full (G_OBJECT (widget), "GT:PCD", change, (GDestroyNotify) property_change_free);

	if (GTK_IS_TOGGLE_BUTTON (widget))
		signal_name = "notify::active";
	else if (GTK_IS_SPIN_BUTTON (widget))
		signal_name = "notify::value";
	else if (GTK_IS_ENTRY (widget))
		signal_name = "notify::text";
	else if (GTK_IS_COMBO_BOX (widget))
		signal_name = "notify::active";
	else if (GTK_IS_COLOR_BUTTON (widget))
		signal_name = "notify::color";
	else if (GTK_IS_FONT_BUTTON (widget))
		signal_name = "notify::font-name";
	else if (GTK_IS_RANGE (widget))
		signal_name = "value-changed";
	else if (GTK_IS_FILE_CHOOSER_BUTTON (widget))
		signal_name = "file-set";
	else if (GTK_IS_FILE_CHOOSER (widget))
		signal_name = "selection-changed";
	else
		g_assert_not_reached ();

	change->widget_notify_id = g_signal_connect_swapped (widget, signal_name, G_CALLBACK (widget_change_notify_cb), change);

	change->object = object;
	change->flags = flags;
	change->object_prop = object_prop;

	g_snprintf (notify_signal_name, sizeof (notify_signal_name), "notify::%s", object_prop);
	object_change_notify_cb (change);
	change->object_notify_id = g_signal_connect_swapped (object, notify_signal_name, G_CALLBACK (object_change_notify_cb), change);
}

#ifdef GDK_WINDOWING_X11

/* We don't want to hop desktops when we unrealize/realize.
 * So we need to save and restore the value of NET_WM_DESKTOP. This isn't
 * exposed through GDK.
 */
gboolean
terminal_util_x11_get_net_wm_desktop (GdkWindow *window,
                                      guint32   *desktop)
{
	#if GTK_CHECK_VERSION(3, 0, 0)
	GdkDisplay *display = gdk_window_get_display (window);
	#else
	GdkDisplay *display = gdk_drawable_get_display (window);
	#endif
	Atom type;
	int format;
	guchar *data;
	gulong n_items, bytes_after;
	gboolean result = FALSE;

	if (XGetWindowProperty (GDK_DISPLAY_XDISPLAY (display),
	                        GDK_DRAWABLE_XID (window),
	                        gdk_x11_get_xatom_by_name_for_display (display,
	                                "_NET_WM_DESKTOP"),
	                        0, G_MAXLONG, False, AnyPropertyType,
	                        &type, &format, &n_items, &bytes_after, &data) == Success &&
	        type != None)
	{
		if (type == XA_CARDINAL && format == 32 && n_items == 1)
		{
			*desktop = *(gulong *)data;
			result = TRUE;
		}

		XFree (data);
	}

	return result;
}

void
terminal_util_x11_set_net_wm_desktop (GdkWindow *window,
                                      guint32    desktop)
{
	/* We can't change the current desktop before mapping our window,
	 * because GDK has the annoying habit of clearing _NET_WM_DESKTOP
	 * before mapping a GdkWindow, So we we have to do it after instead.
	 *
	 * However, doing it after is different whether or not we have a
	 * window manager (if we don't have a window manager, we have to
	 * set the _NET_WM_DESKTOP property so that it picks it up when
	 * it starts)
	 *
	 * http://bugzilla.mate.org/show_bug.cgi?id=586311 asks for GTK+
	 * to just handle everything behind the scenes including the desktop.
	 */
	#if GTK_CHECK_VERSION(3, 0, 0)
	GdkScreen *screen = gdk_window_get_screen (window);
	#else
	GdkScreen *screen = gdk_drawable_get_screen (window);
	#endif
	GdkDisplay *display = gdk_screen_get_display (screen);
	Display *xdisplay = GDK_DISPLAY_XDISPLAY (display);
	char *wm_selection_name;
	Atom wm_selection;
	gboolean have_wm;

	wm_selection_name = g_strdup_printf ("WM_S%d", gdk_screen_get_number (screen));
	wm_selection = gdk_x11_get_xatom_by_name_for_display (display, wm_selection_name);
	g_free(wm_selection_name);

	XGrabServer (xdisplay);

	have_wm = XGetSelectionOwner (xdisplay, wm_selection) != None;

	if (have_wm)
	{
		/* code borrowed from GDK
		 */
		XClientMessageEvent xclient;

		memset (&xclient, 0, sizeof (xclient));
		xclient.type = ClientMessage;
		xclient.serial = 0;
		xclient.send_event = True;
		xclient.window = GDK_WINDOW_XWINDOW (window);
		xclient.message_type = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_DESKTOP");
		xclient.format = 32;

		xclient.data.l[0] = desktop;
		xclient.data.l[1] = 0;
		xclient.data.l[2] = 0;
		xclient.data.l[3] = 0;
		xclient.data.l[4] = 0;

		XSendEvent (xdisplay,
		            GDK_WINDOW_XWINDOW (gdk_screen_get_root_window (screen)),
		            False,
		            SubstructureRedirectMask | SubstructureNotifyMask,
		            (XEvent *)&xclient);
	}
	else
	{
		gulong long_desktop = desktop;

		XChangeProperty (xdisplay,
		                 GDK_DRAWABLE_XID (window),
		                 gdk_x11_get_xatom_by_name_for_display (display,
		                         "_NET_WM_DESKTOP"),
		                 XA_CARDINAL, 32, PropModeReplace,
		                 (guchar *)&long_desktop, 1);
	}

	XUngrabServer (xdisplay);
	XFlush (xdisplay);
}

/* Asks the window manager to turn off the "demands attention" state on the window.
 *
 * This only works for windows that are currently window managed; if the window
 * is unmapped (in the withdrawn state) it would be necessary to change _NET_WM_STATE
 * directly.
 */
void
terminal_util_x11_clear_demands_attention (GdkWindow *window)
{

	#if GTK_CHECK_VERSION(3, 0, 0)
	GdkScreen *screen = gdk_window_get_screen (window);
	#else
	GdkScreen *screen = gdk_drawable_get_screen (window);
	#endif
	GdkDisplay *display = gdk_screen_get_display (screen);
	XClientMessageEvent xclient;

	memset (&xclient, 0, sizeof (xclient));
	xclient.type = ClientMessage;
	xclient.serial = 0;
	xclient.send_event = True;
	xclient.window = GDK_WINDOW_XWINDOW (window);
	xclient.message_type = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_STATE");
	xclient.format = 32;

	xclient.data.l[0] = 0; /* _NET_WM_STATE_REMOVE */
	xclient.data.l[1] = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_STATE_DEMANDS_ATTENTION");
	xclient.data.l[2] = 0;
	xclient.data.l[3] = 0;
	xclient.data.l[4] = 0;

	XSendEvent (GDK_DISPLAY_XDISPLAY (display),
	            GDK_WINDOW_XWINDOW (gdk_screen_get_root_window (screen)),
	            False,
	            SubstructureRedirectMask | SubstructureNotifyMask,
	            (XEvent *)&xclient);
}

/* Check if a GdkWindow is minimized. This is a workaround for a
 * GDK bug/misfeature. gdk_window_get_state (window) has the
 * GDK_WINDOW_STATE_ICONIFIED bit for all unmapped windows,
 * even windows on another desktop.
 *
 * http://bugzilla.mate.org/show_bug.cgi?id=586664
 *
 * Code to read _NET_WM_STATE adapted from GDK
 */
gboolean
terminal_util_x11_window_is_minimized (GdkWindow *window)
{
	#if GTK_CHECK_VERSION(3, 0, 0)
	GdkDisplay *display = gdk_window_get_display (window);
	#else
	GdkDisplay *display = gdk_drawable_get_display (window);
	#endif

	Atom type;
	gint format;
	gulong nitems;
	gulong bytes_after;
	guchar *data;
	Atom *atoms = NULL;
	gulong i;

	gboolean minimized = FALSE;

	type = None;
	gdk_error_trap_push ();
	XGetWindowProperty (GDK_DISPLAY_XDISPLAY (display), GDK_WINDOW_XID (window),
	                    gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_STATE"),
	                    0, G_MAXLONG, False, XA_ATOM, &type, &format, &nitems,
	                    &bytes_after, &data);
	gdk_error_trap_pop ();

	if (type != None)
	{
		Atom hidden_atom = gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_STATE_HIDDEN");

		atoms = (Atom *)data;

		for (i = 0; i < nitems; i++)
		{
			if (atoms[i] == hidden_atom)
				minimized = TRUE;

			++i;
		}

		XFree (atoms);
	}

	return minimized;
}

#endif /* GDK_WINDOWING_X11 */