summary refs log tree commit diff stats
path: root/src/fe-gtk/fe-gtk.c
AgeCommit message (Collapse)Author
2014-09-13Correctly open URLs with non-ASCII characters on Windows.Arnavion
Fixes #1023
2014-09-07Fix opening utf8 urls on OSXTingPing
- Escape hostnames with punycode - Use proper encoding when launching open
2014-05-27osx: Properly handle quittingTingPing
2014-04-02Add marker-line functionality for scrollback, instant seek.RichardHitt
Fixes #662.
2014-02-04osx: Set Menlo as default fontTingPing
This moves the find_font function to the front ends since it depends on pango and cairo
2014-01-30Now make_ping_time() in common/util.c returns the time in milliseconds insteadDiogo Sousa
of microseconds. In fe_set_lag(serv, lag) the lag argument is now in milliseconds as well. Fixes #758.
2014-01-20Enable --command on win32TingPing
and make existing depend on
2014-01-18Update most deprecated gtk functionsTingPing
2014-01-14Build with gtk-mac-integrationTingPing
- Use HexChat logo for dock icon - Use appmenu (not finished)
2013-10-22fix warningTingPing
2013-10-22Open channel list window on /listFarow
2013-10-09Fix messages with server-time not showing as new activityDiogo Sousa
Added a parameter to fe_print_text() to say that the message does not represent new activity. This is used when a log from an old session is loaded. This used to be implicit whenever the timestamp was non-zero, but with server-time this no longer makes sense. Fixes issue #746.
2013-10-03Remove gtkspell supportTingPing
2013-09-27Replace xtext's transparency with full window transparencyTingPing
Xtext's transparency barely worked on windows, didn't work on any modern linux wm and used fake transparency. This uses gtk's built in window opacity that works on more systems and is real transparency. Text area only transparency may return with a transition to cairo, if it works on Windows.
2013-09-20First round of using GTK accessor functionsTingPing
This is the first step to build with GSEAL_ENABLE setup.c uses a deprecated fontchooser, chanview-tabs.c didn't like the conversion, and I am waiting to do some work on xtext.c before converting it.
2013-08-31Improve file dialogsTingPing
- Fixes #314 - Adds file extensions to sound and image browsing - Some options no longer default to our config dir - Always add a shortcut to our config dir - Remove broken 'last_dir' functionality, gtk already knows recent
2013-07-27Tweak cli output and fix related warningsTingPing
2013-07-08Fix warningTingPing
2013-07-01Don't use popup dialogs for helpTingPing
2013-07-01Improve help messageTingPing
2013-07-01Handle extraneous cli args as urlsTingPing
2013-06-18fix merge conflictTingPing
2013-06-19Handle IPv6 addresses correcly in fe_open_url_locale().Diogo Sousa
2013-06-16Fix Open Data Folder on unixTingPing
Mentioned in #646
2013-06-12Don't open libcanberra connection for every eventTingPing
Closes #645
2013-05-11GtkComboBoxText requires GTK+ 2.24, dump code for older versionsBerke Viktor
2013-04-27Use regular canberra not gtkTingPing
2013-04-27More cleanupTingPing
2013-04-27Remove extra omitalerts checkTingPing
2013-04-27use libcanberra for beepsTingPing
2013-04-27Use "Instant Message Notification" system sound on Windows, use gdk_beep() ↵Berke Viktor
on Unix Note: gdk_beep() is expected to be replaced with libcanberra stuff.
2013-04-27Get rid of bundled beepBerke Viktor
2013-04-27typoTingPing
2013-04-27fix url opening on osxpre { 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 <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>

#include "fe-gtk.h"

#include "../common/hexchat.h"
#include "../common/notify.h"
#include "../common/cfgfiles.h"
#include "../common/fe.h"
#include "../common/server.h"
#include "../common/util.h"
#include "../common/userlist.h"
#include "../common/outbound.h"
#include "gtkutil.h"
#include "maingui.h"
#include "palette.h"
#include "notifygui.h"


/* model for the notify treeview */
enum
{
	USER_COLUMN,
	STATUS_COLUMN,
	SERVER_COLUMN,
	SEEN_COLUMN,
	COLOUR_COLUMN,
	NPS_COLUMN, 	/* struct notify_per_server * */
	N_COLUMNS
};


static GtkWidget *notify_window = 0;
static GtkWidget *notify_button_opendialog;
static GtkWidget *notify_button_remove;


static void
notify_closegui (void)
{
	notify_window = 0;
}

/* Need this to be able to set the foreground colour property of a row
 * from a GdkColor * in the model  -Vince
 */
static void
notify_treecell_property_mapper (GtkTreeViewColumn *col, GtkCellRenderer *cell,
                                 GtkTreeModel *model, GtkTreeIter *iter,
                                 gpointer data)
{
	gchar *text;
	GdkColor *colour;
	int model_column = GPOINTER_TO_INT (data);

	gtk_tree_model_get (GTK_TREE_MODEL (model), iter, 
	                    COLOUR_COLUMN, &colour,
	                    model_column, &text, -1);
	g_object_set (G_OBJECT (cell), "text", text, NULL);
	g_object_set (G_OBJECT (cell), "foreground-gdk", colour, NULL);
	g_free (text);
}

static void
notify_row_cb (GtkTreeSelection *sel, GtkTreeView *view)
{
	GtkTreeIter iter;
	struct notify_per_server *servnot;

	if (gtkutil_treeview_get_selected (view, &iter, NPS_COLUMN, &servnot, -1))
	{
		gtk_widget_set_sensitive (notify_button_opendialog, servnot ? servnot->ison : 0);
		gtk_widget_set_sensitive (notify_button_remove, TRUE);
		return;
	}

	gtk_widget_set_sensitive (notify_button_opendialog, FALSE);
	gtk_widget_set_sensitive (notify_button_remove, FALSE);
}

static GtkWidget *
notify_treeview_new (GtkWidget *box)
{
	GtkListStore *store;
	GtkWidget *view;
	GtkTreeViewColumn *col;
	int col_id;

	store = gtk_list_store_new (N_COLUMNS,
	                            G_TYPE_STRING,
	                            G_TYPE_STRING,
	                            G_TYPE_STRING,
	                            G_TYPE_STRING,
	                            G_TYPE_POINTER,	/* can't specify colour! */
										 G_TYPE_POINTER
	                           );
	g_return_val_if_fail (store != NULL, NULL);

	view = gtkutil_treeview_new (box, GTK_TREE_MODEL (store),
	                             notify_treecell_property_mapper,
	                             USER_COLUMN, _("Name"),
	                             STATUS_COLUMN, _("Status"),
	                             SERVER_COLUMN, _("Network"),
	                             SEEN_COLUMN, _("Last Seen"), -1);
	gtk_tree_view_column_set_expand (gtk_tree_view_get_column (GTK_TREE_VIEW (view), 0), TRUE);

	for (col_id=0; (col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), col_id));
	     col_id++)
			gtk_tree_view_column_set_alignment (col, 0.5);

	g_signal_connect (G_OBJECT (gtk_tree_view_get_selection (GTK_TREE_VIEW (view))),
							"changed", G_CALLBACK (notify_row_cb), view);

	gtk_widget_show (view);
	return view;
}

