Update versions for 2.5.5.
[pidgin-git.git] / finch / gntlog.c
blob1dc973fbe4267c4afdb03b23977e5560f57268c5
1 /**
2 * @file gntlog.c GNT Log viewer
3 * @ingroup finch
4 */
6 /* finch
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.
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.
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.
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
26 #include "finch.h"
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>
37 #include "account.h"
38 #include "debug.h"
39 #include "log.h"
40 #include "notify.h"
41 #include "request.h"
42 #include "util.h"
44 #include "gntlog.h"
46 static GHashTable *log_viewers = NULL;
47 static void populate_log_tree(FinchLogViewer *lv);
48 static FinchLogViewer *syslog_viewer = NULL;
50 struct log_viewer_hash_t {
51 PurpleLogType type;
52 char *screenname;
53 PurpleAccount *account;
54 PurpleContact *contact;
57 static guint log_viewer_hash(gconstpointer data)
59 const struct log_viewer_hash_t *viewer = data;
61 if (viewer->contact != NULL)
62 return g_direct_hash(viewer->contact);
64 if (viewer->account) {
65 return g_str_hash(viewer->screenname) +
66 g_str_hash(purple_account_get_username(viewer->account));
69 return (guint)viewer;
72 static gboolean log_viewer_equal(gconstpointer y, gconstpointer z)
74 const struct log_viewer_hash_t *a, *b;
75 int ret;
76 char *normal;
78 a = y;
79 b = z;
81 if (a->contact != NULL) {
82 if (b->contact != NULL)
83 return (a->contact == b->contact);
84 else
85 return FALSE;
86 } else {
87 if (b->contact != NULL)
88 return FALSE;
91 if (a->screenname && b->screenname) {
92 normal = g_strdup(purple_normalize(a->account, a->screenname));
93 ret = (a->account == b->account) &&
94 !strcmp(normal, purple_normalize(b->account, b->screenname));
95 g_free(normal);
96 } else {
97 ret = (a == b);
100 return ret;
103 static const char *log_get_date(PurpleLog *log)
105 if (log->tm)
106 return purple_date_format_full(log->tm);
107 else
108 return purple_date_format_full(localtime(&log->time));
111 static void search_cb(GntWidget *button, FinchLogViewer *lv)
113 const char *search_term = gnt_entry_get_text(GNT_ENTRY(lv->entry));
114 GList *logs;
116 if (!(*search_term)) {
117 /* reset the tree */
118 gnt_tree_remove_all(GNT_TREE(lv->tree));
119 g_free(lv->search);
120 lv->search = NULL;
121 populate_log_tree(lv);
122 return;
125 if (lv->search != NULL && !strcmp(lv->search, search_term)) {
126 return;
129 g_free(lv->search);
130 lv->search = g_strdup(search_term);
132 gnt_tree_remove_all(GNT_TREE(lv->tree));
133 gnt_text_view_clear(GNT_TEXT_VIEW(lv->text));
135 for (logs = lv->logs; logs != NULL; logs = logs->next) {
136 char *read = purple_log_read((PurpleLog*)logs->data, NULL);
137 if (read && *read && purple_strcasestr(read, search_term)) {
138 PurpleLog *log = logs->data;
140 gnt_tree_add_row_last(GNT_TREE(lv->tree),
141 log,
142 gnt_tree_create_row(GNT_TREE(lv->tree), log_get_date(log)),
143 NULL);
145 g_free(read);
150 static void destroy_cb(GntWidget *w, struct log_viewer_hash_t *ht)
152 FinchLogViewer *lv = syslog_viewer;
154 if (ht != NULL) {
155 lv = g_hash_table_lookup(log_viewers, ht);
156 g_hash_table_remove(log_viewers, ht);
158 g_free(ht->screenname);
159 g_free(ht);
160 } else
161 syslog_viewer = NULL;
163 purple_request_close_with_handle(lv);
165 g_list_foreach(lv->logs, (GFunc)purple_log_free, NULL);
166 g_list_free(lv->logs);
168 g_free(lv->search);
169 g_free(lv);
171 gnt_widget_destroy(w);
174 static void log_select_cb(GntWidget *w, gpointer old, gpointer new, FinchLogViewer *viewer)
176 GntTree *tree = GNT_TREE(w);
177 PurpleLog *log = NULL;
178 PurpleLogReadFlags flags;
179 char *read = NULL, *strip, *newline;
181 if (!viewer->search && !gnt_tree_get_parent_key(tree, new))
182 return;
184 log = (PurpleLog *)new;
186 if (log == NULL)
187 return;
189 if (log->type != PURPLE_LOG_SYSTEM) {
190 char *title;
191 if (log->type == PURPLE_LOG_CHAT)
192 title = g_strdup_printf(_("Conversation in %s on %s"),
193 log->name, log_get_date(log));
194 else
195 title = g_strdup_printf(_("Conversation with %s on %s"),
196 log->name, log_get_date(log));
198 gnt_label_set_text(GNT_LABEL(viewer->label), title);
199 g_free(title);
202 read = purple_log_read(log, &flags);
203 if (flags != PURPLE_LOG_READ_NO_NEWLINE) {
204 newline = purple_strdup_withhtml(read);
205 strip = purple_markup_strip_html(newline);
206 g_free(newline);
207 } else {
208 strip = purple_markup_strip_html(read);
210 viewer->flags = flags;
212 purple_signal_emit(finch_log_get_handle(), "log-displaying", viewer, log);
214 gnt_text_view_clear(GNT_TEXT_VIEW(viewer->text));
215 gnt_text_view_append_text_with_flags(GNT_TEXT_VIEW(viewer->text), strip, GNT_TEXT_FLAG_NORMAL);
216 g_free(read);
217 g_free(strip);
220 /* I want to make this smarter, but haven't come up with a cool algorithm to do so, yet.
221 * I want the tree to be divided into groups like "Today," "Yesterday," "Last week,"
222 * "August," "2002," etc. based on how many conversation took place in each subdivision.
224 * For now, I'll just make it a flat list.
226 static void populate_log_tree(FinchLogViewer *lv)
227 /* Logs are made from trees in real life.
228 This is a tree made from logs */
230 const char *pmonth;
231 char *month = NULL;
232 char prev_top_month[30] = "";
233 GList *logs = lv->logs;
235 while (logs != NULL) {
236 PurpleLog *log = logs->data;
238 pmonth = purple_utf8_strftime(_("%B %Y"),
239 log->tm ? log->tm : localtime(&log->time));
241 if (strcmp(pmonth, prev_top_month) != 0) {
242 month = g_strdup(pmonth);
243 /* top level */
244 gnt_tree_add_row_last(GNT_TREE(lv->tree),
245 month,
246 gnt_tree_create_row(GNT_TREE(lv->tree), month),
247 NULL);
248 gnt_tree_set_expanded(GNT_TREE(lv->tree), month, FALSE);
250 strncpy(prev_top_month, month, sizeof(prev_top_month));
253 /* sub */
254 gnt_tree_add_row_last(GNT_TREE(lv->tree),
255 log,
256 gnt_tree_create_row(GNT_TREE(lv->tree), log_get_date(log)),
257 month);
259 logs = logs->next;
263 static FinchLogViewer *display_log_viewer(struct log_viewer_hash_t *ht, GList *logs,
264 const char *title, int log_size)
266 FinchLogViewer *lv;
267 char *text;
268 GntWidget *vbox, *hbox;
269 GntWidget *size_label;
271 if (logs == NULL)
273 /* No logs were found. */
274 const char *log_preferences = NULL;
276 if (ht == NULL) {
277 if (!purple_prefs_get_bool("/purple/logging/log_system"))
278 log_preferences = _("System events will only be logged if the \"Log all status changes to system log\" preference is enabled.");
279 } else {
280 if (ht->type == PURPLE_LOG_IM) {
281 if (!purple_prefs_get_bool("/purple/logging/log_ims"))
282 log_preferences = _("Instant messages will only be logged if the \"Log all instant messages\" preference is enabled.");
283 } else if (ht->type == PURPLE_LOG_CHAT) {
284 if (!purple_prefs_get_bool("/purple/logging/log_chats"))
285 log_preferences = _("Chats will only be logged if the \"Log all chats\" preference is enabled.");
287 g_free(ht->screenname);
288 g_free(ht);
291 purple_notify_info(NULL, title, _("No logs were found"), log_preferences);
292 return NULL;
295 lv = g_new0(FinchLogViewer, 1);
296 lv->logs = logs;
298 if (ht != NULL)
299 g_hash_table_insert(log_viewers, ht, lv);
301 /* Window ***********/
302 lv->window = gnt_vwindow_new(FALSE);
303 gnt_box_set_title(GNT_BOX(lv->window), title);
304 gnt_box_set_toplevel(GNT_BOX(lv->window), TRUE);
305 gnt_box_set_pad(GNT_BOX(lv->window), 0);
306 g_signal_connect(G_OBJECT(lv->window), "destroy", G_CALLBACK(destroy_cb), ht);
308 vbox = gnt_vbox_new(FALSE);
309 gnt_box_add_widget(GNT_BOX(lv->window), vbox);
311 /* Label ************/
312 text = g_strdup_printf("%s", title);
313 lv->label = gnt_label_new_with_format(text, GNT_TEXT_FLAG_BOLD);
314 g_free(text);
315 gnt_box_add_widget(GNT_BOX(vbox), lv->label);
317 hbox = gnt_hbox_new(FALSE);
318 gnt_box_add_widget(GNT_BOX(vbox), hbox);
319 /* List *************/
320 lv->tree = gnt_tree_new();
321 gnt_widget_set_size(lv->tree, 30, 0);
322 populate_log_tree(lv);
323 g_signal_connect (G_OBJECT(lv->tree), "selection-changed",
324 G_CALLBACK (log_select_cb),
325 lv);
326 gnt_box_add_widget(GNT_BOX(hbox), lv->tree);
328 /* Viewer ************/
329 lv->text = gnt_text_view_new();
330 gnt_box_add_widget(GNT_BOX(hbox), lv->text);
331 gnt_text_view_set_flag(GNT_TEXT_VIEW(lv->text), GNT_TEXT_VIEW_TOP_ALIGN);
333 hbox = gnt_hbox_new(FALSE);
334 gnt_box_add_widget(GNT_BOX(vbox), hbox);
335 /* Log size ************/
336 if (log_size) {
337 char *sz_txt = purple_str_size_to_units(log_size);
338 text = g_strdup_printf("%s %s", _("Total log size:"), sz_txt);
339 size_label = gnt_label_new(text);
340 gnt_box_add_widget(GNT_BOX(hbox), size_label);
341 g_free(sz_txt);
342 g_free(text);
345 /* Search box **********/
346 gnt_box_add_widget(GNT_BOX(hbox), gnt_label_new(_("Scroll/Search: ")));
347 lv->entry = gnt_entry_new("");
348 gnt_box_add_widget(GNT_BOX(hbox), lv->entry);
349 g_signal_connect(GNT_ENTRY(lv->entry), "activate", G_CALLBACK(search_cb), lv);
351 gnt_text_view_attach_scroll_widget(GNT_TEXT_VIEW(lv->text), lv->entry);
352 gnt_text_view_attach_pager_widget(GNT_TEXT_VIEW(lv->text), lv->entry);
354 gnt_widget_show(lv->window);
356 return lv;
359 static void
360 our_logging_blows(PurpleLogSet *set, PurpleLogSet *setagain, GList **list)
362 /* The iteration happens on the first list. So we use the shorter list in front */
363 if (set->type != PURPLE_LOG_IM)
364 return;
365 *list = g_list_concat(purple_log_get_logs(PURPLE_LOG_IM, set->name, set->account), *list);
368 void finch_log_show(PurpleLogType type, const char *screenname, PurpleAccount *account)
370 struct log_viewer_hash_t *ht;
371 FinchLogViewer *lv = NULL;
372 const char *name = screenname;
373 char *title;
374 GList *logs = NULL;
375 int size = 0;
377 if (type != PURPLE_LOG_IM) {
378 g_return_if_fail(account != NULL);
379 g_return_if_fail(screenname != NULL);
382 ht = g_new0(struct log_viewer_hash_t, 1);
384 ht->type = type;
385 ht->screenname = g_strdup(screenname);
386 ht->account = account;
388 if (log_viewers == NULL) {
389 log_viewers = g_hash_table_new(log_viewer_hash, log_viewer_equal);
390 } else if ((lv = g_hash_table_lookup(log_viewers, ht))) {
391 gnt_window_present(lv->window);
392 g_free(ht->screenname);
393 g_free(ht);
394 return;
397 if (type == PURPLE_LOG_CHAT) {
398 PurpleChat *chat;
400 chat = purple_blist_find_chat(account, screenname);
401 if (chat != NULL)
402 name = purple_chat_get_name(chat);
404 title = g_strdup_printf(_("Conversations in %s"), name);
405 } else {
406 PurpleBuddy *buddy;
408 if (screenname) {
409 buddy = purple_find_buddy(account, screenname);
410 if (buddy != NULL)
411 name = purple_buddy_get_contact_alias(buddy);
412 title = g_strdup_printf(_("Conversations with %s"), name);
413 } else {
414 title = g_strdup(_("All Conversations"));
418 if (screenname) {
419 logs = purple_log_get_logs(type, screenname, account);
420 size = purple_log_get_total_size(type, screenname, account);
421 } else {
422 /* This will happen only for IMs */
423 GHashTable *table = purple_log_get_log_sets();
424 g_hash_table_foreach(table, (GHFunc)our_logging_blows, &logs);
425 g_hash_table_destroy(table);
426 logs = g_list_sort(logs, purple_log_compare);
427 size = 0;
430 display_log_viewer(ht, logs, title, size);
432 g_free(title);
435 void finch_log_show_contact(PurpleContact *contact)
437 struct log_viewer_hash_t *ht;
438 PurpleBlistNode *child;
439 FinchLogViewer *lv = NULL;
440 GList *logs = NULL;
441 const char *name = NULL;
442 char *title;
443 int total_log_size = 0;
445 g_return_if_fail(contact != NULL);
447 ht = g_new0(struct log_viewer_hash_t, 1);
448 ht->type = PURPLE_LOG_IM;
449 ht->contact = contact;
451 if (log_viewers == NULL) {
452 log_viewers = g_hash_table_new(log_viewer_hash, log_viewer_equal);
453 } else if ((lv = g_hash_table_lookup(log_viewers, ht))) {
454 gnt_window_present(lv->window);
455 g_free(ht);
456 return;
459 for (child = purple_blist_node_get_first_child((PurpleBlistNode*)contact); child;
460 child = purple_blist_node_get_sibling_next(child)) {
461 if (!PURPLE_BLIST_NODE_IS_BUDDY(child))
462 continue;
464 logs = g_list_concat(purple_log_get_logs(PURPLE_LOG_IM, ((PurpleBuddy *)child)->name,
465 ((PurpleBuddy *)child)->account), logs);
466 total_log_size += purple_log_get_total_size(PURPLE_LOG_IM, ((PurpleBuddy *)child)->name, ((PurpleBuddy *)child)->account);
468 logs = g_list_sort(logs, purple_log_compare);
470 name = purple_contact_get_alias(contact);
471 if (!name)
472 name = purple_buddy_get_contact_alias(purple_contact_get_priority_buddy(contact));
474 /* This will happen if the contact doesn't have an alias,
475 * and none of the contact's buddies are online.
476 * There is probably a better way to deal with this. */
477 if (name == NULL) {
478 child = purple_blist_node_get_first_child((PurpleBlistNode*)contact);
479 if (child != NULL && PURPLE_BLIST_NODE_IS_BUDDY(child))
480 name = purple_buddy_get_contact_alias((PurpleBuddy *)child);
481 if (name == NULL)
482 name = "";
485 title = g_strdup_printf(_("Conversations with %s"), name);
486 display_log_viewer(ht, logs, title, total_log_size);
487 g_free(title);
490 void finch_syslog_show()
492 GList *accounts = NULL;
493 GList *logs = NULL;
495 if (syslog_viewer != NULL) {
496 gnt_window_present(syslog_viewer->window);
497 return;
500 for(accounts = purple_accounts_get_all(); accounts != NULL; accounts = accounts->next) {
502 PurpleAccount *account = (PurpleAccount *)accounts->data;
503 if(purple_find_prpl(purple_account_get_protocol_id(account)) == NULL)
504 continue;
506 logs = g_list_concat(purple_log_get_system_logs(account), logs);
508 logs = g_list_sort(logs, purple_log_compare);
510 syslog_viewer = display_log_viewer(NULL, logs, _("System Log"), 0);
513 /****************************************************************************
514 * GNT LOG SUBSYSTEM *******************************************************
515 ****************************************************************************/
517 void *
518 finch_log_get_handle(void)
520 static int handle;
522 return &handle;
525 void finch_log_init(void)
527 void *handle = finch_log_get_handle();
529 purple_signal_register(handle, "log-displaying",
530 purple_marshal_VOID__POINTER_POINTER,
531 NULL, 2,
532 purple_value_new(PURPLE_TYPE_BOXED,
533 "FinchLogViewer *"),
534 purple_value_new(PURPLE_TYPE_SUBTYPE,
535 PURPLE_SUBTYPE_LOG));
538 void
539 finch_log_uninit(void)
541 purple_signals_unregister_by_instance(finch_log_get_handle());