first try to journal_store_get_latest_id() for sqlite
[k8lowj.git] / src / friendgroups.c
blob9bfb4d31fc42b2376117713f7ae7c1ad91e8e52b
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 "liblj/editfriendgroups.h"
9 #include "conf.h"
10 #include "friends.h"
11 #include "friendgroupedit.h"
12 #include "friendgroups.h"
13 #include "network.h"
16 typedef struct {
17 GtkWidget *win;
18 GtkWidget *groups, *inc, *exc;
19 GtkListStore *lgroups, *linc, *lexc;
21 JamAccountLJ *account;
22 GSList *friends;
23 } FriendGroupsUI;
26 enum {
27 FG_COL_NAME,
28 FG_COL_FG
32 enum {
33 FRIEND_COL_NAME,
34 FRIEND_COL_FRIEND
38 static int findfreegroup (FriendGroupsUI *fgui) {
39 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. */
51 static void dlg_add_friend_group (FriendGroupsUI *fgui, LJFriendGroup *fg) {
52 GtkTreeIter iter;
53 gtk_list_store_append(fgui->lgroups, &iter);
54 gtk_list_store_set(fgui->lgroups, &iter, FG_COL_NAME, fg->name, FG_COL_FG, fg, -1);
58 static void new_cb (GtkWidget *w, FriendGroupsUI *fgui) {
59 LJUser *u;
60 LJFriendGroup *fg;
61 int freegroup = findfreegroup(fgui);
62 if (freegroup == -1) return; /* FIXME */
63 fg = friend_group_edit_dlg_run(GTK_WINDOW(fgui->win), fgui->account, NULL, freegroup);
64 if (fg == NULL) return; /* they cancelled. */
65 /* new friend group! */
66 u = jam_account_lj_get_user(fgui->account);
67 u->friendgroups = g_slist_append(u->friendgroups, fg);
68 dlg_add_friend_group(fgui, fg);
72 static LJFriendGroup *get_selected_fg (FriendGroupsUI *fgui, GtkTreeIter *piter) {
73 GtkTreeSelection *sel;
74 GtkTreeModel *model;
75 GtkTreeIter iter;
76 LJFriendGroup *fg;
78 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(fgui->groups));
79 if (!gtk_tree_selection_get_selected(sel, &model, &iter)) return NULL;
80 if (piter) *piter = iter;
82 gtk_tree_model_get(model, &iter, FG_COL_FG, &fg, -1);
83 return fg;
87 static void tree_multi_selected_cb (GtkTreeModel *source, GtkTreePath *path, GtkTreeIter *iter, gpointer data) {
88 *(gboolean *)data = TRUE;
92 static gboolean tree_multi_selected (GtkTreeSelection *sel) {
93 gboolean ret = FALSE;
94 gtk_tree_selection_selected_foreach(sel, tree_multi_selected_cb, &ret);
95 return ret;
99 static void edit_cb (GtkWidget *w, FriendGroupsUI *fgui) {
100 LJFriendGroup *fg;
101 GtkTreeIter iter;
102 fg = get_selected_fg(fgui, &iter);
103 if (fg == NULL) return;
104 fg = friend_group_edit_dlg_run(GTK_WINDOW(fgui->win), fgui->account, fg, -1);
105 if (fg == NULL) return; /* they cancelled. */
106 gtk_list_store_set(GTK_LIST_STORE(fgui->lgroups), &iter, FG_COL_NAME, fg->name, -1);
110 static void remove_cb (GtkWidget *w, FriendGroupsUI *fgui) {
111 NetContext *ctx;
112 LJEditFriendGroups *efg;
113 LJUser *u = jam_account_lj_get_user(fgui->account);
114 LJFriendGroup *fg;
115 GSList *l;
116 guint32 gmask;
117 GtkTreeIter iter;
119 fg = get_selected_fg(fgui, &iter);
120 if (fg == NULL) return;
121 gmask = 1L<<fg->id;
123 efg = lj_editfriendgroups_new(u);
124 lj_editfriendgroups_add_delete(efg, fg->id);
126 /* unlink all friends from this group */
127 /* (unless we do this, these users will erroneously be included in a
128 * future group that gets this doomed group's id!) */
129 for (l = fgui->friends; l != NULL; l = l->next) {
130 LJFriend *f = l->data;
131 /* only update users in this group (good for large friends lists) */
132 if (f->groupmask&gmask) lj_editfriendgroups_add_groupmask(efg, f->username, f->groupmask&~gmask);
135 ctx = net_ctx_gtk_new(GTK_WINDOW(fgui->win), _("Removing Friend Group"));
136 if (!net_run_verb_ctx((LJVerb *) efg, ctx, NULL)) {
137 lj_editfriendgroups_free(efg);
138 net_ctx_gtk_free(ctx);
139 return;
142 /* now that it succeeded, actually update our local friends list. */
143 for (l = fgui->friends; l != NULL; l = l->next) {
144 LJFriend *f = l->data;
145 f->groupmask &= ~gmask;
148 gtk_list_store_remove(fgui->lgroups, &iter);
149 u->friendgroups = g_slist_remove(u->friendgroups, fg);
150 g_free(fg->name);
151 g_free(fg);
153 lj_editfriendgroups_free(efg);
154 net_ctx_gtk_free(ctx);
158 static void populate_grouplist (FriendGroupsUI *fgui) {
159 GSList *lgroup;
160 LJUser *u = jam_account_lj_get_user(fgui->account);
161 for (lgroup = u->friendgroups; lgroup != NULL; lgroup = lgroup->next) {
162 LJFriendGroup *fg = lgroup->data;
163 dlg_add_friend_group(fgui, fg);
168 static void populate_friendlists (FriendGroupsUI *fgui, LJFriendGroup *fg) {
169 GtkTreeIter iter;
170 GtkListStore *list;
171 GSList *l;
172 guint32 mask;
174 gtk_list_store_clear(fgui->linc);
175 gtk_list_store_clear(fgui->lexc);
177 if (fg == NULL) return;
179 mask = 1L<<fg->id;
180 for (l = fgui->friends; l != NULL; l = l->next) {
181 LJFriend *f = l->data;
182 if (!(f->conn&LJ_FRIEND_CONN_MY)) continue;
183 if (f->groupmask&mask) {
184 list = fgui->linc;
185 } else {
186 list = fgui->lexc;
188 gtk_list_store_append(list, &iter);
189 gtk_list_store_set(list, &iter, FRIEND_COL_NAME, f->username, FRIEND_COL_FRIEND, f, -1);
194 static void friendgroup_sel_cb (GtkTreeSelection *sel, FriendGroupsUI *fgui) {
195 GtkTreeIter iter;
196 GtkTreeModel *model;
197 LJFriendGroup *fg = NULL;
198 if (gtk_tree_selection_get_selected(sel, &model, &iter)) {
199 gtk_tree_model_get(model, &iter, FG_COL_FG, &fg, -1);
201 populate_friendlists(fgui, fg);
205 static GtkWidget *fg_list_create (FriendGroupsUI *fgui) {
206 GtkCellRenderer *renderer;
207 GtkTreeViewColumn *column;
208 GtkTreeSelection *sel;
210 fgui->lgroups = gtk_list_store_new(2, G_TYPE_STRING, /* name */ G_TYPE_POINTER /* fg */);
211 populate_grouplist(fgui);
213 fgui->groups = gtk_tree_view_new_with_model(GTK_TREE_MODEL(fgui->lgroups));
214 g_object_unref(G_OBJECT(fgui->lgroups));
216 renderer = gtk_cell_renderer_text_new();
217 column = gtk_tree_view_column_new_with_attributes(_("Friend Groups"), renderer, "text", FG_COL_NAME, NULL);
218 gtk_tree_view_append_column(GTK_TREE_VIEW(fgui->groups), column);
220 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(fgui->groups));
221 g_signal_connect(G_OBJECT(sel), "changed", G_CALLBACK(friendgroup_sel_cb), fgui);
223 return scroll_wrap(fgui->groups);
227 typedef struct {
228 LJEditFriendGroups *efg;
229 guint32 gmask;
230 gboolean add;
231 } FGAddRem;
234 static void friend_addrem_pre_cb (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) {
235 FGAddRem *p = data;
236 LJFriend *f;
237 guint32 mask;
238 gtk_tree_model_get(model, iter, FRIEND_COL_FRIEND, &f, -1);
239 mask = f->groupmask;
240 if (p->add) mask |= p->gmask; else mask &= ~p->gmask;
241 lj_editfriendgroups_add_groupmask(p->efg, f->username, mask);
245 static void friend_addrem_post_cb (GtkTreeModel *source, GtkTreePath *path, GtkTreeIter *iter, gpointer data) {
246 FGAddRem *p = data;
247 LJFriend *f;
248 gtk_tree_model_get(source, iter, FRIEND_COL_FRIEND, &f, -1);
249 if (p->add) f->groupmask |= p->gmask; else f->groupmask &= ~p->gmask;
253 static void addrem_cb (FriendGroupsUI *fgui, GtkTreeView *source, GtkTreeView *dest, gboolean add) {
254 NetContext *ctx;
255 LJEditFriendGroups *efg;
256 LJFriendGroup *fg;
257 guint32 gmask;
258 FGAddRem rreq;
259 GtkTreeSelection *sel;
261 fg = get_selected_fg(fgui, NULL);
262 if (!fg) return;
264 sel = gtk_tree_view_get_selection(source);
265 if (!tree_multi_selected(sel)) return;
267 gmask = 1L<<fg->id;
269 efg = lj_editfriendgroups_new(jam_account_lj_get_user(fgui->account));
271 rreq.efg = efg;
272 rreq.gmask = gmask;
273 rreq.add = add;
275 /* this foreach puts all of the new masks into the network request. */
276 gtk_tree_selection_selected_foreach(sel, friend_addrem_pre_cb, &rreq);
278 ctx = net_ctx_gtk_new(GTK_WINDOW(fgui->win), _("Modifying Friends"));
279 if (!net_run_verb_ctx((LJVerb *) efg, ctx, NULL)) {
280 lj_editfriendgroups_free(efg);
281 net_ctx_gtk_free(ctx);
282 return;
285 /* and this foreach actually puts all of the new masks
286 * into our local copy of the friends data. */
287 gtk_tree_selection_selected_foreach(sel, friend_addrem_post_cb, &rreq);
288 populate_friendlists(fgui, fg);
289 lj_editfriendgroups_free(efg);
290 net_ctx_gtk_free(ctx);
294 static void add_cb (GtkWidget *w, FriendGroupsUI *fgui) {
295 addrem_cb(fgui, GTK_TREE_VIEW(fgui->exc), GTK_TREE_VIEW(fgui->inc), TRUE);
299 static void rem_cb (GtkWidget *w, FriendGroupsUI *fgui) {
300 addrem_cb(fgui, GTK_TREE_VIEW(fgui->inc), GTK_TREE_VIEW(fgui->exc), FALSE);
304 static void rowsel_sensitive_cb (GtkTreeSelection *sel, GtkWidget *w) {
305 gtk_widget_set_sensitive(w, gtk_tree_selection_get_selected(sel, NULL, NULL));
309 static void multisel_sensitive_cb (GtkTreeSelection *sel, GtkWidget *w) {
310 gtk_widget_set_sensitive(w, tree_multi_selected(sel));
314 static GtkWidget *fg_list_buttonbox (FriendGroupsUI *fgui) {
315 GtkWidget *bnew, *bedit, *brem;
316 GtkWidget *box;
317 GtkTreeSelection *sel;
319 bnew = gtk_button_new_from_stock(GTK_STOCK_ADD);
320 gtk_tooltips_set_tip(app.tooltips, bnew, _("Add new friend group"),
321 _("Use this to add a friend group. These are useful for reading "
322 "only a specific protion of your friends list. You could, for "
323 "example, use this to filter out high-volume communities that "
324 "you like to read occasionally but not all the time. If you add "
325 "a friend group here, you can later use it with custom security "
326 "modes, and also check for new entries only from this group."));
327 g_signal_connect(G_OBJECT(bnew), "clicked", G_CALLBACK(new_cb), fgui);
329 bedit = gtk_button_new_from_stock(GTK_STOCK_PROPERTIES);
330 gtk_tooltips_set_tip(app.tooltips, bedit, _("Edit group"),
331 _("Use this to edit a friend group. You can modify its name "
332 "and whether other people can see you have this group."));
333 g_signal_connect(G_OBJECT(bedit), "clicked", G_CALLBACK(edit_cb), fgui);
334 gtk_widget_set_sensitive(GTK_WIDGET(bedit), FALSE);
336 brem = gtk_button_new_from_stock(GTK_STOCK_REMOVE);
337 gtk_tooltips_set_tip(app.tooltips, brem, _("Remove friend group"),
338 _("Use this to erase this friend group from your list of " "friend groups."));
339 g_signal_connect(G_OBJECT(brem), "clicked", G_CALLBACK(remove_cb), fgui);
340 gtk_widget_set_sensitive(GTK_WIDGET(brem), FALSE);
342 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(fgui->groups));
343 g_signal_connect(G_OBJECT(sel), "changed", G_CALLBACK(rowsel_sensitive_cb), bedit);
344 g_signal_connect(G_OBJECT(sel), "changed", G_CALLBACK(rowsel_sensitive_cb), brem);
346 box = jam_dialog_buttonbox_new();
347 jam_dialog_buttonbox_add(box, bnew);
348 jam_dialog_buttonbox_add(box, bedit);
349 jam_dialog_buttonbox_add(box, brem);
350 return box;
354 static void fg_make_friendlist (GtkWidget **pview, GtkListStore **pstore, const char *title) {
355 GtkWidget *view;
356 GtkListStore *store;
357 GtkCellRenderer *renderer;
358 GtkTreeViewColumn *column;
359 GtkTreeSelection *sel;
361 *pstore = store = gtk_list_store_new(2, G_TYPE_STRING, /* name */ G_TYPE_POINTER /* friend */);
362 *pview = view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
363 g_object_unref(G_OBJECT(store));
365 renderer = gtk_cell_renderer_text_new();
366 column = gtk_tree_view_column_new_with_attributes(title, renderer, "text", FRIEND_COL_NAME, NULL);
367 gtk_tree_view_append_column(GTK_TREE_VIEW(view), column);
369 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
370 gtk_tree_selection_set_mode(sel, GTK_SELECTION_MULTIPLE);
374 static GtkWidget *make_incexc_side (FriendGroupsUI *fgui, GtkWidget *view, GtkWidget *button) {
375 GtkWidget *vbox;
376 vbox = gtk_vbox_new(FALSE, 5);
377 gtk_box_pack_start(GTK_BOX(vbox), scroll_wrap(view), TRUE, TRUE, 0);
378 gtk_box_pack_start(GTK_BOX(vbox), button, FALSE, FALSE, 0);
379 return vbox;
383 static GtkWidget *make_include_side (FriendGroupsUI *fgui) {
384 GtkWidget *button;
386 fg_make_friendlist(&fgui->inc, &fgui->linc, _("Included"));
388 button = gtk_button_new();
389 gtk_container_add(GTK_CONTAINER(button), gtk_image_new_from_stock(GTK_STOCK_GO_FORWARD, GTK_ICON_SIZE_MENU));
390 gtk_tooltips_set_tip(app.tooltips, button, _("Remove friend from group"),
391 _("Click here to remove the currently selected user "
392 "from the list of users included in this friend "
393 "group. This does not stop this user from being "
394 "listed as your friend; it only removes them from " "this specific group."));
395 gtk_widget_set_sensitive(button, FALSE);
396 g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(rem_cb), fgui);
398 g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(fgui->inc))), "changed", G_CALLBACK(multisel_sensitive_cb), button);
400 return make_incexc_side(fgui, fgui->inc, button);
404 static GtkWidget *make_exclude_side (FriendGroupsUI *fgui) {
405 GtkWidget *button;
407 fg_make_friendlist(&fgui->exc, &fgui->lexc, _("Excluded"));
409 button = gtk_button_new();
410 gtk_container_add(GTK_CONTAINER(button), gtk_image_new_from_stock(GTK_STOCK_GO_BACK, GTK_ICON_SIZE_MENU));
411 gtk_tooltips_set_tip(app.tooltips, button, _("Add friend to group"),
412 _("Click here to include this user in the currently "
413 "selected friend group. If you have old protected "
414 "entries with custom security set to this group, "
415 "this means among other things that they will now " "have access to these entries."));
416 gtk_widget_set_sensitive(button, FALSE);
417 g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(add_cb), fgui);
419 g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(fgui->exc))), "changed", G_CALLBACK(multisel_sensitive_cb), button);
421 return make_incexc_side(fgui, fgui->exc, button);
425 void friendgroups_dialog_new (GtkWindow *parent, JamAccountLJ *acc, GSList *friends) {
426 FriendGroupsUI *fgui;
427 GtkWidget *paned;
428 GtkWidget *vbox, *hbox;
430 fgui = g_new0(FriendGroupsUI, 1);
431 fgui->account = acc;
432 fgui->friends = friends;
433 fgui->win = gtk_dialog_new_with_buttons(_("Friend Groups"), parent, GTK_DIALOG_MODAL, GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL);
434 jam_win_set_size(GTK_WINDOW(fgui->win), 300, 400);
435 g_signal_connect_swapped(G_OBJECT(fgui->win), "response", G_CALLBACK(gtk_widget_destroy), fgui->win);
436 g_signal_connect_swapped(G_OBJECT(fgui->win), "destroy", G_CALLBACK(g_free), fgui);
438 paned = gtk_vpaned_new();
439 geometry_tie_full(GEOM_FRIENDGROUPS, GTK_WINDOW(fgui->win), GTK_PANED(paned));
441 vbox = gtk_vbox_new(FALSE, 5);
442 gtk_container_set_border_width(GTK_CONTAINER(vbox), 10);
443 gtk_box_pack_start(GTK_BOX(vbox), fg_list_create(fgui), TRUE, TRUE, 0);
444 hbox = gtk_hbox_new(FALSE, 0);
445 gtk_box_pack_end(GTK_BOX(hbox), fg_list_buttonbox(fgui), FALSE, FALSE, 0);
446 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
447 gtk_paned_pack1(GTK_PANED(paned), vbox, TRUE, TRUE);
449 fg_make_friendlist(&fgui->inc, &fgui->linc, _("Included"));
450 fg_make_friendlist(&fgui->exc, &fgui->lexc, _("Excluded"));
452 hbox = gtk_hbox_new(FALSE, 5);
453 gtk_container_set_border_width(GTK_CONTAINER(hbox), 10);
455 gtk_box_pack_start(GTK_BOX(hbox), make_include_side(fgui), TRUE, TRUE, 0);
456 gtk_box_pack_start(GTK_BOX(hbox), make_exclude_side(fgui), TRUE, TRUE, 0);
458 gtk_paned_pack2(GTK_PANED(paned), hbox, TRUE, TRUE);
460 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(fgui->win)->vbox), paned, TRUE, TRUE, 0);
461 gtk_widget_show_all(GTK_DIALOG(fgui->win)->vbox);
463 gtk_widget_show(fgui->win);