void
notify_gui_update (void)
{
	struct notify *notify;
	struct notify_per_server *servnot;
	GSList *list = notify_list;
	GSList *slist;
	gchar *name, *status, *server, *seen;
	int online, servcount;
	time_t lastseen;
	char agobuf[128];

	GtkListStore *store;
	GtkTreeView *view;
	GtkTreeIter iter;
	gboolean valid;	/* true if we don't need to append a new tree row */

	if (!notify_window)
		return;

	view = g_object_get_data (G_OBJECT (notify_window), "view");
	store = GTK_LIST_STORE (gtk_tree_view_get_model (view));
	valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter);

	while (list)
	{
		notify = (struct notify *) list->data;
		name = notify->name;
		status = _("Offline");
		server = "";

		online = FALSE;
		lastseen = 0;
		/* First see if they're online on any servers */
		slist = notify->server_list;
		while (slist)
		{
			servnot = (struct notify_per_server *) slist->data;
			if (servnot->ison)
				online = TRUE;
			if (servnot->lastseen > lastseen)
				lastseen = servnot->lastseen;
			slist = slist->next;
		}

		if (!online)				  /* Offline on all servers */
		{
			if (!lastseen)
				seen = _("Never");
			else
			{
				snprintf (agobuf, sizeof (agobuf), _("%d minutes ago"), (int)(time (0) - lastseen) / 60);
				seen = agobuf;
			}
			if (!valid)	/* create new tree row if required */
				gtk_list_store_append (store, &iter);
			gtk_list_store_set (store, &iter, 0, name, 1, status,
			                    2, server, 3, seen, 4, &colors[4], 5, NULL, -1);
			if (valid)
				valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter);

		} else
		{
			/* Online - add one line per server */
			servcount = 0;
			slist = notify->server_list;
			status = _("Online");
			while (slist)
			{
				servnot = (struct notify_per_server *) slist->data;
				if (servnot->ison)
				{
					if (servcount > 0)
						name = "";
					server = server_get_network (servnot->server, TRUE);

					snprintf (agobuf, sizeof (agobuf), _("%d minutes ago"), (int)(time (0) - lastseen) / 60);
					seen = agobuf;

					if (!valid)	/* create new tree row if required */
						gtk_list_store_append (store, &iter);
					gtk_list_store_set (store, &iter, 0, name, 1, status,
					                    2, server, 3, seen, 4, &colors[3], 5, servnot, -1);
					if (valid)
						valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter);

					servcount++;
				}
				slist = slist->next;
			}
		}
		
		list = list->next;
	}

	while (valid)
	{
		GtkTreeIter old = iter;
		/* get next iter now because removing invalidates old one */
		valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (store),
                                      &iter);
		gtk_list_store_remove (store, &old);
	}
}

