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