1 /* X-Chat
2  * Copyright (C) 1998 Peter Zelezny.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17  */
18 
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <fcntl.h>
23 #include <time.h>
24 
25 #include "fe-gtk.h"
26 
27 #include "../common/hexchat.h"
28 #include "../common/notify.h"
29 #include "../common/cfgfiles.h"
30 #include "../common/fe.h"
31 #include "../common/server.h"
32 #include "../common/util.h"
33 #include "../common/userlist.h"
34 #include "../common/outbound.h"
35 #include "gtkutil.h"
36 #include "maingui.h"
37 #include "palette.h"
38 #include "notifygui.h"
39 
40 
41 /* model for the notify treeview */
42 enum
43 {
44 	USER_COLUMN,
45 	STATUS_COLUMN,
46 	SERVER_COLUMN,
47 	SEEN_COLUMN,
48 	COLOUR_COLUMN,
49 	NPS_COLUMN, 	/* struct notify_per_server * */
50 	N_COLUMNS
51 };
52 
53 
54 static GtkWidget *notify_window = 0;
55 static GtkWidget *notify_button_opendialog;
56 static GtkWidget *notify_button_remove;
57 
58 
59 static void
notify_closegui(void)60 notify_closegui (void)
61 {
62 	notify_window = 0;
63 	notify_save ();
64 }
65 
66 /* Need this to be able to set the foreground colour property of a row
67  * from a GdkColor * in the model  -Vince
68  */
69 static void
notify_treecell_property_mapper(GtkTreeViewColumn * col,GtkCellRenderer * cell,GtkTreeModel * model,GtkTreeIter * iter,gpointer data)70 notify_treecell_property_mapper (GtkTreeViewColumn *col, GtkCellRenderer *cell,
71                                  GtkTreeModel *model, GtkTreeIter *iter,
72                                  gpointer data)
73 {
74 	gchar *text;
75 	GdkColor *colour;
76 	int model_column = GPOINTER_TO_INT (data);
77 
78 	gtk_tree_model_get (GTK_TREE_MODEL (model), iter,
79 	                    COLOUR_COLUMN, &colour,
80 	                    model_column, &text, -1);
81 	g_object_set (G_OBJECT (cell), "text", text, NULL);
82 	g_object_set (G_OBJECT (cell), "foreground-gdk", colour, NULL);
83 	g_free (text);
84 }
85 
86 static void
notify_row_cb(GtkTreeSelection * sel,GtkTreeView * view)87 notify_row_cb (GtkTreeSelection *sel, GtkTreeView *view)
88 {
89 	GtkTreeIter iter;
90 	struct notify_per_server *servnot;
91 
92 	if (gtkutil_treeview_get_selected (view, &iter, NPS_COLUMN, &servnot, -1))
93 	{
94 		gtk_widget_set_sensitive (notify_button_opendialog, servnot ? servnot->ison : 0);
95 		gtk_widget_set_sensitive (notify_button_remove, TRUE);
96 		return;
97 	}
98 
99 	gtk_widget_set_sensitive (notify_button_opendialog, FALSE);
100 	gtk_widget_set_sensitive (notify_button_remove, FALSE);
101 }
102 
103 static GtkWidget *
notify_treeview_new(GtkWidget * box)104 notify_treeview_new (GtkWidget *box)
105 {
106 	GtkListStore *store;
107 	GtkWidget *view;
108 	GtkTreeViewColumn *col;
109 	int col_id;
110 
111 	store = gtk_list_store_new (N_COLUMNS,
112 	                            G_TYPE_STRING,
113 	                            G_TYPE_STRING,
114 	                            G_TYPE_STRING,
115 	                            G_TYPE_STRING,
116 	                            G_TYPE_POINTER,	/* can't specify colour! */
117 										 G_TYPE_POINTER
118 	                           );
119 	g_return_val_if_fail (store != NULL, NULL);
120 
121 	view = gtkutil_treeview_new (box, GTK_TREE_MODEL (store),
122 	                             notify_treecell_property_mapper,
123 	                             USER_COLUMN, _("Name"),
124 	                             STATUS_COLUMN, _("Status"),
125 	                             SERVER_COLUMN, _("Network"),
126 	                             SEEN_COLUMN, _("Last Seen"), -1);
127 	gtk_tree_view_column_set_expand (gtk_tree_view_get_column (GTK_TREE_VIEW (view), 0), TRUE);
128 
129 	for (col_id=0; (col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), col_id));
130 	     col_id++)
131 			gtk_tree_view_column_set_alignment (col, 0.5);
132 
133 	g_signal_connect (G_OBJECT (gtk_tree_view_get_selection (GTK_TREE_VIEW (view))),
134 							"changed", G_CALLBACK (notify_row_cb), view);
135 
136 	gtk_widget_show (view);
137 	return view;
138 }
139 
140 void
notify_gui_update(void)141 notify_gui_update (void)
142 {
143 	struct notify *notify;
144 	struct notify_per_server *servnot;
145 	GSList *list = notify_list;
146 	GSList *slist;
147 	gchar *name, *status, *server, *seen;
148 	int online, servcount, lastseenminutes;
149 	time_t lastseen;
150 	char agobuf[128];
151 
152 	GtkListStore *store;
153 	GtkTreeView *view;
154 	GtkTreeIter iter;
155 	gboolean valid;	/* true if we don't need to append a new tree row */
156 
157 	if (!notify_window)
158 		return;
159 
160 	view = g_object_get_data (G_OBJECT (notify_window), "view");
161 	store = GTK_LIST_STORE (gtk_tree_view_get_model (view));
162 	valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter);
163 
164 	while (list)
165 	{
166 		notify = (struct notify *) list->data;
167 		name = notify->name;
168 		status = _("Offline");
169 		server = "";
170 
171 		online = FALSE;
172 		lastseen = 0;
173 		/* First see if they're online on any servers */
174 		slist = notify->server_list;
175 		while (slist)
176 		{
177 			servnot = (struct notify_per_server *) slist->data;
178 			if (servnot->ison)
179 				online = TRUE;
180 			if (servnot->lastseen > lastseen)
181 				lastseen = servnot->lastseen;
182 			slist = slist->next;
183 		}
184 
185 		if (!online)				  /* Offline on all servers */
186 		{
187 			if (!lastseen)
188 				seen = _("Never");
189 			else
190 			{
191 				lastseenminutes = (int)(time (0) - lastseen) / 60;
192 				if (lastseenminutes < 60)
193 					g_snprintf (agobuf, sizeof (agobuf), _("%d minutes ago"), lastseenminutes);
194 				else if (lastseenminutes < 120)
195 					g_snprintf (agobuf, sizeof (agobuf), _("An hour ago"));
196 				else
197 					g_snprintf (agobuf, sizeof (agobuf), _("%d hours ago"), lastseenminutes / 60);
198 				seen = agobuf;
199 			}
200 			if (!valid)	/* create new tree row if required */
201 				gtk_list_store_append (store, &iter);
202 			gtk_list_store_set (store, &iter, 0, name, 1, status,
203 			                    2, server, 3, seen, 4, &colors[4], 5, NULL, -1);
204 			if (valid)
205 				valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter);
206 
207 		} else
208 		{
209 			/* Online - add one line per server */
210 			servcount = 0;
211 			slist = notify->server_list;
212 			status = _("Online");
213 			while (slist)
214 			{
215 				servnot = (struct notify_per_server *) slist->data;
216 				if (servnot->ison)
217 				{
218 					if (servcount > 0)
219 						name = "";
220 					server = server_get_network (servnot->server, TRUE);
221 
222 					g_snprintf (agobuf, sizeof (agobuf), _("%d minutes ago"), (int)(time (0) - lastseen) / 60);
223 					seen = agobuf;
224 
225 					if (!valid)	/* create new tree row if required */
226 						gtk_list_store_append (store, &iter);
227 					gtk_list_store_set (store, &iter, 0, name, 1, status,
228 					                    2, server, 3, seen, 4, &colors[3], 5, servnot, -1);
229 					if (valid)
230 						valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter);
231 
232 					servcount++;
233 				}
234 				slist = slist->next;
235 			}
236 		}
237 
238 		list = list->next;
239 	}
240 
241 	while (valid)
242 	{
243 		GtkTreeIter old = iter;
244 		/* get next iter now because removing invalidates old one */
245 		valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (store),
246                                       &iter);
247 		gtk_list_store_remove (store, &old);
248 	}
249 
250 	notify_row_cb (gtk_tree_view_get_selection (view), view);
251 }
252 
253 static void
notify_opendialog_clicked(GtkWidget * igad)254 notify_opendialog_clicked (GtkWidget * igad)
255 {
256 	GtkTreeView *view;
257 	GtkTreeIter iter;
258 	struct notify_per_server *servnot;
259 
260 	view = g_object_get_data (G_OBJECT (notify_window), "view");
261 	if (gtkutil_treeview_get_selected (view, &iter, NPS_COLUMN, &servnot, -1))
262 	{
263 		if (servnot)
264 			open_query (servnot->server, servnot->notify->name, TRUE);
265 	}
266 }
267 
268 static void
notify_remove_clicked(GtkWidget * igad)269 notify_remove_clicked (GtkWidget * igad)
270 {
271 	GtkTreeView *view;
272 	GtkTreeModel *model;
273 	GtkTreeIter iter;
274 	GtkTreePath *path = NULL;
275 	gboolean found = FALSE;
276 	char *name;
277 
278 	view = g_object_get_data (G_OBJECT (notify_window), "view");
279 	if (gtkutil_treeview_get_selected (view, &iter, USER_COLUMN, &name, -1))
280 	{
281 		model = gtk_tree_view_get_model (view);
282 		found = (*name != 0);
283 		while (!found)	/* the real nick is some previous node */
284 		{
285 			g_free (name); /* it's useless to us */
286 			if (!path)
287 				path = gtk_tree_model_get_path (model, &iter);
288 			if (!gtk_tree_path_prev (path))	/* arrgh! no previous node! */
289 			{
290 				g_warning ("notify list state is invalid\n");
291 				break;
292 			}
293 			if (!gtk_tree_model_get_iter (model, &iter, path))
294 				break;
295 			gtk_tree_model_get (model, &iter, USER_COLUMN, &name, -1);
296 			found = (*name != 0);
297 		}
298 		if (path)
299 			gtk_tree_path_free (path);
300 		if (!found)
301 			return;
302 
303 		/* ok, now we can remove it */
304 		gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
305 		notify_deluser (name);
306 		g_free (name);
307 	}
308 }
309 
310 static void
notifygui_add_cb(GtkDialog * dialog,gint response,gpointer entry)311 notifygui_add_cb (GtkDialog *dialog, gint response, gpointer entry)
312 {
313 	char *networks;
314 	char *text;
315 
316 	text = (char *)gtk_entry_get_text (GTK_ENTRY (entry));
317 	if (text[0] && response == GTK_RESPONSE_ACCEPT)
318 	{
319 		networks = (char*)gtk_entry_get_text (GTK_ENTRY (g_object_get_data (G_OBJECT (entry), "net")));
320 		if (g_ascii_strcasecmp (networks, "ALL") == 0 || networks[0] == 0)
321 			notify_adduser (text, NULL);
322 		else
323 			notify_adduser (text, networks);
324 	}
325 
326 	gtk_widget_destroy (GTK_WIDGET (dialog));
327 }
328 
329 static void
notifygui_add_enter(GtkWidget * entry,GtkWidget * dialog)330 notifygui_add_enter (GtkWidget *entry, GtkWidget *dialog)
331 {
332 	gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
333 }
334 
335 void
fe_notify_ask(char * nick,char * networks)336 fe_notify_ask (char *nick, char *networks)
337 {
338 	GtkWidget *dialog;
339 	GtkWidget *entry;
340 	GtkWidget *label;
341 	GtkWidget *wid;
342 	GtkWidget *table;
343 	char *msg = _("Enter nickname to add:");
344 	char buf[256];
345 
346 	dialog = gtk_dialog_new_with_buttons (msg, NULL, 0,
347 										GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
348 										GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
349 										NULL);
350 	if (parent_window)
351 		gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent_window));
352 	gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
353 
354 	table = gtk_table_new (2, 3, FALSE);
355 	gtk_container_set_border_width (GTK_CONTAINER (table), 12);
356 	gtk_table_set_row_spacings (GTK_TABLE (table), 3);
357 	gtk_table_set_col_spacings (GTK_TABLE (table), 8);
358 	gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), table);
359 
360 	label = gtk_label_new (msg);
361 	gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, 0, 1);
362 
363 	entry = gtk_entry_new ();
364 	gtk_entry_set_text (GTK_ENTRY (entry), nick);
365 	g_signal_connect (G_OBJECT (entry), "activate",
366 						 	G_CALLBACK (notifygui_add_enter), dialog);
367 	gtk_table_attach_defaults (GTK_TABLE (table), entry, 1, 2, 0, 1);
368 
369 	g_signal_connect (G_OBJECT (dialog), "response",
370 						   G_CALLBACK (notifygui_add_cb), entry);
371 
372 	label = gtk_label_new (_("Notify on these networks:"));
373 	gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, 2, 3);
374 
375 	wid = gtk_entry_new ();
376 	g_object_set_data (G_OBJECT (entry), "net", wid);
377 	g_signal_connect (G_OBJECT (wid), "activate",
378 						 	G_CALLBACK (notifygui_add_enter), dialog);
379 	gtk_entry_set_text (GTK_ENTRY (wid), networks ? networks : "ALL");
380 	gtk_table_attach_defaults (GTK_TABLE (table), wid, 1, 2, 2, 3);
381 
382 	label = gtk_label_new (NULL);
383 	g_snprintf (buf, sizeof (buf), "<i><span size=\"smaller\">%s</span></i>", _("Comma separated list of networks is accepted."));
384 	gtk_label_set_markup (GTK_LABEL (label), buf);
385 	gtk_table_attach_defaults (GTK_TABLE (table), label, 1, 2, 3, 4);
386 
387 	gtk_widget_show_all (dialog);
388 }
389 
390 static void
notify_add_clicked(GtkWidget * igad)391 notify_add_clicked (GtkWidget * igad)
392 {
393 	fe_notify_ask ("", NULL);
394 }
395 
396 void
notify_opengui(void)397 notify_opengui (void)
398 {
399 	GtkWidget *vbox, *bbox;
400 	GtkWidget *view;
401 	char buf[128];
402 
403 	if (notify_window)
404 	{
405 		mg_bring_tofront (notify_window);
406 		return;
407 	}
408 
409 	g_snprintf(buf, sizeof(buf), _("Friends List - %s"), _(DISPLAY_NAME));
410 	notify_window =
411 		mg_create_generic_tab ("Notify", buf, FALSE, TRUE, notify_closegui, NULL, 400,
412 								250, &vbox, 0);
413 	gtkutil_destroy_on_esc (notify_window);
414 
415 	view = notify_treeview_new (vbox);
416 	g_object_set_data (G_OBJECT (notify_window), "view", view);
417 
418 	bbox = gtk_hbutton_box_new ();
419 	gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_SPREAD);
420 	gtk_container_set_border_width (GTK_CONTAINER (bbox), 5);
421 	gtk_box_pack_end (GTK_BOX (vbox), bbox, 0, 0, 0);
422 	gtk_widget_show (bbox);
423 
424 	gtkutil_button (bbox, GTK_STOCK_NEW, 0, notify_add_clicked, 0,
425 	                _("Add..."));
426 
427 	notify_button_remove =
428 	gtkutil_button (bbox, GTK_STOCK_DELETE, 0, notify_remove_clicked, 0,
429 	                _("Remove"));
430 
431 	notify_button_opendialog =
432 	gtkutil_button (bbox, NULL, 0, notify_opendialog_clicked, 0,
433 	                _("Open Dialog"));
434 
435 	gtk_widget_set_sensitive (notify_button_opendialog, FALSE);
436 	gtk_widget_set_sensitive (notify_button_remove, FALSE);
437 
438 	notify_gui_update ();
439 
440 	gtk_widget_show (notify_window);
441 }
442