summary refs log blame commit diff stats
path: root/src/fe-gtk/fe-gtk.c
blob: 389ef899433113c4fbbe9073d2e4f4f17affc44f (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
255
256
257
258
259
260
26
/* X-Chat
 * Copyright (C) 1998-2005 Peter Zelezny.
 *
 * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>

#include <gdk/gdkkeysyms.h>

#include "../common/hexchat.h"
#include "../common/fe.h"
#include "../common/server.h"
#include "../common/hexchatc.h"
#include "../common/outbound.h"
#include "../common/inbound.h"
#include "../common/plugin.h"
#include "../common/modes.h"
#include "../common/url.h"
#include "../common/util.h"
#include "../common/text.h"
#include "../common/chanopt.h"
#include "../common/cfgfiles.h"

#include "fe-gtk.h"
#include "banlist.h"
#include "gtkutil.h"
#include "joind.h"
#include "palette.h"
#include "maingui.h"
#include "menu.h"
#include "fkeys.h"
#include "userlistgui.h"
#include "chanview.h"
#include "pixmaps.h"
#include "plugin-tray.h"
#include "xtext.h"
#include "sexy-spell-entry.h"

#define GUI_SPACING (3)
#define GUI_BORDER (0)

enum
{
	POS_INVALID = 0,
	POS_TOPLEFT = 1,
	POS_BOTTOMLEFT = 2,
	POS_TOPRIGHT = 3,
	POS_BOTTOMRIGHT = 4,
	POS_TOP = 5,	/* for tabs only */
	POS_BOTTOM = 6,
	POS_HIDDEN = 7
};

/* two different types of tabs */
#define TAG_IRC 0		/* server, channel, dialog */
#define TAG_UTIL 1	/* dcc, notify, chanlist */

static void mg_create_entry (session *sess, GtkWidget *box);
static void mg_create_search (session *sess, GtkWidget *box);
static void mg_link_irctab (session *sess, int focus);

static session_gui static_mg_gui;
static session_gui *mg_gui = NULL;	/* the shared irc tab */
static int ignore_chanmode = FALSE;
static const char chan_flags[] = { 'c', 'n', 't', 'i', 'm', 'l', 'k' };

static chan *active_tab = NULL;	/* active tab */
GtkWidget *parent_window = NULL;			/* the master window */

GtkStyle *input_style;

static PangoAttrList *away_list;
static PangoAttrList *newdata_list;
static PangoAttrList *nickseen_list;
static PangoAttrList *newmsg_list;
static PangoAttrList *plain_list = NULL;

static PangoAttrList *
mg_attr_list_create (GdkColor *col, int size)
{
	PangoAttribute *attr;
	PangoAttrList *list;

	list = pango_attr_list_new ();

	if (col)
	{
		attr = pango_attr_foreground_new (col->red, col->green, col->blue);
		attr->start_index = 0;
		attr->end_index = 0xffff;
		pango_attr_list_insert (list, attr);
	}

	if (size > 0)
	{
		attr = pango_attr_scale_new (size == 1 ? PANGO_SCALE_SMALL : PANGO_SCALE_X_SMALL);
		attr->start_index = 0;
		attr->end_index = 0xffff;
		pango_attr_list_insert (list, attr);
	}

	return list;
}

static void
mg_create_tab_colors (void)
{
	if (plain_list)
	{
		pango_attr_list_unref (plain_list);
		pango_attr_list_unref (newmsg_list);
		pango_attr_list_unref (newdata_list);
		pango_attr_list_unref (nickseen_list);
		pango_attr_list_unref (away_list);
	}

	plain_list = mg_attr_list_create (NULL, prefs.hex_gui_tab_small);
	newdata_list = mg_attr_list_create (&colors[COL_NEW_DATA], prefs.hex_gui_tab_small);
	nickseen_list = mg_attr_list_create (&colors[COL_HILIGHT], prefs.hex_gui_tab_small);
	newmsg_list = mg_attr_list_create (&colors[COL_NEW_MSG], prefs.hex_gui_tab_small);
	away_list = mg_attr_list_create (&colors[COL_AWAY], FALSE);
}

static void
set_window_urgency (GtkWidget *win, gboolean set)
{
	gtk_window_set_urgency_hint (GTK_WINDOW (win), set);
}

static void
flash_window (GtkWidget *win)
{
#ifdef HAVE_GTK_MAC
	gtkosx_application_attention_request (osx_app, INFO_REQUEST);
#endif
	set_window_urgency (win, TRUE);
}

static void
unflash_window (GtkWidget *win)
{
	set_window_urgency (win, FALSE);
}

/* flash the taskbar button */

void
fe_flash_window (session *sess)
{
	if (fe_gui_info (sess, 0) != 1)	/* only do it if not focused */
		flash_window (sess->gui->window);
}

/* set a tab plain, red, light-red, or blue */

void
fe_set_tab_color (struct session *sess, int col)
{
	struct session *server_sess = sess->server->server_session;
	if (sess->res->tab && sess->gui->is_tab && (col == 0 || sess != current_tab))
	{
		switch (col)
		{
		case 0:	/* no particular color (theme default) */
			sess->tab_state = TAB_STATE_NONE;
			chan_set_color (sess->res->tab, plain_list);
			break;
		case 1:	/* new data has been displayed (dark red) */
			sess->tab_state = TAB_STATE_NEW_DATA;
			chan_set_color (sess->res->tab, newdata_list);

			if (chan_is_collapsed (sess->res->tab)
				&& !((server_sess->tab_state & TAB_STATE_NEW_MSG)
					 || (server_sess->tab_state & TAB_STATE_NEW_HILIGHT))
				&& !(server_sess == current_tab))
			{
				server_sess->tab_state = TAB_STATE_NEW_DATA;
				chan_set_color (chan_get_parent (sess->res->tab), newdata_list);
			}

			break;
		case 2:	/* new message arrived in channel (light red) */
			sess->tab_state = TAB_STATE_NEW_MSG;
			chan_set_color (sess->res->tab, newmsg_list);

			if (chan_is_collapsed (sess->res->tab)
				&& !(server_sess->tab_state & TAB_STATE_NEW_HILIGHT)
				&& !(server_sess == current_tab))
			{
				server_sess->tab_state = TAB_STATE_NEW_MSG;
				chan_set_color (chan_get_parent (sess->res->tab), newmsg_list);
			}

			break;
		case 3:	/* your nick has been seen (blue) */
			sess->tab_state = TAB_STATE_NEW_HILIGHT;
			chan_set_color (sess->res->tab, nickseen_list);

			if (chan_is_collapsed (sess->res->tab) && !(server_sess == current_tab))
			{
				server_sess->tab_state = TAB_STATE_NEW_MSG;
				chan_set_color (chan_get_parent (sess->res->tab), nickseen_list);
			}

			break;
		}
		lastact_update (sess);
		sess->last_tab_state = sess->tab_state; /* For plugins handling future prints */
	}
}

static void
mg_set_myself_away (session_gui *gui, gboolean away)
{
	gtk_label_set_attributes (GTK_LABEL (gtk_bin_get_child (GTK_BIN (gui->nick_label))),
									  away ? away_list : NULL);
}

/* change the little icon to the left of your nickname */

void
mg_set_access_icon (session_gui *gui, GdkPixbuf *pix, gboolean away)
{
	if (gui->op_xpm)
	{
		if (pix == gtk_image_get_pixbuf (GTK_IMAGE (gui->op_xpm))) /* no change? */
		{
			mg_set_myself_away (gui, away);
			return;
		}

		gtk_widget_destroy (gui->op_xpm);
		gui->op_xpm = NULL;
	}

	if (pix && prefs.hex_gui_input_icon)
	{
		gui->op_xpm = gtk_image_new_from_pixbuf (pix);
		gtk_box_pack_start (GTK_BOX (gui->nick_box), gui->op_xpm, 0, 0, 0);
		gtk_widget_show (gui->op_xpm);
	}

	mg_set_myself_away (gui, away);
}

static gboolean
mg_inputbox_focus (GtkWidget *widget, GdkEventFocus *event, session_gui *gui)
{
	GSList *list;
	session *sess;

	if (gui->is_tab)
		return FALSE;

	list = sess_list;
	while (list)
	{
		sess = list->data;
		if (sess->gui == gui)
		{
			current_sess = sess;
			if (!sess->server->server_session)
				sess->server->server_session = sess;
			break;
		}
		list = list->next;
	}

	return FALSE;
}

