first try to journal_store_get_latest_id() for sqlite
[k8lowj.git] / src / friends.c
blobb5e27e0356c56524ee6fbdac81a43e33563155c0
1 /* logjam - a GTK client for LiveJournal.
2 * Copyright (C) 2000-2003 Evan Martin <evan@livejournal.com>
3 */
4 #include "gtk-all.h"
5 #include "util-gtk.h"
7 #include <ctype.h>
8 #include <errno.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <time.h>
14 #include "liblj/livejournal.h"
15 #include "liblj/getfriends.h"
16 #include "liblj/editfriends.h"
18 #include "account.h"
19 #include "conf.h"
20 #include "spawn.h"
21 #include "friends.h"
22 #include "friendedit.h"
23 #include "friendgroups.h"
24 #include "icons.h"
25 #include "network.h"
28 typedef struct {
29 GtkWindow win; /* parent */
30 GtkWidget *friendview, *statspanel, *statslabel, *statscomm;
31 GtkItemFactory *itemfactory;
32 GtkWidget *omconn, *omtype;
34 GdkPixbuf *pb_user, *pb_comm, *pb_larrow, *pb_rarrow, *pb_lrarrow;
36 JamAccountLJ *account;
37 GSList *friends;
38 LJFriendType filter_type;
39 int filter_conn;
40 } JamFriendsUI;
43 enum {
44 FRIEND_COL_LINK,
45 FRIEND_COL_USERNAME,
46 FRIEND_COL_FULLNAME,
47 FRIEND_COL_COUNT
51 enum {
52 ACTION_EDIT_MENU = 1,
53 ACTION_ADD,
54 ACTION_EDIT,
55 ACTION_REMOVE,
56 ACTION_JOURNAL_VIEW,
57 ACTION_USERINFO_VIEW,
58 ACTION_HIDE_STATISTICS
62 #define FRIENDSUI(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), friendsui_get_type(), JamFriendsUI))
65 static gboolean request_remove_friend (JamFriendsUI *fui, const char *username);
68 static GType friendsui_get_type (void) {
69 static GType new_type = 0;
70 if (!new_type) {
71 const GTypeInfo new_info = {
72 sizeof(GtkWindowClass),
73 NULL,
74 NULL,
75 NULL,
76 NULL,
77 NULL,
78 sizeof(JamFriendsUI),
80 NULL
82 new_type = g_type_register_static(GTK_TYPE_WINDOW, "JamFriendsUI", &new_info, 0);
84 return new_type;
88 static GtkWidget *friendsui_new (void) {
89 JamFriendsUI *fui = FRIENDSUI(g_object_new(friendsui_get_type(), NULL));
90 return GTK_WIDGET(fui);
94 static void recalculate_stats (JamFriendsUI *fui) {
95 GSList *l;
96 LJFriend *f;
97 char buf[1024];
98 gboolean includecomm;
99 int count = 0, fmcount = 0, focount = 0;
100 includecomm = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(fui->statscomm));
101 for (l = fui->friends; l; l = l->next) {
102 f = l->data;
103 if (!includecomm && f->type == LJ_FRIEND_TYPE_COMMUNITY) continue;
104 switch (f->conn) {
105 case LJ_FRIEND_CONN_MY: ++fmcount; break;
106 case LJ_FRIEND_CONN_OF: ++focount; break;
107 default: break;
109 ++count;
111 g_snprintf(buf, 1024,
112 _("Total Connections: %d\n"
113 "\n"
114 "Inclusive:\n"
115 "Friends: %d\n"
116 "Friend Of: %d\n"
117 "Ratio: %d%%\n"
118 "\n"
119 "Exclusive:\n"
120 "Friends: %d\n"
121 "Friend Of: %d\n"
122 "Friend Both: %d\n"),
123 count, count-focount, count-fmcount,
124 (count-fmcount != 0 ? (count-focount)*100/(count-fmcount) : 0), fmcount, focount, count-fmcount-focount);
125 gtk_label_set_markup(GTK_LABEL(fui->statslabel), buf);
129 static LJFriend *friend_exists (JamFriendsUI *fui, char *username) {
130 GSList *l;
131 LJFriend *f;
132 for (l = fui->friends; l; l = l->next) {
133 f = l->data;
134 if (strcmp(f->username, username) == 0) return f;
136 return NULL;
140 static gint friend_compare (gconstpointer a, gconstpointer b) {
141 return g_ascii_strcasecmp(((LJFriend *)a)->username, ((LJFriend *)b)->username);
145 static void friends_hash_list_cb (gpointer key, LJFriend *f, GSList **list) {
146 *list = g_slist_insert_sorted(*list, f, friend_compare);
150 static gboolean load_friends(GtkWindow *parent, JamAccountLJ *acc, GSList **l) {
151 LJGetFriends *getfriends;
152 NetContext *ctx;
153 ctx = net_ctx_gtk_new(parent, _("Loading Friends"));
154 getfriends = lj_getfriends_new(jam_account_lj_get_user(acc));
155 if (!net_run_verb_ctx((LJVerb *) getfriends, ctx, NULL)) {
156 lj_getfriends_free(getfriends, TRUE);
157 net_ctx_gtk_free(ctx);
158 return FALSE;
160 g_hash_table_foreach(getfriends->friends, (GHFunc) friends_hash_list_cb, l);
161 lj_getfriends_free(getfriends, FALSE);
162 net_ctx_gtk_free(ctx);
163 return TRUE;
167 static void populate_model (JamFriendsUI *fui) {
168 GtkListStore *liststore;
169 GSList *l;
170 LJFriend *f;
171 GtkTreeIter iter;
172 liststore = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(fui->friendview)));
173 gtk_list_store_clear(liststore);
174 for (l = fui->friends; l; l = l->next) {
175 f = l->data;
176 if (fui->filter_conn && !(fui->filter_conn == f->conn)) continue;
177 if (fui->filter_type && !(fui->filter_type == f->type)) continue;
178 gtk_list_store_append(liststore, &iter);
179 gtk_list_store_set(liststore, &iter, 0, f, -1);
184 static void close_cb (JamFriendsUI *fui);
185 static void friend_add_cb (JamFriendsUI *fui);
186 static void friend_edit_cb (JamFriendsUI *fui);
187 static void friend_remove_cb (JamFriendsUI *fui);
188 static void friend_journal_view_cb (JamFriendsUI *fui);
189 static void friend_userinfo_view_cb (JamFriendsUI *fui);
190 static void friendgroups_cb (JamFriendsUI *fui);
191 static void stats_cb (JamFriendsUI *fui, gint action, GtkCheckMenuItem *item);
192 static void export_cb (JamFriendsUI *fui);
195 static GtkWidget *make_menu (JamFriendsUI *fui) {
196 GtkWidget *bar;
197 GtkAccelGroup *accelgroup = NULL;
198 GtkItemFactory *item_factory = NULL;
200 static GtkItemFactoryEntry menu_items[] = {
201 {N_("/_Friends"), NULL, NULL, 0, "<Branch>"},
202 {N_("/Friends/_Friend Groups..."), NULL, friendgroups_cb, 0, NULL},
203 {N_("/Friends/sep"), NULL, NULL, 0, "<Separator>"},
204 {N_("/Friends/_Close"), NULL, close_cb, 0, "<StockItem>", GTK_STOCK_CLOSE},
206 {N_("/_Edit"), NULL, NULL, ACTION_EDIT_MENU, "<Branch>"},
207 {N_("/Edit/_Add Friend..."), NULL, friend_add_cb, ACTION_ADD, "<StockItem>", GTK_STOCK_ADD},
208 {N_("/Edit/_Edit Friend..."), NULL, friend_edit_cb, ACTION_EDIT, "<StockItem>", GTK_STOCK_PROPERTIES},
209 {N_("/Edit/_Remove Friend"), NULL, friend_remove_cb, ACTION_REMOVE, "<StockItem>", GTK_STOCK_REMOVE},
210 {N_("/Edit/sep"), NULL, NULL, 0, "<Separator>"},
211 {N_("/Edit/Open _Journal"), NULL, friend_journal_view_cb, ACTION_JOURNAL_VIEW, "<StockItem>", GTK_STOCK_JUMP_TO},
212 {N_("/Edit/Open _User Info"), NULL, friend_userinfo_view_cb, ACTION_USERINFO_VIEW, "<StockItem>", GTK_STOCK_JUMP_TO},
214 {N_("/_Tools"), NULL, NULL, 0, "<Branch>"},
215 {N_("/Tools/Hide _Statistics"), NULL, stats_cb, ACTION_HIDE_STATISTICS, "<CheckItem>"},
216 {N_("/Tools/Export..."), NULL, export_cb, 0, NULL},
218 int itemcount = sizeof(menu_items)/sizeof(menu_items[0]);
220 accelgroup = gtk_accel_group_new();
221 item_factory = gtk_item_factory_new(GTK_TYPE_MENU_BAR, "<main>", accelgroup);
222 gtk_item_factory_set_translate_func(item_factory, gettext_translate_func, NULL, NULL);
223 gtk_item_factory_create_items(item_factory, itemcount, menu_items, fui);
224 gtk_window_add_accel_group(GTK_WINDOW(fui), accelgroup);
226 bar = gtk_item_factory_get_widget(item_factory, "<main>");
227 fui->itemfactory = item_factory;
229 return bar;
233 static void close_cb (JamFriendsUI *fui) {
234 gtk_widget_destroy(GTK_WIDGET(fui));
238 static void update_edit_menu (JamFriendsUI *fui, GtkTreeSelection *sel) {
239 GtkTreeModel *model;
240 GtkTreeIter iter;
241 LJFriend *f = NULL;
242 gboolean issel, active;
243 if (sel == NULL) sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(fui->friendview));
244 issel = gtk_tree_selection_get_selected(sel, &model, &iter);
245 if (issel) gtk_tree_model_get(model, &iter, 0, &f, -1);
246 active = issel && (f->conn&LJ_FRIEND_CONN_MY);
247 gtk_widget_set_sensitive(gtk_item_factory_get_widget_by_action(fui->itemfactory, ACTION_EDIT), active);
248 gtk_widget_set_sensitive(gtk_item_factory_get_widget_by_action(fui->itemfactory, ACTION_REMOVE), active);
249 gtk_widget_set_sensitive(gtk_item_factory_get_widget_by_action(fui->itemfactory, ACTION_JOURNAL_VIEW), issel);
250 gtk_widget_set_sensitive(gtk_item_factory_get_widget_by_action(fui->itemfactory, ACTION_USERINFO_VIEW), issel);
254 static void friend_add_cb (JamFriendsUI *fui) {
255 GtkTreeSelection *sel;
256 GtkTreeModel *model = NULL;
257 GtkTreeIter iter;
258 LJFriend *f = NULL, *newf;
260 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(fui->friendview));
262 if (gtk_tree_selection_get_selected(sel, &model, &iter)) {
263 gtk_tree_model_get(model, &iter, 0, &f, -1);
265 if (f->conn & LJ_FRIEND_CONN_MY) {
266 /* we can't add friends we already have... */
267 f = NULL;
271 newf = friend_edit_dlg_run(GTK_WINDOW(fui), fui->account, FALSE, f);
272 if (!newf) return;
274 if (f != newf) {
275 /* they didn't edit the friend they might have clicked on,
276 * but they could have typed an existing friend's name in. */
277 f = friend_exists(fui, newf->username);
278 if (f) {
279 f->foreground = newf->foreground;
280 f->background = newf->background;
281 lj_friend_free(newf);
282 newf = f;
286 /* did they modify an existing friend, or add a new one? */
287 if (f == newf) {
288 GtkTreePath *path;
290 f->conn |= LJ_FRIEND_CONN_MY;
292 /* now we need to signal to the tree model
293 * that an element has changed. */
294 gtk_tree_model_get_iter_first(model, &iter);
295 do {
296 gtk_tree_model_get(model, &iter, 0, &newf, -1);
297 } while (newf != f && gtk_tree_model_iter_next(model, &iter));
299 /* it's possible they modified a friend
300 * who is not in the current view,
301 * so we only should modify the model
302 * if we found that friend. */
303 if (newf == f) {
304 path = gtk_tree_model_get_path(model, &iter);
305 gtk_tree_model_row_changed(model, path, &iter);
306 gtk_tree_path_free(path);
308 } else {
309 GtkListStore *liststore = GTK_LIST_STORE(model);
310 GtkTreeIter iter;
312 /* if the user adds themself, the connection is immediately two-way. */
313 if (g_ascii_strcasecmp(newf->username, jam_account_get_username(JAM_ACCOUNT(fui->account))) == 0) newf->conn = LJ_FRIEND_CONN_BOTH;
315 fui->friends = g_slist_insert_sorted(fui->friends, newf, friend_compare);
317 gtk_list_store_append(liststore, &iter);
318 gtk_list_store_set(liststore, &iter, 0, newf, -1);
320 update_edit_menu(fui, sel);
321 recalculate_stats(fui);
325 static void friend_edit_cb (JamFriendsUI *fui) {
326 GtkTreeSelection *sel;
327 GtkTreeModel *model;
328 GtkTreeIter iter;
329 GtkTreePath *path;
330 LJFriend *f;
332 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(fui->friendview));
334 if (!gtk_tree_selection_get_selected(sel, &model, &iter)) return;
336 gtk_tree_model_get(model, &iter, 0, &f, -1);
338 f = friend_edit_dlg_run(GTK_WINDOW(fui), fui->account, TRUE, f);
339 if (!f) return;
341 f->conn |= LJ_FRIEND_CONN_MY;
343 path = gtk_tree_model_get_path(model, &iter);
344 gtk_tree_model_row_changed(model, path, &iter);
345 gtk_tree_path_free(path);
347 update_edit_menu(fui, sel);
348 recalculate_stats(fui);
352 static void friend_remove_cb (JamFriendsUI *fui) {
353 GtkTreeSelection *sel;
354 GtkTreeModel *model;
355 GtkTreeIter iter;
356 LJFriend *f;
358 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(fui->friendview));
360 if (!gtk_tree_selection_get_selected(sel, &model, &iter)) return;
362 gtk_tree_model_get(model, &iter, 0, &f, -1);
364 if (!request_remove_friend(fui, f->username)) return;
366 f->conn &= ~LJ_FRIEND_CONN_MY;
368 /* if the user removes themself, the connection is completely gone. */
369 if (g_ascii_strcasecmp(f->username, jam_account_get_username(JAM_ACCOUNT(fui->account))) == 0) f->conn = 0;
371 if (f->conn == 0) {
372 /* delete from our list. */
373 gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
374 fui->friends = g_slist_remove(fui->friends, f);
375 lj_friend_free(f);
376 } else {
377 /* we need to tell the view that we changed something. */
378 GtkTreePath *path;
380 path = gtk_tree_model_get_path(model, &iter);
381 gtk_tree_model_row_changed(model, path, &iter);
382 gtk_tree_path_free(path);
385 update_edit_menu(fui, sel);
386 recalculate_stats(fui);
390 static void friendgroups_cb (JamFriendsUI *fui) {
391 friendgroups_dialog_new(GTK_WINDOW(fui), fui->account, fui->friends);
395 static void stats_cb (JamFriendsUI *fui, gint action, GtkCheckMenuItem *item) {
396 conf.options.friends_hidestats = gtk_check_menu_item_get_active(item);
397 jam_widget_set_visible(fui->statspanel, !conf.options.friends_hidestats);
401 static void row_selected (GtkTreeSelection *sel, JamFriendsUI *fui) {
402 update_edit_menu(fui, sel);
406 static gboolean request_remove_friend (JamFriendsUI *fui, const char *username) {
407 NetContext *ctx;
408 LJEditFriends *ef;
409 gboolean ret;
411 ef = lj_editfriends_new(jam_account_lj_get_user(fui->account));
412 lj_editfriends_add_delete(ef, username);
413 ctx = net_ctx_gtk_new(GTK_WINDOW(fui), _("Deleting Friend"));
414 ret = net_run_verb_ctx((LJVerb *)ef, ctx, NULL);
415 lj_editfriends_free(ef);
416 net_ctx_gtk_free(ctx);
418 return ret;
422 static void link_data_func (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *model, GtkTreeIter *iter, gpointer data) {
423 JamFriendsUI *fui = data;
424 LJFriend *friend;
425 gtk_tree_model_get(model, iter, 0, &friend, -1);
426 switch (friend->conn) {
427 case LJ_FRIEND_CONN_MY:
428 g_object_set(cell, "pixbuf", fui->pb_rarrow, NULL);
429 break;
430 case LJ_FRIEND_CONN_OF:
431 g_object_set(cell, "pixbuf", fui->pb_larrow, NULL);
432 break;
433 case LJ_FRIEND_CONN_BOTH:
434 g_object_set(cell, "pixbuf", fui->pb_lrarrow, NULL);
435 break;
440 static gint link_sort_func (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer data) {
441 LJFriend *fa, *fb;
442 gtk_tree_model_get(model, a, 0, &fa, -1);
443 gtk_tree_model_get(model, b, 0, &fb, -1);
444 if (fa->conn < fb->conn) return -1;
445 if (fa->conn > fb->conn) return 1;
446 return 0;
450 static void user_pixbuf_data_func (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *model, GtkTreeIter *iter, gpointer data) {
451 JamFriendsUI *fui = data;
452 LJFriend *friend;
453 gtk_tree_model_get(model, iter, 0, &friend, -1);
454 switch (friend->type) {
455 case LJ_FRIEND_TYPE_COMMUNITY:
456 g_object_set(cell, "pixbuf", fui->pb_comm, NULL);
457 break;
458 default:
459 g_object_set(cell, "pixbuf", fui->pb_user, NULL);
464 static void rgb_to_gdkcolor (guint32 color, GdkColor *gdkcolor) {
465 /* gdkcolors are 16 bits per channel. */
466 gdkcolor->red = (color&0x00FF0000)>>8;
467 gdkcolor->green = (color&0x0000FF00);
468 gdkcolor->blue = (color&0x000000FF)<<8;
472 static void username_data_func (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *model, GtkTreeIter *iter, gpointer data) {
473 LJFriend *friend;
474 GdkColor fg = { 0 }, bg = { 0 };
476 gtk_tree_model_get(model, iter, 0, &friend, -1);
478 rgb_to_gdkcolor(friend->foreground, &fg);
479 rgb_to_gdkcolor(friend->background, &bg);
481 g_object_set(cell, "text", friend->username, "foreground-gdk", &fg, "background-gdk", &bg, NULL);
485 static gint username_sort_func (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer data) {
486 LJFriend *fa, *fb;
487 gtk_tree_model_get(model, a, 0, &fa, -1);
488 gtk_tree_model_get(model, b, 0, &fb, -1);
489 return g_ascii_strcasecmp(fa->username, fb->username);
493 static void fullname_data_func (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *model, GtkTreeIter *iter, gpointer data) {
494 LJFriend *friend;
495 gtk_tree_model_get(model, iter, 0, &friend, -1);
496 g_object_set(cell, "text", friend->fullname, "style", PANGO_STYLE_ITALIC, NULL);
500 static gint fullname_sort_func (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer data) {
501 LJFriend *fa, *fb;
502 gtk_tree_model_get(model, a, 0, &fa, -1);
503 gtk_tree_model_get(model, b, 0, &fb, -1);
504 return g_ascii_strcasecmp(fa->fullname, fb->fullname);
508 static gchar *selected_username_get (JamFriendsUI *fui) {
509 GtkWidget *view = fui->friendview;
510 GtkTreeSelection *sel;
511 GtkTreeModel *model;
512 GtkTreeIter iter;
513 LJFriend *f = NULL;
514 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
515 if (!gtk_tree_selection_get_selected(sel, &model, &iter)) return NULL;
516 gtk_tree_model_get(model, &iter, 0, &f, -1);
517 return (f ? f->username : NULL);
521 static void friend_journal_view_cb (JamFriendsUI *fui) {
522 gchar *friendname = selected_username_get(fui);
523 gchar url[2000];
524 g_snprintf(url, sizeof(url), "%s/users/%s/", jam_account_lj_get_server(fui->account)->url, friendname);
525 spawn_url(GTK_WINDOW(fui), url);
529 static void friend_userinfo_view_cb (JamFriendsUI *fui) {
530 gchar *friendname = selected_username_get(fui);
531 gchar url[2000];
532 g_snprintf(url, sizeof(url), "%s/userinfo.bml?user=%s", jam_account_lj_get_server(fui->account)->url, friendname);
533 spawn_url(GTK_WINDOW(fui), url);
537 static gboolean button_press_cb (GtkTreeView *view, GdkEventButton *e, JamFriendsUI *fui) {
538 GtkWidget *frmenu;
539 if (e->button != 3) return FALSE;
540 frmenu = gtk_item_factory_get_widget_by_action(fui->itemfactory, ACTION_EDIT_MENU);
541 gtk_menu_popup(GTK_MENU(frmenu), NULL, NULL, NULL, NULL, 3, e->time);
542 return FALSE; /* we must let this event through. see the FIXME where this signal is hooked up. */
546 static gboolean search_equal_cb (GtkTreeModel *model, gint column, const gchar *key, GtkTreeIter *iter, gpointer data) {
547 LJFriend *f;
548 gtk_tree_model_get(model, iter, 0, &f, -1);
549 return g_ascii_strcasecmp(key, f->username) > 0;
553 static GtkWidget *friends_list_create (JamFriendsUI *fui) {
554 GtkWidget *view;
555 GtkTreeViewColumn *column;
556 GtkCellRenderer *cell_renderer;
557 GtkTreeModel *friendstore;
558 GtkTreeSelection *sel;
560 friendstore = GTK_TREE_MODEL(gtk_list_store_new(1, G_TYPE_POINTER));
561 fui->friendview = view = gtk_tree_view_new_with_model(friendstore);
562 g_object_unref(G_OBJECT(friendstore));
564 populate_model(fui);
566 /* HACK: should be connect_after, because we want the list item
567 * to be selected first. due to a gtk bug(?) a connect_after'd event never
568 * is called. it works out ok in this case anyway because the row_selected
569 * event dynamically enables/disables the menu items, but that's weird.
571 * This prompts some kludgery for handling the context menu correctly.
573 g_signal_connect(G_OBJECT(view), "button-press-event", G_CALLBACK(button_press_cb), fui);
575 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
576 update_edit_menu(fui, sel);
577 g_signal_connect(G_OBJECT(sel), "changed", G_CALLBACK(row_selected), fui);
579 fui->pb_user = gtk_widget_render_icon(view, "logjam-ljuser", GTK_ICON_SIZE_MENU, NULL);
580 fui->pb_comm = gtk_widget_render_icon(view, "logjam-ljcomm", GTK_ICON_SIZE_MENU, NULL);
581 fui->pb_larrow = icons_larrow_pixbuf();
582 fui->pb_rarrow = icons_rarrow_pixbuf();
583 fui->pb_lrarrow = icons_lrarrow_pixbuf();
585 column = gtk_tree_view_column_new();
586 gtk_tree_view_column_set_title(column, _("Link"));
588 cell_renderer = gtk_cell_renderer_pixbuf_new();
589 gtk_tree_view_column_pack_start(column, cell_renderer, TRUE);
590 gtk_tree_view_column_set_cell_data_func(column, cell_renderer, link_data_func, fui, NULL);
591 gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
592 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(friendstore), FRIEND_COL_LINK, link_sort_func, NULL, NULL);
593 gtk_tree_view_column_set_sort_column_id(column, FRIEND_COL_LINK);
595 column = gtk_tree_view_column_new();
596 gtk_tree_view_column_set_title(column, _("User"));
598 cell_renderer = gtk_cell_renderer_pixbuf_new();
599 gtk_tree_view_column_pack_start(column, cell_renderer, FALSE);
600 gtk_tree_view_column_set_cell_data_func(column, cell_renderer, user_pixbuf_data_func, fui, NULL);
602 cell_renderer = gtk_cell_renderer_text_new();
603 gtk_tree_view_column_pack_start(column, cell_renderer, TRUE);
604 gtk_tree_view_column_set_cell_data_func(column, cell_renderer, username_data_func, NULL, NULL);
605 gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
607 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(friendstore), FRIEND_COL_USERNAME, username_sort_func, NULL, NULL);
608 gtk_tree_view_column_set_sort_column_id(column, FRIEND_COL_USERNAME);
610 column = gtk_tree_view_column_new();
611 gtk_tree_view_column_set_title(column, _("Full Name"));
613 cell_renderer = gtk_cell_renderer_text_new();
614 gtk_tree_view_column_pack_start(column, cell_renderer, TRUE);
615 gtk_tree_view_column_set_cell_data_func(column, cell_renderer, fullname_data_func, fui, NULL);
616 gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
618 gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(friendstore), FRIEND_COL_FULLNAME, fullname_sort_func, NULL, NULL);
619 gtk_tree_view_column_set_sort_column_id(column, FRIEND_COL_FULLNAME);
621 gtk_tree_sortable_set_default_sort_func(GTK_TREE_SORTABLE(friendstore), username_sort_func, NULL, NULL);
622 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(friendstore), GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, GTK_SORT_ASCENDING);
624 gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(view), search_equal_cb, NULL, NULL);
625 gtk_tree_view_set_search_column(GTK_TREE_VIEW(view), FRIEND_COL_USERNAME);
626 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(view), TRUE);
628 return scroll_wrap(view);
632 static GtkWidget *make_stats_panel (JamFriendsUI *fui) {
633 GtkWidget *vbox, *label;
635 vbox = gtk_vbox_new(FALSE, 5);
636 gtk_container_set_border_width(GTK_CONTAINER(vbox), 10);
638 label = gtk_label_new(NULL);
639 gtk_label_set_markup(GTK_LABEL(label), _("<b>Statistics</b>"));
640 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
642 label = gtk_label_new(NULL);
643 gtk_label_set_selectable(GTK_LABEL(label), TRUE);
644 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
646 fui->statscomm = gtk_check_button_new_with_label(_("Include Communities"));
647 g_signal_connect_swapped(G_OBJECT(fui->statscomm), "toggled", G_CALLBACK(recalculate_stats), fui);
648 gtk_box_pack_start(GTK_BOX(vbox), fui->statscomm, FALSE, FALSE, 0);
650 fui->statspanel = vbox;
651 fui->statslabel = label;
652 recalculate_stats(fui);
654 return vbox;
658 static void friendsui_destroy_cb (GtkWidget *w, JamFriendsUI *fui) {
659 g_slist_foreach(fui->friends, (GFunc)lj_friend_free, NULL);
660 g_slist_free(fui->friends);
661 UNREF_AND_NULL(fui->pb_user);
662 UNREF_AND_NULL(fui->pb_comm);
663 UNREF_AND_NULL(fui->pb_larrow);
664 UNREF_AND_NULL(fui->pb_rarrow);
665 UNREF_AND_NULL(fui->pb_lrarrow);
669 static GtkWidget *make_menu_item (GdkPixbuf *pb, const char *label) {
670 GtkWidget *hbox, *item;
672 hbox = gtk_hbox_new(FALSE, 3);
673 gtk_box_pack_start(GTK_BOX(hbox), gtk_image_new_from_pixbuf(pb), FALSE, FALSE, 0);
674 gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(label), FALSE, FALSE, 0);
676 item = gtk_menu_item_new();
677 gtk_container_add(GTK_CONTAINER(item), hbox);
678 return item;
682 static void filter_type_cb (GtkOptionMenu *om, JamFriendsUI *fui) {
683 /* yuck, hard-coded magic numbers. */
684 switch (gtk_option_menu_get_history(om)) {
685 case 0: fui->filter_type = 0; break; /* reset */
686 case 2: fui->filter_type = LJ_FRIEND_TYPE_USER; break;
687 case 3: fui->filter_type = LJ_FRIEND_TYPE_COMMUNITY; break;
689 populate_model(fui);
693 static void filter_conn_cb (GtkOptionMenu *om, JamFriendsUI *fui) {
694 /* yuck, hard-coded magic numbers. */
695 switch (gtk_option_menu_get_history(om)) {
696 case 0: fui->filter_conn = 0; break; /* reset */
697 case 2: fui->filter_conn = LJ_FRIEND_CONN_BOTH; break;
698 case 3: fui->filter_conn = LJ_FRIEND_CONN_MY; break;
699 case 4: fui->filter_conn = LJ_FRIEND_CONN_OF; break;
701 populate_model(fui);
705 static GtkWidget *make_filter_box (JamFriendsUI *fui) {
706 GtkWidget *box, *menu, *item;;
708 fui->omtype = gtk_option_menu_new();
709 menu = gtk_menu_new();
710 item = make_menu_item(NULL, _("All Journal Types"));
711 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
712 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_menu_item_new());
713 item = make_menu_item(fui->pb_user, _("Users"));
714 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
715 item = make_menu_item(fui->pb_comm, _("Communitites"));
716 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
717 gtk_option_menu_set_menu(GTK_OPTION_MENU(fui->omtype), menu);
718 g_signal_connect(G_OBJECT(fui->omtype), "changed", G_CALLBACK(filter_type_cb), fui);
720 fui->omconn = gtk_option_menu_new();
721 menu = gtk_menu_new();
722 item = make_menu_item(NULL, _("All Connections"));
723 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
724 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_menu_item_new());
725 item = make_menu_item(fui->pb_lrarrow, _("Two-way Friends"));
726 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
727 item = make_menu_item(fui->pb_rarrow, _("One-way Friends"));
728 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
729 item = make_menu_item(fui->pb_larrow, _("One-way Friendofs"));
730 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
731 gtk_option_menu_set_menu(GTK_OPTION_MENU(fui->omconn), menu);
732 g_signal_connect(G_OBJECT(fui->omconn), "changed", G_CALLBACK(filter_conn_cb), fui);
734 box = gtk_hbox_new(FALSE, 5);
735 gtk_box_pack_start(GTK_BOX(box), gtk_label_new(_("Show:")), FALSE, FALSE, 5);
736 gtk_box_pack_start(GTK_BOX(box), fui->omtype, FALSE, FALSE, 5);
737 gtk_box_pack_start(GTK_BOX(box), fui->omconn, FALSE, FALSE, 5);
739 return box;
743 void friends_manager_show (GtkWindow *mainwin, JamAccountLJ *acc) {
744 JamFriendsUI *fui;
745 GtkWidget *mainbox, *hbox;
746 GSList *friends = NULL;
748 if (!load_friends(mainwin, acc, &friends)) return;
750 fui = FRIENDSUI(friendsui_new());
751 fui->account = acc;
752 fui->friends = friends;
753 gtk_window_set_title(GTK_WINDOW(fui), _("Friends Manager"));
754 gtk_window_set_default_size(GTK_WINDOW(fui), 400, 300);
755 geometry_tie(GTK_WIDGET(fui), GEOM_FRIENDS);
756 g_signal_connect(G_OBJECT(fui), "destroy", G_CALLBACK(friendsui_destroy_cb), fui);
758 mainbox = gtk_vbox_new(FALSE, 0);
759 gtk_box_pack_start(GTK_BOX(mainbox), make_menu(fui), FALSE, FALSE, 0);
761 hbox = gtk_hbox_new(FALSE, 0);
762 gtk_box_pack_start(GTK_BOX(hbox), friends_list_create(fui), TRUE, TRUE, 0);
763 gtk_box_pack_start(GTK_BOX(hbox), make_stats_panel(fui), FALSE, FALSE, 0);
765 gtk_box_pack_start(GTK_BOX(mainbox), hbox, TRUE, TRUE, 0);
767 gtk_box_pack_start(GTK_BOX(mainbox), make_filter_box(fui), FALSE, FALSE, 5);
769 gtk_widget_show_all(mainbox);
771 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gtk_item_factory_get_widget_by_action(fui->itemfactory, ACTION_HIDE_STATISTICS)), conf.options.friends_hidestats);
773 gtk_container_add(GTK_CONTAINER(fui), mainbox);
775 gtk_widget_show(GTK_WIDGET(fui));
779 static void export_do (JamFriendsUI *fui, GtkFileChooser *fsel) {
780 const char *filename = gtk_file_chooser_get_filename(fsel);
781 FILE *fout;
782 LJFriend *f;
783 GSList *l;
784 if ((fout = fopen(filename, "w")) == NULL) {
785 jam_warning(GTK_WINDOW(fui), _("Unable to open %s: %s\n"), filename, g_strerror(errno));
786 return;
788 for (l = fui->friends; l != NULL; l = l->next) {
789 f = l->data;
790 fprintf(fout, "%c-%c %s\n", (f->conn & LJ_FRIEND_CONN_OF ? '<' : ' '), (f->conn & LJ_FRIEND_CONN_MY ? '>' : ' '), f->username);
792 fclose(fout);
796 static void suggest_cb (GtkWidget *w, GtkFileChooser *fsel) {
797 char buf[50];
798 time_t curtime;
799 struct tm *date;
800 time(&curtime);
801 date = localtime(&curtime);
802 sprintf(buf, "friends.%04d-%02d-%02d", date->tm_year + 1900, date->tm_mon + 1, date->tm_mday);
803 gtk_file_chooser_set_filename(fsel, buf);
807 static void export_cb (JamFriendsUI *fui) {
808 GtkWidget *fsel;
809 GtkWidget *bstamp;
811 fsel = gtk_file_chooser_dialog_new(_("Select output file"), GTK_WINDOW(fui),
812 GTK_FILE_CHOOSER_ACTION_SAVE,
813 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, NULL);
815 bstamp = gtk_button_new_with_label(_(" Suggest Filename "));
817 gtk_widget_show(bstamp);
818 gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(fsel), bstamp);
819 g_signal_connect(G_OBJECT(bstamp), "clicked", G_CALLBACK(suggest_cb), GTK_FILE_CHOOSER(fsel));
821 if (gtk_dialog_run(GTK_DIALOG(fsel)) == GTK_RESPONSE_ACCEPT) export_do(fui, GTK_FILE_CHOOSER(fsel));
822 gtk_widget_destroy(fsel);