static void
notify_opendialog_clicked (GtkWidget * igad)
{
	GtkTreeView *view;
	GtkTreeIter iter;
	struct notify_per_server *servnot;

	view = g_object_get_data (G_OBJECT (notify_window), "view");
	if (gtkutil_treeview_get_selected (view, &iter, NPS_COLUMN, &servnot, -1))
	{
		if (servnot)
			open_query (servnot->server, servnot->notify->name, TRUE);
	}
}

static void
notify_remove_clicked (GtkWidget * igad)
{
	GtkTreeView *view;
	GtkTreeModel *model;
	GtkTreeIter iter;
	GtkTreePath *path = NULL;
	gboolean found = FALSE;
	char *name;

	view = g_object_get_data (G_OBJECT (notify_window), "view");
	if (gtkutil_treeview_get_selected (view, &iter, USER_COLUMN, &name, -1))
	{
		model = gtk_tree_view_get_model (view);
		found = (*name != 0);
		while (!found)	/* the real nick is some previous node */
		{
			g_free (name); /* it's useless to us */
			if (!path)
				path = gtk_tree_model_get_path (model, &iter);
			if (!gtk_tree_path_prev (path))	/* arrgh! no previous node! */
			{
				g_warning ("notify list state is invalid\n");
				break;
			}
			if (!gtk_tree_model_get_iter (model, &iter, path))
				break;
			gtk_tree_model_get (model, &iter, USER_COLUMN, &name, -1);
			found = (*name != 0);
		}
		if (path)
			gtk_tree_path_free (path);
		if (!found)
			return;

		/* ok, now we can remove it */
		gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
		notify_deluser (name);
		g_free (name);
	}
}

static void
notifygui_add_cb (GtkDialog *dialog, gint response, gpointer entry)
{
	char *networks;
	char *text;

	text = GTK_ENTRY (entry)->text;
	if (text[0] && response == GTK_RESPONSE_ACCEPT)
	{
		networks = GTK_ENTRY (g_object_get_data (G_OBJECT (entry), "net"))->text;
		if (g_ascii_strcasecmp (networks, "ALL") == 0 || networks[0] == 0)
			notify_adduser (text, NULL);
		else
			notify_adduser (text, networks);
	}

	gtk_widget_destroy (GTK_WIDGET (dialog));
}