void
mg_inputbox_cb (GtkWidget *igad, session_gui *gui)
{
	char *cmd;
	static int ignore = FALSE;
	GSList *list;
	session *sess = NULL;

	if (ignore)
		return;

	cmd = SPELL_ENTRY_GET_TEXT (igad);
	if (cmd[0] == 0)
		return;

	cmd = g_strdup (cmd);

	/* avoid recursive loop */
	ignore = TRUE;
	SPELL_ENTRY_SET_TEXT (igad, "");
	ignore = FALSE;

	/* where did this event come from? */
	if (gui->is_tab)
	{
		sess = current_tab;
	} else
	{
		list = sess_list;
		while (list)
		{
			sess = list->data;
			if (sess->gui == gui)
				breakpre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
/* X-Chat
 * Copyright (C) 1998 Peter Zelezny.
 *
 * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "fe-gtk.h"

#ifdef WIN32
#include <gdk/gdkwin32.h>
#include <windows.h>
#else
#include <unistd.h>
#endif

#include "../common/hexchat.h"
#include "../common/fe.h"
#include "../common/util.h"
#include "../common/text.h"
#include "../common/cfgfiles.h"
#include "../common/hexchatc.h"
#include "../common/plugin.h"
#include "../common/server.h"
#include "../common/url.h"
#include "gtkutil.h"
#include "maingui.h"
#include "pixmaps.h"
#include "chanlist.h"
#include "joind.h"
#include "xtext.h"
#include "palette.h"
#include "menu.h"
#include "notifygui.h"
#include "textgui.h"
#include "fkeys.h"
#include "plugin-tray.h"
#include "urlgrab.h"
#include "setup.h"

#ifdef USE_LIBCANBERRA
#include <canberra.h>
#endif

GdkPixmap *channelwin_pix;

#ifdef USE_LIBCANBERRA
static ca_context *ca_con;
#endif

#ifdef HAVE_GTK_MAC
GtkosxApplication *osx_app;
#endif

/* === command-line parameter parsing : requires glib 2.6 === */

static char *arg_cfgdir = NULL;
static gint arg_show_autoload = 0;
static gint arg_show_config = 0;
static gint arg_show_version = 0;
static gint arg_minimize = 0;

static const GOptionEntry gopt_entries[] = 
{
 {"no-auto",	'a', 0, G_OPTION_ARG_NONE,	&arg_dont_autoconnect, N_("Don't auto connect to servers"), NULL},
 {"cfgdir",	'd', 0, G_OPTION_ARG_STRING,	&arg_cfgdir, N_("Use a different config directory"), "PATH"},
 {"no-plugins",	'n', 0, G_OPTION_ARG_NONE,	&arg_skip_plugins, N_("Don't auto load any plugins"), NULL},
 {"plugindir",	'p', 0, G_OPTION_ARG_NONE,	&arg_show_autoload, N_("Show plugin/script auto-load directory"), NULL},
 {"configdir",	'u', 0, G_OPTION_ARG_NONE,	&arg_show_config, N_("Show user config directory"), NULL},
 {"url",	 0,  G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &arg_url, N_("Open an irc://server:port/channel?key URL"), "URL"},
 {"command",	'c', 0, G_OPTION_ARG_STRING,	&arg_command, N_("Execute command:"), "COMMAND"},
#ifdef USE_DBUS
 {"existing",	'e', 0, G_OPTION_ARG_NONE,	&arg_existing, N_("Open URL or execute command in an existing HexChat"), NULL},
#endif
 {"minimize",	 0,  0, G_OPTION_ARG_INT,	&arg_minimize, N_("Begin minimized. Level 0=Normal 1=Iconified 2=Tray"), N_("level")},
 {"version",	'v', 0, G_OPTION_ARG_NONE,	&arg_show_version, N_("Show version information"), NULL},
 {G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_STRING_ARRAY, &arg_urls, N_("Open an irc://server:port/channel?key URL"), "URL"},
 {NULL}
};

#ifdef WIN32
static void
create_msg_dialog (gchar *title, gchar *message)
{
	GtkWidget *dialog;

	dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, "%s", message);
	gtk_window_set_title (GTK_WINDOW (dialog), title);

/* On Win32 we automatically have the icon. If we try to load it explicitly, it will look ugly for some reason. */
#ifndef WIN32
	pixmaps_init ();
	gtk_window_set_icon (GTK_WINDOW (dialog), pix_hexchat);
#endif

	gtk_dialog_run (GTK_DIALOG (dialog));
	gtk_widget_destroy (dialog);
}
#endif

int
fe_args (int argc, char *argv[])
{
	GError *error = NULL;
	GOptionContext *context;
	char *buffer;

#ifdef ENABLE_NLS
	bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
	bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
	textdomain (GETTEXT_PACKAGE);
#endif

	context = g_option_context_new (NULL);
#ifdef WIN32
	g_option_context_set_help_enabled (context, FALSE);	/* disable stdout help as stdout is unavailable for subsystem:windows */
#endif
	g_option_context_add_main_entries (context, gopt_entries, GETTEXT_PACKAGE);
	g_option_context_add_group (context, gtk_get_option_group (FALSE));
	g_option_context_parse (context, &argc, &argv, &error);

#ifdef WIN32
	if (error)											/* workaround for argv not being available when using subsystem:windows */
	{
		if (error->message)								/* the error message contains argv so search for patterns in that */
		{
			if (strstr (error->message, "--help-all") != NULL)
			{
				buffer = g_strdup_printf (g_option_context_get_help (context, FALSE, NULL));
				gtk_init (&argc, &argv);
				create_msg_dialog ("Long Help", buffer);
				g_free (buffer);
				return 0;
			}
			else if (strstr (error->message, "--help") != NULL || strstr (error->message, "-?") != NULL)
			{
				buffer = g_strdup_printf (g_option_context_get_help (context, TRUE, NULL));
				gtk_init (&argc, &argv);
				create_msg_dialog ("Help", buffer);
				g_free (buffer);
				return 0;
			}
			else 
			{
				buffer = g_strdup_printf ("%s\n", error->message);
				gtk_init (&argc, &argv);
				create_msg_dialog ("Error", buffer);
				g_free (buffer);
				return 1;
			}
		}
	}
#else
	if (error)
	{
		if (error->message)
			printf ("%s\n", error->message);
		return 1;
	}
#endif

	g_option_context_free (context);

	if (arg_show_version)
	{
		buffer = g_strdup_printf ("%s %s", PACKAGE_NAME, PACKAGE_VERSION);
#ifdef WIN32
		gtk_init (&argc, &argv);
		create_msg_dialog ("Version Information", buffer);
#else
		puts (buffer);
#endif
		g_free (buffer);

		return 0;
	}

	if (arg_show_autoload)
	{
		buffer = g_strdup_printf ("%s%caddons%c", get_xdir(), G_DIR_SEPARATOR, G_DIR_SEPARATOR);
#ifdef WIN32
		gtk_init (&argc, &argv);
		create_msg_dialog ("Plugin/Script Auto-load Directory", buffer);
#else
		puts (buffer);
#endif
		g_free (buffer);

		return 0;
	}

	if (arg_show_config)
	{
		buffer = g_strdup_printf ("%s%c", get_xdir(), G_DIR_SEPARATOR);
#ifdef WIN32
		gtk_init (&argc, &argv);
		create_msg_dialog ("User Config Directory", buffer);
#else
		puts (buffer);
#endif
		g_free (buffer);

		return 0;
	}

#ifdef WIN32
	/* this is mainly for irc:// URL handling. When windows calls us from */
	/* I.E, it doesn't give an option of "Start in" directory, like short */
	/* cuts can. So we have to set the current dir manually, to the path  */
	/* of the exe. */
	{
		char *tmp = strdup (argv[0]);
		char *sl;

		sl = strrchr (tmp, G_DIR_SEPARATOR);
		if (sl)
		{
			*sl = 0;
			chdir (tmp);
		}
		free (tmp);
	}
#endif

	gtk_init (&argc, &argv);

#ifdef HAVE_GTK_MAC
	osx_app = g_object_new(GTKOSX_TYPE_APPLICATION, NULL);
#endif

	return -1;
}

const char cursor_color_rc[] =
	"style \"xc-ib-st\""
	"{"
		"GtkEntry::cursor-color=\"#%02x%02x%02x\""
	"}"
	"widget \"*.hexchat-inputbox\" style : application \"xc-ib-st\"";

GtkStyle *
create_input_style (GtkStyle *style)
{
	char buf[256];
	static int done_rc = FALSE;

	pango_font_description_free (style->font_desc);
	style->font_desc = pango_font_description_from_string (prefs.hex_text_font);

	/* fall back */
	if (pango_font_description_get_size (style->font_desc) == 0)
	{
		snprintf (buf, sizeof (buf), _("Failed to open font:\n\n%s"), prefs.hex_text_font);
		fe_message (buf, FE_MSG_ERROR);
		pango_font_description_free (style->font_desc);
		style->font_desc = pango_font_description_from_string ("sans 11");
	}

	if (prefs.hex_gui_input_style && !done_rc)
	{
		done_rc = TRUE;
		sprintf (buf, cursor_color_rc, (colors[COL_FG].red >> 8),
			(colors[COL_FG].green >> 8), (colors[COL_FG].blue >> 8));
		gtk_rc_parse_string (buf);
	}

	style->bg[GTK_STATE_NORMAL] = colors[COL_FG];
	style->base[GTK_STATE_NORMAL] = colors[COL_BG];
	style->text[GTK_STATE_NORMAL] = colors[COL_FG];

	return style;
}

void
fe_init (void)
{
	palette_load ();
	key_init ();
	pixmaps_init ();

#ifdef HAVE_GTK_MAC
	gtkosx_application_set_dock_icon_pixbuf (osx_app, pix_hexchat);
#endif
	channelwin_pix = pixmap_load_from_file (prefs.hex_text_background);
	input_style = create_input_style (gtk_style_new ());
}

void
fe_main (void)
{
#ifdef HAVE_GTK_MAC
	gtkosx_application_ready(osx_app);
#endif

	gtk_main ();

	/* sleep for 2 seconds so any QUIT messages are not lost. The  */
	/* GUI is closed at this point, so the user doesn't even know! */
	if (prefs.wait_on_exit)
		sleep (2);
}

void
fe_cleanup (void)
{
	/* it's saved when pressing OK in setup.c */
	/*palette_save ();*/
}

void
fe_exit (void)
{
	gtk_main_quit ();
}

int
fe_timeout_add (int interval, void *callback, void *userdata)
{
	return g_timeout_add (interval, (GSourceFunc) callback, userdata);
}

void
fe_timeout_remove (int tag)
{
	g_source_remove (tag);
}

#ifdef WIN32

static void
log_handler (const gchar   *log_domain,
		       GLogLevelFlags log_level,
		       const gchar   *message,
		       gpointer	      unused_data)
{
	session *sess;

	/* if (getenv ("HEXCHAT_WARNING_IGNORE")) this gets ignored sometimes, so simply just disable all warnings */
		return;

	sess = find_dialog (serv_list->data, "(warnings)");
	if (!sess)
		sess = new_ircwindow (serv_list->data, "(warnings)", SESS_DIALOG, 0);

	PrintTextf (sess, "%s\t%s\n", log_domain, message);
	if (getenv ("HEXCHAT_WARNING_ABORT"))
		abort ();
}

#endif

/* install tray stuff */

static int
fe_idle (gpointer data)
{
	session *sess = sess_list->data;

	plugin_add (sess, NULL, NULL, tray_plugin_init, tray_plugin_deinit, NULL, FALSE);

	if (arg_minimize == 1)
		gtk_window_iconify (GTK_WINDOW (sess->gui->window));
	else if (arg_minimize == 2)
		tray_toggle_visibility (FALSE);

	return 0;
}

void
fe_new_window (session *sess, int focus)
{
	int tab = FALSE;

	if (sess->type == SESS_DIALOG)
	{
		if (prefs.hex_gui_tab_dialogs)
			tab = TRUE;
	} else
	{
		if (prefs.hex_gui_tab_chans)
			tab = TRUE;
	}

	mg_changui_new (sess, NULL, tab, focus);

#ifdef WIN32
	g_log_set_handler ("GLib", G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING, (GLogFunc)log_handler, 0);
	g_log_set_handler ("GLib-GObject", G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING, (GLogFunc)log_handler, 0);
	g_log_set_handler ("Gdk", G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING, (GLogFunc)log_handler, 0);
	g_log_set_handler ("Gtk", G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING, (GLogFunc)log_handler, 0);
#endif

	if (!sess_list->next)
		g_idle_add (fe_idle, NULL);
}

void
fe_new_server (struct server *serv)
{
	serv->gui = malloc (sizeof (struct server_gui));
	memset (serv->gui, 0, sizeof (struct server_gui));
}

void
fe_message (char *msg, int flags)
{
	GtkWidget *dialog;
	int type = GTK_MESSAGE_WARNING;

	if (flags & FE_MSG_ERROR)
		type = GTK_MESSAGE_ERROR;
	if (flags & FE_MSG_INFO)
		type = GTK_MESSAGE_INFO;

	dialog = gtk_message_dialog_new (GTK_WINDOW (parent_window), 0, type,
												GTK_BUTTONS_OK, "%s", msg);
	if (flags & FE_MSG_MARKUP)
		gtk_message_dialog_set_markup (GTK_MESSAGE_DIALOG (dialog), msg);
	g_signal_connect (G_OBJECT (dialog), "response",
							G_CALLBACK (gtk_widget_destroy), 0);
	gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
	gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
	gtk_widget_show (dialog);

	if (flags & FE_MSG_WAIT)
		gtk_dialog_run (GTK_DIALOG (dialog));
}

void
fe_idle_add (void *func, void *data)
{
	g_idle_add (func, data);
}

void
fe_input_remove (int tag)
{
	g_source_remove (tag);
}

int
fe_input_add (int sok, int flags, void *func, void *data)
{
	int tag, type = 0;
	GIOChannel *channel;

#ifdef WIN32
	if (flags & FIA_FD)
		channel = g_io_channel_win32_new_fd (sok);
	else
		channel = g_io_channel_win32_new_socket (sok);
#else
	channel = g_io_channel_unix_new (sok);
#endif

	if (flags & FIA_READ)
		type |= G_IO_IN | G_IO_HUP | G_IO_ERR;
	if (flags & FIA_WRITE)
		type |= G_IO_OUT | G_IO_ERR;
	if (flags & FIA_EX)
		type |= G_IO_PRI;

	tag = g_io_add_watch (channel, type, (GIOFunc) func, data);
	g_io_channel_unref (channel);

	return tag;
}

void
fe_set_topic (session *sess, char *topic, char *stripped_topic)
{
	if (!sess->gui->is_tab || sess == current_tab)
	{
		if (prefs.hex_text_stripcolor_topic)
		{
			gtk_entry_set_text (GTK_ENTRY (sess->gui->topic_entry), stripped_topic);
		}
		else
		{
			gtk_entry_set_text (GTK_ENTRY (sess->gui->topic_entry), topic);
		}
		mg_set_topic_tip (sess);
	}
	else
	{
		if (sess->res->topic_text)
		{
			free (sess->res->topic_text);
		}

		if (prefs.hex_text_stripcolor_topic)
		{
			sess->res->topic_text = strdup (stripped_topic);
		}
		else
		{
			sess->res->topic_text = strdup (topic);
		}
	}
}

void
fe_set_hilight (struct session *sess)
{
	if (sess->gui->is_tab)
		fe_set_tab_color (sess, 3);	/* set tab to blue */

	if (prefs.hex_input_flash_hilight && (!prefs.hex_away_omit_alerts || !sess->server->is_away))
		fe_flash_window (sess); /* taskbar flash */
}

static void
fe_update_mode_entry (session *sess, GtkWidget *entry, char **text, char *new_text)
{
	if (!sess->gui->is_tab || sess == current_tab)
	{
		if (sess->gui->flag_wid[0])	/* channel mode buttons enabled? */
			gtk_entry_set_text (GTK_ENTRY (entry), new_text);
	} else
	{
		if (sess->gui->is_tab)
		{
			if (*text)
				free (*text);
			*text = strdup (new_text);
		}
	}
}

void
fe_update_channel_key (struct session *sess)
{
	fe_update_mode_entry (sess, sess->gui->key_entry,
								 &sess->res->key_text, sess->channelkey);
	fe_set_title (sess);
}

void
fe_update_channel_limit (struct session *sess)
{
	char tmp[16];

	sprintf (tmp, "%d", sess->limit);
	fe_update_mode_entry (sess, sess->gui->limit_entry,
								 &sess->res->limit_text, tmp);
	fe_set_title (sess);
}

int
fe_is_chanwindow (struct server *serv)
{
	if (!serv->gui->chanlist_window)
		return 0;
	return 1;
}

void
fe_notify_update (char *name)
{
	if (!name)
		notify_gui_update ();
}

void
fe_text_clear (struct session *sess, int lines)
{
	gtk_xtext_clear (sess->res->buffer, lines);
}

void
fe_close_window (struct session *sess)
{
	if (sess->gui->is_tab)
		mg_tab_close (sess);
	else
		gtk_widget_destroy (sess->gui->window);
}

void
fe_progressbar_start (session *sess)
{
	if (!sess->gui->is_tab || current_tab == sess)
	/* if it's the focused tab, create it for real! */
		mg_progressbar_create (sess->gui);
	else
	/* otherwise just remember to create on when it gets focused */
		sess->res->c_graph = TRUE;
}

void
fe_progressbar_end (server *serv)
{
	GSList *list = sess_list;
	session *sess;

	while (list)				  /* check all windows that use this server and  *
									   * remove the connecting graph, if it has one. */
	{
		sess = list->data;
		if (sess->server == serv)
		{
			if (sess->gui->bar)
				mg_progressbar_destroy (sess->gui);
			sess->res->c_graph = FALSE;
		}
		list = list->next;
	}
}

void
fe_print_text (struct session *sess, char *text, time_t stamp,
			   gboolean no_activity)
{
	PrintTextRaw (sess->res->buffer, (unsigned char *)text, prefs.hex_text_indent, stamp);

	if (!no_activity && !sess->new_data && sess != current_tab &&
		sess->gui->is_tab && !sess->nick_said)
	{
		sess->new_data = TRUE;
		lastact_update (sess);
		if (sess->msg_said)
			fe_set_tab_color (sess, 2);
		else
			fe_set_tab_color (sess, 1);
	}
}

void
fe_beep (session *sess)
{
#ifdef WIN32
	if (!PlaySound ("Notification.IM", NULL, SND_ALIAS|SND_ASYNC))
	{
		/* This is really just a fallback attempt, may or may not work on new Windows releases, especially on x64.
		 * You should set up the "Instant Message Notification" system sound instead, supported on Vista and up.
		 */
		Beep (1000, 50);
	}
#else
#ifdef USE_LIBCANBERRA
	if (ca_con == NULL)
	{
		ca_context_create (&ca_con);
		ca_context_change_props (ca_con,
										CA_PROP_APPLICATION_ID, "hexchat",
										CA_PROP_APPLICATION_NAME, DISPLAY_NAME,
										CA_PROP_APPLICATION_ICON_NAME, "hexchat", NULL);
	}

	if (ca_context_play (ca_con, 0, CA_PROP_EVENT_ID, "message-new-instant", NULL) != 0)
#endif
	gdk_beep ();
#endif
}

void
fe_lastlog (session *sess, session *lastlog_sess, char *sstr, gtk_xtext_search_flags flags)
{
	GError *err = NULL;
	xtext_buffer *buf, *lbuf;

	buf = sess->res->buffer;

	if (gtk_xtext_is_empty (buf))
	{
		PrintText (lastlog_sess, _("Search buffer is empty.\n"));
		return;
	}

	lbuf = lastlog_sess->res->buffer;
	if (flags & regexp)
	{
		GRegexCompileFlags gcf = (flags & case_match)? 0: G_REGEX_CASELESS;

		lbuf->search_re = g_regex_new (sstr, gcf, 0, &err);
		if (err)
		{
			PrintText (lastlog_sess, _(err->message));
			g_error_free (err);
			return;
		}
	}
	else
	{
		if (flags & case_match)
		{
			lbuf->search_nee = g_strdup (sstr);
		}
		else
		{
			lbuf->search_nee = g_utf8_casefold (sstr, strlen (sstr));
		}
		lbuf->search_lnee = strlen (lbuf->search_nee);
	}
	lbuf->search_flags = flags;
	lbuf->search_text = strdup (sstr);
	gtk_xtext_lastlog (lbuf, buf);
}

void
fe_set_lag (server *serv, int lag)
{
	GSList *list = sess_list;
	session *sess;
	gdouble per;
	char lagtext[64];
	char lagtip[128];
	unsigned long nowtim;

	if (lag == -1)
	{
		if (!serv->lag_sent)
			return;
		nowtim = make_ping_time ();
		lag = (nowtim - serv->lag_sent) / 100000;
	}

	/* if there is no pong for >30s report the lag as +30s */
	if (lag > 300 && serv->lag_sent)
		lag=300;

	per = (double)((double)lag / (double)10);
	if (per > 1.0)
		per = 1.0;

	snprintf (lagtext, sizeof (lagtext) - 1, "%s%d.%ds",
				 serv->lag_sent ? "+" : "", lag / 10, lag % 10);
	snprintf (lagtip, sizeof (lagtip) - 1, "Lag: %s%d.%d seconds",
				 serv->lag_sent ? "+" : "", lag / 10, lag % 10);

	while (list)
	{
		sess = list->data;
		if (sess->server == serv)
		{
			if (sess->res->lag_tip)
				free (sess->res->lag_tip);
			sess->res->lag_tip = strdup (lagtip);

			if (!sess->gui->is_tab || current_tab == sess)
			{
				if (sess->gui->lagometer)
				{
					gtk_progress_bar_set_fraction ((GtkProgressBar *) sess->gui->lagometer, per);
					gtk_widget_set_tooltip_text (gtk_widget_get_parent (sess->gui->lagometer), lagtip);
				}
				if (sess->gui->laginfo)
					gtk_label_set_text ((GtkLabel *) sess->gui->laginfo, lagtext);
			} else
			{
				sess->res->lag_value = per;
				if (sess->res->lag_text)
					free (sess->res->lag_text);
				sess->res->lag_text = strdup (lagtext);
			}
		}
		list = list->next;
	}
}

void
fe_set_throttle (server *serv)
{
	GSList *list = sess_list;
	struct session *sess;
	float per;
	char tbuf[96];
	char tip[160];

	per = (float) serv->sendq_len / 1024.0;
	if (per > 1.0)
		per = 1.0;

	while (list)
	{
		sess = list->data;
		if (sess->server == serv)
		{
			snprintf (tbuf, sizeof (tbuf) - 1, _("%d bytes"), serv->sendq_len);
			snprintf (tip, sizeof (tip) - 1, _("Network send queue: %d bytes"), serv->sendq_len);

			if (sess->res->queue_tip)
				free (sess->res->queue_tip);
			sess->res->queue_tip = strdup (tip);

			if (!sess->gui->is_tab || current_tab == sess)
			{
				if (sess->gui->throttlemeter)
				{
					gtk_progress_bar_set_fraction ((GtkProgressBar *) sess->gui->throttlemeter, per);
					gtk_widget_set_tooltip_text (gtk_widget_get_parent (sess->gui->throttlemeter), tip);
				}
				if (sess->gui->throttleinfo)
					gtk_label_set_text ((GtkLabel *) sess->gui->throttleinfo, tbuf);
			} else
			{
				sess->res->queue_value = per;
				if (sess->res->queue_text)
					free (sess->res->queue_text);
				sess->res->queue_text = strdup (tbuf);
			}
		}
		list = list->next;
	}
}

void
fe_ctrl_gui (session *sess, fe_gui_action action, int arg)
{
	switch (action)
	{
	case FE_GUI_HIDE:
		gtk_widget_hide (sess->gui->window); break;
	case FE_GUI_SHOW:
		gtk_widget_show (sess->gui->window);
		gtk_window_present (GTK_WINDOW (sess->gui->window));
		break;
	case FE_GUI_FOCUS:
		mg_bring_tofront_sess (sess); break;
	case FE_GUI_FLASH:
		fe_flash_window (sess); break;
	case FE_GUI_COLOR:
		fe_set_tab_color (sess, arg); break;
	case FE_GUI_ICONIFY:
		gtk_window_iconify (GTK_WINDOW (sess->gui->window)); break;
	case FE_GUI_MENU:
		menu_bar_toggle ();	/* toggle menubar on/off */
		break;
	case FE_GUI_ATTACH:
		mg_detach (sess, arg);	/* arg: 0=toggle 1=detach 2=attach */
		break;
	case FE_GUI_APPLY:
		setup_apply_real (TRUE, TRUE, TRUE);
	}
}

static void
dcc_saveas_cb (struct DCC *dcc, char *file)
{
	if (is_dcc (dcc))
	{
		if (dcc->dccstat == STAT_QUEUED)
		{
			if (file)
				dcc_get_with_destfile (dcc, file);
			else if (dcc->resume_sent == 0)
				dcc_abort (dcc->serv->front_session, dcc);
		}
	}
}

void
fe_confirm (const char *message, void (*yesproc)(void *), void (*noproc)(void *), void *ud)
{
	/* warning, assuming fe_confirm is used by DCC only! */
	struct DCC *dcc = ud;
	char *filepath;

	if (dcc->file)
	{
		filepath = g_build_filename (prefs.hex_dcc_dir, dcc->file, NULL);
		gtkutil_file_req (message, dcc_saveas_cb, ud, filepath, NULL,
								FRF_WRITE|FRF_NOASKOVERWRITE|FRF_FILTERISINITIAL);
		g_free (filepath);
	}
}

int
fe_gui_info (session *sess, int info_type)
{
	switch (info_type)
	{
	case 0:	/* window status */
		if (!gtk_widget_get_visible (GTK_WIDGET (sess->gui->window)))
		{
			return 2;	/* hidden (iconified or systray) */
		}

		if (gtk_window_is_active (GTK_WINDOW (sess->gui->window)))
		{
			return 1;	/* active/focused */
		}

		return 0;		/* normal (no keyboard focus or behind a window) */
	}

	return -1;
}

void *
fe_gui_info_ptr (session *sess, int info_type)
{
	switch (info_type)
	{
	case 0:	/* native window pointer (for plugins) */
#ifdef WIN32
		return gdk_win32_window_get_impl_hwnd (gtk_widget_get_window (sess->gui->window));
#else
		return sess->gui->window;
#endif
		break;

	case 1:	/* GtkWindow * (for plugins) */
		return sess->gui->window;
	}
	return NULL;
}

char *
fe_get_inputbox_contents (session *sess)
{
	/* not the current tab */
	if (sess->res->input_text)
		return sess->res->input_text;

	/* current focused tab */
	return SPELL_ENTRY_GET_TEXT (sess->gui->input_box);
}

int
fe_get_inputbox_cursor (session *sess)
{
	/* not the current tab (we don't remember the cursor pos) */
	if (sess->res->input_text)
		return 0;

	/* current focused tab */
	return SPELL_ENTRY_GET_POS (sess->gui->input_box);
}

void
fe_set_inputbox_cursor (session *sess, int delta, int pos)
{
	if (!sess->gui->is_tab || sess == current_tab)
	{
		if (delta)
			pos += SPELL_ENTRY_GET_POS (sess->gui->input_box);
		SPELL_ENTRY_SET_POS (sess->gui->input_box, pos);
	} else
	{
		/* we don't support changing non-front tabs yet */
	}
}

void
fe_set_inputbox_contents (session *sess, char *text)
{
	if (!sess->gui->is_tab || sess == current_tab)
	{
		SPELL_ENTRY_SET_TEXT (sess->gui->input_box, text);
	} else
	{
		if (sess->res->input_text)
			free (sess->res->input_text);
		sess->res->input_text = strdup (text);
	}
}

static void
fe_open_url_inner (const char *url)
{
#ifdef WIN32
	ShellExecute (0, "open", url, NULL, NULL, SW_SHOWNORMAL);
#elif defined __APPLE__
	/* on Mac you can just 'open http://foo.bar/' */
	gchar open[512];
	g_snprintf (open, sizeof(open), "%s %s", g_find_program_in_path ("open"), url, NULL);
	hexchat_exec (open);
#else
	gtk_show_uri (NULL, url, GDK_CURRENT_TIME, NULL);
#endif
}

static void
fe_open_url_locale (const char *url)
{
	int url_type = url_check_word (url);
	char *uri;

	/* gvfs likes file:// */
	if (url_type == WORD_PATH)
	{
#ifndef WIN32
		uri = g_strconcat ("file://", url, NULL);
		fe_open_url_inner (uri);
		g_free (uri);
#else
		fe_open_url_inner (url);
#endif
	}
	/* IPv6 addr. Add http:// */
	else if (url_type == WORD_HOST6)
	{
		/* IPv6 addrs in urls should be enclosed in [ ] */
		if (*url != '[')
			uri = g_strdup_printf ("http://[%s]", url);
		else
			uri = g_strdup_printf ("http://%s", url);

		fe_open_url_inner (uri);
		g_free (uri);
	}
	/* the http:// part's missing, prepend it, otherwise it won't always work */
	else if (strchr (url, ':') == NULL)
	{
		url = g_strdup_printf ("http://%s", url);
		fe_open_url_inner (url);
		g_free ((char *)url);
	}
	/* we have a sane URL, send it to the browser untouched */
	else
	{
		fe_open_url_inner (url);
	}
}

void
fe_open_url (const char *url)
{
	char *loc;

	if (prefs.utf8_locale)
	{
		fe_open_url_locale (url);
		return;
	}

	/* the OS expects it in "locale" encoding. This makes it work on
	   unix systems that use ISO-8859-x and Win32. */
	loc = g_locale_from_utf8 (url, -1, 0, 0, 0);
	if (loc)
	{
		fe_open_url_locale (loc);
		g_free (loc);
	}
}

void
fe_server_event (server *serv, int type, int arg)
{
	GSList *list = sess_list;
	session *sess;

	while (list)
	{
		sess = list->data;
		if (sess->server == serv && (current_tab == sess || !sess->gui->is_tab))
		{
			session_gui *gui = sess->gui;

			switch (type)
			{
			case FE_SE_CONNECTING:	/* connecting in progress */
			case FE_SE_RECONDELAY:	/* reconnect delay begun */
				/* enable Disconnect item */
				gtk_widget_set_sensitive (gui->menu_item[MENU_ID_DISCONNECT], 1);
				break;

			case FE_SE_CONNECT:
				/* enable Disconnect and Away menu items */
				gtk_widget_set_sensitive (gui->menu_item[MENU_ID_AWAY], 1);
				gtk_widget_set_sensitive (gui->menu_item[MENU_ID_DISCONNECT], 1);
				break;

			case FE_SE_LOGGEDIN:	/* end of MOTD */
				gtk_widget_set_sensitive (gui->menu_item[MENU_ID_JOIN], 1);
				/* if number of auto-join channels is zero, open joind */
				if (arg == 0)
					joind_open (serv);
				break;

			case FE_SE_DISCONNECT:
				/* disable Disconnect and Away menu items */
				gtk_widget_set_sensitive (gui->menu_item[MENU_ID_AWAY], 0);
				gtk_widget_set_sensitive (gui->menu_item[MENU_ID_DISCONNECT], 0);
				gtk_widget_set_sensitive (gui->menu_item[MENU_ID_JOIN], 0);
				/* close the join-dialog, if one exists */
				joind_close (serv);
			}
		}
		list = list->next;
	}
}

void
fe_get_file (const char *title, char *initial,
				 void (*callback) (void *userdata, char *file), void *userdata,
				 int flags)
				
{
	/* OK: Call callback once per file, then once more with file=NULL. */
	/* CANCEL: Call callback once with file=NULL. */
	gtkutil_file_req (title, callback, userdata, initial, NULL, flags | FRF_FILTERISINITIAL);
}

void
fe_open_chan_list (server *serv, char *filter, int do_refresh)
{
	chanlist_opengui (serv, do_refresh);
}
n>item))), text); g_signal_connect (G_OBJECT (item), "activate", G_CALLBACK (mg_color_insert), GINT_TO_POINTER (arg)); gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); gtk_widget_show (item); } GtkWidget * mg_submenu (GtkWidget *menu, char *text) { GtkWidget *submenu, *item; item = gtk_menu_item_new_with_mnemonic (text); gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); gtk_widget_show (item); submenu = gtk_menu_new (); gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu); gtk_widget_show (submenu); return submenu; } static void mg_create_color_menu (GtkWidget *menu, session *sess) { GtkWidget *submenu; GtkWidget *subsubmenu; char buf[256]; int i; submenu = mg_submenu (menu, _("Insert Attribute or Color Code")); mg_markup_item (submenu, _("<b>Bold</b>"), 100); mg_markup_item (submenu, _("<u>Underline</u>"), 101); mg_markup_item (submenu, _("<i>Italic</i>"), 102); mg_markup_item (submenu, _("Normal"), 103); subsubmenu = mg_submenu (submenu, _("Colors 0-7")); for (i = 0; i < 8; i++) { sprintf (buf, "<tt><sup>%02d</sup> <span background=\"#%02x%02x%02x\">" " </span></tt>", i, colors[i].red >> 8, colors[i].green >> 8, colors[i].blue >> 8); mg_markup_item (subsubmenu, buf, i); } subsubmenu = mg_submenu (submenu, _("Colors 8-15")); for (i = 8; i < 16; i++) { sprintf (buf, "<tt><sup>%02d</sup> <span background=\"#%02x%02x%02x\">" " </span></tt>", i, colors[i].red >> 8, colors[i].green >> 8, colors[i].blue >> 8); mg_markup_item (subsubmenu, buf, i); } } static void mg_set_guint8 (GtkCheckMenuItem *item, guint8 *setting) { session *sess = current_sess; guint8 logging = sess->text_logging; *setting = SET_OFF; if (gtk_check_menu_item_get_active (item)) *setting = SET_ON; /* has the logging setting changed? */ if (logging != sess->text_logging) log_open_or_close (sess); chanopt_save (sess); chanopt_save_all (FALSE); } static void mg_perchan_menu_item (char *label, GtkWidget *menu, guint8 *setting, guint global) { guint8 initial_value = *setting; /* if it's using global value, use that as initial state */ if (initial_value == SET_DEFAULT) initial_value = global; menu_toggle_item (label, menu, mg_set_guint8, setting, initial_value); } static void mg_create_perchannelmenu (session *sess, GtkWidget *menu) { GtkWidget *submenu; submenu = menu_quick_sub (_("_Settings"), menu, NULL, XCMENU_MNEMONIC, -1); mg_perchan_menu_item (_("_Log to Disk"), submenu, &sess->text_logging, prefs.hex_irc_logging); mg_perchan_menu_item (_("_Reload Scrollback"), submenu, &sess->text_scrollback, prefs.hex_text_replay); if (sess->type == SESS_CHANNEL) { mg_perchan_menu_item (_("Strip _Colors"), submenu, &sess->text_strip, prefs.hex_text_stripcolor_msg); mg_perchan_menu_item (_("_Hide Join/Part Messages"), submenu, &sess->text_hidejoinpart, prefs.hex_irc_conf_mode); } } static void mg_create_alertmenu (session *sess, GtkWidget *menu) { GtkWidget *submenu; submenu = menu_quick_sub (_("_Extra Alerts"), menu, NULL, XCMENU_MNEMONIC, -1); mg_perchan_menu_item (_("Beep on _Message"), submenu, &sess->alert_beep, prefs.hex_input_beep_chans); mg_perchan_menu_item (_("Blink Tray _Icon"), submenu, &sess->alert_tray, prefs.hex_input_tray_chans); mg_perchan_menu_item (_("Blink Task _Bar"), submenu, &sess->alert_taskbar, prefs.hex_input_flash_chans); } static void mg_create_tabmenu (session *sess, GdkEventButton *event, chan *ch) { GtkWidget *menu, *item; char buf[256]; menu = gtk_menu_new (); if (sess) { char *name = g_markup_escape_text (sess->channel[0] ? sess->channel : _("<none>"), -1); g_snprintf (buf, sizeof (buf), "<span foreground=\"#3344cc\"><b>%s</b></span>", name); g_free (name); item = gtk_menu_item_new_with_label (""); gtk_label_set_markup (GTK_LABEL (gtk_bin_get_child (GTK_BIN (item))), buf); gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); gtk_widget_show (item); /* separator */ menu_quick_item (0, 0, menu, XCMENU_SHADED, 0, 0); /* per-channel alerts */ mg_create_alertmenu (sess, menu); /* per-channel settings */ mg_create_perchannelmenu (sess, menu); /* separator */ menu_quick_item (0, 0, menu, XCMENU_SHADED, 0, 0); if (sess->type == SESS_CHANNEL) menu_addfavoritemenu (sess->server, menu, sess->channel, TRUE); else if (sess->type == SESS_SERVER) menu_addconnectmenu (sess->server, menu); } mg_create_icon_item (_("_Detach"), GTK_STOCK_REDO, menu, mg_detach_tab_cb, ch); mg_create_icon_item (_("_Close"), GTK_STOCK_CLOSE, menu, mg_destroy_tab_cb, ch); if (sess && tabmenu_list) menu_create (menu, tabmenu_list, sess->channel, FALSE); if (sess) menu_add_plugin_items (menu, "\x4$TAB", sess->channel); if (event->window) gtk_menu_set_screen (GTK_MENU (menu), gdk_window_get_screen (event->window)); g_object_ref (menu); g_object_ref_sink (menu); g_object_unref (menu); g_signal_connect (G_OBJECT (menu), "selection-done", G_CALLBACK (mg_menu_destroy), NULL); gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, 0, event->time); } static gboolean mg_tab_contextmenu_cb (chanview *cv, chan *ch, int tag, gpointer ud, GdkEventButton *event) { /* middle-click or shift-click to close a tab */ if (((prefs.hex_gui_tab_middleclose && event->button == 2) || (event->button == 1 && event->state & STATE_SHIFT)) && event->type == GDK_BUTTON_PRESS) { mg_xbutton_cb (cv, ch, tag, ud); return TRUE; } if (event->button != 3) return FALSE; if (tag == TAG_IRC) mg_create_tabmenu (ud, event, ch); else mg_create_tabmenu (NULL, event, ch); return TRUE; } void mg_dnd_drop_file (session *sess, char *target, char *uri) { char *p, *data, *next, *fname; p = data = g_strdup (uri); while (*p) { next = strchr (p, '\r'); if (g_ascii_strncasecmp ("file:", p, 5) == 0) { if (next) *next = 0; fname = g_filename_from_uri (p, NULL, NULL); if (fname) { /* dcc_send() expects utf-8 */ p = g_filename_from_utf8 (fname, -1, 0, 0, 0); if (p) { dcc_send (sess, target, p, prefs.hex_dcc_max_send_cps, 0); g_free (p); } g_free (fname); } } if (!next) break; p = next + 1; if (*p == '\n') p++; } g_free (data); } static void mg_dialog_dnd_drop (GtkWidget * widget, GdkDragContext * context, gint x, gint y, GtkSelectionData * selection_data, guint info, guint32 time, gpointer ud) { if (current_sess->type == SESS_DIALOG) /* sess->channel is really the nickname of dialogs */ mg_dnd_drop_file (current_sess, current_sess->channel, (char *)gtk_selection_data_get_data (selection_data)); } /* add a tabbed channel */ static void mg_add_chan (session *sess) { GdkPixbuf *icon; char *name = _("<none>"); if (sess->channel[0]) name = sess->channel; switch (sess->type) { case SESS_CHANNEL: icon = pix_tree_channel; break; case SESS_SERVER: icon = pix_tree_server; break; default: icon = pix_tree_dialog; } sess->res->tab = chanview_add (sess->gui->chanview, name, sess->server, sess, sess->type == SESS_SERVER ? FALSE : TRUE, TAG_IRC, icon); if (plain_list == NULL) mg_create_tab_colors (); chan_set_color (sess->res->tab, plain_list); if (sess->res->buffer == NULL) { sess->res->buffer = gtk_xtext_buffer_new (GTK_XTEXT (sess->gui->xtext)); gtk_xtext_set_time_stamp (sess->res->buffer, prefs.hex_stamp_text); sess->res->user_model = userlist_create_model (sess); } } static void mg_userlist_button (GtkWidget * box, char *label, char *cmd, int a, int b, int c, int d) { GtkWidget *wid = gtk_button_new_with_label (label); g_signal_connect (G_OBJECT (wid), "clicked", G_CALLBACK (userlist_button_cb), cmd); gtk_table_attach_defaults (GTK_TABLE (box), wid, a, b, c, d); show_and_unfocus (wid); } static GtkWidget * mg_create_userlistbuttons (GtkWidget *box) { struct popup *pop; GSList *list = button_list; int a = 0, b = 0; GtkWidget *tab; tab = gtk_table_new (5, 2, FALSE); gtk_box_pack_end (GTK_BOX (box), tab, FALSE, FALSE, 0); while (list) { pop = list->data; if (pop->cmd[0]) { mg_userlist_button (tab, pop->name, pop->cmd, a, a + 1, b, b + 1); a++; if (a == 2) { a = 0; b++; } } list = list->next; } return tab; } static void mg_topic_cb (GtkWidget *entry, gpointer userdata) { session *sess = current_sess; char *text; if (sess->channel[0] && sess->server->connected && sess->type == SESS_CHANNEL) { text = (char *)gtk_entry_get_text (GTK_ENTRY (entry)); if (text[0] == 0) text = NULL; sess->server->p_topic (sess->server, sess->channel, text); } else gtk_entry_set_text (GTK_ENTRY (entry), ""); /* restore focus to the input widget, where the next input will most likely be */ gtk_widget_grab_focus (sess->gui->input_box); } static void mg_tabwindow_kill_cb (GtkWidget *win, gpointer userdata) { GSList *list, *next; session *sess; /* puts("enter mg_tabwindow_kill_cb");*/ hexchat_is_quitting = TRUE; /* see if there's any non-tab windows left */ list = sess_list; while (list) { sess = list->data; next = list->next; if (!sess->gui->is_tab) { hexchat_is_quitting = FALSE; /* puts("-> will not exit, some toplevel windows left");*/ } else { mg_ircdestroy (sess); } list = next; } current_tab = NULL; active_tab = NULL; mg_gui = NULL; parent_window = NULL; } static GtkWidget * mg_changui_destroy (session *sess) { GtkWidget *ret = NULL; if (sess->gui->is_tab) { /* avoid calling the "destroy" callback */ g_signal_handlers_disconnect_by_func (G_OBJECT (sess->gui->window), mg_tabwindow_kill_cb, 0); /* remove the tab from the chanview */ if (!mg_chan_remove (sess->res->tab)) /* if the window still exists, restore the signal handler */ g_signal_connect (G_OBJECT (sess->gui->window), "destroy", G_CALLBACK (mg_tabwindow_kill_cb), 0); } else { /* avoid calling the "destroy" callback */ g_signal_handlers_disconnect_by_func (G_OBJECT (sess->gui->window), mg_topdestroy_cb, sess); /*gtk_widget_destroy (sess->gui->window);*/ /* don't destroy until the new one is created. Not sure why, but */ /* it fixes: Gdk-CRITICAL **: gdk_colormap_get_screen: */ /* assertion `GDK_IS_COLORMAP (cmap)' failed */ ret = sess->gui->window; g_free (sess->gui); sess->gui = NULL; } return ret; } static void mg_link_irctab (session *sess, int focus) { GtkWidget *win; if (sess->gui->is_tab) { win = mg_changui_destroy (sess); mg_changui_new (sess, sess->res, 0, focus); mg_populate (sess); hexchat_is_quitting = FALSE; if (win) gtk_widget_destroy (win); return; } mg_unpopulate (sess); win = mg_changui_destroy (sess); mg_changui_new (sess, sess->res, 1, focus); /* the buffer is now attached to a different widget */ ((xtext_buffer *)sess->res->buffer)->xtext = (GtkXText *)sess->gui->xtext; if (win) gtk_widget_destroy (win); } void mg_detach (session *sess, int mode) { switch (mode) { /* detach only */ case 1: if (sess->gui->is_tab) mg_link_irctab (sess, 1); break; /* attach only */ case 2: if (!sess->gui->is_tab) mg_link_irctab (sess, 1); break; /* toggle */ default: mg_link_irctab (sess, 1); } } static int check_is_number (char *t) { while (*t) { if (*t < '0' || *t > '9') return FALSE; t++; } return TRUE; } static void mg_change_flag (GtkWidget * wid, session *sess, char flag) { server *serv = sess->server; char mode[3]; mode[1] = flag; mode[2] = '\0'; if (serv->connected && sess->channel[0]) { if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (wid))) mode[0] = '+'; else mode[0] = '-'; serv->p_mode (serv, sess->channel, mode); serv->p_join_info (serv, sess->channel); sess->ignore_mode = TRUE; sess->ignore_date = TRUE; } } static void flagl_hit (GtkWidget * wid, struct session *sess) { char modes[512]; const char *limit_str; server *serv = sess->server; if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (wid))) { if (serv->connected && sess->channel[0]) { limit_str = gtk_entry_get_text (GTK_ENTRY (sess->gui->limit_entry)); if (check_is_number ((char *)limit_str) == FALSE) { fe_message (_("User limit must be a number!\n"), FE_MSG_ERROR); gtk_entry_set_text (GTK_ENTRY (sess->gui->limit_entry), ""); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid), FALSE); return; } g_snprintf (modes, sizeof (modes), "+l %d", atoi (limit_str)); serv->p_mode (serv, sess->channel, modes); serv->p_join_info (serv, sess->channel); } } else mg_change_flag (wid, sess, 'l'); } static void flagk_hit (GtkWidget * wid, struct session *sess) { char modes[512]; server *serv = sess->server; if (serv->connected && sess->channel[0]) { g_snprintf (modes, sizeof (modes), "-k %s", gtk_entry_get_text (GTK_ENTRY (sess->gui->key_entry))); if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (wid))) modes[0] = '+'; serv->p_mode (serv, sess->channel, modes); } } static void mg_flagbutton_cb (GtkWidget *but, char *flag) { session *sess; char mode; if (ignore_chanmode) return; sess = current_sess; mode = tolower ((unsigned char) flag[0]); switch (mode) { case 'l': flagl_hit (but, sess); break; case 'k': flagk_hit (but, sess); break; case 'b': ignore_chanmode = TRUE; gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (sess->gui->flag_b), FALSE); ignore_chanmode = FALSE; banlist_opengui (sess); break; default: mg_change_flag (but, sess, mode); } } static GtkWidget * mg_create_flagbutton (char *tip, GtkWidget *box, char *face) { GtkWidget *btn, *lbl; char label_markup[16]; g_snprintf (label_markup, sizeof(label_markup), "<tt>%s</tt>", face); lbl = gtk_label_new (NULL); gtk_label_set_markup (GTK_LABEL(lbl), label_markup); btn = gtk_toggle_button_new (); gtk_widget_set_size_request (btn, -1, 0); gtk_widget_set_tooltip_text (btn, tip); gtk_container_add (GTK_CONTAINER(btn), lbl); gtk_box_pack_start (GTK_BOX (box), btn, 0, 0, 0); g_signal_connect (G_OBJECT (btn), "toggled", G_CALLBACK (mg_flagbutton_cb), face); show_and_unfocus (btn); return btn; } static void mg_key_entry_cb (GtkWidget * igad, gpointer userdata) { char modes[512]; session *sess = current_sess; server *serv = sess->server; if (serv->connected && sess->channel[0]) { g_snprintf (modes, sizeof (modes), "+k %s", gtk_entry_get_text (GTK_ENTRY (igad))); serv->p_mode (serv, sess->channel, modes); serv->p_join_info (serv, sess->channel); } } static void mg_limit_entry_cb (GtkWidget * igad, gpointer userdata) { char modes[512]; session *sess = current_sess; server *serv = sess->server; if (serv->connected && sess->channel[0]) { if (check_is_number ((char *)gtk_entry_get_text (GTK_ENTRY (igad))) == FALSE) { gtk_entry_set_text (GTK_ENTRY (igad), ""); fe_message (_("User limit must be a number!\n"), FE_MSG_ERROR); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (sess->gui->flag_l), FALSE); return; } g_snprintf (modes, sizeof(modes), "+l %d", atoi (gtk_entry_get_text (GTK_ENTRY (igad)))); serv->p_mode (serv, sess->channel, modes); serv->p_join_info (serv, sess->channel); } } static void mg_apply_entry_style (GtkWidget *entry) { gtk_widget_modify_base (entry, GTK_STATE_NORMAL, &colors[COL_BG]); gtk_widget_modify_text (entry, GTK_STATE_NORMAL, &colors[COL_FG]); gtk_widget_modify_font (entry, input_style->font_desc); } static void mg_create_chanmodebuttons (session_gui *gui, GtkWidget *box) { gui->flag_c = mg_create_flagbutton (_("Filter Colors"), box, "c"); gui->flag_n = mg_create_flagbutton (_("No outside messages"), box, "n"); gui->flag_t = mg_create_flagbutton (_("Topic Protection"), box, "t"); gui->flag_i = mg_create_flagbutton (_("Invite Only"), box, "i"); gui->flag_m = mg_create_flagbutton (_("Moderated"), box, "m"); gui->flag_b = mg_create_flagbutton (_("Ban List"), box, "b"); gui->flag_k = mg_create_flagbutton (_("Keyword"), box, "k"); gui->key_entry = gtk_entry_new (); gtk_widget_set_name (gui->key_entry, "hexchat-inputbox"); gtk_entry_set_max_length (GTK_ENTRY (gui->key_entry), 23); gtk_widget_set_size_request (gui->key_entry, 115, -1); gtk_box_pack_start (GTK_BOX (box), gui->key_entry, 0, 0, 0); g_signal_connect (G_OBJECT (gui->key_entry), "activate", G_CALLBACK (mg_key_entry_cb), NULL); if (prefs.hex_gui_input_style) mg_apply_entry_style (gui->key_entry); gui->flag_l = mg_create_flagbutton (_("User Limit"), box, "l"); gui->limit_entry = gtk_entry_new (); gtk_widget_set_name (gui->limit_entry, "hexchat-inputbox"); gtk_entry_set_max_length (GTK_ENTRY (gui->limit_entry), 10); gtk_widget_set_size_request (gui->limit_entry, 30, -1); gtk_box_pack_start (GTK_BOX (box), gui->limit_entry, 0, 0, 0); g_signal_connect (G_OBJECT (gui->limit_entry), "activate", G_CALLBACK (mg_limit_entry_cb), NULL); if (prefs.hex_gui_input_style) mg_apply_entry_style (gui->limit_entry); } /*static void mg_create_link_buttons (GtkWidget *box, gpointer userdata) { gtkutil_button (box, GTK_STOCK_CLOSE, _("Close this tab/window"), mg_x_click_cb, userdata, 0); if (!userdata) gtkutil_button (box, GTK_STOCK_REDO, _("Attach/Detach this tab"), mg_link_cb, userdata, 0); }*/ static void mg_dialog_button_cb (GtkWidget *wid, char *cmd) { /* the longest cmd is 12, and the longest nickname is 64 */ char buf[128]; char *host = ""; char *topic; if (!current_sess) return; topic = (char *)(gtk_entry_get_text (GTK_ENTRY (current_sess->gui->topic_entry))); topic = strrchr (topic, '@'); if (topic) host = topic + 1; auto_insert (buf, sizeof (buf), cmd, 0, 0, "", "", "", server_get_network (current_sess->server, TRUE), host, "", current_sess->channel, ""); handle_command (current_sess, buf, TRUE); /* dirty trick to avoid auto-selection */ SPELL_ENTRY_SET_EDITABLE (current_sess->gui->input_box, FALSE); gtk_widget_grab_focus (current_sess->gui->input_box); SPELL_ENTRY_SET_EDITABLE (current_sess->gui->input_box, TRUE); } static void mg_dialog_button (GtkWidget *box, char *name, char *cmd) { GtkWidget *wid; wid = gtk_button_new_with_label (name); gtk_box_pack_start (GTK_BOX (box), wid, FALSE, FALSE, 0); g_signal_connect (G_OBJECT (wid), "clicked", G_CALLBACK (mg_dialog_button_cb), cmd); gtk_widget_set_size_request (wid, -1, 0); } static void mg_create_dialogbuttons (GtkWidget *box) { struct popup *pop; GSList *list = dlgbutton_list; while (list) { pop = list->data; if (pop->cmd[0]) mg_dialog_button (box, pop->name, pop->cmd); list = list->next; } } static void mg_create_topicbar (session *sess, GtkWidget *box) { GtkWidget *hbox, *topic, *bbox; session_gui *gui = sess->gui; gui->topic_bar = hbox = gtk_hbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (box), hbox, 0, 0, 0); if (!gui->is_tab) sess->res->tab = NULL; gui->topic_entry = topic = sexy_spell_entry_new (); gtk_widget_set_name (topic, "hexchat-inputbox"); sexy_spell_entry_set_checked (SEXY_SPELL_ENTRY (topic), FALSE); gtk_container_add (GTK_CONTAINER (hbox), topic); g_signal_connect (G_OBJECT (topic), "activate", G_CALLBACK (mg_topic_cb), 0); if (prefs.hex_gui_input_style) mg_apply_entry_style (topic); gui->topicbutton_box = bbox = gtk_hbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (hbox), bbox, 0, 0, 0); mg_create_chanmodebuttons (gui, bbox); gui->dialogbutton_box = bbox = gtk_hbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (hbox), bbox, 0, 0, 0); mg_create_dialogbuttons (bbox); } /* check if a word is clickable */ static int mg_word_check (GtkWidget * xtext, char *word) { session *sess = current_sess; int ret; ret = url_check_word (word); if (ret == 0 && sess->type == SESS_DIALOG) return WORD_DIALOG; return ret; } /* mouse click inside text area */ static void mg_word_clicked (GtkWidget *xtext, char *word, GdkEventButton *even) { session *sess = current_sess; int word_type = 0, start, end; char *tmp; if (word) { word_type = mg_word_check (xtext, word); url_last (&start, &end); } if (even->button == 1) /* left button */ { if (word == NULL) { mg_focus (sess); return; } if ((even->state & 13) == prefs.hex_gui_url_mod) { switch (word_type) { case WORD_URL: case WORD_HOST6: case WORD_HOST: word[end] = 0; fe_open_url (word + start); } } return; } if (even->button == 2) { if (sess->type == SESS_DIALOG) menu_middlemenu (sess, even); else if (even->type == GDK_2BUTTON_PRESS) userlist_select (sess, word); return; } if (word == NULL) return; switch (word_type) { case 0: case WORD_PATH: menu_middlemenu (sess, even); break; case WORD_URL: case WORD_HOST6: case WORD_HOST: word[end] = 0; word += start; menu_urlmenu (even, word); break; case WORD_NICK: word[end] = 0; word += start; menu_nickmenu (sess, even, word, FALSE); break; case WORD_CHANNEL: word[end] = 0; word += start; menu_chanmenu (sess, even, word); break; case WORD_EMAIL: word[end] = 0; word += start; tmp = g_strdup_printf ("mailto:%s", word + (ispunct (*word) ? 1 : 0)); menu_urlmenu (even, tmp); g_free (tmp); break; case WORD_DIALOG: menu_nickmenu (sess, even, sess->channel, FALSE); break; } } void mg_update_xtext (GtkWidget *wid) { GtkXText *xtext = GTK_XTEXT (wid); gtk_xtext_set_palette (xtext, colors); gtk_xtext_set_max_lines (xtext, prefs.hex_text_max_lines); gtk_xtext_set_background (xtext, channelwin_pix); gtk_xtext_set_wordwrap (xtext, prefs.hex_text_wordwrap); gtk_xtext_set_show_marker (xtext, prefs.hex_text_show_marker); gtk_xtext_set_show_separator (xtext, prefs.hex_text_indent ? prefs.hex_text_show_sep : 0); gtk_xtext_set_indent (xtext, prefs.hex_text_indent); if (!gtk_xtext_set_font (xtext, prefs.hex_text_font)) { fe_message ("Failed to open any font. I'm out of here!", FE_MSG_WAIT | FE_MSG_ERROR); exit (1); } gtk_xtext_refresh (xtext); } static void mg_create_textarea (session *sess, GtkWidget *box) { GtkWidget *inbox, *vbox, *frame; GtkXText *xtext; session_gui *gui = sess->gui; static const GtkTargetEntry dnd_targets[] = { {"text/uri-list", 0, 1} }; static const GtkTargetEntry dnd_dest_targets[] = { {"HEXCHAT_CHANVIEW", GTK_TARGET_SAME_APP, 75 }, {"HEXCHAT_USERLIST", GTK_TARGET_SAME_APP, 75 } }; vbox = gtk_vbox_new (FALSE, 0); gtk_container_add (GTK_CONTAINER (box), vbox); inbox = gtk_hbox_new (FALSE, 2); gtk_container_add (GTK_CONTAINER (vbox), inbox); frame = gtk_frame_new (NULL); gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); gtk_container_add (GTK_CONTAINER (inbox), frame); gui->xtext = gtk_xtext_new (colors, TRUE); xtext = GTK_XTEXT (gui->xtext); gtk_xtext_set_max_indent (xtext, prefs.hex_text_max_indent); gtk_xtext_set_thin_separator (xtext, prefs.hex_text_thin_sep); gtk_xtext_set_urlcheck_function (xtext, mg_word_check); gtk_xtext_set_max_lines (xtext, prefs.hex_text_max_lines); gtk_container_add (GTK_CONTAINER (frame), GTK_WIDGET (xtext)); mg_update_xtext (GTK_WIDGET (xtext)); g_signal_connect (G_OBJECT (xtext), "word_click", G_CALLBACK (mg_word_clicked), NULL); gui->vscrollbar = gtk_vscrollbar_new (GTK_XTEXT (xtext)->adj); gtk_box_pack_start (GTK_BOX (inbox), gui->vscrollbar, FALSE, TRUE, 0); gtk_drag_dest_set (gui->vscrollbar, 5, dnd_dest_targets, 2, GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK); g_signal_connect (G_OBJECT (gui->vscrollbar), "drag_begin", G_CALLBACK (mg_drag_begin_cb), NULL); g_signal_connect (G_OBJECT (gui->vscrollbar), "drag_drop", G_CALLBACK (mg_drag_drop_cb), NULL); g_signal_connect (G_OBJECT (gui->vscrollbar), "drag_motion", G_CALLBACK (mg_drag_motion_cb), gui->vscrollbar); g_signal_connect (G_OBJECT (gui->vscrollbar), "drag_end", G_CALLBACK (mg_drag_end_cb), NULL); gtk_drag_dest_set (gui->xtext, GTK_DEST_DEFAULT_ALL, dnd_targets, 1, GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK); g_signal_connect (G_OBJECT (gui->xtext), "drag_data_received", G_CALLBACK (mg_dialog_dnd_drop), NULL); } static GtkWidget * mg_create_infoframe (GtkWidget *box) { GtkWidget *frame, *label, *hbox; frame = gtk_frame_new (0); gtk_frame_set_shadow_type ((GtkFrame*)frame, GTK_SHADOW_OUT); gtk_container_add (GTK_CONTAINER (box), frame); hbox = gtk_hbox_new (0, 0); gtk_container_add (GTK_CONTAINER (frame), hbox); label = gtk_label_new (NULL); gtk_container_add (GTK_CONTAINER (hbox), label); return label; } static void mg_create_meters (session_gui *gui, GtkWidget *parent_box) { GtkWidget *infbox, *wid, *box; gui->meter_box = infbox = box = gtk_vbox_new (0, 1); gtk_box_pack_start (GTK_BOX (parent_box), box, 0, 0, 0); if ((prefs.hex_gui_lagometer & 2) || (prefs.hex_gui_throttlemeter & 2)) { infbox = gtk_hbox_new (0, 0); gtk_box_pack_start (GTK_BOX (box), infbox, 0, 0, 0); } if (prefs.hex_gui_lagometer & 1) { gui->lagometer = wid = gtk_progress_bar_new (); #ifdef WIN32 gtk_widget_set_size_request (wid, 1, 10); #else gtk_widget_set_size_request (wid, 1, 8); #endif wid = gtk_event_box_new (); gtk_container_add (GTK_CONTAINER (wid), gui->lagometer); gtk_box_pack_start (GTK_BOX (box), wid, 0, 0, 0); } if (prefs.hex_gui_lagometer & 2) { gui->laginfo = wid = mg_create_infoframe (infbox); gtk_label_set_text ((GtkLabel *) wid, "Lag"); } if (prefs.hex_gui_throttlemeter & 1) { gui->throttlemeter = wid = gtk_progress_bar_new (); #ifdef WIN32 gtk_widget_set_size_request (wid, 1, 10); #else gtk_widget_set_size_request (wid, 1, 8); #endif wid = gtk_event_box_new (); gtk_container_add (GTK_CONTAINER (wid), gui->throttlemeter); gtk_box_pack_start (GTK_BOX (box), wid, 0, 0, 0); } if (prefs.hex_gui_throttlemeter & 2) { gui->throttleinfo = wid = mg_create_infoframe (infbox); gtk_label_set_text ((GtkLabel *) wid, "Throttle"); } } void mg_update_meters (session_gui *gui) { gtk_widget_destroy (gui->meter_box); gui->lagometer = NULL; gui->laginfo = NULL; gui->throttlemeter = NULL; gui->throttleinfo = NULL; mg_create_meters (gui, gui->button_box_parent); gtk_widget_show_all (gui->meter_box); } static void mg_create_userlist (session_gui *gui, GtkWidget *box) { GtkWidget *frame, *ulist, *vbox; vbox = gtk_vbox_new (0, 1); gtk_container_add (GTK_CONTAINER (box), vbox); frame = gtk_frame_new (NULL); if (prefs.hex_gui_ulist_count) gtk_box_pack_start (GTK_BOX (vbox), frame, 0, 0, GUI_SPACING); gui->namelistinfo = gtk_label_new (NULL); gtk_container_add (GTK_CONTAINER (frame), gui->namelistinfo); gui->user_tree = ulist = userlist_create (vbox); if (prefs.hex_gui_ulist_style) { gtk_widget_set_style (ulist, input_style); gtk_widget_modify_base (ulist, GTK_STATE_NORMAL, &colors[COL_BG]); } mg_create_meters (gui, vbox); gui->button_box_parent = vbox; gui->button_box = mg_create_userlistbuttons (vbox); } static void mg_vpane_cb (GtkPaned *pane, GParamSpec *param, session_gui *gui) { prefs.hex_gui_pane_divider_position = gtk_paned_get_position (pane); } static void mg_leftpane_cb (GtkPaned *pane, GParamSpec *param, session_gui *gui) { prefs.hex_gui_pane_left_size = gtk_paned_get_position (pane); } static void mg_rightpane_cb (GtkPaned *pane, GParamSpec *param, session_gui *gui) { int handle_size; GtkAllocation allocation; gtk_widget_style_get (GTK_WIDGET (pane), "handle-size", &handle_size, NULL); /* record the position from the RIGHT side */ gtk_widget_get_allocation (GTK_WIDGET(pane), &allocation); prefs.hex_gui_pane_right_size = allocation.width - gtk_paned_get_position (pane) - handle_size; } static gboolean mg_add_pane_signals (session_gui *gui) { g_signal_connect (G_OBJECT (gui->hpane_right), "notify::position", G_CALLBACK (mg_rightpane_cb), gui); g_signal_connect (G_OBJECT (gui->hpane_left), "notify::position", G_CALLBACK (mg_leftpane_cb), gui); g_signal_connect (G_OBJECT (gui->vpane_left), "notify::position", G_CALLBACK (mg_vpane_cb), gui); g_signal_connect (G_OBJECT (gui->vpane_right), "notify::position", G_CALLBACK (mg_vpane_cb), gui); return FALSE; } static void mg_create_center (session *sess, session_gui *gui, GtkWidget *box) { GtkWidget *vbox, *hbox, *book; /* sep between top and bottom of left side */ gui->vpane_left = gtk_vpaned_new (); /* sep between top and bottom of right side */ gui->vpane_right = gtk_vpaned_new (); /* sep between left and xtext */ gui->hpane_left = gtk_hpaned_new (); gtk_paned_set_position (GTK_PANED (gui->hpane_left), prefs.hex_gui_pane_left_size); /* sep between xtext and right side */ gui->hpane_right = gtk_hpaned_new (); if (prefs.hex_gui_win_swap) { gtk_paned_pack2 (GTK_PANED (gui->hpane_left), gui->vpane_left, FALSE, TRUE); gtk_paned_pack1 (GTK_PANED (gui->hpane_left), gui->hpane_right, TRUE, TRUE); } else { gtk_paned_pack1 (GTK_PANED (gui->hpane_left), gui->vpane_left, FALSE, TRUE); gtk_paned_pack2 (GTK_PANED (gui->hpane_left), gui->hpane_right, TRUE, TRUE); } gtk_paned_pack2 (GTK_PANED (gui->hpane_right), gui->vpane_right, FALSE, TRUE); gtk_container_add (GTK_CONTAINER (box), gui->hpane_left); gui->note_book = book = gtk_notebook_new (); gtk_notebook_set_show_tabs (GTK_NOTEBOOK (book), FALSE); gtk_notebook_set_show_border (GTK_NOTEBOOK (book), FALSE); gtk_paned_pack1 (GTK_PANED (gui->hpane_right), book, TRUE, TRUE); hbox = gtk_hbox_new (FALSE, 0); gtk_paned_pack1 (GTK_PANED (gui->vpane_right), hbox, FALSE, TRUE); mg_create_userlist (gui, hbox); gui->user_box = hbox; vbox = gtk_vbox_new (FALSE, 3); gtk_notebook_append_page (GTK_NOTEBOOK (book), vbox, NULL); mg_create_topicbar (sess, vbox); if (prefs.hex_gui_search_pos) { mg_create_search (sess, vbox); mg_create_textarea (sess, vbox); } else { mg_create_textarea (sess, vbox); mg_create_search (sess, vbox); } mg_create_entry (sess, vbox); mg_add_pane_signals (gui); } static void mg_change_nick (int cancel, char *text, gpointer userdata) { char buf[256]; if (!cancel) { g_snprintf (buf, sizeof (buf), "nick %s", text); handle_command (current_sess, buf, FALSE); } } static void mg_nickclick_cb (GtkWidget *button, gpointer userdata) { fe_get_str (_("Enter new nickname:"), current_sess->server->nick, mg_change_nick, (void *) 1); } /* make sure chanview and userlist positions are sane */ static void mg_sanitize_positions (int *cv, int *ul) { if (prefs.hex_gui_tab_layout == 2) { /* treeview can't be on TOP or BOTTOM */ if (*cv == POS_TOP || *cv == POS_BOTTOM) *cv = POS_TOPLEFT; } /* userlist can't be on TOP or BOTTOM */ if (*ul == POS_TOP || *ul == POS_BOTTOM) *ul = POS_TOPRIGHT; /* can't have both in the same place */ if (*cv == *ul) { *cv = POS_TOPRIGHT; if (*ul == POS_TOPRIGHT) *cv = POS_BOTTOMRIGHT; } } static void mg_place_userlist_and_chanview_real (session_gui *gui, GtkWidget *userlist, GtkWidget *chanview) { int unref_userlist = FALSE; int unref_chanview = FALSE; /* first, remove userlist/treeview from their containers */ if (userlist && gtk_widget_get_parent (userlist)) { g_object_ref (userlist); gtk_container_remove (GTK_CONTAINER (gtk_widget_get_parent (userlist)), userlist); unref_userlist = TRUE; } if (chanview && gtk_widget_get_parent (chanview)) { g_object_ref (chanview); gtk_container_remove (GTK_CONTAINER (gtk_widget_get_parent (chanview)), chanview); unref_chanview = TRUE; } if (chanview) { /* incase the previous pos was POS_HIDDEN */ gtk_widget_show (chanview); gtk_table_set_row_spacing (GTK_TABLE (gui->main_table), 1, 0); gtk_table_set_row_spacing (GTK_TABLE (gui->main_table), 2, 2); /* then place them back in their new positions */ switch (prefs.hex_gui_tab_pos) { case POS_TOPLEFT: gtk_paned_pack1 (GTK_PANED (gui->vpane_left), chanview, FALSE, TRUE); break; case POS_BOTTOMLEFT: gtk_paned_pack2 (GTK_PANED (gui->vpane_left), chanview, FALSE, TRUE); break; case POS_TOPRIGHT: gtk_paned_pack1 (GTK_PANED (gui->vpane_right), chanview, FALSE, TRUE); break; case POS_BOTTOMRIGHT: gtk_paned_pack2 (GTK_PANED (gui->vpane_right), chanview, FALSE, TRUE); break; case POS_TOP: gtk_table_set_row_spacing (GTK_TABLE (gui->main_table), 1, GUI_SPACING-1); gtk_table_attach (GTK_TABLE (gui->main_table), chanview, 1, 2, 1, 2, GTK_FILL, GTK_FILL, 0, 0); break; case POS_HIDDEN: gtk_widget_hide (chanview); /* always attach it to something to avoid ref_count=0 */ if (prefs.hex_gui_ulist_pos == POS_TOP) gtk_table_attach (GTK_TABLE (gui->main_table), chanview, 1, 2, 3, 4, GTK_FILL, GTK_FILL, 0, 0); else gtk_table_attach (GTK_TABLE (gui->main_table), chanview, 1, 2, 1, 2, GTK_FILL, GTK_FILL, 0, 0); break; default:/* POS_BOTTOM */ gtk_table_set_row_spacing (GTK_TABLE (gui->main_table), 2, 3); gtk_table_attach (GTK_TABLE (gui->main_table), chanview, 1, 2, 3, 4, GTK_FILL, GTK_FILL, 0, 0); } } if (userlist) { switch (prefs.hex_gui_ulist_pos) { case POS_TOPLEFT: gtk_paned_pack1 (GTK_PANED (gui->vpane_left), userlist, FALSE, TRUE); break; case POS_BOTTOMLEFT: gtk_paned_pack2 (GTK_PANED (gui->vpane_left), userlist, FALSE, TRUE); break; case POS_BOTTOMRIGHT: gtk_paned_pack2 (GTK_PANED (gui->vpane_right), userlist, FALSE, TRUE); break; /*case POS_HIDDEN: break;*/ /* Hide using the VIEW menu instead */ default:/* POS_TOPRIGHT */ gtk_paned_pack1 (GTK_PANED (gui->vpane_right), userlist, FALSE, TRUE); } } if (mg_is_userlist_and_tree_combined () && prefs.hex_gui_pane_divider_position != 0) { gtk_paned_set_position (GTK_PANED (gui->vpane_left), prefs.hex_gui_pane_divider_position); gtk_paned_set_position (GTK_PANED (gui->vpane_right), prefs.hex_gui_pane_divider_position); } if (unref_chanview) g_object_unref (chanview); if (unref_userlist) g_object_unref (userlist); mg_hide_empty_boxes (gui); } static void mg_place_userlist_and_chanview (session_gui *gui) { GtkOrientation orientation; GtkWidget *chanviewbox = NULL; int pos; mg_sanitize_positions (&prefs.hex_gui_tab_pos, &prefs.hex_gui_ulist_pos); if (gui->chanview) { pos = prefs.hex_gui_tab_pos; orientation = chanview_get_orientation (gui->chanview); if ((pos == POS_BOTTOM || pos == POS_TOP) && orientation == GTK_ORIENTATION_VERTICAL) chanview_set_orientation (gui->chanview, FALSE); else if ((pos == POS_TOPLEFT || pos == POS_BOTTOMLEFT || pos == POS_TOPRIGHT || pos == POS_BOTTOMRIGHT) && orientation == GTK_ORIENTATION_HORIZONTAL) chanview_set_orientation (gui->chanview, TRUE); chanviewbox = chanview_get_box (gui->chanview); } mg_place_userlist_and_chanview_real (gui, gui->user_box, chanviewbox); } void mg_change_layout (int type) { if (mg_gui) { /* put tabs at the bottom */ if (type == 0 && prefs.hex_gui_tab_pos != POS_BOTTOM && prefs.hex_gui_tab_pos != POS_TOP) prefs.hex_gui_tab_pos = POS_BOTTOM; mg_place_userlist_and_chanview (mg_gui); chanview_set_impl (mg_gui->chanview, type); } } static void mg_inputbox_rightclick (GtkEntry *entry, GtkWidget *menu) { mg_create_color_menu (menu, NULL); } /* Search bar adapted from Conspire's by William Pitcock */ #define SEARCH_CHANGE 1 #define SEARCH_NEXT 2 #define SEARCH_PREVIOUS 3 #define SEARCH_REFRESH 4 static void search_handle_event(int search_type, session *sess) { textentry *last; const gchar *text = NULL; gtk_xtext_search_flags flags; GError *err = NULL; gboolean backwards = FALSE; /* When just typing show most recent first */ if (search_type == SEARCH_PREVIOUS || search_type == SEARCH_CHANGE) backwards = TRUE; flags = ((prefs.hex_text_search_case_match == 1? case_match: 0) | (backwards? backward: 0) | (prefs.hex_text_search_highlight_all == 1? highlight: 0) | (prefs.hex_text_search_follow == 1? follow: 0) | (prefs.hex_text_search_regexp == 1? regexp: 0)); if (search_type != SEARCH_REFRESH) text = gtk_entry_get_text (GTK_ENTRY(sess->gui->shentry)); last = gtk_xtext_search (GTK_XTEXT (sess->gui->xtext), text, flags, &err); if (err) { gtk_entry_set_icon_from_stock (GTK_ENTRY (sess->gui->shentry), GTK_ENTRY_ICON_SECONDARY, GTK_STOCK_DIALOG_ERROR); gtk_entry_set_icon_tooltip_text (GTK_ENTRY (sess->gui->shentry), GTK_ENTRY_ICON_SECONDARY, _(err->message)); g_error_free (err); } else if (!last) { if (text && text[0] == 0) /* empty string, no error */ { gtk_entry_set_icon_from_stock (GTK_ENTRY (sess->gui->shentry), GTK_ENTRY_ICON_SECONDARY, NULL); } else { /* Either end of search or not found, try again to wrap if only end */ last = gtk_xtext_search (GTK_XTEXT (sess->gui->xtext), text, flags, &err); if (!last) /* Not found error */ { gtk_entry_set_icon_from_stock (GTK_ENTRY (sess->gui->shentry), GTK_ENTRY_ICON_SECONDARY, GTK_STOCK_DIALOG_ERROR); gtk_entry_set_icon_tooltip_text (GTK_ENTRY (sess->gui->shentry), GTK_ENTRY_ICON_SECONDARY, _("No results found.")); } } } else { gtk_entry_set_icon_from_stock (GTK_ENTRY (sess->gui->shentry), GTK_ENTRY_ICON_SECONDARY, NULL); } } static void search_handle_change(GtkWidget *wid, session *sess) { search_handle_event(SEARCH_CHANGE, sess); } static void search_handle_refresh(GtkWidget *wid, session *sess) { search_handle_event(SEARCH_REFRESH, sess); } void mg_search_handle_previous(GtkWidget *wid, session *sess) { search_handle_event(SEARCH_PREVIOUS, sess); } void mg_search_handle_next(GtkWidget *wid, session *sess) { search_handle_event(SEARCH_NEXT, sess); } static void search_set_option (GtkToggleButton *but, guint *pref) { *pref = gtk_toggle_button_get_active(but); save_config(); } void mg_search_toggle(session *sess) { if (gtk_widget_get_visible(sess->gui->shbox)) { gtk_widget_hide(sess->gui->shbox); gtk_widget_grab_focus(sess->gui->input_box); gtk_entry_set_text(GTK_ENTRY(sess->gui->shentry), ""); } else { /* Reset search state */ gtk_entry_set_icon_from_stock (GTK_ENTRY (sess->gui->shentry), GTK_ENTRY_ICON_SECONDARY, NULL); /* Show and focus */ gtk_widget_show(sess->gui->shbox); gtk_widget_grab_focus(sess->gui->shentry); } } static gboolean search_handle_esc (GtkWidget *win, GdkEventKey *key, session *sess) { if (key->keyval == GDK_KEY_Escape) mg_search_toggle(sess); return FALSE; } static void mg_create_search(session *sess, GtkWidget *box) { GtkWidget *entry, *label, *next, *previous, *highlight, *matchcase, *regex, *close; session_gui *gui = sess->gui; gui->shbox = gtk_hbox_new(FALSE, 5); gtk_box_pack_start(GTK_BOX(box), gui->shbox, FALSE, FALSE, 0); close = gtk_button_new (); gtk_button_set_image (GTK_BUTTON (close), gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU)); gtk_button_set_relief(GTK_BUTTON(close), GTK_RELIEF_NONE); gtk_widget_set_can_focus (close, FALSE); gtk_box_pack_start(GTK_BOX(gui->shbox), close, FALSE, FALSE, 0); g_signal_connect_swapped(G_OBJECT(close), "clicked", G_CALLBACK(mg_search_toggle), sess); label = gtk_label_new(_("Find:")); gtk_box_pack_start(GTK_BOX(gui->shbox), label, FALSE, FALSE, 0); gui->shentry = entry = gtk_entry_new(); gtk_box_pack_start(GTK_BOX(gui->shbox), entry, FALSE, FALSE, 0); gtk_widget_set_size_request (gui->shentry, 180, -1); gui->search_changed_signal = g_signal_connect(G_OBJECT(entry), "changed", G_CALLBACK(search_handle_change), sess); g_signal_connect (G_OBJECT (entry), "key_press_event", G_CALLBACK (search_handle_esc), sess); g_signal_connect(G_OBJECT(entry), "activate", G_CALLBACK(mg_search_handle_next), sess); gtk_entry_set_icon_activatable (GTK_ENTRY (entry), GTK_ENTRY_ICON_SECONDARY, FALSE); gtk_entry_set_icon_tooltip_text (GTK_ENTRY (sess->gui->shentry), GTK_ENTRY_ICON_SECONDARY, _("Search hit end or not found.")); previous = gtk_button_new (); gtk_button_set_image (GTK_BUTTON (previous), gtk_image_new_from_stock (GTK_STOCK_GO_BACK, GTK_ICON_SIZE_MENU)); gtk_button_set_relief(GTK_BUTTON(previous), GTK_RELIEF_NONE); gtk_widget_set_can_focus (previous, FALSE); gtk_box_pack_start(GTK_BOX(gui->shbox), previous, FALSE, FALSE, 0); g_signal_connect(G_OBJECT(previous), "clicked", G_CALLBACK(mg_search_handle_previous), sess); next = gtk_button_new (); gtk_button_set_image (GTK_BUTTON (next), gtk_image_new_from_stock (GTK_STOCK_GO_FORWARD, GTK_ICON_SIZE_MENU)); gtk_button_set_relief(GTK_BUTTON(next), GTK_RELIEF_NONE); gtk_widget_set_can_focus (next, FALSE); gtk_box_pack_start(GTK_BOX(gui->shbox), next, FALSE, FALSE, 0); g_signal_connect(G_OBJECT(next), "clicked", G_CALLBACK(mg_search_handle_next), sess); highlight = gtk_check_button_new_with_mnemonic (_("_Highlight all")); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(highlight), prefs.hex_text_search_highlight_all); gtk_widget_set_can_focus (highlight, FALSE); g_signal_connect (G_OBJECT (highlight), "toggled", G_CALLBACK (search_set_option), &prefs.hex_text_search_highlight_all); g_signal_connect (G_OBJECT (highlight), "toggled", G_CALLBACK (search_handle_refresh), sess); gtk_box_pack_start(GTK_BOX(gui->shbox), highlight, FALSE, FALSE, 0); gtk_widget_set_tooltip_text (highlight, _("Highlight all occurrences, and underline the current occurrence.")); matchcase = gtk_check_button_new_with_mnemonic (_("Mat_ch case")); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(matchcase), prefs.hex_text_search_case_match); gtk_widget_set_can_focus (matchcase, FALSE); g_signal_connect (G_OBJECT (matchcase), "toggled", G_CALLBACK (search_set_option), &prefs.hex_text_search_case_match); gtk_box_pack_start(GTK_BOX(gui->shbox), matchcase, FALSE, FALSE, 0); gtk_widget_set_tooltip_text (matchcase, _("Perform a case-sensitive search.")); regex = gtk_check_button_new_with_mnemonic (_("_Regex")); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(regex), prefs.hex_text_search_regexp); gtk_widget_set_can_focus (regex, FALSE); g_signal_connect (G_OBJECT (regex), "toggled", G_CALLBACK (search_set_option), &prefs.hex_text_search_regexp); gtk_box_pack_start(GTK_BOX(gui->shbox), regex, FALSE, FALSE, 0); gtk_widget_set_tooltip_text (regex, _("Regard search string as a regular expression.")); } static void mg_create_entry (session *sess, GtkWidget *box) { GtkWidget *hbox, *but, *entry; session_gui *gui = sess->gui; hbox = gtk_hbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (box), hbox, 0, 0, 0); gui->nick_box = gtk_hbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (hbox), gui->nick_box, 0, 0, 0); gui->nick_label = but = gtk_button_new_with_label (sess->server->nick); gtk_button_set_relief (GTK_BUTTON (but), GTK_RELIEF_NONE); gtk_widget_set_can_focus (but, FALSE); gtk_box_pack_end (GTK_BOX (gui->nick_box), but, 0, 0, 0); g_signal_connect (G_OBJECT (but), "clicked", G_CALLBACK (mg_nickclick_cb), NULL); gui->input_box = entry = sexy_spell_entry_new (); sexy_spell_entry_set_checked ((SexySpellEntry *)entry, prefs.hex_gui_input_spell); sexy_spell_entry_set_parse_attributes ((SexySpellEntry *)entry, prefs.hex_gui_input_attr); gtk_entry_set_max_length (GTK_ENTRY (gui->input_box), 0); g_signal_connect (G_OBJECT (entry), "activate", G_CALLBACK (mg_inputbox_cb), gui); gtk_container_add (GTK_CONTAINER (hbox), entry); gtk_widget_set_name (entry, "hexchat-inputbox"); g_signal_connect (G_OBJECT (entry), "key_press_event", G_CALLBACK (key_handle_key_press), NULL); g_signal_connect (G_OBJECT (entry), "focus_in_event", G_CALLBACK (mg_inputbox_focus), gui); g_signal_connect (G_OBJECT (entry), "populate_popup", G_CALLBACK (mg_inputbox_rightclick), NULL); g_signal_connect (G_OBJECT (entry), "word-check", G_CALLBACK (mg_spellcheck_cb), NULL); gtk_widget_grab_focus (entry); if (prefs.hex_gui_input_style) mg_apply_entry_style (entry); } static void mg_switch_tab_cb (chanview *cv, chan *ch, int tag, gpointer ud) { chan *old; session *sess = ud; old = active_tab; active_tab = ch; if (tag == TAG_IRC) { if (active_tab != old) { if (old && current_tab) mg_unpopulate (current_tab); mg_populate (sess); } } else if (old != active_tab) { /* userdata for non-irc tabs is actually the GtkBox */ mg_show_generic_tab (ud); if (!mg_is_userlist_and_tree_combined ()) mg_userlist_showhide (current_sess, FALSE); /* hide */ } } /* compare two tabs (for tab sorting function) */ static int mg_tabs_compare (session *a, session *b) { /* server tabs always go first */ if (a->type == SESS_SERVER) return -1; /* then channels */ if (a->type == SESS_CHANNEL && b->type != SESS_CHANNEL) return -1; if (a->type != SESS_CHANNEL && b->type == SESS_CHANNEL) return 1; return g_ascii_strcasecmp (a->channel, b->channel); } static void mg_create_tabs (session_gui *gui) { gboolean use_icons = FALSE; /* if any one of these PNGs exist, the chanview will create * the extra column for icons. */ if (prefs.hex_gui_tab_icons && (pix_tree_channel || pix_tree_dialog || pix_tree_server || pix_tree_util)) { use_icons = TRUE; } gui->chanview = chanview_new (prefs.hex_gui_tab_layout, prefs.hex_gui_tab_trunc, prefs.hex_gui_tab_sort, use_icons, prefs.hex_gui_ulist_style ? input_style : NULL); chanview_set_callbacks (gui->chanview, mg_switch_tab_cb, mg_xbutton_cb, mg_tab_contextmenu_cb, (void *)mg_tabs_compare); mg_place_userlist_and_chanview (gui); } static gboolean mg_tabwin_focus_cb (GtkWindow * win, GdkEventFocus *event, gpointer userdata) { current_sess = current_tab; if (current_sess) { gtk_xtext_check_marker_visibility (GTK_XTEXT (current_sess->gui->xtext)); plugin_emit_dummy_print (current_sess, "Focus Window"); } unflash_window (GTK_WIDGET (win)); return FALSE; } static gboolean mg_topwin_focus_cb (GtkWindow * win, GdkEventFocus *event, session *sess) { current_sess = sess; if (!sess->server->server_session) sess->server->server_session = sess; gtk_xtext_check_marker_visibility(GTK_XTEXT (current_sess->gui->xtext)); unflash_window (GTK_WIDGET (win)); plugin_emit_dummy_print (sess, "Focus Window"); return FALSE; } static void mg_create_menu (session_gui *gui, GtkWidget *table, int away_state) { GtkAccelGroup *accel_group; accel_group = gtk_accel_group_new (); gtk_window_add_accel_group (GTK_WINDOW (gtk_widget_get_toplevel (table)), accel_group); g_object_unref (accel_group); gui->menu = menu_create_main (accel_group, TRUE, away_state, !gui->is_tab, gui->menu_item); gtk_table_attach (GTK_TABLE (table), gui->menu, 0, 3, 0, 1, GTK_EXPAND | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); } static void mg_create_irctab (session *sess, GtkWidget *table) { GtkWidget *vbox; session_gui *gui = sess->gui; vbox = gtk_vbox_new (FALSE, 0); gtk_table_attach (GTK_TABLE (table), vbox, 1, 2, 2, 3, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); mg_create_center (sess, gui, vbox); } static void mg_create_topwindow (session *sess) { GtkWidget *win; GtkWidget *table; if (sess->type == SESS_DIALOG) win = gtkutil_window_new ("HexChat", NULL, prefs.hex_gui_dialog_width, prefs.hex_gui_dialog_height, 0); else win = gtkutil_window_new ("HexChat", NULL, prefs.hex_gui_win_width, prefs.hex_gui_win_height, 0); sess->gui->window = win; gtk_container_set_border_width (GTK_CONTAINER (win), GUI_BORDER); gtk_window_set_opacity (GTK_WINDOW (win), (prefs.hex_gui_transparency / 255.)); g_signal_connect (G_OBJECT (win), "focus_in_event", G_CALLBACK (mg_topwin_focus_cb), sess); g_signal_connect (G_OBJECT (win), "destroy", G_CALLBACK (mg_topdestroy_cb), sess); g_signal_connect (G_OBJECT (win), "configure_event", G_CALLBACK (mg_configure_cb), sess); palette_alloc (win); table = gtk_table_new (4, 3, FALSE); /* spacing under the menubar */ gtk_table_set_row_spacing (GTK_TABLE (table), 0, GUI_SPACING); /* left and right borders */ gtk_table_set_col_spacing (GTK_TABLE (table), 0, 1); gtk_table_set_col_spacing (GTK_TABLE (table), 1, 1); gtk_container_add (GTK_CONTAINER (win), table); mg_create_irctab (sess, table); mg_create_menu (sess->gui, table, sess->server->is_away); if (sess->res->buffer == NULL) { sess->res->buffer = gtk_xtext_buffer_new (GTK_XTEXT (sess->gui->xtext)); gtk_xtext_buffer_show (GTK_XTEXT (sess->gui->xtext), sess->res->buffer, TRUE); gtk_xtext_set_time_stamp (sess->res->buffer, prefs.hex_stamp_text); sess->res->user_model = userlist_create_model (sess); } userlist_show (sess); gtk_widget_show_all (table); if (prefs.hex_gui_hide_menu) gtk_widget_hide (sess->gui->menu); /* Will be shown when needed */ gtk_widget_hide (sess->gui->topic_bar); if (!prefs.hex_gui_ulist_buttons) gtk_widget_hide (sess->gui->button_box); if (!prefs.hex_gui_input_nick) gtk_widget_hide (sess->gui->nick_box); gtk_widget_hide(sess->gui->shbox); mg_decide_userlist (sess, FALSE); if (sess->type == SESS_DIALOG) { /* hide the chan-mode buttons */ gtk_widget_hide (sess->gui->topicbutton_box); } else { gtk_widget_hide (sess->gui->dialogbutton_box); if (!prefs.hex_gui_mode_buttons) gtk_widget_hide (sess->gui->topicbutton_box); } mg_place_userlist_and_chanview (sess->gui); gtk_widget_show (win); } static gboolean mg_tabwindow_de_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data) { GSList *list; session *sess; if (prefs.hex_gui_tray_close && !unity_mode () && tray_toggle_visibility (FALSE)) return TRUE; /* check for remaining toplevel windows */ list = sess_list; while (list) { sess = list->data; if (!sess->gui->is_tab) return FALSE; list = list->next; } mg_open_quit_dialog (TRUE); return TRUE; } static void mg_create_tabwindow (session *sess) { GtkWidget *win; GtkWidget *table; win = gtkutil_window_new ("HexChat", NULL, prefs.hex_gui_win_width, prefs.hex_gui_win_height, 0); sess->gui->window = win; gtk_window_move (GTK_WINDOW (win), prefs.hex_gui_win_left, prefs.hex_gui_win_top); if (prefs.hex_gui_win_state) gtk_window_maximize (GTK_WINDOW (win)); if (prefs.hex_gui_win_fullscreen) gtk_window_fullscreen (GTK_WINDOW (win)); gtk_window_set_opacity (GTK_WINDOW (win), (prefs.hex_gui_transparency / 255.)); gtk_container_set_border_width (GTK_CONTAINER (win), GUI_BORDER); g_signal_connect (G_OBJECT (win), "delete_event", G_CALLBACK (mg_tabwindow_de_cb), 0); g_signal_connect (G_OBJECT (win), "destroy", G_CALLBACK (mg_tabwindow_kill_cb), 0); g_signal_connect (G_OBJECT (win), "focus_in_event", G_CALLBACK (mg_tabwin_focus_cb), NULL); g_signal_connect (G_OBJECT (win), "configure_event", G_CALLBACK (mg_configure_cb), NULL); g_signal_connect (G_OBJECT (win), "window_state_event", G_CALLBACK (mg_windowstate_cb), NULL); palette_alloc (win); sess->gui->main_table = table = gtk_table_new (4, 3, FALSE); /* spacing under the menubar */ gtk_table_set_row_spacing (GTK_TABLE (table), 0, GUI_SPACING); /* left and right borders */ gtk_table_set_col_spacing (GTK_TABLE (table), 0, 1); gtk_table_set_col_spacing (GTK_TABLE (table), 1, 1); gtk_container_add (GTK_CONTAINER (win), table); mg_create_irctab (sess, table); mg_create_tabs (sess->gui); mg_create_menu (sess->gui, table, sess->server->is_away); mg_focus (sess); gtk_widget_show_all (table); if (prefs.hex_gui_hide_menu) gtk_widget_hide (sess->gui->menu); mg_decide_userlist (sess, FALSE); /* Will be shown when needed */ gtk_widget_hide (sess->gui->topic_bar); if (!prefs.hex_gui_mode_buttons) gtk_widget_hide (sess->gui->topicbutton_box); if (!prefs.hex_gui_ulist_buttons) gtk_widget_hide (sess->gui->button_box); if (!prefs.hex_gui_input_nick) gtk_widget_hide (sess->gui->nick_box); gtk_widget_hide (sess->gui->shbox); mg_place_userlist_and_chanview (sess->gui); gtk_widget_show (win); } void mg_apply_setup (void) { GSList *list = sess_list; session *sess; int done_main = FALSE; mg_create_tab_colors (); while (list) { sess = list->data; gtk_xtext_set_time_stamp (sess->res->buffer, prefs.hex_stamp_text); ((xtext_buffer *)sess->res->buffer)->needs_recalc = TRUE; if (!sess->gui->is_tab || !done_main) mg_place_userlist_and_chanview (sess->gui); if (sess->gui->is_tab) done_main = TRUE; list = list->next; } } static chan * mg_add_generic_tab (char *name, char *title, void *family, GtkWidget *box) { chan *ch; gtk_notebook_append_page (GTK_NOTEBOOK (mg_gui->note_book), box, NULL); gtk_widget_show (box); ch = chanview_add (mg_gui->chanview, name, NULL, box, TRUE, TAG_UTIL, pix_tree_util); chan_set_color (ch, plain_list); g_object_set_data_full (G_OBJECT (box), "title", g_strdup (title), g_free); g_object_set_data (G_OBJECT (box), "ch", ch); if (prefs.hex_gui_tab_newtofront) chan_focus (ch); return ch; } void fe_buttons_update (session *sess) { session_gui *gui = sess->gui; gtk_widget_destroy (gui->button_box); gui->button_box = mg_create_userlistbuttons (gui->button_box_parent); if (prefs.hex_gui_ulist_buttons) gtk_widget_show (sess->gui->button_box); else gtk_widget_hide (sess->gui->button_box); } void fe_clear_channel (session *sess) { char tbuf[CHANLEN+6]; session_gui *gui = sess->gui; if (sess->gui->is_tab) { if (sess->waitchannel[0]) { if (prefs.hex_gui_tab_trunc > 2 && g_utf8_strlen (sess->waitchannel, -1) > prefs.hex_gui_tab_trunc) { /* truncate long channel names */ tbuf[0] = '('; strcpy (tbuf + 1, sess->waitchannel); g_utf8_offset_to_pointer(tbuf, prefs.hex_gui_tab_trunc)[0] = 0; strcat (tbuf, "..)"); } else { sprintf (tbuf, "(%s)", sess->waitchannel); } } else strcpy (tbuf, _("<none>")); chan_rename (sess->res->tab, tbuf, prefs.hex_gui_tab_trunc); } if (!sess->gui->is_tab || sess == current_tab) { gtk_entry_set_text (GTK_ENTRY (gui->topic_entry), ""); if (gui->op_xpm) { gtk_widget_destroy (gui->op_xpm); gui->op_xpm = 0; } } else { if (sess->res->topic_text) { g_free (sess->res->topic_text); sess->res->topic_text = NULL; } } } void fe_set_nonchannel (session *sess, int state) { } void fe_dlgbuttons_update (session *sess) { GtkWidget *box; session_gui *gui = sess->gui; gtk_widget_destroy (gui->dialogbutton_box); gui->dialogbutton_box = box = gtk_hbox_new (0, 0); gtk_box_pack_start (GTK_BOX (gui->topic_bar), box, 0, 0, 0); gtk_box_reorder_child (GTK_BOX (gui->topic_bar), box, 3); mg_create_dialogbuttons (box); gtk_widget_show_all (box); if (current_tab && current_tab->type != SESS_DIALOG) gtk_widget_hide (current_tab->gui->dialogbutton_box); } void fe_update_mode_buttons (session *sess, char mode, char sign) { int state, i; if (sign == '+') state = TRUE; else state = FALSE; for (i = 0; i < NUM_FLAG_WIDS - 1; i++) { if (chan_flags[i] == mode) { if (!sess->gui->is_tab || sess == current_tab) { ignore_chanmode = TRUE; if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (sess->gui->flag_wid[i])) != state) gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (sess->gui->flag_wid[i]), state); ignore_chanmode = FALSE; } else { sess->res->flag_wid_state[i] = state; } return; } } } void fe_set_nick (server *serv, char *newnick) { GSList *list = sess_list; session *sess; while (list) { sess = list->data; if (sess->server == serv) { if (current_tab == sess || !sess->gui->is_tab) gtk_button_set_label (GTK_BUTTON (sess->gui->nick_label), newnick); } list = list->next; } } void fe_set_away (server *serv) { GSList *list = sess_list; session *sess; while (list) { sess = list->data; if (sess->server == serv) { if (!sess->gui->is_tab || sess == current_tab) { menu_set_away (sess->gui, serv->is_away); /* gray out my nickname */ mg_set_myself_away (sess->gui, serv->is_away); } } list = list->next; } } void fe_set_channel (session *sess) { if (sess->res->tab != NULL) chan_rename (sess->res->tab, sess->channel, prefs.hex_gui_tab_trunc); } void mg_changui_new (session *sess, restore_gui *res, int tab, int focus) { int first_run = FALSE; session_gui *gui; if (res == NULL) { res = g_new0 (restore_gui, 1); } sess->res = res; if (sess->server->front_session == NULL) { sess->server->front_session = sess; } if (!tab) { gui = g_new0 (session_gui, 1); gui->is_tab = FALSE; sess->gui = gui; mg_create_topwindow (sess); fe_set_title (sess); return; } if (mg_gui == NULL) { first_run = TRUE; gui = &static_mg_gui; memset (gui, 0, sizeof (session_gui)); gui->is_tab = TRUE; sess->gui = gui; mg_create_tabwindow (sess); mg_gui = gui; parent_window = gui->window; } else { sess->gui = gui = mg_gui; gui->is_tab = TRUE; } mg_add_chan (sess); if (first_run || (prefs.hex_gui_tab_newtofront == FOCUS_NEW_ONLY_ASKED && focus) || prefs.hex_gui_tab_newtofront == FOCUS_NEW_ALL ) chan_focus (res->tab); } GtkWidget * mg_create_generic_tab (char *name, char *title, int force_toplevel, int link_buttons, void *close_callback, void *userdata, int width, int height, GtkWidget **vbox_ret, void *family) { GtkWidget *vbox, *win; if (prefs.hex_gui_tab_pos == POS_HIDDEN && prefs.hex_gui_tab_utils) prefs.hex_gui_tab_utils = 0; if (force_toplevel || !prefs.hex_gui_tab_utils) { win = gtkutil_window_new (title, name, width, height, 2); vbox = gtk_vbox_new (0, 0); *vbox_ret = vbox; gtk_container_add (GTK_CONTAINER (win), vbox); gtk_widget_show (vbox); if (close_callback) g_signal_connect (G_OBJECT (win), "destroy", G_CALLBACK (close_callback), userdata); return win; } vbox = gtk_vbox_new (0, 2); g_object_set_data (G_OBJECT (vbox), "w", GINT_TO_POINTER (width)); g_object_set_data (G_OBJECT (vbox), "h", GINT_TO_POINTER (height)); gtk_container_set_border_width (GTK_CONTAINER (vbox), 3); *vbox_ret = vbox; if (close_callback) g_signal_connect (G_OBJECT (vbox), "destroy", G_CALLBACK (close_callback), userdata); mg_add_generic_tab (name, title, family, vbox); /* if (link_buttons) { hbox = gtk_hbox_new (FALSE, 0); gtk_box_pack_start (GTK_BOX (vbox), hbox, 0, 0, 0); mg_create_link_buttons (hbox, ch); gtk_widget_show (hbox); }*/ return vbox; } void mg_move_tab (session *sess, int delta) { if (sess->gui->is_tab) chan_move (sess->res->tab, delta); } void mg_move_tab_family (session *sess, int delta) { if (sess->gui->is_tab) chan_move_family (sess->res->tab, delta); } void mg_set_title (GtkWidget *vbox, char *title) /* for non-irc tab/window only */ { char *old; old = g_object_get_data (G_OBJECT (vbox), "title"); if (old) { g_object_set_data_full (G_OBJECT (vbox), "title", g_strdup (title), g_free); } else { gtk_window_set_title (GTK_WINDOW (vbox), title); } } void fe_server_callback (server *serv) { joind_close (serv); if (serv->gui->chanlist_window) mg_close_gen (NULL, serv->gui->chanlist_window); if (serv->gui->rawlog_window) mg_close_gen (NULL, serv->gui->rawlog_window); g_free (serv->gui); } /* called when a session is being killed */ void fe_session_callback (session *sess) { gtk_xtext_buffer_free (sess->res->buffer); g_object_unref (G_OBJECT (sess->res->user_model)); if (sess->res->banlist && sess->res->banlist->window) mg_close_gen (NULL, sess->res->banlist->window); g_free (sess->res->input_text); g_free (sess->res->topic_text); g_free (sess->res->limit_text); g_free (sess->res->key_text); g_free (sess->res->queue_text); g_free (sess->res->queue_tip); g_free (sess->res->lag_text); g_free (sess->res->lag_tip); if (sess->gui->bartag) fe_timeout_remove (sess->gui->bartag); if (sess->gui != &static_mg_gui) g_free (sess->gui); g_free (sess->res); } /* ===== DRAG AND DROP STUFF ===== */ static gboolean is_child_of (GtkWidget *widget, GtkWidget *parent) { while (widget) { if (gtk_widget_get_parent (widget) == parent) return TRUE; widget = gtk_widget_get_parent (widget); } return FALSE; } static void mg_handle_drop (GtkWidget *widget, int y, int *pos, int *other_pos) { int height; session_gui *gui = current_sess->gui; height = gdk_window_get_height (gtk_widget_get_window (widget)); if (y < height / 2) { if (is_child_of (widget, gui->vpane_left)) *pos = 1; /* top left */ else *pos = 3; /* top right */ } else { if (is_child_of (widget, gui->vpane_left)) *pos = 2; /* bottom left */ else *pos = 4; /* bottom right */ } /* both in the same pos? must move one */ if (*pos == *other_pos) { switch (*other_pos) { case 1: *other_pos = 2; break; case 2: *other_pos = 1; break; case 3: *other_pos = 4; break; case 4: *other_pos = 3; break; } } mg_place_userlist_and_chanview (gui); } static gboolean mg_is_gui_target (GdkDragContext *context) { char *target_name; if (!context || !gdk_drag_context_list_targets (context) || !gdk_drag_context_list_targets (context)->data) return FALSE; target_name = gdk_atom_name (gdk_drag_context_list_targets (context)->data); if (target_name) { /* if it's not HEXCHAT_CHANVIEW or HEXCHAT_USERLIST */ /* we should ignore it. */ if (target_name[0] != 'H') { g_free (target_name); return FALSE; } g_free (target_name); } return TRUE; } /* this begin callback just creates an nice of the source */ gboolean mg_drag_begin_cb (GtkWidget *widget, GdkDragContext *context, gpointer userdata) { int width, height; GdkColormap *cmap; GdkPixbuf *pix, *pix2; /* ignore file drops */ if (!mg_is_gui_target (context)) return FALSE; cmap = gtk_widget_get_colormap (widget); width = gdk_window_get_width (gtk_widget_get_window (widget)); height = gdk_window_get_height (gtk_widget_get_window (widget)); pix = gdk_pixbuf_get_from_drawable (NULL, gtk_widget_get_window (widget), cmap, 0, 0, 0, 0, width, height); pix2 = gdk_pixbuf_scale_simple (pix, width * 4 / 5, height / 2, GDK_INTERP_HYPER); g_object_unref (pix); gtk_drag_set_icon_pixbuf (context, pix2, 0, 0); g_object_set_data (G_OBJECT (widget), "ico", pix2); return TRUE; } void mg_drag_end_cb (GtkWidget *widget, GdkDragContext *context, gpointer userdata) { /* ignore file drops */ if (!mg_is_gui_target (context)) return; g_object_unref (g_object_get_data (G_OBJECT (widget), "ico")); } /* drop complete */ gboolean mg_drag_drop_cb (GtkWidget *widget, GdkDragContext *context, int x, int y, guint time, gpointer user_data) { /* ignore file drops */ if (!mg_is_gui_target (context)) return FALSE; switch (gdk_drag_context_get_selected_action (context)) { case GDK_ACTION_MOVE: /* from userlist */ mg_handle_drop (widget, y, &prefs.hex_gui_ulist_pos, &prefs.hex_gui_tab_pos); break; case GDK_ACTION_COPY: /* from tree - we use GDK_ACTION_COPY for the tree */ mg_handle_drop (widget, y, &prefs.hex_gui_tab_pos, &prefs.hex_gui_ulist_pos); break; default: return FALSE; } return TRUE; } /* draw highlight rectangle in the destination */ gboolean mg_drag_motion_cb (GtkWidget *widget, GdkDragContext *context, int x, int y, guint time, gpointer scbar) { GdkGC *gc; GdkColor col; GdkGCValues val; int half, width, height; int ox, oy; GdkDrawable *draw; GtkAllocation allocation; /* ignore file drops */ if (!mg_is_gui_target (context)) return FALSE; if (scbar) /* scrollbar */ { gtk_widget_get_allocation (widget, &allocation); ox = allocation.x; oy = allocation.y; width = allocation.width; height = allocation.height; draw = gtk_widget_get_window (widget); } else { ox = oy = 0; width = gdk_window_get_width (gtk_widget_get_window (widget)); height = gdk_window_get_height (gtk_widget_get_window (widget)); draw = gtk_widget_get_window (widget); } val.subwindow_mode = GDK_INCLUDE_INFERIORS; val.graphics_exposures = 0; val.function = GDK_XOR; gc = gdk_gc_new_with_values (gtk_widget_get_window (widget), &val, GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW | GDK_GC_FUNCTION); col.red = rand() % 0xffff; col.green = rand() % 0xffff; col.blue = rand() % 0xffff; gdk_colormap_alloc_color (gtk_widget_get_colormap (widget), &col, FALSE, TRUE); gdk_gc_set_foreground (gc, &col); half = height / 2; #if 0 /* are both tree/userlist on the same side? */ paned = (GtkPaned *)widget->parent->parent; if (paned->child1 != NULL && paned->child2 != NULL) { gdk_draw_rectangle (draw, gc, 0, 1, 2, width - 3, height - 4); gdk_draw_rectangle (draw, gc, 0, 0, 1, width - 1, height - 2); g_object_unref (gc); return TRUE; } #endif if (y < half) { gdk_draw_rectangle (draw, gc, FALSE, 1 + ox, 2 + oy, width - 3, half - 4); gdk_draw_rectangle (draw, gc, FALSE, 0 + ox, 1 + oy, width - 1, half - 2); gtk_widget_queue_draw_area (widget, ox, half + oy, width, height - half); } else { gdk_draw_rectangle (draw, gc, FALSE, 0 + ox, half + 1 + oy, width - 1, half - 2); gdk_draw_rectangle (draw, gc, FALSE, 1 + ox, half + 2 + oy, width - 3, half - 4); gtk_widget_queue_draw_area (widget, ox, oy, width, half); } g_object_unref (gc); return TRUE; }