1 /**
2  * @file gtknotify.c GTK+ Notification API
3  * @ingroup pidgin
4  */
5 
6 /* pidgin
7  *
8  * Pidgin is the legal property of its developers, whose names are too numerous
9  * to list here.  Please refer to the COPYRIGHT file distributed with this
10  * source distribution.
11  *
12  * This program is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or
15  * (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software
24  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
25  */
26 #include "internal.h"
27 #include "pidgin.h"
28 
29 #include <gdk/gdkkeysyms.h>
30 
31 #include "account.h"
32 #include "connection.h"
33 #include "debug.h"
34 #include "glibcompat.h"
35 #include "prefs.h"
36 #include "pidginstock.h"
37 #include "util.h"
38 
39 #include "gtkblist.h"
40 #include "gtkimhtml.h"
41 #include "gtknotify.h"
42 #include "gtkpounce.h"
43 #include "gtkutils.h"
44 
45 typedef struct
46 {
47 	GtkWidget *window;
48 	int count;
49 } PidginUserInfo;
50 
51 typedef struct
52 {
53 	PurpleAccount *account;
54 	char *url;
55 	GtkWidget *label;
56 	int count;
57 	gboolean purple_has_handle;
58 } PidginNotifyMailData;
59 
60 typedef struct
61 {
62 	PurpleAccount *account;
63 	PurplePounce *pounce;
64 	char *pouncee;
65 } PidginNotifyPounceData;
66 
67 
68 typedef struct
69 {
70 	PurpleAccount *account;
71 	GtkListStore *model;
72 	GtkWidget *treeview;
73 	GtkWidget *window;
74 	gpointer user_data;
75 	PurpleNotifySearchResults *results;
76 
77 } PidginNotifySearchResultsData;
78 
79 typedef struct
80 {
81 	PurpleNotifySearchButton *button;
82 	PidginNotifySearchResultsData *data;
83 
84 } PidginNotifySearchResultsButtonData;
85 
86 enum
87 {
88 	PIDGIN_MAIL_ICON,
89 	PIDGIN_MAIL_TEXT,
90 	PIDGIN_MAIL_DATA,
91 	COLUMNS_PIDGIN_MAIL
92 };
93 
94 enum
95 {
96 	PIDGIN_POUNCE_ICON,
97 	PIDGIN_POUNCE_ALIAS,
98 	PIDGIN_POUNCE_EVENT,
99 	PIDGIN_POUNCE_TEXT,
100 	PIDGIN_POUNCE_DATE,
101 	PIDGIN_POUNCE_DATA,
102 	COLUMNS_PIDGIN_POUNCE
103 };
104 
105 
106 typedef struct _PidginNotifyDialog
107 {
108 	/*
109 	 * This must be first so PidginNotifyDialog can masquerade as the
110 	 * dialog widget.
111 	 */
112 	GtkWidget *dialog;
113 	GtkWidget *treeview;
114 	GtkTreeStore *treemodel;
115 	GtkLabel *label;
116 	GtkWidget *open_button;
117 	GtkWidget *dismiss_button;
118 	GtkWidget *edit_button;
119 	int total_count;
120 	gboolean in_use;
121 } PidginNotifyDialog;
122 
123 typedef enum
124 {
125 	PIDGIN_NOTIFY_MAIL,
126 	PIDGIN_NOTIFY_POUNCE,
127 	PIDGIN_NOTIFY_TYPES
128 } PidginNotifyType;
129 
130 static PidginNotifyDialog *mail_dialog = NULL;
131 static PidginNotifyDialog *pounce_dialog = NULL;
132 
133 static PidginNotifyDialog *pidgin_create_notification_dialog(PidginNotifyType type);
134 static void *pidgin_notify_emails(PurpleConnection *gc, size_t count, gboolean detailed,
135 									const char **subjects,
136 									const char **froms, const char **tos,
137 									const char **urls);
138 
139 static void pidgin_close_notify(PurpleNotifyType type, void *ui_handle);
140 
141 static void
message_response_cb(GtkDialog * dialog,gint id,GtkWidget * widget)142 message_response_cb(GtkDialog *dialog, gint id, GtkWidget *widget)
143 {
144 	purple_notify_close(PURPLE_NOTIFY_MESSAGE, widget);
145 }
146 
147 static void
pounce_response_close(PidginNotifyDialog * dialog)148 pounce_response_close(PidginNotifyDialog *dialog)
149 {
150 	GtkTreeIter iter;
151 	PidginNotifyPounceData *pounce_data;
152 
153 	while (gtk_tree_model_get_iter_first(
154 				GTK_TREE_MODEL(pounce_dialog->treemodel), &iter)) {
155 		gtk_tree_model_get(GTK_TREE_MODEL(pounce_dialog->treemodel), &iter,
156 				PIDGIN_POUNCE_DATA, &pounce_data,
157 				-1);
158 		gtk_tree_store_remove(dialog->treemodel, &iter);
159 
160 		g_free(pounce_data->pouncee);
161 		g_free(pounce_data);
162 	}
163 
164 	gtk_widget_destroy(pounce_dialog->dialog);
165 	g_free(pounce_dialog);
166 	pounce_dialog = NULL;
167 }
168 
169 static void
delete_foreach(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)170 delete_foreach(GtkTreeModel *model, GtkTreePath *path,
171 		GtkTreeIter *iter, gpointer data)
172 {
173 	PidginNotifyPounceData *pounce_data;
174 
175 	gtk_tree_model_get(model, iter,
176 			PIDGIN_POUNCE_DATA, &pounce_data,
177 			-1);
178 
179 	if (pounce_data != NULL) {
180 		g_free(pounce_data->pouncee);
181 		g_free(pounce_data);
182 	}
183 }
184 
185 static void
open_im_foreach(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)186 open_im_foreach(GtkTreeModel *model, GtkTreePath *path,
187 		GtkTreeIter *iter, gpointer data)
188 {
189 	PidginNotifyPounceData *pounce_data;
190 
191 	gtk_tree_model_get(model, iter,
192 			PIDGIN_POUNCE_DATA, &pounce_data,
193 			-1);
194 
195 	if (pounce_data != NULL) {
196 		PurpleConversation *conv;
197 
198 		conv = purple_conversation_new(PURPLE_CONV_TYPE_IM,
199 				pounce_data->account, pounce_data->pouncee);
200 		purple_conversation_present(conv);
201 	}
202 }
203 
204 static void
append_to_list(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)205 append_to_list(GtkTreeModel *model, GtkTreePath *path,
206 		GtkTreeIter *iter, gpointer data)
207 {
208 	GList **list = data;
209 	*list = g_list_prepend(*list, gtk_tree_path_copy(path));
210 }
211 
212 static void
pounce_response_dismiss()213 pounce_response_dismiss()
214 {
215 	GtkTreeModel *model = GTK_TREE_MODEL(pounce_dialog->treemodel);
216 	GtkTreeSelection *selection;
217 	GtkTreeIter iter;
218 	GtkTreeIter new_selection;
219 	GList *list = NULL;
220 	gboolean found_selection = FALSE;
221 
222 	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(pounce_dialog->treeview));
223 	gtk_tree_selection_selected_foreach(selection, delete_foreach, pounce_dialog);
224 	gtk_tree_selection_selected_foreach(selection, append_to_list, &list);
225 
226 	g_return_if_fail(list != NULL);
227 
228 	if (list->next == NULL) {
229 		gtk_tree_model_get_iter(model, &new_selection, list->data);
230 		if (gtk_tree_model_iter_next(model, &new_selection))
231 			found_selection = TRUE;
232 		else {
233 			/* This is the last thing in the list */
234 			GtkTreePath *path;
235 
236 			/* Because gtk_tree_model_iter_prev doesn't exist... */
237 			gtk_tree_model_get_iter(model, &new_selection, list->data);
238 			path = gtk_tree_model_get_path(model, &new_selection);
239 			if (gtk_tree_path_prev(path)) {
240 				gtk_tree_model_get_iter(model, &new_selection, path);
241 				found_selection = TRUE;
242 			}
243 
244 			gtk_tree_path_free(path);
245 		}
246 	}
247 
248 	while (list) {
249 		if (gtk_tree_model_get_iter(model, &iter, list->data)) {
250 			gtk_tree_store_remove(GTK_TREE_STORE(pounce_dialog->treemodel), &iter);
251 		}
252 		gtk_tree_path_free(list->data);
253 		list = g_list_delete_link(list, list);
254 	}
255 
256 	if (gtk_tree_model_get_iter_first(model, &iter)) {
257 		if (found_selection)
258 			gtk_tree_selection_select_iter(selection, &new_selection);
259 		else
260 			gtk_tree_selection_select_iter(selection, &iter);
261 	} else
262 		pounce_response_close(pounce_dialog);
263 }
264 
265 static void
pounce_response_open_ims()266 pounce_response_open_ims()
267 {
268 	GtkTreeSelection *selection;
269 
270 	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(pounce_dialog->treeview));
271 	gtk_tree_selection_selected_foreach(selection, open_im_foreach, pounce_dialog);
272 
273 	pounce_response_dismiss();
274 }
275 
276 static void
pounce_response_edit_cb(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)277 pounce_response_edit_cb(GtkTreeModel *model, GtkTreePath *path,
278 		GtkTreeIter *iter, gpointer data)
279 {
280 	PidginNotifyPounceData *pounce_data;
281 	PidginNotifyDialog *dialog = (PidginNotifyDialog*)data;
282 	PurplePounce *pounce;
283 	GList *list;
284 
285 	list = purple_pounces_get_all();
286 
287 	gtk_tree_model_get(GTK_TREE_MODEL(dialog->treemodel), iter,
288 			PIDGIN_POUNCE_DATA, &pounce_data,
289 			-1);
290 
291 	for (; list != NULL; list = list->next) {
292 		pounce = list->data;
293 		if (pounce == pounce_data->pounce) {
294 			pidgin_pounce_editor_show(pounce_data->account, NULL, pounce_data->pounce);
295 			return;
296 		}
297 	}
298 
299 	purple_debug_warning("gtknotify", "Pounce was destroyed.\n");
300 }
301 
302 static void
pounce_response_cb(GtkDialog * dlg,gint id,PidginNotifyDialog * dialog)303 pounce_response_cb(GtkDialog *dlg, gint id, PidginNotifyDialog *dialog)
304 {
305 	GtkTreeSelection *selection = NULL;
306 
307 	switch (id) {
308 		case GTK_RESPONSE_CLOSE:
309 		case GTK_RESPONSE_DELETE_EVENT:
310 			pounce_response_close(dialog);
311 			break;
312 		case GTK_RESPONSE_YES:
313 			pounce_response_open_ims();
314 			break;
315 		case GTK_RESPONSE_NO:
316 			pounce_response_dismiss();
317 			break;
318 		case GTK_RESPONSE_APPLY:
319 			selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->treeview));
320 			gtk_tree_selection_selected_foreach(selection, pounce_response_edit_cb,
321 					dialog);
322 			break;
323 	}
324 }
325 
326 static void
pounce_row_selected_cb(GtkTreeView * tv,GtkTreePath * path,GtkTreeViewColumn * col,gpointer data)327 pounce_row_selected_cb(GtkTreeView *tv, GtkTreePath *path,
328 	GtkTreeViewColumn *col, gpointer data)
329 {
330 	GtkTreeSelection *selection;
331 	int count;
332 
333 	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(pounce_dialog->treeview));
334 
335 	count = gtk_tree_selection_count_selected_rows(selection);
336 
337 	if (count == 0) {
338 		gtk_widget_set_sensitive(pounce_dialog->open_button, FALSE);
339 		gtk_widget_set_sensitive(pounce_dialog->edit_button, FALSE);
340 		gtk_widget_set_sensitive(pounce_dialog->dismiss_button, FALSE);
341 	} else if (count == 1) {
342 		GList *pounces;
343 		GList *list;
344 		PidginNotifyPounceData *pounce_data;
345 		GtkTreeIter iter;
346 
347 		list = gtk_tree_selection_get_selected_rows(selection, NULL);
348 		gtk_tree_model_get_iter(GTK_TREE_MODEL(pounce_dialog->treemodel),
349 				&iter, list->data);
350 		gtk_tree_model_get(GTK_TREE_MODEL(pounce_dialog->treemodel), &iter,
351 				PIDGIN_POUNCE_DATA, &pounce_data,
352 				-1);
353 		g_list_free_full(list, (GDestroyNotify)gtk_tree_path_free);
354 
355 		pounces = purple_pounces_get_all();
356 		for (; pounces != NULL; pounces = pounces->next) {
357 			PurplePounce *pounce = pounces->data;
358 			if (pounce == pounce_data->pounce) {
359 				gtk_widget_set_sensitive(pounce_dialog->edit_button, TRUE);
360 				break;
361 			}
362 		}
363 
364 		gtk_widget_set_sensitive(pounce_dialog->open_button, TRUE);
365 		gtk_widget_set_sensitive(pounce_dialog->dismiss_button, TRUE);
366 	} else {
367 		gtk_widget_set_sensitive(pounce_dialog->open_button, TRUE);
368 		gtk_widget_set_sensitive(pounce_dialog->edit_button, FALSE);
369 		gtk_widget_set_sensitive(pounce_dialog->dismiss_button, TRUE);
370 	}
371 
372 
373 }
374 
375 static void
reset_mail_dialog(GtkDialog * unused)376 reset_mail_dialog(GtkDialog *unused)
377 {
378 	if (mail_dialog->in_use)
379 		return;
380 	gtk_widget_destroy(mail_dialog->dialog);
381 	g_free(mail_dialog);
382 	mail_dialog = NULL;
383 }
384 
385 static void
email_response_cb(GtkDialog * unused,gint id,PidginNotifyDialog * unused2)386 email_response_cb(GtkDialog *unused, gint id, PidginNotifyDialog *unused2)
387 {
388 	PidginNotifyMailData *data = NULL;
389 	GtkTreeModel *model = GTK_TREE_MODEL(mail_dialog->treemodel);
390 	GtkTreeIter iter;
391 
392 	if (id == GTK_RESPONSE_YES)
393 	{
394 		/* A single row activated. Remove that row. */
395 		GtkTreeSelection *selection;
396 
397 		selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(mail_dialog->treeview));
398 
399 		if (gtk_tree_selection_get_selected(selection, NULL, &iter))
400 		{
401 			gtk_tree_model_get(model, &iter, PIDGIN_MAIL_DATA, &data, -1);
402 			purple_notify_uri(NULL, data->url);
403 
404 			gtk_tree_store_remove(mail_dialog->treemodel, &iter);
405 			if (data->purple_has_handle)
406 				purple_notify_close(PURPLE_NOTIFY_EMAILS, data);
407 			else
408 				pidgin_close_notify(PURPLE_NOTIFY_EMAILS, data);
409 
410 			if (gtk_tree_model_get_iter_first(model, &iter))
411 				return;
412 		}
413 		else
414 			return;
415 	}
416 	else
417 	{
418 		/* Remove all the rows */
419 		while (gtk_tree_model_get_iter_first(model, &iter))
420 		{
421 			gtk_tree_model_get(model, &iter, PIDGIN_MAIL_DATA, &data, -1);
422 
423 			if (id == GTK_RESPONSE_ACCEPT)
424 				purple_notify_uri(NULL, data->url);
425 
426 			gtk_tree_store_remove(mail_dialog->treemodel, &iter);
427 			if (data->purple_has_handle)
428 				purple_notify_close(PURPLE_NOTIFY_EMAILS, data);
429 			else
430 				pidgin_close_notify(PURPLE_NOTIFY_EMAILS, data);
431 		}
432 	}
433 
434 	reset_mail_dialog(NULL);
435 }
436 
437 static void
email_row_activated_cb(GtkTreeView * tv,GtkTreePath * path,GtkTreeViewColumn * col,gpointer data)438 email_row_activated_cb(GtkTreeView *tv, GtkTreePath *path,
439                        GtkTreeViewColumn *col, gpointer data)
440 {
441 	email_response_cb(NULL, GTK_RESPONSE_YES, NULL);
442 }
443 
444 static gboolean
formatted_close_cb(GtkWidget * win,GdkEvent * event,void * user_data)445 formatted_close_cb(GtkWidget *win, GdkEvent *event, void *user_data)
446 {
447 	purple_notify_close(PURPLE_NOTIFY_FORMATTED, win);
448 	return FALSE;
449 }
450 
451 static gboolean
searchresults_close_cb(PidginNotifySearchResultsData * data,GdkEvent * event,gpointer user_data)452 searchresults_close_cb(PidginNotifySearchResultsData *data, GdkEvent *event, gpointer user_data)
453 {
454 	purple_notify_close(PURPLE_NOTIFY_SEARCHRESULTS, data);
455 	return FALSE;
456 }
457 
458 static void
searchresults_callback_wrapper_cb(GtkWidget * widget,PidginNotifySearchResultsButtonData * bd)459 searchresults_callback_wrapper_cb(GtkWidget *widget, PidginNotifySearchResultsButtonData *bd)
460 {
461 	PidginNotifySearchResultsData *data = bd->data;
462 
463 	GtkTreeSelection *selection;
464 	GtkTreeModel *model;
465 	GtkTreeIter iter;
466 	PurpleNotifySearchButton *button;
467 	GList *row = NULL;
468 	gchar *str;
469 	int i;
470 
471 	g_return_if_fail(data != NULL);
472 
473 	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(data->treeview));
474 
475 	if (gtk_tree_selection_get_selected(selection, &model, &iter))
476 	{
477 		for (i = 1; i < gtk_tree_model_get_n_columns(GTK_TREE_MODEL(model)); i++) {
478 			gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, i, &str, -1);
479 			row = g_list_append(row, str);
480 		}
481 	}
482 
483 	button = bd->button;
484 	button->callback(purple_account_get_connection(data->account), row, data->user_data);
485 	g_list_free_full(row, (GDestroyNotify)g_free);
486 }
487 
488 static void *
pidgin_notify_message(PurpleNotifyMsgType type,const char * title,const char * primary,const char * secondary)489 pidgin_notify_message(PurpleNotifyMsgType type, const char *title,
490 						const char *primary, const char *secondary)
491 {
492 	GtkWidget *dialog;
493 	GtkWidget *hbox;
494 	GtkWidget *label;
495 	GtkWidget *img = NULL;
496 	char label_text[2048];
497 	const char *icon_name = NULL;
498 	char *primary_esc, *secondary_esc;
499 
500 	switch (type)
501 	{
502 		case PURPLE_NOTIFY_MSG_ERROR:
503 			icon_name = PIDGIN_STOCK_DIALOG_ERROR;
504 			break;
505 
506 		case PURPLE_NOTIFY_MSG_WARNING:
507 			icon_name = PIDGIN_STOCK_DIALOG_WARNING;
508 			break;
509 
510 		case PURPLE_NOTIFY_MSG_INFO:
511 			icon_name = PIDGIN_STOCK_DIALOG_INFO;
512 			break;
513 
514 		default:
515 			icon_name = NULL;
516 			break;
517 	}
518 
519 	if (icon_name != NULL)
520 	{
521 		img = gtk_image_new_from_stock(icon_name, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_HUGE));
522 		gtk_misc_set_alignment(GTK_MISC(img), 0, 0);
523 	}
524 
525 	dialog = gtk_dialog_new_with_buttons(title ? title : PIDGIN_ALERT_TITLE,
526 										 NULL, 0, GTK_STOCK_CLOSE,
527 										 GTK_RESPONSE_CLOSE, NULL);
528 
529 	gtk_window_set_role(GTK_WINDOW(dialog), "notify_dialog");
530 
531 	g_signal_connect(G_OBJECT(dialog), "response",
532 					 G_CALLBACK(message_response_cb), dialog);
533 
534 	gtk_container_set_border_width(GTK_CONTAINER(dialog), PIDGIN_HIG_BORDER);
535 	gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
536 	gtk_dialog_set_has_separator(GTK_DIALOG(dialog), FALSE);
537 	gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog)->vbox), PIDGIN_HIG_BORDER);
538 	gtk_container_set_border_width(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), PIDGIN_HIG_BOX_SPACE);
539 
540 	hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BORDER);
541 	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), hbox);
542 
543 	if (img != NULL)
544 		gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0);
545 
546 	primary_esc = g_markup_escape_text(primary, -1);
547 	secondary_esc = (secondary != NULL) ? g_markup_escape_text(secondary, -1) : NULL;
548 	g_snprintf(label_text, sizeof(label_text),
549 			   "<span weight=\"bold\" size=\"larger\">%s</span>%s%s",
550 			   primary_esc, (secondary ? "\n\n" : ""),
551 			   (secondary ? secondary_esc : ""));
552 	g_free(primary_esc);
553 	g_free(secondary_esc);
554 
555 	label = gtk_label_new(NULL);
556 
557 	gtk_label_set_markup(GTK_LABEL(label), label_text);
558 	gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
559 	gtk_label_set_selectable(GTK_LABEL(label), TRUE);
560 	gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
561 	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
562 
563 	pidgin_auto_parent_window(dialog);
564 
565 	gtk_widget_show_all(dialog);
566 
567 	return dialog;
568 }
569 
570 static void
selection_changed_cb(GtkTreeSelection * sel,PidginNotifyDialog * dialog)571 selection_changed_cb(GtkTreeSelection *sel, PidginNotifyDialog *dialog)
572 {
573 	GtkTreeIter iter;
574 	GtkTreeModel *model;
575 	PidginNotifyMailData *data;
576 	gboolean active = TRUE;
577 
578 	if (gtk_tree_selection_get_selected(sel, &model, &iter) == FALSE)
579 		active = FALSE;
580 	else
581 	{
582 		gtk_tree_model_get(model, &iter, PIDGIN_MAIL_DATA, &data, -1);
583 		if (data->url == NULL)
584 			active = FALSE;
585 	}
586 
587 	gtk_widget_set_sensitive(dialog->open_button, active);
588 }
589 
590 static void *
pidgin_notify_email(PurpleConnection * gc,const char * subject,const char * from,const char * to,const char * url)591 pidgin_notify_email(PurpleConnection *gc, const char *subject, const char *from,
592 					  const char *to, const char *url)
593 {
594 	return pidgin_notify_emails(gc, 1, (subject != NULL),
595 								  (subject == NULL ? NULL : &subject),
596 								  (from    == NULL ? NULL : &from),
597 								  (to      == NULL ? NULL : &to),
598 								  (url     == NULL ? NULL : &url));
599 }
600 
601 static int
mail_window_focus_cb(GtkWidget * widget,GdkEventFocus * focus,gpointer null)602 mail_window_focus_cb(GtkWidget *widget, GdkEventFocus *focus, gpointer null)
603 {
604 	pidgin_set_urgent(GTK_WINDOW(widget), FALSE);
605 	return 0;
606 }
607 
608 /* count == 0 means this is a detailed mail notification.
609  * count > 0 mean non-detailed.
610  */
611 static void *
pidgin_notify_add_mail(GtkTreeStore * treemodel,PurpleAccount * account,char * notification,const char * url,int count,gboolean clear,gboolean * new_data)612 pidgin_notify_add_mail(GtkTreeStore *treemodel, PurpleAccount *account, char *notification, const char *url, int count, gboolean clear, gboolean *new_data)
613 {
614 	PidginNotifyMailData *data = NULL;
615 	GtkTreeIter iter;
616 	GdkPixbuf *icon;
617 	gboolean new_n = TRUE;
618 
619 	if (count > 0 || clear) {
620 		/* Allow only one non-detailed email notification for each account */
621 		if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(treemodel), &iter)) {
622 			gboolean advanced;
623 			do {
624 				advanced = FALSE;
625 				gtk_tree_model_get(GTK_TREE_MODEL(treemodel), &iter,
626 						PIDGIN_MAIL_DATA, &data, -1);
627 				if (data && data->account == account) {
628 					if (clear) {
629 						advanced = gtk_tree_store_remove(treemodel, &iter);
630 						mail_dialog->total_count -= data->count;
631 
632 						if (data->purple_has_handle)
633 							purple_notify_close(PURPLE_NOTIFY_EMAILS, data);
634 						else
635 							pidgin_close_notify(PURPLE_NOTIFY_EMAILS, data);
636 						/* We're completely done if we've processed all entries */
637 						if (!advanced)
638 							return NULL;
639 					} else if (data->count > 0) {
640 						new_n = FALSE;
641 						g_free(data->url);
642 						data->url = NULL;
643 						mail_dialog->total_count -= data->count;
644 						break;
645 					}
646 				}
647 			} while (advanced || gtk_tree_model_iter_next(GTK_TREE_MODEL(treemodel), &iter));
648 		}
649 	}
650 
651 	if (clear)
652 		return NULL;
653 
654 	icon = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_MEDIUM);
655 
656 	if (new_n) {
657 		data = g_new0(PidginNotifyMailData, 1);
658 		data->purple_has_handle = TRUE;
659 		gtk_tree_store_append(treemodel, &iter, NULL);
660 	}
661 
662 	if (url != NULL)
663 		data->url = g_strdup(url);
664 
665 	gtk_tree_store_set(treemodel, &iter,
666 								PIDGIN_MAIL_ICON, icon,
667 								PIDGIN_MAIL_TEXT, notification,
668 								PIDGIN_MAIL_DATA, data,
669 								-1);
670 	data->account = account;
671 	/* count == 0 indicates we're adding a single detailed e-mail */
672 	data->count = count > 0 ? count : 1;
673 
674 	if (icon)
675 		g_object_unref(icon);
676 
677 	if (new_data)
678 		*new_data = new_n;
679 	return data;
680 }
681 
682 static void *
pidgin_notify_emails(PurpleConnection * gc,size_t count,gboolean detailed,const char ** subjects,const char ** froms,const char ** tos,const char ** urls)683 pidgin_notify_emails(PurpleConnection *gc, size_t count, gboolean detailed,
684 					   const char **subjects, const char **froms,
685 					   const char **tos, const char **urls)
686 {
687 	char *notification;
688 	PurpleAccount *account;
689 	PidginNotifyMailData *data = NULL, *data2;
690 	gboolean new_data = FALSE;
691 
692 	/* Don't bother updating if there aren't new emails and we don't have any displayed currently */
693 	if (count == 0 && mail_dialog == NULL)
694 		return NULL;
695 
696 	account = purple_connection_get_account(gc);
697 	if (mail_dialog == NULL)
698 		mail_dialog = pidgin_create_notification_dialog(PIDGIN_NOTIFY_MAIL);
699 
700 	mail_dialog->total_count += count;
701 	if (detailed) {
702 		for ( ; count; --count) {
703 			char *to_text = NULL;
704 			char *from_text = NULL;
705 			char *subject_text = NULL;
706 			char *tmp;
707 			gboolean first = TRUE;
708 
709 			if (tos != NULL) {
710 				tmp = g_markup_escape_text(*tos, -1);
711 				to_text = g_strdup_printf("<b>%s</b>: %s\n", _("Account"), tmp);
712 				g_free(tmp);
713 				first = FALSE;
714 				tos++;
715 			}
716 			if (froms != NULL) {
717 				tmp = g_markup_escape_text(*froms, -1);
718 				from_text = g_strdup_printf("%s<b>%s</b>: %s\n", first ? "<br>" : "", _("Sender"), tmp);
719 				g_free(tmp);
720 				first = FALSE;
721 				froms++;
722 			}
723 			if (subjects != NULL) {
724 				tmp = g_markup_escape_text(*subjects, -1);
725 				subject_text = g_strdup_printf("%s<b>%s</b>: %s", first ? "<br>" : "", _("Subject"), tmp);
726 				g_free(tmp);
727 				/* this is a dead assignment, but if you add another row you'll
728 				 * need it, so I commented it out for now.
729 				 */
730 				/* first = FALSE; */
731 				subjects++;
732 			}
733 #define SAFE(x) ((x) ? (x) : "")
734 			notification = g_strdup_printf("%s%s%s", SAFE(to_text), SAFE(from_text), SAFE(subject_text));
735 #undef SAFE
736 			g_free(to_text);
737 			g_free(from_text);
738 			g_free(subject_text);
739 
740 			/* If we don't keep track of this, will leak "data" for each of the notifications except the last */
741 			data2 = pidgin_notify_add_mail(mail_dialog->treemodel, account, notification, urls ? *urls : NULL, 0, FALSE, &new_data);
742 			if (data2 && new_data) {
743 				if (data)
744 					data->purple_has_handle = FALSE;
745 				data = data2;
746 			}
747 			g_free(notification);
748 
749 			if (urls != NULL)
750 				urls++;
751 		}
752 	} else {
753 		if (count > 0) {
754 			notification = g_strdup_printf(ngettext("%s has %d new message.",
755 							   "%s has %d new messages.",
756 							   (int)count),
757 							   tos != NULL ? *tos : NULL, (int)count);
758 			data2 = pidgin_notify_add_mail(mail_dialog->treemodel, account, notification, urls ? *urls : NULL, count, FALSE, &new_data);
759 			if (data2 && new_data) {
760 				if (data)
761 					data->purple_has_handle = FALSE;
762 				data = data2;
763 			}
764 			g_free(notification);
765 		} else {
766 			/* Clear out all mails for the account */
767 			pidgin_notify_add_mail(mail_dialog->treemodel, account, NULL, NULL, 0, TRUE, NULL);
768 
769 			if (mail_dialog->total_count == 0) {
770 				/*
771 				 * There is no API to clear the headline specifically
772 				 * This will trigger reset_mail_dialog()
773 				 */
774 				pidgin_blist_set_headline(NULL, NULL, NULL, NULL, NULL);
775 				return NULL;
776 			}
777 		}
778 	}
779 
780 	if (!GTK_WIDGET_VISIBLE(mail_dialog->dialog)) {
781 		GdkPixbuf *pixbuf = gtk_widget_render_icon(mail_dialog->dialog, PIDGIN_STOCK_DIALOG_MAIL,
782 							   gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL), NULL);
783 		char *label_text = g_strdup_printf(ngettext("<b>%d new email.</b>",
784 							    "<b>%d new emails.</b>",
785 							    mail_dialog->total_count), mail_dialog->total_count);
786 		mail_dialog->in_use = TRUE;     /* So that _set_headline doesn't accidentally
787 										   remove the notifications when replacing an
788 										   old notification. */
789 		pidgin_blist_set_headline(label_text,
790 					    pixbuf, G_CALLBACK(gtk_widget_show_all), mail_dialog->dialog,
791 					    (GDestroyNotify)reset_mail_dialog);
792 		mail_dialog->in_use = FALSE;
793 		g_free(label_text);
794 		if (pixbuf)
795 			g_object_unref(pixbuf);
796 	} else if (!GTK_WIDGET_HAS_FOCUS(mail_dialog->dialog))
797 		pidgin_set_urgent(GTK_WINDOW(mail_dialog->dialog), TRUE);
798 
799 	return data;
800 }
801 
802 static gboolean
formatted_input_cb(GtkWidget * win,GdkEventKey * event,gpointer data)803 formatted_input_cb(GtkWidget *win, GdkEventKey *event, gpointer data)
804 {
805 	if (event->keyval == GDK_Escape)
806 	{
807 		purple_notify_close(PURPLE_NOTIFY_FORMATTED, win);
808 
809 		return TRUE;
810 	}
811 
812 	return FALSE;
813 }
814 
815 static GtkIMHtmlOptions
notify_imhtml_options(void)816 notify_imhtml_options(void)
817 {
818 	GtkIMHtmlOptions options = 0;
819 
820 	if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/show_incoming_formatting"))
821 		options |= GTK_IMHTML_NO_COLOURS | GTK_IMHTML_NO_FONTS | GTK_IMHTML_NO_SIZES;
822 
823 	options |= GTK_IMHTML_NO_COMMENTS;
824 	options |= GTK_IMHTML_NO_TITLE;
825 	options |= GTK_IMHTML_NO_NEWLINE;
826 	options |= GTK_IMHTML_NO_SCROLL;
827 	return options;
828 }
829 
830 static void *
pidgin_notify_formatted(const char * title,const char * primary,const char * secondary,const char * text)831 pidgin_notify_formatted(const char *title, const char *primary,
832 						  const char *secondary, const char *text)
833 {
834 	GtkWidget *window;
835 	GtkWidget *vbox;
836 	GtkWidget *label;
837 	GtkWidget *button;
838 	GtkWidget *imhtml;
839 	GtkWidget *frame;
840 	char label_text[2048];
841 	char *linked_text, *primary_esc, *secondary_esc;
842 
843 	window = gtk_dialog_new();
844 	gtk_window_set_title(GTK_WINDOW(window), title);
845 	gtk_container_set_border_width(GTK_CONTAINER(window), PIDGIN_HIG_BORDER);
846 	gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
847 
848 	g_signal_connect(G_OBJECT(window), "delete_event",
849 					 G_CALLBACK(formatted_close_cb), NULL);
850 
851 	/* Setup the main vbox */
852 	vbox = GTK_DIALOG(window)->vbox;
853 
854 	/* Setup the descriptive label */
855 	primary_esc = g_markup_escape_text(primary, -1);
856 	secondary_esc = (secondary != NULL) ? g_markup_escape_text(secondary, -1) : NULL;
857 	g_snprintf(label_text, sizeof(label_text),
858 			   "<span weight=\"bold\" size=\"larger\">%s</span>%s%s",
859 			   primary_esc,
860 			   (secondary ? "\n" : ""),
861 			   (secondary ? secondary_esc : ""));
862 	g_free(primary_esc);
863 	g_free(secondary_esc);
864 
865 	label = gtk_label_new(NULL);
866 
867 	gtk_label_set_markup(GTK_LABEL(label), label_text);
868 	gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
869 	gtk_label_set_selectable(GTK_LABEL(label), TRUE);
870 	gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
871 	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
872 	gtk_widget_show(label);
873 
874 	/* Add the imhtml */
875 	frame = pidgin_create_imhtml(FALSE, &imhtml, NULL, NULL);
876 	gtk_widget_set_name(imhtml, "pidgin_notify_imhtml");
877 	gtk_imhtml_set_format_functions(GTK_IMHTML(imhtml),
878 			gtk_imhtml_get_format_functions(GTK_IMHTML(imhtml)) | GTK_IMHTML_IMAGE);
879 	gtk_widget_set_size_request(imhtml, 300, 250);
880 	gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
881 	gtk_widget_show(frame);
882 
883 	/* Add the Close button. */
884 	button = gtk_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
885 	gtk_widget_grab_focus(button);
886 
887 	g_signal_connect_swapped(G_OBJECT(button), "clicked",
888 							 G_CALLBACK(formatted_close_cb), window);
889 	g_signal_connect(G_OBJECT(window), "key_press_event",
890 					 G_CALLBACK(formatted_input_cb), NULL);
891 
892 	/* Make sure URLs are clickable */
893 	linked_text = purple_markup_linkify(text);
894 	gtk_imhtml_append_text(GTK_IMHTML(imhtml), linked_text, notify_imhtml_options());
895 	g_free(linked_text);
896 
897 	g_object_set_data(G_OBJECT(window), "info-widget", imhtml);
898 
899 	/* Show the window */
900 	pidgin_auto_parent_window(window);
901 
902 	gtk_widget_show(window);
903 
904 	return window;
905 }
906 
907 static void
pidgin_notify_searchresults_new_rows(PurpleConnection * gc,PurpleNotifySearchResults * results,void * data_)908 pidgin_notify_searchresults_new_rows(PurpleConnection *gc, PurpleNotifySearchResults *results,
909 									   void *data_)
910 {
911 	PidginNotifySearchResultsData *data = data_;
912 	GtkTreeSelection *selection;
913 	GtkListStore *model = data->model;
914 	GtkTreeIter iter;
915 	GdkPixbuf *pixbuf;
916 	GList *row, *column;
917 	gchar *previous_selection = NULL;
918 	guint n;
919 
920 	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(data->treeview));
921 	if (gtk_tree_selection_get_selected(selection, NULL, &iter))
922 		gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, 1, &previous_selection, -1);
923 
924 	gtk_list_store_clear(data->model);
925 
926 	pixbuf = pidgin_create_prpl_icon(purple_connection_get_account(gc), PIDGIN_PRPL_ICON_SMALL);
927 
928 	for (row = results->rows; row != NULL; row = row->next) {
929 
930 		gtk_list_store_append(model, &iter);
931 		gtk_list_store_set(model, &iter, 0, pixbuf, -1);
932 
933 		n = 1;
934 		column = row->data;
935 		/* Select this row, if the first column matches the previously
936 		 * selected row OR if there is only one row in the results. */
937 		if (!g_strcmp0(previous_selection, column->data) ||
938 		    (row == results->rows && !row->next))
939 			gtk_tree_selection_select_iter(selection, &iter);
940 
941 		for (; column != NULL; column = column->next) {
942 			GValue v;
943 
944 			v.g_type = 0;
945 			g_value_init(&v, G_TYPE_STRING);
946 
947 			g_value_set_static_string(&v, column->data);
948 			gtk_list_store_set_value(model, &iter, n, &v);
949 			n++;
950 		}
951 	}
952 
953 	/* The first set of results need to stick around, as we reference
954 	 * the buttons from there. But any updates from later calls to
955 	 * purple_notify_searchresults_new_rows() must be freed. */
956 	if (results != data->results)
957 		purple_notify_searchresults_free(results);
958 
959 	g_free(previous_selection);
960 
961 	if (pixbuf != NULL)
962 		g_object_unref(pixbuf);
963 }
964 
965 static void *
pidgin_notify_searchresults(PurpleConnection * gc,const char * title,const char * primary,const char * secondary,PurpleNotifySearchResults * results,gpointer user_data)966 pidgin_notify_searchresults(PurpleConnection *gc, const char *title,
967 							  const char *primary, const char *secondary,
968 							  PurpleNotifySearchResults *results, gpointer user_data)
969 {
970 	GtkWidget *window;
971 	GtkWidget *treeview;
972 	GtkWidget *close_button;
973 	GType *col_types;
974 	GtkListStore *model;
975 	GtkCellRenderer *renderer;
976 	guint col_num;
977 	GList *columniter;
978 	guint i;
979 	GList *l;
980 
981 	GtkWidget *vbox;
982 	GtkWidget *label;
983 	PidginNotifySearchResultsData *data;
984 	char *label_text;
985 	char *primary_esc, *secondary_esc;
986 
987 	g_return_val_if_fail(gc != NULL, NULL);
988 	g_return_val_if_fail(results != NULL, NULL);
989 
990 	data = g_malloc(sizeof(PidginNotifySearchResultsData));
991 	data->user_data = user_data;
992 	data->results = results;
993 
994 	/* Create the window */
995 	window = gtk_dialog_new();
996 	gtk_window_set_title(GTK_WINDOW(window), title ? title :_("Search Results"));
997 	gtk_container_set_border_width(GTK_CONTAINER(window), PIDGIN_HIG_BORDER);
998 	gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
999 
1000 	g_signal_connect_swapped(G_OBJECT(window), "delete_event",
1001 							 G_CALLBACK(searchresults_close_cb), data);
1002 
1003 	/* Setup the main vbox */
1004 	vbox = GTK_DIALOG(window)->vbox;
1005 
1006 	/* Setup the descriptive label */
1007 	primary_esc = (primary != NULL) ? g_markup_escape_text(primary, -1) : NULL;
1008 	secondary_esc = (secondary != NULL) ? g_markup_escape_text(secondary, -1) : NULL;
1009 	label_text = g_strdup_printf(
1010 			"<span weight=\"bold\" size=\"larger\">%s</span>%s%s",
1011 			(primary ? primary_esc : ""),
1012 			(primary && secondary ? "\n" : ""),
1013 			(secondary ? secondary_esc : ""));
1014 	g_free(primary_esc);
1015 	g_free(secondary_esc);
1016 	label = gtk_label_new(NULL);
1017 	gtk_label_set_markup(GTK_LABEL(label), label_text);
1018 	gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1019 	gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
1020 	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1021 	gtk_widget_show(label);
1022 	g_free(label_text);
1023 
1024 	/* +1 is for the automagically created Status column. */
1025 	col_num = g_list_length(results->columns) + 1;
1026 
1027 	/* Setup the list model */
1028 	col_types = g_new0(GType, col_num);
1029 
1030 	/* There always is this first column. */
1031 	col_types[0] = GDK_TYPE_PIXBUF;
1032 	for (i = 1; i < col_num; i++) {
1033 		col_types[i] = G_TYPE_STRING;
1034 	}
1035 	model = gtk_list_store_newv(col_num, col_types);
1036 	g_free(col_types);
1037 
1038 	/* Setup the treeview */
1039 	treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
1040 	g_object_unref(G_OBJECT(model));
1041 	gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(treeview), TRUE);
1042 	gtk_widget_set_size_request(treeview, 500, 400);
1043 	gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview)),
1044 								GTK_SELECTION_SINGLE);
1045 	gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview), TRUE);
1046 	gtk_box_pack_start(GTK_BOX(vbox),
1047 		pidgin_make_scrollable(treeview, GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS, GTK_SHADOW_IN, -1, -1),
1048 		TRUE, TRUE, 0);
1049 	gtk_widget_show(treeview);
1050 
1051 	renderer = gtk_cell_renderer_pixbuf_new();
1052 	gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(treeview),
1053 					-1, "", renderer, "pixbuf", 0, NULL);
1054 
1055 	i = 1;
1056 	for (columniter = results->columns; columniter != NULL; columniter = columniter->next) {
1057 		PurpleNotifySearchColumn *column = columniter->data;
1058 		renderer = gtk_cell_renderer_text_new();
1059 
1060 		gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(treeview), -1,
1061 				column->title, renderer, "text", i, NULL);
1062 		i++;
1063 	}
1064 
1065 	for (l = results->buttons; l; l = l->next) {
1066 		PurpleNotifySearchButton *b = l->data;
1067 		GtkWidget *button = NULL;
1068 		switch (b->type) {
1069 			case PURPLE_NOTIFY_BUTTON_LABELED:
1070 				if(b->label) {
1071 					button = gtk_dialog_add_button(GTK_DIALOG(window), b->label, GTK_RESPONSE_NONE);
1072 				} else {
1073 					purple_debug_warning("gtknotify", "Missing button label\n");
1074 				}
1075 				break;
1076 			case PURPLE_NOTIFY_BUTTON_CONTINUE:
1077 				button = gtk_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_GO_FORWARD, GTK_RESPONSE_NONE);
1078 				break;
1079 			case PURPLE_NOTIFY_BUTTON_ADD:
1080 				button = gtk_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_ADD, GTK_RESPONSE_NONE);
1081 				break;
1082 			case PURPLE_NOTIFY_BUTTON_INFO:
1083 				button = gtk_dialog_add_button(GTK_DIALOG(window), PIDGIN_STOCK_TOOLBAR_USER_INFO, GTK_RESPONSE_NONE);
1084 				break;
1085 			case PURPLE_NOTIFY_BUTTON_IM:
1086 				button = gtk_dialog_add_button(GTK_DIALOG(window), PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW, GTK_RESPONSE_NONE);
1087 				break;
1088 			case PURPLE_NOTIFY_BUTTON_JOIN:
1089 				button = gtk_dialog_add_button(GTK_DIALOG(window), PIDGIN_STOCK_CHAT, GTK_RESPONSE_NONE);
1090 				break;
1091 			case PURPLE_NOTIFY_BUTTON_INVITE:
1092 				button = gtk_dialog_add_button(GTK_DIALOG(window), PIDGIN_STOCK_INVITE, GTK_RESPONSE_NONE);
1093 				break;
1094 			default:
1095 				purple_debug_warning("gtknotify", "Incorrect button type: %d\n", b->type);
1096 		}
1097 		if (button != NULL) {
1098 			PidginNotifySearchResultsButtonData *bd;
1099 
1100 			bd = g_new0(PidginNotifySearchResultsButtonData, 1);
1101 			bd->button = b;
1102 			bd->data = data;
1103 
1104 			g_signal_connect(G_OBJECT(button), "clicked",
1105 			                 G_CALLBACK(searchresults_callback_wrapper_cb), bd);
1106 			g_signal_connect_swapped(G_OBJECT(button), "destroy", G_CALLBACK(g_free), bd);
1107 		}
1108 	}
1109 
1110 	/* Add the Close button */
1111 	close_button = gtk_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
1112 
1113 	g_signal_connect_swapped(G_OBJECT(close_button), "clicked",
1114 	                         G_CALLBACK(searchresults_close_cb), data);
1115 
1116 	data->account = gc->account;
1117 	data->model = model;
1118 	data->treeview = treeview;
1119 	data->window = window;
1120 
1121 	/* Insert rows. */
1122 	pidgin_notify_searchresults_new_rows(gc, results, data);
1123 
1124 	/* Show the window */
1125 	pidgin_auto_parent_window(window);
1126 
1127 	gtk_widget_show(window);
1128 	return data;
1129 }
1130 
1131 /** Xerox'ed from Finch! How the tables have turned!! ;) **/
1132 /** User information. **/
1133 static GHashTable *userinfo;
1134 
1135 static char *
userinfo_hash(PurpleAccount * account,const char * who)1136 userinfo_hash(PurpleAccount *account, const char *who)
1137 {
1138 	char key[256];
1139 	snprintf(key, sizeof(key), "%s - %s", purple_account_get_username(account), purple_normalize(account, who));
1140 	return g_utf8_strup(key, -1);
1141 }
1142 
1143 static void
remove_userinfo(GtkWidget * widget,gpointer key)1144 remove_userinfo(GtkWidget *widget, gpointer key)
1145 {
1146 	PidginUserInfo *pinfo = g_hash_table_lookup(userinfo, key);
1147 
1148 	while (pinfo->count--)
1149 		purple_notify_close(PURPLE_NOTIFY_USERINFO, widget);
1150 
1151 	g_hash_table_remove(userinfo, key);
1152 }
1153 
1154 static void *
pidgin_notify_userinfo(PurpleConnection * gc,const char * who,PurpleNotifyUserInfo * user_info)1155 pidgin_notify_userinfo(PurpleConnection *gc, const char *who,
1156 						 PurpleNotifyUserInfo *user_info)
1157 {
1158 	char *info;
1159 	void *ui_handle;
1160 	char *key = userinfo_hash(purple_connection_get_account(gc), who);
1161 	PidginUserInfo *pinfo = NULL;
1162 
1163 	if (!userinfo) {
1164 		userinfo = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
1165 	}
1166 
1167 	info = purple_notify_user_info_get_text_with_newline(user_info, "<br />");
1168 	pinfo = g_hash_table_lookup(userinfo, key);
1169 	if (pinfo != NULL) {
1170 		GtkIMHtml *imhtml = g_object_get_data(G_OBJECT(pinfo->window), "info-widget");
1171 		char *linked_text = purple_markup_linkify(info);
1172 		gtk_imhtml_clear(imhtml);
1173 		gtk_imhtml_append_text(imhtml, linked_text, notify_imhtml_options());
1174 		g_free(linked_text);
1175 		g_free(key);
1176 		ui_handle = pinfo->window;
1177 		pinfo->count++;
1178 	} else {
1179 		char *primary = g_strdup_printf(_("Info for %s"), who);
1180 		ui_handle = pidgin_notify_formatted(_("Buddy Information"), primary, NULL, info);
1181 		g_signal_handlers_disconnect_by_func(G_OBJECT(ui_handle), G_CALLBACK(formatted_close_cb), NULL);
1182 		g_signal_connect(G_OBJECT(ui_handle), "destroy", G_CALLBACK(remove_userinfo), key);
1183 		g_free(primary);
1184 		pinfo = g_new0(PidginUserInfo, 1);
1185 		pinfo->window = ui_handle;
1186 		pinfo->count = 1;
1187 		g_hash_table_insert(userinfo, key, pinfo);
1188 	}
1189 	g_free(info);
1190 	return ui_handle;
1191 }
1192 
1193 static void
pidgin_close_notify(PurpleNotifyType type,void * ui_handle)1194 pidgin_close_notify(PurpleNotifyType type, void *ui_handle)
1195 {
1196 	if (type == PURPLE_NOTIFY_EMAIL || type == PURPLE_NOTIFY_EMAILS)
1197 	{
1198 		PidginNotifyMailData *data = (PidginNotifyMailData *)ui_handle;
1199 
1200 		if (data) {
1201 			g_free(data->url);
1202 			g_free(data);
1203 		}
1204 	}
1205 	else if (type == PURPLE_NOTIFY_SEARCHRESULTS)
1206 	{
1207 		PidginNotifySearchResultsData *data = (PidginNotifySearchResultsData *)ui_handle;
1208 
1209 		gtk_widget_destroy(data->window);
1210 		purple_notify_searchresults_free(data->results);
1211 
1212 		g_free(data);
1213 	}
1214 	else if (ui_handle != NULL)
1215 		gtk_widget_destroy(GTK_WIDGET(ui_handle));
1216 }
1217 
1218 #ifndef _WIN32
1219 static gboolean
uri_command(GSList * arg_list,gboolean sync)1220 uri_command(GSList *arg_list, gboolean sync)
1221 {
1222 	gchar *tmp;
1223 	GError *error = NULL;
1224 	GSList *it;
1225 	gchar **argv;
1226 	gint argc, i;
1227 	gchar *program;
1228 
1229 	g_return_val_if_fail(arg_list != NULL, FALSE);
1230 
1231 	program = arg_list->data;
1232 	purple_debug_misc("gtknotify", "Executing %s (%s)\n", program,
1233 		sync ? "sync" : "async");
1234 
1235 	if (!purple_program_is_valid(program)) {
1236 		purple_debug_error("gtknotify", "Command \"%s\" is invalid\n",
1237 			program);
1238 		tmp = g_strdup_printf(
1239 			_("The browser command \"%s\" is invalid."),
1240 			program ? program : "(null)");
1241 		purple_notify_error(NULL, NULL, _("Unable to open URL"), tmp);
1242 		g_free(tmp);
1243 
1244 		return FALSE;
1245 	}
1246 
1247 	argc = g_slist_length(arg_list);
1248 	argv = g_new(gchar*, argc + 1);
1249 	i = 0;
1250 	for (it = arg_list; it; it = g_slist_next(it)) {
1251 		if (purple_debug_is_verbose()) {
1252 			purple_debug_misc("gtknotify", "argv[%d] = \"%s\"\n",
1253 				i, (gchar*)it->data);
1254 		}
1255 		argv[i++] = it->data;
1256 	}
1257 	argv[i] = NULL;
1258 
1259 	if (sync) {
1260 		gint exit_status = 0;
1261 
1262 		if (g_spawn_sync(NULL, argv, NULL, G_SPAWN_SEARCH_PATH |
1263 			G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL,
1264 			NULL, NULL, NULL, NULL, &exit_status, &error) &&
1265 			exit_status == 0)
1266 		{
1267 			g_free(argv);
1268 			return TRUE;
1269 		}
1270 
1271 		purple_debug_error("gtknotify",
1272 			"Error launching \"%s\": %s (status: %d)\n", program,
1273 			error ? error->message : "(null)", exit_status);
1274 		tmp = g_strdup_printf(_("Error launching \"%s\": %s"), program,
1275 			error ? error->message : "(null)");
1276 		g_error_free(error);
1277 		purple_notify_error(NULL, NULL, _("Unable to open URL"), tmp);
1278 		g_free(tmp);
1279 
1280 		g_free(argv);
1281 		return FALSE;
1282 	}
1283 
1284 	if (g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH |
1285 		G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL, NULL,
1286 		NULL, NULL, &error))
1287 	{
1288 		g_free(argv);
1289 		return TRUE;
1290 	}
1291 
1292 	purple_debug_warning("gtknotify", "Error launching \"%s\": %s\n",
1293 		program, error ? error->message : "(null)");
1294 	g_error_free(error);
1295 
1296 	g_free(argv);
1297 	return FALSE;
1298 }
1299 #endif /* _WIN32 */
1300 
1301 static void *
pidgin_notify_uri(const char * uri)1302 pidgin_notify_uri(const char *uri)
1303 {
1304 #ifndef _WIN32
1305 	const char *web_browser;
1306 	int place;
1307 	gchar *uri_escaped, *uri_custom = NULL;
1308 	GSList *argv = NULL, *argv_remote = NULL;
1309 	gchar **usercmd_argv = NULL;
1310 
1311 	uri_escaped = purple_uri_escape_for_open(uri);
1312 
1313 	web_browser = purple_prefs_get_string(PIDGIN_PREFS_ROOT
1314 		"/browsers/browser");
1315 	place = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/browsers/place");
1316 
1317 	/* if they are running gnome, use the gnome web browser */
1318 	if (purple_running_gnome() == TRUE) {
1319 		char *tmp = g_find_program_in_path("xdg-open");
1320 		if (tmp == NULL)
1321 			argv = g_slist_append(argv, "gnome-open");
1322 		else
1323 			argv = g_slist_append(argv, "xdg-open");
1324 		g_free(tmp);
1325 		argv = g_slist_append(argv, uri_escaped);
1326 	} else if (purple_running_osx() == TRUE) {
1327 		argv = g_slist_append(argv, "open");
1328 		argv = g_slist_append(argv, uri_escaped);
1329 	} else if (purple_strequal(web_browser, "epiphany") ||
1330 		purple_strequal(web_browser, "galeon"))
1331 	{
1332 		argv = g_slist_append(argv, (gpointer)web_browser);
1333 
1334 		if (place == PIDGIN_BROWSER_NEW_WINDOW)
1335 			argv = g_slist_append(argv, "-w");
1336 		else if (place == PIDGIN_BROWSER_NEW_TAB)
1337 			argv = g_slist_append(argv, "-n");
1338 
1339 		argv = g_slist_append(argv, uri_escaped);
1340 	} else if (purple_strequal(web_browser, "xdg-open")) {
1341 		argv = g_slist_append(argv, "xdg-open");
1342 		argv = g_slist_append(argv, uri_escaped);
1343 	} else if (purple_strequal(web_browser, "gnome-open")) {
1344 		argv = g_slist_append(argv, "gnome-open");
1345 		argv = g_slist_append(argv, uri_escaped);
1346 	} else if (purple_strequal(web_browser, "kfmclient")) {
1347 		argv = g_slist_append(argv, "kfmclient");
1348 		argv = g_slist_append(argv, "openURL");
1349 		argv = g_slist_append(argv, uri_escaped);
1350 		/*
1351 		 * Does Konqueror have options to open in new tab
1352 		 * and/or current window?
1353 		 */
1354 	} else if (purple_strequal(web_browser, "firefox")) {
1355 		argv = g_slist_append(argv, "firefox");
1356 		if (place == PIDGIN_BROWSER_NEW_WINDOW) {
1357 			argv = g_slist_append(argv, "--new-window");
1358 		} else if (place == PIDGIN_BROWSER_NEW_TAB) {
1359 			argv = g_slist_append(argv, "--new-tab");
1360 		}
1361 		argv = g_slist_append(argv, uri_escaped);
1362 	} else if (purple_strequal(web_browser, "mozilla") ||
1363 		purple_strequal(web_browser, "mozilla-firebird") ||
1364 		purple_strequal(web_browser, "seamonkey"))
1365 	{
1366 		argv = g_slist_append(argv, (gpointer)web_browser);
1367 		argv = g_slist_append(argv, uri_escaped);
1368 
1369 		g_assert(uri_custom == NULL);
1370 		if (place == PIDGIN_BROWSER_NEW_WINDOW) {
1371 			uri_custom = g_strdup_printf("openURL(%s,new-window)",
1372 				uri_escaped);
1373 		} else if (place == PIDGIN_BROWSER_NEW_TAB) {
1374 			uri_custom = g_strdup_printf("openURL(%s,new-tab)",
1375 				uri_escaped);
1376 		} else if (place == PIDGIN_BROWSER_CURRENT) {
1377 			uri_custom = g_strdup_printf("openURL(%s)",
1378 				uri_escaped);
1379 		}
1380 
1381 		if (uri_custom != NULL) {
1382 			argv_remote = g_slist_append(argv_remote,
1383 				(gpointer)web_browser);
1384 
1385 			argv_remote = g_slist_append(argv_remote, "-remote");
1386 			argv_remote = g_slist_append(argv_remote, uri_custom);
1387 		}
1388 	} else if (purple_strequal(web_browser, "netscape")) {
1389 		argv = g_slist_append(argv, "netscape");
1390 		argv = g_slist_append(argv, uri_escaped);
1391 
1392 		if (place == PIDGIN_BROWSER_NEW_WINDOW) {
1393 			uri_custom = g_strdup_printf("openURL(%s,new-window)",
1394 				uri_escaped);
1395 		} else if (place == PIDGIN_BROWSER_CURRENT) {
1396 			uri_custom = g_strdup_printf("openURL(%s)",
1397 				uri_escaped);
1398 		}
1399 
1400 		if (uri_custom) {
1401 			argv_remote = g_slist_append(argv_remote, "netscape");
1402 			argv_remote = g_slist_append(argv_remote, "-remote");
1403 			argv_remote = g_slist_append(argv_remote, uri_custom);
1404 		}
1405 	} else if (purple_strequal(web_browser, "opera")) {
1406 		argv = g_slist_append(argv, "opera");
1407 
1408 		if (place == PIDGIN_BROWSER_NEW_WINDOW)
1409 			argv = g_slist_append(argv, "-newwindow");
1410 		else if (place == PIDGIN_BROWSER_NEW_TAB)
1411 			argv = g_slist_append(argv, "-newpage");
1412 		else if (place == PIDGIN_BROWSER_CURRENT)
1413 			argv = g_slist_append(argv, "-activetab");
1414 		/* `opera -remote "openURL(%s)"` command causes Pidgin's hang,
1415 		 * if there is no Opera instance running.
1416 		 */
1417 
1418 		argv = g_slist_append(argv, uri_escaped);
1419 	} else if (purple_strequal(web_browser, "google-chrome")) {
1420 		/* Google Chrome doesn't have command-line arguments that
1421 		 * control the opening of links from external calls. This is
1422 		 * controlled solely from a preference within Google Chrome.
1423 		 */
1424 		argv = g_slist_append(argv, "google-chrome");
1425 		argv = g_slist_append(argv, uri_escaped);
1426 	} else if (purple_strequal(web_browser, "chrome")) {
1427 		/* Chromium doesn't have command-line arguments that control
1428 		 * the opening of links from external calls. This is controlled
1429 		 * solely from a preference within Chromium.
1430 		 */
1431 		argv = g_slist_append(argv, "chrome");
1432 		argv = g_slist_append(argv, uri_escaped);
1433 	} else if (purple_strequal(web_browser, "chromium-browser")) {
1434 		/* Chromium doesn't have command-line arguments that control the
1435 		 * opening of links from external calls. This is controlled
1436 		 * solely from a preference within Chromium.
1437 		 */
1438 		argv = g_slist_append(argv, "chromium-browser");
1439 		argv = g_slist_append(argv, uri_escaped);
1440 	} else if (purple_strequal(web_browser, "custom")) {
1441 		GError *error = NULL;
1442 		const char *usercmd_command;
1443 		gint usercmd_argc, i;
1444 		gboolean uri_added = FALSE;
1445 
1446 		usercmd_command = purple_prefs_get_string(PIDGIN_PREFS_ROOT
1447 			"/browsers/manual_command");
1448 
1449 		if (usercmd_command == NULL || *usercmd_command == '\0') {
1450 			purple_notify_error(NULL, NULL, _("Unable to open URL"),
1451 				_("The 'Manual' browser command has been "
1452 				"chosen, but no command has been set."));
1453 			g_free(uri_escaped);
1454 			return NULL;
1455 		}
1456 
1457 		if (!g_shell_parse_argv(usercmd_command, &usercmd_argc,
1458 			&usercmd_argv, &error))
1459 		{
1460 			purple_notify_error(NULL, NULL, _("Unable to open URL: "
1461 				"the 'Manual' browser command seems invalid."),
1462 				error ? error->message : NULL);
1463 			g_error_free(error);
1464 			g_free(uri_escaped);
1465 			return NULL;
1466 		}
1467 
1468 		for (i = 0; i < usercmd_argc; i++) {
1469 			gchar *cmd_part = usercmd_argv[i];
1470 
1471 			if (uri_added || strstr(cmd_part, "%s") == NULL) {
1472 				argv = g_slist_append(argv, cmd_part);
1473 				continue;
1474 			}
1475 
1476 			uri_custom = purple_strreplace(cmd_part, "%s",
1477 				uri_escaped);
1478 			argv = g_slist_append(argv, uri_custom);
1479 			uri_added = TRUE;
1480 		}
1481 
1482 		/* There is no "%s" in the browser command. Assume the user
1483 		 * wanted the URL tacked on to the end of the command.
1484 		 */
1485 		if (!uri_added)
1486 			argv = g_slist_append(argv, uri_escaped);
1487 	}
1488 
1489 	if (argv_remote != NULL) {
1490 		/* try the remote command first */
1491 		if (!uri_command(argv_remote, TRUE))
1492 			uri_command(argv, FALSE);
1493 	} else
1494 		uri_command(argv, FALSE);
1495 
1496 	g_strfreev(usercmd_argv);
1497 	g_free(uri_escaped);
1498 	g_free(uri_custom);
1499 	g_slist_free(argv);
1500 	g_slist_free(argv_remote);
1501 
1502 #else /* !_WIN32 */
1503 	winpidgin_notify_uri(uri);
1504 #endif /* !_WIN32 */
1505 
1506 	return NULL;
1507 }
1508 
1509 void
pidgin_notify_pounce_add(PurpleAccount * account,PurplePounce * pounce,const char * alias,const char * event,const char * message,const char * date)1510 pidgin_notify_pounce_add(PurpleAccount *account, PurplePounce *pounce,
1511 		const char *alias, const char *event, const char *message, const char *date)
1512 {
1513 	GdkPixbuf *icon;
1514 	GtkTreeIter iter;
1515 	PidginNotifyPounceData *pounce_data;
1516 	gboolean first = (pounce_dialog == NULL);
1517 
1518 	if (pounce_dialog == NULL)
1519 		pounce_dialog = pidgin_create_notification_dialog(PIDGIN_NOTIFY_POUNCE);
1520 
1521 	icon = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
1522 
1523 	pounce_data = g_new(PidginNotifyPounceData, 1);
1524 
1525 	pounce_data->account = account;
1526 	pounce_data->pounce = pounce;
1527 	pounce_data->pouncee = g_strdup(purple_pounce_get_pouncee(pounce));
1528 
1529 	gtk_tree_store_append(pounce_dialog->treemodel, &iter, NULL);
1530 
1531 	gtk_tree_store_set(pounce_dialog->treemodel, &iter,
1532 			PIDGIN_POUNCE_ICON, icon,
1533 			PIDGIN_POUNCE_ALIAS, alias,
1534 			PIDGIN_POUNCE_EVENT, event,
1535 			PIDGIN_POUNCE_TEXT, (message != NULL)? message : _("No message"),
1536 			PIDGIN_POUNCE_DATE, date,
1537 			PIDGIN_POUNCE_DATA, pounce_data,
1538 			-1);
1539 
1540 	if (first) {
1541 		GtkTreeSelection *selection =
1542 				gtk_tree_view_get_selection(GTK_TREE_VIEW(pounce_dialog->treeview));
1543 		gtk_tree_selection_select_iter(selection, &iter);
1544 	}
1545 
1546 	if (icon)
1547 		g_object_unref(icon);
1548 
1549 	gtk_widget_show_all(pounce_dialog->dialog);
1550 
1551 	return;
1552 }
1553 
1554 static PidginNotifyDialog *
pidgin_create_notification_dialog(PidginNotifyType type)1555 pidgin_create_notification_dialog(PidginNotifyType type)
1556 {
1557 	GtkTreeStore *model = NULL;
1558 	GtkWidget *dialog = NULL;
1559 	GtkWidget *label = NULL;
1560 	GtkCellRenderer *rend;
1561 	GtkTreeViewColumn *column;
1562 	GtkWidget *button = NULL;
1563 	GtkWidget *vbox = NULL;
1564 	GtkTreeSelection *sel;
1565 	PidginNotifyDialog *spec_dialog = NULL;
1566 
1567 	g_return_val_if_fail(type < PIDGIN_NOTIFY_TYPES, NULL);
1568 
1569 	if (type == PIDGIN_NOTIFY_MAIL) {
1570 		g_return_val_if_fail(mail_dialog == NULL, mail_dialog);
1571 
1572 		model = gtk_tree_store_new(COLUMNS_PIDGIN_MAIL,
1573 						GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_POINTER);
1574 
1575 	} else if (type == PIDGIN_NOTIFY_POUNCE) {
1576 		g_return_val_if_fail(pounce_dialog == NULL, pounce_dialog);
1577 
1578 		model = gtk_tree_store_new(COLUMNS_PIDGIN_POUNCE,
1579 				GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
1580 				G_TYPE_STRING, G_TYPE_POINTER);
1581 	}
1582 
1583 	dialog = gtk_dialog_new();
1584 
1585 	/* Setup the dialog */
1586 	gtk_container_set_border_width(GTK_CONTAINER(dialog), PIDGIN_HIG_BOX_SPACE);
1587 	gtk_container_set_border_width(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), PIDGIN_HIG_BOX_SPACE);
1588 	gtk_dialog_set_has_separator(GTK_DIALOG(dialog), FALSE);
1589 	gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog)->vbox), PIDGIN_HIG_BORDER);
1590 
1591 	/* Vertical box */
1592 	vbox = GTK_DIALOG(dialog)->vbox;
1593 
1594 	/* Golden ratio it up! */
1595 	gtk_widget_set_size_request(dialog, 550, 400);
1596 
1597 	spec_dialog = g_new0(PidginNotifyDialog, 1);
1598 	spec_dialog->dialog = dialog;
1599 
1600 	spec_dialog->treemodel = model;
1601 	spec_dialog->treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
1602 	g_object_unref(G_OBJECT(model));
1603 
1604 	gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(spec_dialog->treeview), TRUE);
1605 
1606 	if (type == PIDGIN_NOTIFY_MAIL) {
1607 		gtk_window_set_title(GTK_WINDOW(dialog), _("New Mail"));
1608 		gtk_window_set_role(GTK_WINDOW(dialog), "new_mail_detailed");
1609 		g_signal_connect(G_OBJECT(dialog), "focus-in-event",
1610 					G_CALLBACK(mail_window_focus_cb), NULL);
1611 
1612 		gtk_dialog_add_button(GTK_DIALOG(dialog),
1613 					 _("Open All Messages"), GTK_RESPONSE_ACCEPT);
1614 
1615 		button = gtk_dialog_add_button(GTK_DIALOG(dialog),
1616 						 PIDGIN_STOCK_OPEN_MAIL, GTK_RESPONSE_YES);
1617 		spec_dialog->open_button = button;
1618 
1619 		gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(spec_dialog->treeview), FALSE);
1620 
1621 		gtk_tree_view_set_search_column(GTK_TREE_VIEW(spec_dialog->treeview), PIDGIN_MAIL_TEXT);
1622 		gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(spec_dialog->treeview),
1623 			             pidgin_tree_view_search_equal_func, NULL, NULL);
1624 
1625 		g_signal_connect(G_OBJECT(dialog), "response",
1626 						 G_CALLBACK(email_response_cb), spec_dialog);
1627 		g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(spec_dialog->treeview))),
1628 						 "changed", G_CALLBACK(selection_changed_cb), spec_dialog);
1629 		g_signal_connect(G_OBJECT(spec_dialog->treeview), "row-activated", G_CALLBACK(email_row_activated_cb), NULL);
1630 
1631 		column = gtk_tree_view_column_new();
1632 		gtk_tree_view_column_set_resizable(column, TRUE);
1633 		rend = gtk_cell_renderer_pixbuf_new();
1634 		gtk_tree_view_column_pack_start(column, rend, FALSE);
1635 
1636 		gtk_tree_view_column_set_attributes(column, rend, "pixbuf", PIDGIN_MAIL_ICON, NULL);
1637 		rend = gtk_cell_renderer_text_new();
1638 		gtk_tree_view_column_pack_start(column, rend, TRUE);
1639 		gtk_tree_view_column_set_attributes(column, rend, "markup", PIDGIN_MAIL_TEXT, NULL);
1640 		gtk_tree_view_append_column(GTK_TREE_VIEW(spec_dialog->treeview), column);
1641 
1642 		label = gtk_label_new(NULL);
1643 		gtk_label_set_markup(GTK_LABEL(label), _("<span weight=\"bold\" size=\"larger\">You have mail!</span>"));
1644 
1645 	} else if (type == PIDGIN_NOTIFY_POUNCE) {
1646 		gtk_window_set_title(GTK_WINDOW(dialog), _("New Pounces"));
1647 
1648 		button = gtk_dialog_add_button(GTK_DIALOG(dialog),
1649 						_("IM"), GTK_RESPONSE_YES);
1650 		gtk_widget_set_sensitive(button, FALSE);
1651 		spec_dialog->open_button = button;
1652 
1653 		button = gtk_dialog_add_button(GTK_DIALOG(dialog),
1654 						PIDGIN_STOCK_MODIFY, GTK_RESPONSE_APPLY);
1655 		gtk_widget_set_sensitive(button, FALSE);
1656 		spec_dialog->edit_button = button;
1657 
1658 		/* Translators: Make sure you translate "Dismiss" differently than
1659 		   "close"!  This string is used in the "You have pounced" dialog
1660 		   that appears when one of your Buddy Pounces is triggered.  In
1661 		   this context "Dismiss" means "I acknowledge that I've seen that
1662 		   this pounce was triggered--remove it from this list."  Translating
1663 		   it as "Remove" is acceptable if you can't think of a more precise
1664 		   word. */
1665 		button = gtk_dialog_add_button(GTK_DIALOG(dialog), _("Dismiss"),
1666 				GTK_RESPONSE_NO);
1667 		gtk_widget_set_sensitive(button, FALSE);
1668 		spec_dialog->dismiss_button = button;
1669 
1670 		g_signal_connect(G_OBJECT(dialog), "response",
1671 						 G_CALLBACK(pounce_response_cb), spec_dialog);
1672 
1673 		column = gtk_tree_view_column_new();
1674 		gtk_tree_view_column_set_title(column, _("Buddy"));
1675 		gtk_tree_view_column_set_resizable(column, TRUE);
1676 		rend = gtk_cell_renderer_pixbuf_new();
1677 		gtk_tree_view_column_pack_start(column, rend, FALSE);
1678 
1679 		gtk_tree_view_column_set_attributes(column, rend, "pixbuf", PIDGIN_POUNCE_ICON, NULL);
1680 		rend = gtk_cell_renderer_text_new();
1681 		gtk_tree_view_column_pack_start(column, rend, FALSE);
1682 		gtk_tree_view_column_add_attribute(column, rend, "text", PIDGIN_POUNCE_ALIAS);
1683 		gtk_tree_view_append_column(GTK_TREE_VIEW(spec_dialog->treeview), column);
1684 
1685 		column = gtk_tree_view_column_new();
1686 		gtk_tree_view_column_set_title(column, _("Event"));
1687 		gtk_tree_view_column_set_resizable(column, TRUE);
1688 		rend = gtk_cell_renderer_text_new();
1689 		gtk_tree_view_column_pack_start(column, rend, FALSE);
1690 		gtk_tree_view_column_add_attribute(column, rend, "text", PIDGIN_POUNCE_EVENT);
1691 		gtk_tree_view_append_column(GTK_TREE_VIEW(spec_dialog->treeview), column);
1692 
1693 		column = gtk_tree_view_column_new();
1694 		gtk_tree_view_column_set_title(column, _("Message"));
1695 		gtk_tree_view_column_set_resizable(column, TRUE);
1696 		rend = gtk_cell_renderer_text_new();
1697 		gtk_tree_view_column_pack_start(column, rend, FALSE);
1698 		gtk_tree_view_column_add_attribute(column, rend, "text", PIDGIN_POUNCE_TEXT);
1699 		gtk_tree_view_append_column(GTK_TREE_VIEW(spec_dialog->treeview), column);
1700 
1701 		column = gtk_tree_view_column_new();
1702 		gtk_tree_view_column_set_title(column, _("Date"));
1703 		gtk_tree_view_column_set_resizable(column, TRUE);
1704 		rend = gtk_cell_renderer_text_new();
1705 		gtk_tree_view_column_pack_start(column, rend, FALSE);
1706 		gtk_tree_view_column_add_attribute(column, rend, "text", PIDGIN_POUNCE_DATE);
1707 		gtk_tree_view_append_column(GTK_TREE_VIEW(spec_dialog->treeview), column);
1708 
1709 		label = gtk_label_new(NULL);
1710 		gtk_label_set_markup(GTK_LABEL(label), _("<span weight=\"bold\" size=\"larger\">You have pounced!</span>"));
1711 
1712 		sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(spec_dialog->treeview));
1713 		gtk_tree_selection_set_mode(sel, GTK_SELECTION_MULTIPLE);
1714 		g_signal_connect(G_OBJECT(sel), "changed",
1715 			G_CALLBACK(pounce_row_selected_cb), NULL);
1716 		g_signal_connect(G_OBJECT(spec_dialog->treeview), "row-activated",
1717 			G_CALLBACK(pounce_response_open_ims), NULL);
1718 	}
1719 
1720 	gtk_dialog_add_button(GTK_DIALOG(dialog),
1721 		GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
1722 
1723 	gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1724 	gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
1725 	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1726 	gtk_box_pack_start(GTK_BOX(vbox),
1727 		pidgin_make_scrollable(spec_dialog->treeview, GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS, GTK_SHADOW_IN, -1, -1),
1728 		TRUE, TRUE, 2);
1729 
1730 	return spec_dialog;
1731 }
1732 
1733 static void
signed_off_cb(PurpleConnection * gc,gpointer unused)1734 signed_off_cb(PurpleConnection *gc, gpointer unused)
1735 {
1736 	/* Clear any pending emails for this account */
1737 	pidgin_notify_emails(gc, 0, FALSE, NULL, NULL, NULL, NULL);
1738 
1739 	if (mail_dialog != NULL && mail_dialog->total_count == 0)
1740 		reset_mail_dialog(NULL);
1741 }
1742 
1743 static void*
pidgin_notify_get_handle(void)1744 pidgin_notify_get_handle(void)
1745 {
1746 	static int handle;
1747 	return &handle;
1748 }
1749 
pidgin_notify_init(void)1750 void pidgin_notify_init(void)
1751 {
1752 	void *handle = pidgin_notify_get_handle();
1753 
1754 	purple_signal_connect(purple_connections_get_handle(), "signed-off",
1755 			handle, PURPLE_CALLBACK(signed_off_cb), NULL);
1756 }
1757 
pidgin_notify_uninit(void)1758 void pidgin_notify_uninit(void)
1759 {
1760 	purple_signals_disconnect_by_handle(pidgin_notify_get_handle());
1761 }
1762 
1763 static PurpleNotifyUiOps ops =
1764 {
1765 	pidgin_notify_message,
1766 	pidgin_notify_email,
1767 	pidgin_notify_emails,
1768 	pidgin_notify_formatted,
1769 	pidgin_notify_searchresults,
1770 	pidgin_notify_searchresults_new_rows,
1771 	pidgin_notify_userinfo,
1772 	pidgin_notify_uri,
1773 	pidgin_close_notify,
1774 	NULL,
1775 	NULL,
1776 	NULL,
1777 	NULL
1778 };
1779 
1780 PurpleNotifyUiOps *
pidgin_notify_get_ui_ops(void)1781 pidgin_notify_get_ui_ops(void)
1782 {
1783 	return &ops;
1784 }
1785