'smart qotes' now will replace '<', '>' and '&' inside 'pre' and 'code' tags
[k8lowj.git] / src / friends.c
blobd319b201c0a36fd201a24c65c19bdd2ef732beea
1 /* logjam - a GTK client for LiveJournal.
2 * Copyright (C) 2000-2003 Evan Martin <evan@livejournal.com>
4 * vim: tabstop=4 shiftwidth=4 noexpandtab :
5 */
7 #include "gtk-all.h"
8 #include <time.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <ctype.h>
13 #include <errno.h>
15 #include "liblj/livejournal.h"
16 #include "liblj/getfriends.h"
17 #include "liblj/editfriends.h"
19 #include "network.h"
20 #include "conf.h"
21 #include "util-gtk.h"
22 #include "spawn.h"
23 #include "account.h"
24 #include "friends.h"
25 #include "friendedit.h"
26 #include "friendgroups.h"
28 #include "icons.h"
30 #include "friends.h"
32 typedef struct {
33 GtkWindow win; /* parent */
34 GtkWidget *friendview, *statspanel, *statslabel, *statscomm;
35 GtkItemFactory *itemfactory;
36 GtkWidget *omconn, *omtype;
38 GdkPixbuf *pb_user, *pb_comm, *pb_larrow, *pb_rarrow, *pb_lrarrow;
40 JamAccountLJ *account;
41 GSList *friends;
42 LJFriendType filter_type;
43 int filter_conn;
44 } JamFriendsUI;
46 enum {
47 FRIEND_COL_LINK,
48 FRIEND_COL_USERNAME,
49 FRIEND_COL_FULLNAME,
50 FRIEND_COL_COUNT
53 enum {
54 ACTION_EDIT_MENU=1,
55 ACTION_ADD,
56 ACTION_EDIT,
57 ACTION_REMOVE,
58 ACTION_JOURNAL_VIEW,
59 ACTION_USERINFO_VIEW,
60 ACTION_HIDE_STATISTICS
63 #define FRIENDSUI(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), friendsui_get_type(), JamFriendsUI))
65 static GType
66 friendsui_get_type(void) {
67 static GType new_type = 0;
68 if (!new_type) {
69 const GTypeInfo new_info = {
70 sizeof (GtkWindowClass),
71 NULL,
72 NULL,
73 NULL,
74 NULL,
75 NULL,
76 sizeof (JamFriendsUI),
78 NULL
80 new_type = g_type_register_static(GTK_TYPE_WINDOW,
81 "JamFriendsUI", &new_info, 0);
83 return new_type;
86 static GtkWidget*
87 friendsui_new(void) {
88 JamFriendsUI *fui = FRIENDSUI(g_object_new(friendsui_get_type(), NULL));
89 return GTK_WIDGET(fui);
92 static gboolean request_remove_friend(JamFriendsUI *fui, const char *username);
94 static void
95 recalculate_stats(JamFriendsUI *fui) {
96 GSList *l;
97 LJFriend *f;
98 char buf[1024];
99 gboolean includecomm;
101 int count, fmcount, focount;
102 count = fmcount = focount = 0;
104 includecomm = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(fui->statscomm));
106 for (l = fui->friends; l; l = l->next) {
107 f = l->data;
109 if (!includecomm && f->type == LJ_FRIEND_TYPE_COMMUNITY)
110 continue;
112 switch (f->conn) {
113 case LJ_FRIEND_CONN_MY:
114 fmcount++; break;
115 case LJ_FRIEND_CONN_OF:
116 focount++; break;
117 default:
118 break;
120 count++;
123 g_snprintf(buf, 1024, _("Total Connections: %d\n"
124 "\n"
125 "Inclusive:\n"
126 "Friends: %d\n"
127 "Friend Of: %d\n"
128 "Ratio: %d%%\n"
129 "\n"
130 "Exclusive:\n"
131 "Friends: %d\n"
132 "Friend Of: %d\n"
133 "Friend Both: %d\n"),
134 count,
135 count-focount, count-fmcount,
136 ((count-fmcount) != 0) ? ((count-focount)*100)/(count-fmcount) : 0,
137 fmcount, focount, count-fmcount-focount);
138 gtk_label_set_markup(GTK_LABEL(fui->statslabel), buf);
141 static LJFriend*
142 friend_exists(JamFriendsUI *fui, char *username) {
143 GSList *l;
144 LJFriend *f;
146 for (l = fui->friends; l; l = l->next) {
147 f = l->data;
148 if (strcmp(f->username, username) == 0)
149 return f;
152 return NULL;
155 static gint
156 friend_compare(gconstpointer a, gconstpointer b) {
157 return g_ascii_strcasecmp(((LJFriend*)a)->username, ((LJFriend*)b)->username);
160 static void
161 friends_hash_list_cb(gpointer key, LJFriend *f, GSList **list) {
162 *list = g_slist_insert_sorted(*list, f, friend_compare);
165 static gboolean
166 load_friends(GtkWindow *parent, JamAccountLJ *acc, GSList **l) {
167 LJGetFriends *getfriends;
168 NetContext *ctx;
170 ctx = net_ctx_gtk_new(parent, _("Loading Friends"));
171 getfriends = lj_getfriends_new(jam_account_lj_get_user(acc));
172 if (!net_run_verb_ctx((LJVerb*)getfriends, ctx, NULL)) {
173 lj_getfriends_free(getfriends, TRUE);
174 net_ctx_gtk_free(ctx);
175 return FALSE;
178 g_hash_table_foreach(getfriends->friends, (GHFunc)friends_hash_list_cb, l);
180 lj_getfriends_free(getfriends, FALSE);
181 net_ctx_gtk_free(ctx);
183 return TRUE;
186 static void
187 populate_model(JamFriendsUI *fui) {
188 GtkListStore *liststore;
189 GSList *l;
190 LJFriend *f;
191 GtkTreeIter iter;
193 liststore = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(fui->friendview)));
194 gtk_list_store_clear(liststore);
196 for (l = fui->friends; l; l = l->next) {
197 f = l->data;
199 if (fui->filter_conn && !(fui->filter_conn == f->conn))
200 continue;
202 if (fui->filter_type && !(fui->filter_type == f->type))
203 continue;
205 gtk_list_store_append(liststore, &iter);
206 gtk_list_store_set(liststore, &iter,
207 0, f,
208 -1);
212 static void close_cb (JamFriendsUI *fui);
213 static void friend_add_cb (JamFriendsUI *fui);
214 static void friend_edit_cb (JamFriendsUI *fui);
215 static void friend_remove_cb (JamFriendsUI *fui);
216 static void friend_journal_view_cb(JamFriendsUI *fui);
217 static void friend_userinfo_view_cb(JamFriendsUI *fui);
218 static void friendgroups_cb (JamFriendsUI *fui);
219 static void stats_cb (JamFriendsUI *fui,
220 gint action, GtkCheckMenuItem *item);
221 static void export_cb (JamFriendsUI *fui);
223 static GtkWidget*
224 make_menu(JamFriendsUI *fui) {
225 GtkWidget *bar;
226 GtkAccelGroup *accelgroup = NULL;
227 GtkItemFactory *item_factory = NULL;
229 static GtkItemFactoryEntry menu_items[] = {
230 { N_("/_Friends"), NULL, NULL, 0, "<Branch>" },
231 { N_("/Friends/_Friend Groups..."), NULL, friendgroups_cb, 0, NULL },
232 { N_("/Friends/sep"), NULL, NULL, 0, "<Separator>" },
233 { N_("/Friends/_Close"), NULL, close_cb, 0,
234 "<StockItem>", GTK_STOCK_CLOSE },
236 { N_("/_Edit"), NULL, NULL, ACTION_EDIT_MENU, "<Branch>" },
237 { N_("/Edit/_Add Friend..."), NULL, friend_add_cb, ACTION_ADD,
238 "<StockItem>", GTK_STOCK_ADD },
239 { N_("/Edit/_Edit Friend..."), NULL, friend_edit_cb, ACTION_EDIT,
240 "<StockItem>", GTK_STOCK_PROPERTIES },
241 { N_("/Edit/_Remove Friend"), NULL, friend_remove_cb, ACTION_REMOVE,
242 "<StockItem>", GTK_STOCK_REMOVE },
243 { N_("/Edit/sep"), NULL, NULL, 0, "<Separator>" },
244 { N_("/Edit/Open _Journal"), NULL, friend_journal_view_cb, ACTION_JOURNAL_VIEW,
245 "<StockItem>", GTK_STOCK_JUMP_TO },
246 { N_("/Edit/Open _User Info"), NULL, friend_userinfo_view_cb, ACTION_USERINFO_VIEW,
247 "<StockItem>", GTK_STOCK_JUMP_TO },
249 { N_("/_Tools"), NULL, NULL, 0, "<Branch>" },
250 { N_("/Tools/Hide _Statistics"), NULL, stats_cb, ACTION_HIDE_STATISTICS, "<CheckItem>" },
251 { N_("/Tools/Export..."), NULL, export_cb, 0, NULL },
253 int itemcount = sizeof(menu_items) / sizeof(menu_items[0]);
255 accelgroup = gtk_accel_group_new();
256 item_factory = gtk_item_factory_new(GTK_TYPE_MENU_BAR, "<main>", accelgroup);
257 gtk_item_factory_set_translate_func(item_factory, gettext_translate_func, NULL, NULL);
258 gtk_item_factory_create_items(item_factory, itemcount, menu_items, fui);
259 gtk_window_add_accel_group(GTK_WINDOW(fui), accelgroup);
261 bar = gtk_item_factory_get_widget(item_factory, "<main>");
262 fui->itemfactory = item_factory;
264 return bar;
267 static void
268 close_cb(JamFriendsUI *fui) {
269 gtk_widget_destroy(GTK_WIDGET(fui));
272 static void
273 update_edit_menu(JamFriendsUI *fui, GtkTreeSelection *sel) {
274 GtkTreeModel *model;
275 GtkTreeIter iter;
276 LJFriend *f = NULL;
277 gboolean issel, active;
279 if (sel == NULL)
280 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(fui->friendview));
282 issel = gtk_tree_selection_get_selected(sel, &model, &iter);
284 if (issel)
285 gtk_tree_model_get(model, &iter,
286 0, &f,
287 -1);
289 active = issel && f->conn & LJ_FRIEND_CONN_MY;
290 gtk_widget_set_sensitive(
291 gtk_item_factory_get_widget_by_action(fui->itemfactory,
292 ACTION_EDIT),
293 active);
294 gtk_widget_set_sensitive(
295 gtk_item_factory_get_widget_by_action(fui->itemfactory,
296 ACTION_REMOVE),
297 active);
298 gtk_widget_set_sensitive(
299 gtk_item_factory_get_widget_by_action(fui->itemfactory,
300 ACTION_JOURNAL_VIEW),
301 issel);
302 gtk_widget_set_sensitive(
303 gtk_item_factory_get_widget_by_action(fui->itemfactory,
304 ACTION_USERINFO_VIEW),
305 issel);
308 static void
309 friend_add_cb(JamFriendsUI *fui) {
310 GtkTreeSelection *sel;
311 GtkTreeModel *model = NULL;
312 GtkTreeIter iter;
314 LJFriend *f = NULL, *newf;
316 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(fui->friendview));
318 if (gtk_tree_selection_get_selected(sel, &model, &iter)) {
319 gtk_tree_model_get(model, &iter,
320 0, &f,
321 -1);
323 if (f->conn & LJ_FRIEND_CONN_MY) {
324 /* we can't add friends we already have... */
325 f = NULL;
329 newf = friend_edit_dlg_run(GTK_WINDOW(fui), fui->account, FALSE, f);
330 if (!newf) return;
332 if (f != newf) {
333 /* they didn't edit the friend they might have clicked on,
334 * but they could have typed an existing friend's name in. */
335 f = friend_exists(fui, newf->username);
336 if (f) {
337 f->foreground = newf->foreground;
338 f->background = newf->background;
339 lj_friend_free(newf);
340 newf = f;
344 /* did they modify an existing friend, or add a new one? */
345 if (f == newf) {
346 GtkTreePath *path;
348 f->conn |= LJ_FRIEND_CONN_MY;
350 /* now we need to signal to the tree model
351 * that an element has changed. */
352 gtk_tree_model_get_iter_first(model, &iter);
353 do {
354 gtk_tree_model_get(model, &iter, 0, &newf, -1);
355 } while (newf != f && gtk_tree_model_iter_next(model, &iter));
357 /* it's possible they modified a friend
358 * who is not in the current view,
359 * so we only should modify the model
360 * if we found that friend. */
361 if (newf == f) {
362 path = gtk_tree_model_get_path(model, &iter);
363 gtk_tree_model_row_changed(model, path, &iter);
364 gtk_tree_path_free(path);
366 } else {
367 GtkListStore *liststore = GTK_LIST_STORE(model);
368 GtkTreeIter iter;
370 /* if the user adds themself, the connection is immediately two-way. */
371 if (g_ascii_strcasecmp(newf->username,
372 jam_account_get_username(JAM_ACCOUNT(fui->account))) == 0)
373 newf->conn = LJ_FRIEND_CONN_BOTH;
375 fui->friends = g_slist_insert_sorted(fui->friends, newf, friend_compare);
377 gtk_list_store_append(liststore, &iter);
378 gtk_list_store_set(liststore, &iter,
379 0, newf,
380 -1);
382 update_edit_menu(fui, sel);
383 recalculate_stats(fui);
386 static void
387 friend_edit_cb(JamFriendsUI *fui) {
388 GtkTreeSelection *sel;
389 GtkTreeModel *model;
390 GtkTreeIter iter;
391 GtkTreePath *path;
393 LJFriend *f;
395 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(fui->friendview));
397 if (!gtk_tree_selection_get_selected(sel, &model, &iter))
398 return;
400 gtk_tree_model_get(model, &iter,
401 0, &f,
402 -1);
404 f = friend_edit_dlg_run(GTK_WINDOW(fui), fui->account, TRUE, f);
405 if (!f) return;
407 f->conn |= LJ_FRIEND_CONN_MY;
409 path = gtk_tree_model_get_path(model, &iter);
410 gtk_tree_model_row_changed(model, path, &iter);
411 gtk_tree_path_free(path);
413 update_edit_menu(fui, sel);
414 recalculate_stats(fui);
417 static void
418 friend_remove_cb(JamFriendsUI *fui) {
419 GtkTreeSelection *sel;
420 GtkTreeModel *model;
421 GtkTreeIter iter;
423 LJFriend *f;
425 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(fui->friendview));
427 if (!gtk_tree_selection_get_selected(sel, &model, &iter))
428 return;
430 gtk_tree_model_get(model, &iter,
431 0, &f,
432 -1);
434 if (!request_remove_friend(fui, f->username))
435 return;
437 f->conn &= ~LJ_FRIEND_CONN_MY;
439 /* if the user removes themself, the connection is completely gone. */
440 if (g_ascii_strcasecmp(f->username,
441 jam_account_get_username(JAM_ACCOUNT(fui->account))) == 0)
442 f->conn = 0;
444 if (f->conn == 0) {
445 /* delete from our list. */
446 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
447 fui->friends = g_slist_remove(fui->friends, f);
448 lj_friend_free(f);
449 } else {
450 /* we need to tell the view that we changed something. */
451 GtkTreePath *path;
453 path = gtk_tree_model_get_path(model, &iter);
454 gtk_tree_model_row_changed(model, path, &iter);
455 gtk_tree_path_free(path);
458 update_edit_menu(fui, sel);
459 recalculate_stats(fui);
462 static void
463 friendgroups_cb(JamFriendsUI *fui) {
464 friendgroups_dialog_new(GTK_WINDOW(fui), fui->account, fui->friends);
467 static void
468 stats_cb(JamFriendsUI *fui, gint action, GtkCheckMenuItem *item) {
469 conf.options.friends_hidestats = gtk_check_menu_item_get_active(item);
470 jam_widget_set_visible(fui->statspanel, !conf.options.friends_hidestats);
473 static void
474 row_selected(GtkTreeSelection *sel, JamFriendsUI *fui) {
475 update_edit_menu(fui, sel);
478 static gboolean
479 request_remove_friend(JamFriendsUI *fui, const char *username) {
480 NetContext *ctx;
481 LJEditFriends *ef;
482 gboolean ret;
484 ef = lj_editfriends_new(jam_account_lj_get_user(fui->account));
485 lj_editfriends_add_delete(ef, username);
486 ctx = net_ctx_gtk_new(GTK_WINDOW(fui), _("Deleting Friend"));
487 ret = net_run_verb_ctx((LJVerb*)ef, ctx, NULL);
488 lj_editfriends_free(ef);
489 net_ctx_gtk_free(ctx);
491 return ret;
494 static void
495 link_data_func(GtkTreeViewColumn *tree_column,
496 GtkCellRenderer *cell,
497 GtkTreeModel *model,
498 GtkTreeIter *iter,
499 gpointer data)
501 JamFriendsUI *fui = data;
502 LJFriend *friend;
503 gtk_tree_model_get(model, iter,
504 0, &friend,
505 -1);
506 switch (friend->conn) {
507 case LJ_FRIEND_CONN_MY:
508 g_object_set(cell,
509 "pixbuf", fui->pb_rarrow,
510 NULL);
511 break;
512 case LJ_FRIEND_CONN_OF:
513 g_object_set(cell,
514 "pixbuf", fui->pb_larrow,
515 NULL);
516 break;
517 case LJ_FRIEND_CONN_BOTH:
518 g_object_set(cell,
519 "pixbuf", fui->pb_lrarrow,
520 NULL);
521 break;
525 static gint
526 link_sort_func(GtkTreeModel *model,
527 GtkTreeIter *a,
528 GtkTreeIter *b,
529 gpointer data)
531 LJFriend *fa, *fb;
532 gtk_tree_model_get(model, a,
533 0, &fa,
534 -1);
535 gtk_tree_model_get(model, b,
536 0, &fb,
537 -1);
538 if (fa->conn < fb->conn)
539 return -1;
540 if (fa->conn > fb->conn)
541 return 1;
542 return 0;
545 static void
546 user_pixbuf_data_func(GtkTreeViewColumn *tree_column,
547 GtkCellRenderer *cell,
548 GtkTreeModel *model,
549 GtkTreeIter *iter,
550 gpointer data)
552 JamFriendsUI *fui = data;
553 LJFriend *friend;
554 gtk_tree_model_get(model, iter,
555 0, &friend,
556 -1);
557 switch (friend->type) {
558 case LJ_FRIEND_TYPE_COMMUNITY:
559 g_object_set(cell,
560 "pixbuf", fui->pb_comm,
561 NULL);
562 break;
563 default:
564 g_object_set(cell,
565 "pixbuf", fui->pb_user,
566 NULL);
570 static void
571 rgb_to_gdkcolor(guint32 color, GdkColor *gdkcolor) {
572 /* gdkcolors are 16 bits per channel. */
573 gdkcolor->red = (color & 0x00FF0000) >> 8;
574 gdkcolor->green = (color & 0x0000FF00);
575 gdkcolor->blue = (color & 0x000000FF) << 8;
578 static void
579 username_data_func(GtkTreeViewColumn *tree_column,
580 GtkCellRenderer *cell,
581 GtkTreeModel *model,
582 GtkTreeIter *iter,
583 gpointer data)
585 LJFriend *friend;
586 GdkColor fg = {0}, bg = {0};
588 gtk_tree_model_get(model, iter,
589 0, &friend,
590 -1);
592 rgb_to_gdkcolor(friend->foreground, &fg);
593 rgb_to_gdkcolor(friend->background, &bg);
595 g_object_set(cell,
596 "text", friend->username,
597 "foreground-gdk", &fg,
598 "background-gdk", &bg,
599 NULL);
602 static gint
603 username_sort_func(GtkTreeModel *model,
604 GtkTreeIter *a,
605 GtkTreeIter *b,
606 gpointer data)
608 LJFriend *fa, *fb;
609 gtk_tree_model_get(model, a,
610 0, &fa,
611 -1);
612 gtk_tree_model_get(model, b,
613 0, &fb,
614 -1);
615 return g_ascii_strcasecmp(fa->username, fb->username);
618 static void
619 fullname_data_func(GtkTreeViewColumn *tree_column,
620 GtkCellRenderer *cell,
621 GtkTreeModel *model,
622 GtkTreeIter *iter,
623 gpointer data)
625 LJFriend *friend;
626 gtk_tree_model_get(model, iter,
627 0, &friend,
628 -1);
629 g_object_set(cell,
630 "text", friend->fullname,
631 "style", PANGO_STYLE_ITALIC,
632 NULL);
635 static gint
636 fullname_sort_func(GtkTreeModel *model,
637 GtkTreeIter *a,
638 GtkTreeIter *b,
639 gpointer data)
641 LJFriend *fa, *fb;
642 gtk_tree_model_get(model, a,
643 0, &fa,
644 -1);
645 gtk_tree_model_get(model, b,
646 0, &fb,
647 -1);
648 return g_ascii_strcasecmp(fa->fullname, fb->fullname);
651 static gchar*
652 selected_username_get(JamFriendsUI *fui) {
653 GtkWidget *view = fui->friendview;
654 GtkTreeSelection *sel;
655 GtkTreeModel *model;
656 GtkTreeIter iter;
658 LJFriend *f = NULL;
660 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
662 if (!gtk_tree_selection_get_selected(sel, &model, &iter))
663 return NULL;
665 gtk_tree_model_get(model, &iter, 0, &f, -1);
667 return f ?f->username : NULL;
670 static void
671 friend_journal_view_cb(JamFriendsUI *fui) {
672 gchar *friendname = selected_username_get(fui);
673 gchar url[2000];
675 g_snprintf(url, 2000, "%s/users/%s/",
676 jam_account_lj_get_server(fui->account)->url, friendname);
677 spawn_url(GTK_WINDOW(fui), url);
680 static void
681 friend_userinfo_view_cb(JamFriendsUI *fui) {
682 gchar *friendname = selected_username_get(fui);
683 gchar url[2000];
685 g_snprintf(url, 2000, "%s/userinfo.bml?user=%s",
686 jam_account_lj_get_server(fui->account)->url, friendname);
687 spawn_url(GTK_WINDOW(fui), url);
690 static gboolean
691 button_press_cb(GtkTreeView *view, GdkEventButton *e, JamFriendsUI *fui) {
692 GtkWidget *frmenu;
694 if (e->button != 3)
695 return FALSE;
697 frmenu = gtk_item_factory_get_widget_by_action(fui->itemfactory, ACTION_EDIT_MENU);
698 gtk_menu_popup(GTK_MENU(frmenu), NULL, NULL, NULL, NULL,
699 3, e->time);
701 return FALSE; /* we must let this event through.
702 see the FIXME where this signal is hooked up. */
705 static gboolean
706 search_equal_cb(GtkTreeModel *model, gint column, const gchar *key,
707 GtkTreeIter *iter, gpointer data) {
708 LJFriend *f;
709 gtk_tree_model_get(model, iter,
710 0, &f,
711 -1);
712 return g_ascii_strcasecmp(key, f->username) > 0;
715 static GtkWidget*
716 friends_list_create(JamFriendsUI *fui) {
717 GtkWidget *view;
718 GtkTreeViewColumn *column;
719 GtkCellRenderer *cell_renderer;
720 GtkTreeModel *friendstore;
721 GtkTreeSelection *sel;
723 friendstore = GTK_TREE_MODEL(gtk_list_store_new(1, G_TYPE_POINTER));
724 fui->friendview = view = gtk_tree_view_new_with_model(friendstore);
725 g_object_unref(G_OBJECT(friendstore));
727 populate_model(fui);
729 /* HACK: should be connect_after, because we want the list item
730 * to be selected first. due to a gtk bug(?) a connect_after'd event never
731 * is called. it works out ok in this case anyway because the row_selected
732 * event dynamically enables/disables the menu items, but that's weird.
734 * This prompts some kludgery for handling the context menu correctly.
736 g_signal_connect(G_OBJECT(view), "button-press-event",
737 G_CALLBACK(button_press_cb), fui);
739 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
740 update_edit_menu(fui, sel);
741 g_signal_connect(G_OBJECT(sel), "changed",
742 G_CALLBACK(row_selected), fui);
744 fui->pb_user = gtk_widget_render_icon(view,
745 "logjam-ljuser", GTK_ICON_SIZE_MENU, NULL);
746 fui->pb_comm = gtk_widget_render_icon(view,
747 "logjam-ljcomm", GTK_ICON_SIZE_MENU, NULL);
748 fui->pb_larrow = icons_larrow_pixbuf();
749 fui->pb_rarrow = icons_rarrow_pixbuf();
750 fui->pb_lrarrow = icons_lrarrow_pixbuf();
752 column = gtk_tree_view_column_new();
753 gtk_tree_view_column_set_title(column, _("Link"));
755 cell_renderer = gtk_cell_renderer_pixbuf_new();
756 gtk_tree_view_column_pack_start(column, cell_renderer, TRUE);
757 gtk_tree_view_column_set_cell_data_func(column, cell_renderer,
758 link_data_func, fui, NULL);
759 gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
760 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(friendstore),
761 FRIEND_COL_LINK,
762 link_sort_func, NULL, NULL);
763 gtk_tree_view_column_set_sort_column_id(column, FRIEND_COL_LINK);
765 column = gtk_tree_view_column_new();
766 gtk_tree_view_column_set_title(column, _("User"));
768 cell_renderer = gtk_cell_renderer_pixbuf_new();
769 gtk_tree_view_column_pack_start(column, cell_renderer, FALSE);
770 gtk_tree_view_column_set_cell_data_func(column, cell_renderer,
771 user_pixbuf_data_func, fui, NULL);
773 cell_renderer = gtk_cell_renderer_text_new();
774 gtk_tree_view_column_pack_start(column, cell_renderer, TRUE);
775 gtk_tree_view_column_set_cell_data_func(column, cell_renderer,
776 username_data_func, NULL, NULL);
777 gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
779 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(friendstore),
780 FRIEND_COL_USERNAME,
781 username_sort_func, NULL, NULL);
782 gtk_tree_view_column_set_sort_column_id(column, FRIEND_COL_USERNAME);
784 column = gtk_tree_view_column_new();
785 gtk_tree_view_column_set_title(column, _("Full Name"));
787 cell_renderer = gtk_cell_renderer_text_new();
788 gtk_tree_view_column_pack_start(column, cell_renderer, TRUE);
789 gtk_tree_view_column_set_cell_data_func(column, cell_renderer,
790 fullname_data_func, fui, NULL);
791 gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
793 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(friendstore),
794 FRIEND_COL_FULLNAME,
795 fullname_sort_func, NULL, NULL);
796 gtk_tree_view_column_set_sort_column_id(column, FRIEND_COL_FULLNAME);
798 gtk_tree_sortable_set_default_sort_func(GTK_TREE_SORTABLE(friendstore),
799 username_sort_func, NULL, NULL);
800 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(friendstore),
801 GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, GTK_SORT_ASCENDING);
803 gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(view),
804 search_equal_cb, NULL, NULL);
805 gtk_tree_view_set_search_column(GTK_TREE_VIEW(view), FRIEND_COL_USERNAME);
806 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(view), TRUE);
808 return scroll_wrap(view);
811 static GtkWidget*
812 make_stats_panel(JamFriendsUI *fui) {
813 GtkWidget *vbox, *label;
815 vbox = gtk_vbox_new(FALSE, 5);
816 gtk_container_set_border_width(GTK_CONTAINER(vbox), 10);
818 label = gtk_label_new(NULL);
819 gtk_label_set_markup(GTK_LABEL(label), _("<b>Statistics</b>"));
820 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
822 label = gtk_label_new(NULL);
823 gtk_label_set_selectable(GTK_LABEL(label), TRUE);
824 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
826 fui->statscomm = gtk_check_button_new_with_label(_("Include Communities"));
827 g_signal_connect_swapped(G_OBJECT(fui->statscomm), "toggled",
828 G_CALLBACK(recalculate_stats), fui);
829 gtk_box_pack_start(GTK_BOX(vbox), fui->statscomm, FALSE, FALSE, 0);
831 fui->statspanel = vbox;
832 fui->statslabel = label;
833 recalculate_stats(fui);
835 return vbox;
838 static void
839 friendsui_destroy_cb(GtkWidget *w, JamFriendsUI *fui) {
840 g_slist_foreach(fui->friends, (GFunc)lj_friend_free, NULL);
841 g_slist_free(fui->friends);
843 UNREF_AND_NULL(fui->pb_user);
844 UNREF_AND_NULL(fui->pb_comm);
845 UNREF_AND_NULL(fui->pb_larrow);
846 UNREF_AND_NULL(fui->pb_rarrow);
847 UNREF_AND_NULL(fui->pb_lrarrow);
850 static GtkWidget*
851 make_menu_item(GdkPixbuf *pb, const char *label) {
852 GtkWidget *hbox, *item;
854 hbox = gtk_hbox_new(FALSE, 3);
855 gtk_box_pack_start(GTK_BOX(hbox),
856 gtk_image_new_from_pixbuf(pb),
857 FALSE, FALSE, 0);
858 gtk_box_pack_start(GTK_BOX(hbox),
859 gtk_label_new(label), FALSE, FALSE, 0);
861 item = gtk_menu_item_new();
862 gtk_container_add(GTK_CONTAINER(item), hbox);
863 return item;
866 static void
867 filter_type_cb(GtkOptionMenu *om, JamFriendsUI *fui) {
868 /* yuck, hard-coded magic numbers. */
869 switch (gtk_option_menu_get_history(om)) {
870 case 0: /* reset */
871 fui->filter_type = 0;
872 break;
873 case 2:
874 fui->filter_type = LJ_FRIEND_TYPE_USER;
875 break;
876 case 3:
877 fui->filter_type = LJ_FRIEND_TYPE_COMMUNITY;
878 break;
880 populate_model(fui);
883 static void
884 filter_conn_cb(GtkOptionMenu *om, JamFriendsUI *fui) {
885 /* yuck, hard-coded magic numbers. */
886 switch (gtk_option_menu_get_history(om)) {
887 case 0: /* reset */
888 fui->filter_conn = 0;
889 break;
890 case 2:
891 fui->filter_conn = LJ_FRIEND_CONN_BOTH;
892 break;
893 case 3:
894 fui->filter_conn = LJ_FRIEND_CONN_MY;
895 break;
896 case 4:
897 fui->filter_conn = LJ_FRIEND_CONN_OF;
898 break;
900 populate_model(fui);
903 static GtkWidget*
904 make_filter_box(JamFriendsUI *fui) {
905 GtkWidget *box, *menu, *item;;
907 fui->omtype = gtk_option_menu_new();
908 menu = gtk_menu_new();
909 item = make_menu_item(NULL, _("All Journal Types"));
910 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
911 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_menu_item_new());
912 item = make_menu_item(fui->pb_user, _("Users"));
913 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
914 item = make_menu_item(fui->pb_comm, _("Communitites"));
915 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
916 gtk_option_menu_set_menu(GTK_OPTION_MENU(fui->omtype), menu);
917 g_signal_connect(G_OBJECT(fui->omtype), "changed",
918 G_CALLBACK(filter_type_cb), fui);
920 fui->omconn = gtk_option_menu_new();
921 menu = gtk_menu_new();
922 item = make_menu_item(NULL, _("All Connections"));
923 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
924 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_menu_item_new());
925 item = make_menu_item(fui->pb_lrarrow, _("Two-way Friends"));
926 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
927 item = make_menu_item(fui->pb_rarrow, _("One-way Friends"));
928 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
929 item = make_menu_item(fui->pb_larrow, _("One-way Friendofs"));
930 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
931 gtk_option_menu_set_menu(GTK_OPTION_MENU(fui->omconn), menu);
932 g_signal_connect(G_OBJECT(fui->omconn), "changed",
933 G_CALLBACK(filter_conn_cb), fui);
935 box = gtk_hbox_new(FALSE, 5);
936 gtk_box_pack_start(GTK_BOX(box), gtk_label_new(_("Show:")), FALSE, FALSE, 5);
937 gtk_box_pack_start(GTK_BOX(box), fui->omtype, FALSE, FALSE, 5);
938 gtk_box_pack_start(GTK_BOX(box), fui->omconn, FALSE, FALSE, 5);
940 return box;
943 void
944 friends_manager_show(GtkWindow *mainwin, JamAccountLJ *acc) {
945 JamFriendsUI *fui;
946 GtkWidget *mainbox, *hbox;
947 GSList *friends = NULL;
949 if (!load_friends(mainwin, acc, &friends))
950 return;
952 fui = FRIENDSUI(friendsui_new());
953 fui->account = acc;
954 fui->friends = friends;
955 gtk_window_set_title(GTK_WINDOW(fui), _("Friends Manager"));
956 gtk_window_set_default_size(GTK_WINDOW(fui), 400, 300);
957 geometry_tie(GTK_WIDGET(fui), GEOM_FRIENDS);
958 g_signal_connect(G_OBJECT(fui), "destroy",
959 G_CALLBACK(friendsui_destroy_cb), fui);
961 mainbox = gtk_vbox_new(FALSE, 0);
962 gtk_box_pack_start(GTK_BOX(mainbox), make_menu(fui), FALSE, FALSE, 0);
964 hbox = gtk_hbox_new(FALSE, 0);
965 gtk_box_pack_start(GTK_BOX(hbox), friends_list_create(fui), TRUE, TRUE, 0);
966 gtk_box_pack_start(GTK_BOX(hbox), make_stats_panel(fui), FALSE, FALSE, 0);
968 gtk_box_pack_start(GTK_BOX(mainbox), hbox, TRUE, TRUE, 0);
970 gtk_box_pack_start(GTK_BOX(mainbox), make_filter_box(fui), FALSE, FALSE, 5);
972 gtk_widget_show_all(mainbox);
974 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(
975 gtk_item_factory_get_widget_by_action(fui->itemfactory, ACTION_HIDE_STATISTICS)),
976 conf.options.friends_hidestats);
978 gtk_container_add(GTK_CONTAINER(fui), mainbox);
980 gtk_widget_show(GTK_WIDGET(fui));
983 static void
984 export_do(JamFriendsUI *fui, GtkFileChooser *fsel) {
985 const char *filename = gtk_file_chooser_get_filename(fsel);
986 FILE *fout;
987 LJFriend *f;
988 GSList *l;
990 if ((fout = fopen(filename, "w")) == NULL) {
991 jam_warning(GTK_WINDOW(fui), _("Unable to open %s: %s\n"),
992 filename, g_strerror(errno));
993 return;
995 for (l = fui->friends; l != NULL; l = l->next) {
996 f = l->data;
998 fprintf(fout, "%c-%c %s\n",
999 (f->conn & LJ_FRIEND_CONN_OF ? '<' : ' '),
1000 (f->conn & LJ_FRIEND_CONN_MY ? '>' : ' '),
1001 f->username);
1004 fclose(fout);
1007 static void
1008 suggest_cb(GtkWidget *w, GtkFileChooser *fsel) {
1009 char buf[50];
1010 time_t curtime;
1011 struct tm *date;
1013 time(&curtime);
1014 date = localtime(&curtime);
1016 sprintf(buf, "friends.%04d-%02d-%02d",
1017 date->tm_year+1900, date->tm_mon+1, date->tm_mday);
1019 gtk_file_chooser_set_filename(fsel, buf);
1022 static void
1023 export_cb(JamFriendsUI *fui) {
1024 GtkWidget *fsel;
1025 GtkWidget *bstamp;
1027 fsel = gtk_file_chooser_dialog_new(_("Select output file"), GTK_WINDOW(fui),
1028 GTK_FILE_CHOOSER_ACTION_SAVE,
1029 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1030 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
1031 NULL);
1033 bstamp = gtk_button_new_with_label(_(" Suggest Filename "));
1035 gtk_widget_show(bstamp);
1036 gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(fsel), bstamp);
1037 g_signal_connect(G_OBJECT(bstamp), "clicked",
1038 G_CALLBACK(suggest_cb),
1039 GTK_FILE_CHOOSER(fsel));
1041 if (gtk_dialog_run(GTK_DIALOG(fsel)) == GTK_RESPONSE_ACCEPT)
1042 export_do(fui, GTK_FILE_CHOOSER(fsel));
1043 gtk_widget_destroy(fsel);