comparison finch/gntlog.c @ 22248:88796aff14d6

Add a finch log viewer. This is largely copied from Pidgin. The search box must be focused in order to scroll the log or fire up the pager, and is labelled such. Feel free to come up with a better widget to attach these to.
author Richard Nelson <wabz@pidgin.im>
date Sat, 02 Feb 2008 03:51:19 +0000
parents
children 561729870929 c96b34b58b85
comparison
equal deleted inserted replaced
22247:5ffb0b5f785f 22248:88796aff14d6
1 /**
2 * @file gntlog.c GNT Log viewer
3 * @ingroup finch
4 */
5
6 /* finch
7 *
8 * Finch 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
28 #include <gnt.h>
29 #include <gntbox.h>
30 #include <gntbutton.h>
31 #include <gntentry.h>
32 #include <gntlabel.h>
33 #include <gnttextview.h>
34 #include <gnttree.h>
35 #include <gntwindow.h>
36
37 #include "account.h"
38 #include "debug.h"
39 #include "log.h"
40 #include "notify.h"
41 #include "request.h"
42 #include "util.h"
43
44 #include "gntlog.h"
45
46 static GHashTable *log_viewers = NULL;
47 static void populate_log_tree(FinchLogViewer *lv);
48 static FinchLogViewer *syslog_viewer = NULL;
49
50 struct log_viewer_hash_t {
51 PurpleLogType type;
52 char *screenname;
53 PurpleAccount *account;
54 PurpleContact *contact;
55 };
56
57 static guint log_viewer_hash(gconstpointer data)
58 {
59 const struct log_viewer_hash_t *viewer = data;
60
61 if (viewer->contact != NULL)
62 return g_direct_hash(viewer->contact);
63
64 return g_str_hash(viewer->screenname) +
65 g_str_hash(purple_account_get_username(viewer->account));
66 }
67
68 static gboolean log_viewer_equal(gconstpointer y, gconstpointer z)
69 {
70 const struct log_viewer_hash_t *a, *b;
71 int ret;
72 char *normal;
73
74 a = y;
75 b = z;
76
77 if (a->contact != NULL) {
78 if (b->contact != NULL)
79 return (a->contact == b->contact);
80 else
81 return FALSE;
82 } else {
83 if (b->contact != NULL)
84 return FALSE;
85 }
86
87 normal = g_strdup(purple_normalize(a->account, a->screenname));
88 ret = (a->account == b->account) &&
89 !strcmp(normal, purple_normalize(b->account, b->screenname));
90 g_free(normal);
91
92 return ret;
93 }
94
95 static const char *log_get_date(PurpleLog *log)
96 {
97 if (log->tm)
98 return purple_date_format_full(log->tm);
99 else
100 return purple_date_format_full(localtime(&log->time));
101 }
102
103 static void search_cb(GntWidget *button, FinchLogViewer *lv)
104 {
105 const char *search_term = gnt_entry_get_text(GNT_ENTRY(lv->entry));
106 GList *logs;
107
108 if (!(*search_term)) {
109 /* reset the tree */
110 gnt_tree_remove_all(GNT_TREE(lv->tree));
111 g_free(lv->search);
112 lv->search = NULL;
113 populate_log_tree(lv);
114 return;
115 }
116
117 if (lv->search != NULL && !strcmp(lv->search, search_term)) {
118 return;
119 }
120
121 g_free(lv->search);
122 lv->search = g_strdup(search_term);
123
124 gnt_tree_remove_all(GNT_TREE(lv->tree));
125 gnt_text_view_clear(GNT_TEXT_VIEW(lv->text));
126
127 for (logs = lv->logs; logs != NULL; logs = logs->next) {
128 char *read = purple_log_read((PurpleLog*)logs->data, NULL);
129 if (read && *read && purple_strcasestr(read, search_term)) {
130 PurpleLog *log = logs->data;
131
132 gnt_tree_add_row_last(GNT_TREE(lv->tree),
133 log,
134 gnt_tree_create_row(GNT_TREE(lv->tree), log_get_date(log)),
135 NULL);
136 }
137 g_free(read);
138 }
139
140 }
141
142 static void destroy_cb(GntWidget *w, struct log_viewer_hash_t *ht) {
143 FinchLogViewer *lv = syslog_viewer;
144
145 if (ht != NULL) {
146 lv = g_hash_table_lookup(log_viewers, ht);
147 g_hash_table_remove(log_viewers, ht);
148
149 g_free(ht->screenname);
150 g_free(ht);
151 } else
152 syslog_viewer = NULL;
153
154 purple_request_close_with_handle(lv);
155
156 g_list_foreach(lv->logs, (GFunc)purple_log_free, NULL);
157 g_list_free(lv->logs);
158
159 g_free(lv->search);
160 g_free(lv);
161
162 gnt_widget_destroy(w);
163 }
164
165 static void log_select_cb(GntWidget *w, gpointer old, gpointer new, FinchLogViewer *viewer) {
166 GntTree *tree = GNT_TREE(w);
167 PurpleLog *log = NULL;
168 PurpleLogReadFlags flags;
169 char *read = NULL, *strip, *newline;
170 int h;
171
172 if (!viewer->search && !gnt_tree_get_parent_key(tree, new))
173 return;
174
175 log = (PurpleLog *)new;
176
177 if (log == NULL)
178 return;
179
180 if (log->type != PURPLE_LOG_SYSTEM) {
181 char *title;
182 if (log->type == PURPLE_LOG_CHAT)
183 title = g_strdup_printf(_("Conversation in %s on %s"),
184 log->name, log_get_date(log));
185 else
186 title = g_strdup_printf(_("Conversation with %s on %s"),
187 log->name, log_get_date(log));
188
189 gnt_label_set_text(GNT_LABEL(viewer->label), title);
190 g_free(title);
191 }
192
193 read = purple_log_read(log, &flags);
194 if (flags != PURPLE_LOG_READ_NO_NEWLINE) {
195 newline = purple_strdup_withhtml(read);
196 strip = purple_markup_strip_html(newline);
197 g_free(newline);
198 } else {
199 strip = purple_markup_strip_html(read);
200 }
201 viewer->flags = flags;
202
203 purple_signal_emit(finch_log_get_handle(), "log-displaying", viewer, log);
204
205 gnt_text_view_clear(GNT_TEXT_VIEW(viewer->text));
206 gnt_text_view_append_text_with_flags(GNT_TEXT_VIEW(viewer->text), strip, GNT_TEXT_FLAG_NORMAL);
207 gnt_widget_get_size(viewer->text, NULL, &h);
208 gnt_text_view_scroll(GNT_TEXT_VIEW(viewer->text), h - 2);
209 g_free(read);
210 g_free(strip);
211 }
212
213 /* I want to make this smarter, but haven't come up with a cool algorithm to do so, yet.
214 * I want the tree to be divided into groups like "Today," "Yesterday," "Last week,"
215 * "August," "2002," etc. based on how many conversation took place in each subdivision.
216 *
217 * For now, I'll just make it a flat list.
218 */
219 static void populate_log_tree(FinchLogViewer *lv)
220 /* Logs are made from trees in real life.
221 This is a tree made from logs */
222 {
223 const char *pmonth;
224 char *month = NULL;
225 char prev_top_month[30] = "";
226 GList *logs = lv->logs;
227
228 while (logs != NULL) {
229 PurpleLog *log = logs->data;
230
231 pmonth = purple_utf8_strftime(_("%B %Y"),
232 log->tm ? log->tm : localtime(&log->time));
233
234 if (strcmp(pmonth, prev_top_month) != 0) {
235 month = g_strdup(pmonth);
236 /* top level */
237 gnt_tree_add_row_last(GNT_TREE(lv->tree),
238 month,
239 gnt_tree_create_row(GNT_TREE(lv->tree), month),
240 NULL);
241 gnt_tree_set_expanded(GNT_TREE(lv->tree), month, FALSE);
242
243 strncpy(prev_top_month, month, sizeof(prev_top_month));
244 }
245
246 /* sub */
247 gnt_tree_add_row_last(GNT_TREE(lv->tree),
248 log,
249 gnt_tree_create_row(GNT_TREE(lv->tree), log_get_date(log)),
250 month);
251
252 logs = logs->next;
253 }
254 }
255
256 static FinchLogViewer *display_log_viewer(struct log_viewer_hash_t *ht, GList *logs,
257 const char *title, int log_size)
258 {
259 FinchLogViewer *lv;
260 char *text;
261 GntWidget *vbox, *hbox;
262 GntWidget *size_label;
263
264 if (logs == NULL)
265 {
266 /* No logs were found. */
267 const char *log_preferences = NULL;
268
269 if (ht == NULL) {
270 if (!purple_prefs_get_bool("/purple/logging/log_system"))
271 log_preferences = _("System events will only be logged if the \"Log all status changes to system log\" preference is enabled.");
272 } else {
273 if (ht->type == PURPLE_LOG_IM) {
274 if (!purple_prefs_get_bool("/purple/logging/log_ims"))
275 log_preferences = _("Instant messages will only be logged if the \"Log all instant messages\" preference is enabled.");
276 } else if (ht->type == PURPLE_LOG_CHAT) {
277 if (!purple_prefs_get_bool("/purple/logging/log_chats"))
278 log_preferences = _("Chats will only be logged if the \"Log all chats\" preference is enabled.");
279 }
280 g_free(ht->screenname);
281 g_free(ht);
282 }
283
284 purple_notify_info(NULL, title, _("No logs were found"), log_preferences);
285 return NULL;
286 }
287
288 lv = g_new0(FinchLogViewer, 1);
289 lv->logs = logs;
290
291 if (ht != NULL)
292 g_hash_table_insert(log_viewers, ht, lv);
293
294 /* Window ***********/
295 lv->window = gnt_vwindow_new(FALSE);
296 gnt_box_set_title(GNT_BOX(lv->window), title);
297 gnt_box_set_toplevel(GNT_BOX(lv->window), TRUE);
298 gnt_box_set_pad(GNT_BOX(lv->window), 0);
299 g_signal_connect(G_OBJECT(lv->window), "destroy", G_CALLBACK(destroy_cb), ht);
300
301 vbox = gnt_vbox_new(FALSE);
302 gnt_box_add_widget(GNT_BOX(lv->window), vbox);
303
304 /* Label ************/
305 text = g_strdup_printf("%s", title);
306 lv->label = gnt_label_new(text);
307 g_free(text);
308 gnt_box_add_widget(GNT_BOX(vbox), lv->label);
309
310 hbox = gnt_hbox_new(FALSE);
311 gnt_box_add_widget(GNT_BOX(vbox), hbox);
312 /* List *************/
313 lv->tree = gnt_tree_new();
314 gnt_widget_set_size(lv->tree, 30, 0);
315 populate_log_tree(lv);
316 g_signal_connect (G_OBJECT(lv->tree), "selection-changed",
317 G_CALLBACK (log_select_cb),
318 lv);
319 gnt_box_add_widget(GNT_BOX(hbox), lv->tree);
320
321 /* Viewer ************/
322 lv->text = gnt_text_view_new();
323 gnt_box_add_widget(GNT_BOX(hbox), lv->text);
324
325 hbox = gnt_hbox_new(FALSE);
326 gnt_box_add_widget(GNT_BOX(vbox), hbox);
327 /* Log size ************/
328 if (log_size) {
329 char *sz_txt = purple_str_size_to_units(log_size);
330 text = g_strdup_printf("%s %s", _("Total log size:"), sz_txt);
331 size_label = gnt_label_new(text);
332 gnt_box_add_widget(GNT_BOX(hbox), size_label);
333 g_free(sz_txt);
334 g_free(text);
335 }
336
337 /* Search box **********/
338 gnt_box_add_widget(GNT_BOX(hbox), gnt_label_new(_("Scroll/Search: ")));
339 lv->entry = gnt_entry_new("");
340 gnt_box_add_widget(GNT_BOX(hbox), lv->entry);
341 g_signal_connect(GNT_ENTRY(lv->entry), "activate", G_CALLBACK(search_cb), lv);
342
343 gnt_text_view_attach_scroll_widget(GNT_TEXT_VIEW(lv->text), lv->entry);
344 gnt_text_view_attach_pager_widget(GNT_TEXT_VIEW(lv->text), lv->entry);
345
346 gnt_widget_show(lv->window);
347
348 return lv;
349 }
350
351 void finch_log_show(PurpleLogType type, const char *screenname, PurpleAccount *account) {
352 struct log_viewer_hash_t *ht;
353 FinchLogViewer *lv = NULL;
354 const char *name = screenname;
355 char *title;
356
357 g_return_if_fail(account != NULL);
358 g_return_if_fail(screenname != NULL);
359
360 ht = g_new0(struct log_viewer_hash_t, 1);
361
362 ht->type = type;
363 ht->screenname = g_strdup(screenname);
364 ht->account = account;
365
366 if (log_viewers == NULL) {
367 log_viewers = g_hash_table_new(log_viewer_hash, log_viewer_equal);
368 } else if ((lv = g_hash_table_lookup(log_viewers, ht))) {
369 gnt_window_present(lv->window);
370 g_free(ht->screenname);
371 g_free(ht);
372 return;
373 }
374
375 if (type == PURPLE_LOG_CHAT) {
376 PurpleChat *chat;
377
378 chat = purple_blist_find_chat(account, screenname);
379 if (chat != NULL)
380 name = purple_chat_get_name(chat);
381
382 title = g_strdup_printf(_("Conversations in %s"), name);
383 } else {
384 PurpleBuddy *buddy;
385
386 buddy = purple_find_buddy(account, screenname);
387 if (buddy != NULL)
388 name = purple_buddy_get_contact_alias(buddy);
389
390 title = g_strdup_printf(_("Conversations with %s"), name);
391 }
392
393 display_log_viewer(ht, purple_log_get_logs(type, screenname, account),
394 title, purple_log_get_total_size(type, screenname, account));
395
396 g_free(title);
397 }
398
399 void finch_log_show_contact(PurpleContact *contact) {
400 struct log_viewer_hash_t *ht;
401 PurpleBlistNode *child;
402 FinchLogViewer *lv = NULL;
403 GList *logs = NULL;
404 const char *name = NULL;
405 char *title;
406 int total_log_size = 0;
407
408 g_return_if_fail(contact != NULL);
409
410 ht = g_new0(struct log_viewer_hash_t, 1);
411 ht->type = PURPLE_LOG_IM;
412 ht->contact = contact;
413
414 if (log_viewers == NULL) {
415 log_viewers = g_hash_table_new(log_viewer_hash, log_viewer_equal);
416 } else if ((lv = g_hash_table_lookup(log_viewers, ht))) {
417 gnt_window_present(lv->window);
418 g_free(ht);
419 return;
420 }
421
422 for (child = contact->node.child ; child ; child = child->next) {
423 if (!PURPLE_BLIST_NODE_IS_BUDDY(child))
424 continue;
425
426 logs = g_list_concat(purple_log_get_logs(PURPLE_LOG_IM, ((PurpleBuddy *)child)->name,
427 ((PurpleBuddy *)child)->account), logs);
428 total_log_size += purple_log_get_total_size(PURPLE_LOG_IM, ((PurpleBuddy *)child)->name, ((PurpleBuddy *)child)->account);
429 }
430 logs = g_list_sort(logs, purple_log_compare);
431
432 if (contact->alias != NULL)
433 name = contact->alias;
434 else if (contact->priority != NULL)
435 name = purple_buddy_get_contact_alias(contact->priority);
436
437 /* This will happen if the contact doesn't have an alias,
438 * and none of the contact's buddies are online.
439 * There is probably a better way to deal with this. */
440 if (name == NULL) {
441 if (contact->node.child != NULL && PURPLE_BLIST_NODE_IS_BUDDY(contact->node.child))
442 name = purple_buddy_get_contact_alias((PurpleBuddy *) contact->node.child);
443 if (name == NULL)
444 name = "";
445 }
446
447 title = g_strdup_printf(_("Conversations with %s"), name);
448 display_log_viewer(ht, logs, title, total_log_size);
449 g_free(title);
450 }
451
452 void finch_syslog_show()
453 {
454 GList *accounts = NULL;
455 GList *logs = NULL;
456
457 if (syslog_viewer != NULL) {
458 gnt_window_present(syslog_viewer->window);
459 return;
460 }
461
462 for(accounts = purple_accounts_get_all(); accounts != NULL; accounts = accounts->next) {
463
464 PurpleAccount *account = (PurpleAccount *)accounts->data;
465 if(purple_find_prpl(purple_account_get_protocol_id(account)) == NULL)
466 continue;
467
468 logs = g_list_concat(purple_log_get_system_logs(account), logs);
469 }
470 logs = g_list_sort(logs, purple_log_compare);
471
472 syslog_viewer = display_log_viewer(NULL, logs, _("System Log"), 0);
473 }
474
475 /****************************************************************************
476 * GNT LOG SUBSYSTEM *******************************************************
477 ****************************************************************************/
478
479 void *
480 finch_log_get_handle(void)
481 {
482 static int handle;
483
484 return &handle;
485 }
486
487 void finch_log_init(void)
488 {
489 void *handle = finch_log_get_handle();
490
491 purple_signal_register(handle, "log-displaying",
492 purple_marshal_VOID__POINTER_POINTER,
493 NULL, 2,
494 purple_value_new(PURPLE_TYPE_BOXED,
495 "FinchLogViewer *"),
496 purple_value_new(PURPLE_TYPE_SUBTYPE,
497 PURPLE_SUBTYPE_LOG));
498 }
499
500 void
501 finch_log_uninit(void)
502 {
503 purple_signals_unregister_by_instance(finch_log_get_handle());
504 }