static void
notifygui_add_enter (GtkWidget *entry, GtkWidget *dialog)
{
	gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
}

void
fe_notify_ask (char *nick, char *networks)
{
	GtkWidget *dialog;
	GtkWidget *entry;
	GtkWidget *label;
	GtkWidget *wid;
	GtkWidget *table;
	char *msg = _("Enter nickname to add:");
	char buf[256];

	dialog = gtk_dialog_new_with_buttons (msg, NULL, 0,
										GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
										GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
										NULL);
	if (parent_window)
		gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent_window));
	gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);

	table = gtk_table_new (2, 3, FALSE);
	gtk_container_set_border_width (GTK_CONTAINER (table), 12);
	gtk_table_set_row_spacings (GTK_TABLE (table), 3);
	gtk_table_set_col_spacings (GTK_TABLE (table), 8);
	gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), table);

	label = gtk_label_new (msg);
	gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, 0, 1);

	entry = gtk_entry_new ();
	gtk_entry_set_text (GTK_ENTRY (entry), nick);
	g_signal_connect (G_OBJECT (entry), "activate",
						 	G_CALLBACK (notifygui_add_enter), dialog);
	gtk_table_attach_defaults (GTK_TABLE (table), entry, 1, 2, 0, 1);

	g_signal_connect (G_OBJECT (dialog), "response",
						   G_CALLBACK (notifygui_add_cb), entry);

	label = gtk_label_new (_("Notify on these networks:"));
	gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, 2, 3);

	wid = gtk_entry_new ();
	g_object_set_data (G_OBJECT (entry), "net", wid);
	g_signal_connect (G_OBJECT (wid), "activate",
						 	G_CALLBACK (notifygui_add_enter), dialog);
	gtk_entry_set_text (GTK_ENTRY (wid), networks ? networks : "ALL");
	gtk_table_attach_defaults (GTK_TABLE (table), wid, 1, 2, 2, 3);

	label = gtk_label_new (NULL);
	snprintf (buf, sizeof (buf), "<i><span size=\"smaller\">%s</span></i>", _("Comma separated list of networks is accepted."));
	gtk_label_set_markup (GTK_LABEL (label), buf);
	gtk_table_attach_defaults (GTK_TABLE (table), label, 1, 2, 3, 4);

	gtk_widget_show_all (dialog);
}

static void
notify_add_clicked (GtkWidget * igad)
{
	fe_notify_ask ("", NULL);
}

void
notify_opengui (void)
{
	GtkWidget *vbox, *bbox;
	GtkWidget *view;

	if (notify_window)
	{
		mg_bring_tofront (notify_window);
		return;
	}

	notify_window =
		mg_create_generic_tab ("Notify", _(DISPLAY_NAME": Friends List"), FALSE, TRUE,
		                       notify_closegui, NULL, 400, 250, &vbox, 0);
	gtkutil_destroy_on_esc (notify_window);

	view = notify_treeview_new (vbox);
	g_object_set_data (G_OBJECT (notify_window), "view", view);
  
	bbox = gtk_hbutton_box_new ();
	gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_SPREAD);
	gtk_container_set_border_width (GTK_CONTAINER (bbox), 5);
	gtk_box_pack_end (GTK_BOX (vbox), bbox, 0, 0, 0);
	gtk_widget_show (bbox);

	gtkutil_button (bbox, GTK_STOCK_NEW, 0, notify_add_clicked, 0,
	                _("Add..."));

	notify_button_remove =
	gtkutil_button (bbox, GTK_STOCK_DELETE, 0, notify_remove_clicked, 0,
	                _("Remove"));

	notify_button_opendialog =
	gtkutil_button (bbox, NULL, 0, notify_opendialog_clicked, 0,
	                _("Open Dialog"));

	gtk_widget_set_sensitive (notify_button_opendialog, FALSE);
	gtk_widget_set_sensitive (notify_button_remove, FALSE);

	notify_gui_update ();

	gtk_widget_show (notify_window);
}