about.c: cosmetix
[k8lowj.git] / src / friendgroups.c
blobae39e2036fdd16bf030ace0fd2f52a405211d417
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"
9 #include "liblj/editfriendgroups.h"
11 #include "conf.h"
12 #include "util-gtk.h"
13 #include "network.h"
14 #include "friends.h"
15 #include "friendgroupedit.h"
16 #include "friendgroups.h"
18 typedef struct {
19 GtkWidget *win;
20 GtkWidget *groups, *inc, *exc;
21 GtkListStore *lgroups, *linc, *lexc;
23 JamAccountLJ *account;
24 GSList *friends;
25 } FriendGroupsUI;
27 enum {
28 FG_COL_NAME,
29 FG_COL_FG
31 enum {
32 FRIEND_COL_NAME,
33 FRIEND_COL_FRIEND
36 static int
37 findfreegroup(FriendGroupsUI *fgui) {
38 int freegroup;
40 for (freegroup = 1; freegroup <= 30; freegroup++) {
41 if (lj_friendgroup_from_id(jam_account_lj_get_user(fgui->account), freegroup) == NULL) {
42 /* this id isn't in use! */
43 return freegroup;
46 jam_warning(GTK_WINDOW(fgui->win), _("Couldn't find free friend group(?)"));
47 return -1; /* we didn't find one. */
50 static void
51 dlg_add_friend_group(FriendGroupsUI *fgui, LJFriendGroup *fg) {
52 GtkTreeIter iter;
54 gtk_list_store_append(fgui->lgroups, &iter);
55 gtk_list_store_set(fgui->lgroups, &iter,
56 FG_COL_NAME, fg->name,
57 FG_COL_FG, fg,
58 -1);
61 static void
62 new_cb(GtkWidget *w, FriendGroupsUI *fgui) {
63 LJUser *u;
64 LJFriendGroup *fg;
65 int freegroup;
67 freegroup = findfreegroup(fgui);
68 if (freegroup == -1) return; /* FIXME */
70 fg = friend_group_edit_dlg_run(GTK_WINDOW(fgui->win), fgui->account, NULL, freegroup);
71 if (fg == NULL) return; /* they cancelled. */
73 /* new friend group! */
74 u = jam_account_lj_get_user(fgui->account);
75 u->friendgroups = g_slist_append(u->friendgroups, fg);
76 dlg_add_friend_group(fgui, fg);
79 static LJFriendGroup*
80 get_selected_fg(FriendGroupsUI *fgui, GtkTreeIter *piter) {
81 GtkTreeSelection *sel;
82 GtkTreeModel *model;
83 GtkTreeIter iter;
84 LJFriendGroup *fg;
86 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(fgui->groups));
87 if (!gtk_tree_selection_get_selected(sel, &model, &iter))
88 return NULL;
89 if (piter)
90 *piter = iter;
92 gtk_tree_model_get(model, &iter,
93 FG_COL_FG, &fg,
94 -1);
95 return fg;
98 static void
99 tree_multi_selected_cb(GtkTreeModel *source, GtkTreePath *path, GtkTreeIter *iter, gpointer data) {
100 *(gboolean*)data = TRUE;
103 static gboolean
104 tree_multi_selected(GtkTreeSelection *sel) {
105 gboolean ret = FALSE;
106 gtk_tree_selection_selected_foreach(sel, tree_multi_selected_cb, &ret);
107 return ret;
110 static void
111 edit_cb(GtkWidget *w, FriendGroupsUI *fgui) {
112 LJFriendGroup *fg;
113 GtkTreeIter iter;
115 fg = get_selected_fg(fgui, &iter);
116 if (fg == NULL)
117 return;
119 fg = friend_group_edit_dlg_run(GTK_WINDOW(fgui->win), fgui->account, fg, -1);
120 if (fg == NULL) return; /* they cancelled. */
122 gtk_list_store_set(GTK_LIST_STORE(fgui->lgroups), &iter,
123 FG_COL_NAME, fg->name,
124 -1);
127 static void
128 remove_cb(GtkWidget *w, FriendGroupsUI *fgui) {
129 NetContext *ctx;
130 LJEditFriendGroups *efg;
131 LJUser *u = jam_account_lj_get_user(fgui->account);
132 LJFriendGroup *fg;
133 GSList *l;
134 guint32 gmask;
135 GtkTreeIter iter;
137 fg = get_selected_fg(fgui, &iter);
138 if (fg == NULL)
139 return;
140 gmask = 1L << fg->id;
142 efg = lj_editfriendgroups_new(u);
143 lj_editfriendgroups_add_delete(efg, fg->id);
145 /* unlink all friends from this group */
146 /* (unless we do this, these users will erroneously be included in a
147 * future group that gets this doomed group's id!) */
148 for (l = fgui->friends; l != NULL; l = l->next) {
149 LJFriend *f = l->data;
150 /* only update users in this group (good for large friends lists) */
151 if (f->groupmask & gmask)
152 lj_editfriendgroups_add_groupmask(efg, f->username,
153 f->groupmask & ~gmask);
156 ctx = net_ctx_gtk_new(GTK_WINDOW(fgui->win), _("Removing Friend Group"));
157 if (!net_run_verb_ctx((LJVerb*)efg, ctx, NULL)) {
158 lj_editfriendgroups_free(efg);
159 net_ctx_gtk_free(ctx);
160 return;
163 /* now that it succeeded, actually update our local friends list. */
164 for (l = fgui->friends; l != NULL; l = l->next) {
165 LJFriend *f = l->data;
166 f->groupmask &= ~gmask;
169 gtk_list_store_remove(fgui->lgroups, &iter);
170 u->friendgroups = g_slist_remove(u->friendgroups, fg);
171 g_free(fg->name);
172 g_free(fg);
174 lj_editfriendgroups_free(efg);
175 net_ctx_gtk_free(ctx);
178 static void
179 populate_grouplist(FriendGroupsUI *fgui) {
180 LJUser *u = jam_account_lj_get_user(fgui->account);
181 GSList *lgroup;
183 for (lgroup = u->friendgroups; lgroup != NULL; lgroup = lgroup->next) {
184 LJFriendGroup *fg = lgroup->data;
186 dlg_add_friend_group(fgui, fg);
190 static void
191 populate_friendlists(FriendGroupsUI *fgui, LJFriendGroup *fg) {
192 GtkTreeIter iter;
193 GtkListStore *list;
194 GSList *l;
195 guint32 mask;
197 gtk_list_store_clear(fgui->linc);
198 gtk_list_store_clear(fgui->lexc);
200 if (fg == NULL)
201 return;
203 mask = 1L << fg->id;
204 for (l = fgui->friends; l != NULL; l = l->next) {
205 LJFriend *f = l->data;
207 if (!(f->conn & LJ_FRIEND_CONN_MY))
208 continue;
210 if (f->groupmask & mask) {
211 list = fgui->linc;
212 } else {
213 list = fgui->lexc;
216 gtk_list_store_append(list, &iter);
217 gtk_list_store_set(list, &iter,
218 FRIEND_COL_NAME, f->username,
219 FRIEND_COL_FRIEND, f,
220 -1);
224 static void
225 friendgroup_sel_cb(GtkTreeSelection *sel, FriendGroupsUI *fgui) {
226 GtkTreeIter iter;
227 GtkTreeModel *model;
228 LJFriendGroup *fg = NULL;
230 if (gtk_tree_selection_get_selected(sel, &model, &iter)) {
231 gtk_tree_model_get(model, &iter,
232 FG_COL_FG, &fg,
233 -1);
235 populate_friendlists(fgui, fg);
238 static GtkWidget*
239 fg_list_create(FriendGroupsUI *fgui) {
240 GtkCellRenderer *renderer;
241 GtkTreeViewColumn *column;
242 GtkTreeSelection *sel;
244 fgui->lgroups = gtk_list_store_new(2,
245 G_TYPE_STRING, /* name */
246 G_TYPE_POINTER /* fg */);
247 populate_grouplist(fgui);
249 fgui->groups = gtk_tree_view_new_with_model(GTK_TREE_MODEL(fgui->lgroups));
250 g_object_unref(G_OBJECT(fgui->lgroups));
252 renderer = gtk_cell_renderer_text_new();
253 column = gtk_tree_view_column_new_with_attributes(_("Friend Groups"), renderer,
254 "text", FG_COL_NAME,
255 NULL);
256 gtk_tree_view_append_column(GTK_TREE_VIEW(fgui->groups), column);
258 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(fgui->groups));
259 g_signal_connect(G_OBJECT(sel), "changed",
260 G_CALLBACK(friendgroup_sel_cb), fgui);
262 return scroll_wrap(fgui->groups);
265 typedef struct {
266 LJEditFriendGroups *efg;
267 guint32 gmask;
268 gboolean add;
269 } FGAddRem;
271 static void
272 friend_addrem_pre_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) {
273 FGAddRem *p = data;
274 LJFriend *f;
275 guint32 mask;
277 gtk_tree_model_get(model, iter,
278 FRIEND_COL_FRIEND, &f,
279 -1);
281 mask = f->groupmask;
282 if (p->add)
283 mask |= p->gmask;
284 else
285 mask &= ~p->gmask;
287 lj_editfriendgroups_add_groupmask(p->efg, f->username, mask);
290 static void
291 friend_addrem_post_cb(GtkTreeModel *source, GtkTreePath *path, GtkTreeIter *iter, gpointer data) {
292 FGAddRem *p = data;
293 LJFriend *f;
295 gtk_tree_model_get(source, iter,
296 FRIEND_COL_FRIEND, &f,
297 -1);
299 if (p->add)
300 f->groupmask |= p->gmask;
301 else
302 f->groupmask &= ~p->gmask;
305 static void
306 addrem_cb(FriendGroupsUI *fgui,
307 GtkTreeView *source, GtkTreeView *dest, gboolean add) {
308 NetContext *ctx;
309 LJEditFriendGroups *efg;
310 LJFriendGroup *fg;
311 guint32 gmask;
312 FGAddRem rreq;
313 GtkTreeSelection *sel;
315 fg = get_selected_fg(fgui, NULL);
316 if (!fg) return;
318 sel = gtk_tree_view_get_selection(source);
319 if (!tree_multi_selected(sel))
320 return;
322 gmask = (1L << fg->id);
324 efg = lj_editfriendgroups_new(jam_account_lj_get_user(fgui->account));
326 rreq.efg = efg;
327 rreq.gmask = gmask;
328 rreq.add = add;
330 /* this foreach puts all of the new masks into the network request. */
331 gtk_tree_selection_selected_foreach(sel, friend_addrem_pre_cb, &rreq);
333 ctx = net_ctx_gtk_new(GTK_WINDOW(fgui->win), _("Modifying Friends"));
334 if (!net_run_verb_ctx((LJVerb*)efg, ctx, NULL)) {
335 lj_editfriendgroups_free(efg);
336 net_ctx_gtk_free(ctx);
337 return;
340 /* and this foreach actually puts all of the new masks
341 * into our local copy of the friends data. */
342 gtk_tree_selection_selected_foreach(sel, friend_addrem_post_cb, &rreq);
343 populate_friendlists(fgui, fg);
344 lj_editfriendgroups_free(efg);
345 net_ctx_gtk_free(ctx);
348 static void
349 add_cb(GtkWidget *w, FriendGroupsUI *fgui) {
350 addrem_cb(fgui, GTK_TREE_VIEW(fgui->exc), GTK_TREE_VIEW(fgui->inc), TRUE);
353 static void
354 rem_cb(GtkWidget *w, FriendGroupsUI *fgui) {
355 addrem_cb(fgui, GTK_TREE_VIEW(fgui->inc), GTK_TREE_VIEW(fgui->exc), FALSE);
358 static void
359 rowsel_sensitive_cb(GtkTreeSelection *sel, GtkWidget *w) {
360 gtk_widget_set_sensitive(w,
361 gtk_tree_selection_get_selected(sel, NULL, NULL));
363 static void
364 multisel_sensitive_cb(GtkTreeSelection *sel, GtkWidget *w) {
365 gtk_widget_set_sensitive(w, tree_multi_selected(sel));
368 static GtkWidget*
369 fg_list_buttonbox(FriendGroupsUI *fgui) {
370 GtkWidget *bnew, *bedit, *brem;
371 GtkWidget *box;
372 GtkTreeSelection *sel;
374 bnew = gtk_button_new_from_stock(GTK_STOCK_ADD);
375 gtk_tooltips_set_tip(app.tooltips, bnew, _("Add new friend group"),
376 _("Use this to add a friend group. These are useful for reading "
377 "only a specific protion of your friends list. You could, for "
378 "example, use this to filter out high-volume communities that "
379 "you like to read occasionally but not all the time. If you add "
380 "a friend group here, you can later use it with custom security "
381 "modes, and also check for new entries only from this group."));
382 g_signal_connect(G_OBJECT(bnew), "clicked",
383 G_CALLBACK(new_cb), fgui);
385 bedit = gtk_button_new_from_stock(GTK_STOCK_PROPERTIES);
386 gtk_tooltips_set_tip(app.tooltips, bedit, _("Edit group"),
387 _("Use this to edit a friend group. You can modify its name "
388 "and whether other people can see you have this group."));
389 g_signal_connect(G_OBJECT(bedit), "clicked",
390 G_CALLBACK(edit_cb), fgui);
391 gtk_widget_set_sensitive(GTK_WIDGET(bedit), FALSE);
393 brem = gtk_button_new_from_stock(GTK_STOCK_REMOVE);
394 gtk_tooltips_set_tip(app.tooltips, brem, _("Remove friend group"),
395 _("Use this to erase this friend group from your list of "
396 "friend groups."));
397 g_signal_connect(G_OBJECT(brem), "clicked",
398 G_CALLBACK(remove_cb), fgui);
399 gtk_widget_set_sensitive(GTK_WIDGET(brem), FALSE);
401 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(fgui->groups));
402 g_signal_connect(G_OBJECT(sel), "changed",
403 G_CALLBACK(rowsel_sensitive_cb), bedit);
404 g_signal_connect(G_OBJECT(sel), "changed",
405 G_CALLBACK(rowsel_sensitive_cb), brem);
407 box = jam_dialog_buttonbox_new();
408 jam_dialog_buttonbox_add(box, bnew);
409 jam_dialog_buttonbox_add(box, bedit);
410 jam_dialog_buttonbox_add(box, brem);
411 return box;
414 static void
415 fg_make_friendlist(GtkWidget **pview, GtkListStore **pstore, const char *title) {
416 GtkWidget *view;
417 GtkListStore *store;
418 GtkCellRenderer *renderer;
419 GtkTreeViewColumn *column;
420 GtkTreeSelection *sel;
422 *pstore = store = gtk_list_store_new(2,
423 G_TYPE_STRING, /* name */
424 G_TYPE_POINTER /* friend */);
425 *pview = view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
426 g_object_unref(G_OBJECT(store));
428 renderer = gtk_cell_renderer_text_new();
429 column = gtk_tree_view_column_new_with_attributes(title, renderer,
430 "text", FRIEND_COL_NAME,
431 NULL);
432 gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
434 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
435 gtk_tree_selection_set_mode(sel, GTK_SELECTION_MULTIPLE);
438 static GtkWidget*
439 make_incexc_side(FriendGroupsUI *fgui, GtkWidget *view, GtkWidget *button) {
440 GtkWidget *vbox;
441 vbox = gtk_vbox_new(FALSE, 5);
442 gtk_box_pack_start(GTK_BOX(vbox), scroll_wrap(view), TRUE, TRUE, 0);
443 gtk_box_pack_start(GTK_BOX(vbox), button, FALSE, FALSE, 0);
444 return vbox;
447 static GtkWidget*
448 make_include_side(FriendGroupsUI *fgui) {
449 GtkWidget *button;
451 fg_make_friendlist(&fgui->inc, &fgui->linc, _("Included"));
453 button = gtk_button_new();
454 gtk_container_add(GTK_CONTAINER(button),
455 gtk_image_new_from_stock(
456 GTK_STOCK_GO_FORWARD, GTK_ICON_SIZE_MENU));
457 gtk_tooltips_set_tip(app.tooltips, button,
458 _("Remove friend from group"),
459 _("Click here to remove the currently selected user "
460 "from the list of users included in this friend "
461 "group. This does not stop this user from being "
462 "listed as your friend; it only removes them from "
463 "this specific group."));
464 gtk_widget_set_sensitive(button, FALSE);
465 g_signal_connect(G_OBJECT(button), "clicked",
466 G_CALLBACK(rem_cb), fgui);
468 g_signal_connect(G_OBJECT(
469 gtk_tree_view_get_selection(GTK_TREE_VIEW(fgui->inc))),
470 "changed",
471 G_CALLBACK(multisel_sensitive_cb), button);
473 return make_incexc_side(fgui, fgui->inc, button);
475 static GtkWidget*
476 make_exclude_side(FriendGroupsUI *fgui) {
477 GtkWidget *button;
479 fg_make_friendlist(&fgui->exc, &fgui->lexc, _("Excluded"));
481 button = gtk_button_new();
482 gtk_container_add(GTK_CONTAINER(button),
483 gtk_image_new_from_stock(
484 GTK_STOCK_GO_BACK, GTK_ICON_SIZE_MENU));
485 gtk_tooltips_set_tip(app.tooltips, button, _("Add friend to group"),
486 _("Click here to include this user in the currently "
487 "selected friend group. If you have old protected "
488 "entries with custom security set to this group, "
489 "this means among other things that they will now "
490 "have access to these entries."));
491 gtk_widget_set_sensitive(button, FALSE);
492 g_signal_connect(G_OBJECT(button), "clicked",
493 G_CALLBACK(add_cb), fgui);
495 g_signal_connect(G_OBJECT(
496 gtk_tree_view_get_selection(GTK_TREE_VIEW(fgui->exc))),
497 "changed",
498 G_CALLBACK(multisel_sensitive_cb), button);
500 return make_incexc_side(fgui, fgui->exc, button);
503 void
504 friendgroups_dialog_new(GtkWindow *parent, JamAccountLJ *acc, GSList *friends) {
505 FriendGroupsUI *fgui;
506 GtkWidget *paned;
507 GtkWidget *vbox, *hbox;
509 fgui = g_new0(FriendGroupsUI, 1);
510 fgui->account = acc;
511 fgui->friends = friends;
512 fgui->win = gtk_dialog_new_with_buttons(_("Friend Groups"),
513 parent, GTK_DIALOG_MODAL,
514 GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
515 NULL);
516 jam_win_set_size(GTK_WINDOW(fgui->win), 300, 400);
517 g_signal_connect_swapped(G_OBJECT(fgui->win), "response",
518 G_CALLBACK(gtk_widget_destroy), fgui->win);
519 g_signal_connect_swapped(G_OBJECT(fgui->win), "destroy",
520 G_CALLBACK(g_free), fgui);
522 paned = gtk_vpaned_new();
523 geometry_tie_full(GEOM_FRIENDGROUPS, GTK_WINDOW(fgui->win), GTK_PANED(paned));
525 vbox = gtk_vbox_new(FALSE, 5);
526 gtk_container_set_border_width(GTK_CONTAINER(vbox), 10);
527 gtk_box_pack_start(GTK_BOX(vbox),
528 fg_list_create(fgui), TRUE, TRUE, 0);
529 hbox = gtk_hbox_new(FALSE, 0);
530 gtk_box_pack_end(GTK_BOX(hbox),
531 fg_list_buttonbox(fgui), FALSE, FALSE, 0);
532 gtk_box_pack_start(GTK_BOX(vbox),
533 hbox, FALSE, FALSE, 0);
534 gtk_paned_pack1(GTK_PANED(paned), vbox, TRUE, TRUE);
536 fg_make_friendlist(&fgui->inc, &fgui->linc, _("Included"));
537 fg_make_friendlist(&fgui->exc, &fgui->lexc, _("Excluded"));
539 hbox = gtk_hbox_new(FALSE, 5);
540 gtk_container_set_border_width(GTK_CONTAINER(hbox), 10);
542 gtk_box_pack_start(GTK_BOX(hbox),
543 make_include_side(fgui), TRUE, TRUE, 0);
544 gtk_box_pack_start(GTK_BOX(hbox),
545 make_exclude_side(fgui), TRUE, TRUE, 0);
547 gtk_paned_pack2(GTK_PANED(paned), hbox, TRUE, TRUE);
549 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(fgui->win)->vbox), paned, TRUE, TRUE, 0);
550 gtk_widget_show_all(GTK_DIALOG(fgui->win)->vbox);
552 gtk_widget_show(fgui->win);