poll.h: cosmetix
[k8lowj.git] / src / manager.c
blob5b3da12f33d955a0d2e87d534a521a268134b22b
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 <stdlib.h>
9 #include "account.h"
10 #include "checkfriends.h"
11 #include "conf.h"
12 #include "groupedbox.h"
13 #include "security.h"
14 #include "tie.h"
15 #include "util.h"
18 /* manager:
19 * - host
20 * - account
21 * - (entries, outbox, etc?)
25 typedef struct _ManagerUI ManagerUI;
26 typedef struct _AccountUI AccountUI;
29 struct _ManagerUI {
30 GtkWidget *win;
32 GtkTreeStore *store;
33 GtkWidget *treeview;
35 GSList *pixbuf_names;
36 GSList *pixbufs;
38 GtkWidget *add;
39 GtkWidget *edit;
40 GtkWidget *del;
42 GtkWidget *filterlabel;
43 gpointer selection;
47 enum {
48 COL_ICON,
49 COL_TEXT,
50 COL_ISHOST,
51 COL_DATA,
52 COL_COUNT
56 static GdkPixbuf *get_pixbuf (ManagerUI *mui, const char *stockid) {
57 int i;
58 GSList *l;
59 GdkPixbuf *pixbuf;
61 for (i = 0, l = mui->pixbuf_names; l; ++i, l = l->next) {
62 if (strcmp(l->data, stockid) == 0) return g_slist_nth(mui->pixbufs, i)->data;
65 pixbuf = gtk_widget_render_icon(mui->treeview, stockid, GTK_ICON_SIZE_MENU, NULL);
66 mui->pixbuf_names = g_slist_append(mui->pixbuf_names, g_strdup(stockid));
67 mui->pixbufs = g_slist_append(mui->pixbufs, pixbuf);
68 return pixbuf;
72 static GtkWidget *label_list(GSList *l, gboolean friendgroups/*FIXME: hack*/) {
73 GtkWidget *label;
74 label = jam_form_label_new("");
75 if (l) {
76 GString *str = g_string_sized_new(1024);
77 while (l) {
78 if (str->len > 0) g_string_append(str, ", ");
79 if (friendgroups) {
80 LJFriendGroup *fg = l->data;
81 g_string_append_printf(str, "<b>%s</b>", fg->name);
82 } else {
83 g_string_append_printf(str, "<b>%s</b>", (char *)l->data);
85 l = l->next;
87 gtk_label_set_markup(GTK_LABEL(label), str->str);
88 g_string_free(str, TRUE);
89 } else {
90 gtk_label_set_markup(GTK_LABEL(label), _("<i>none</i>"));
92 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
93 return label;
97 static void run_cfmask_manager_dlg (GtkWidget *b, ManagerUI *mui) {
98 JamAccountLJ *acc = mui->selection;
99 guint newmask, oldmask;
100 oldmask = jam_account_lj_get_cfmask(acc);
101 newmask = custom_security_dlg_run(GTK_WINDOW(mui->win), oldmask, acc);
102 g_assert(mui->filterlabel);
103 if (newmask != oldmask) {
104 jam_account_lj_set_cfmask(acc, newmask);
105 /* XXX how do we notify the cfmgr of this new mask?
106 * cfmgr_set_mask(cfmgr_new(acc), newmask);*/
107 gtk_label_set_text(GTK_LABEL(mui->filterlabel), newmask ? _("active") : _("inactive"));
112 static GtkWidget *lj_user_make_ui_identity (ManagerUI *mui, LJUser *user) {
113 GtkSizeGroup *sg;
114 GtkWidget *vbox, *epassword;
115 sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
116 vbox = gtk_vbox_new(FALSE, 6);
117 gtk_container_set_border_width(GTK_CONTAINER(vbox), 12);
118 gtk_box_pack_start(GTK_BOX(vbox), labelled_box_new_sg(_("_User Name:"), jam_form_label_new(user->username), sg), FALSE, FALSE, 0);
119 gtk_box_pack_start(GTK_BOX(vbox), labelled_box_new_sg(_("_Full Name:"), jam_form_label_new(user->fullname), sg), FALSE, FALSE, 0);
120 epassword = gtk_entry_new();
121 gtk_entry_set_visibility(GTK_ENTRY(epassword), FALSE);
122 gtk_entry_set_text(GTK_ENTRY(epassword), user->password);
123 gtk_widget_set_usize(epassword, 100, -1);
124 gtk_box_pack_start(GTK_BOX(vbox), labelled_box_new_sg(_("_Password:"), epassword, sg), FALSE, FALSE, 0);
125 return vbox;
129 static void account_add_ui_loginoptions (ManagerUI *mui, GtkBox *vbox, JamAccount *acc) {
130 GtkWidget *cruser, *crpassword;
131 gboolean ru, rp;
132 jam_account_get_remember(acc, &ru, &rp);
133 cruser = gtk_check_button_new_with_label(_("Remember user"));
134 tie_toggle(GTK_TOGGLE_BUTTON(cruser), &acc->remember_user);
135 gtk_box_pack_start(vbox, cruser, FALSE, FALSE, 0);
136 crpassword = gtk_check_button_new_with_label(_("Remember password"));
137 tie_toggle(GTK_TOGGLE_BUTTON(crpassword), &acc->remember_password);
138 gtk_box_pack_start(vbox, crpassword, FALSE, FALSE, 0);
142 static GtkWidget *user_make_ui_pictures (ManagerUI *mui, LJUser *user) {
143 GtkWidget *box = groupedbox_new();
144 gtk_container_set_border_width(GTK_CONTAINER(box), 12);
145 groupedbox_set_header(GROUPEDBOX(box), _("User picture keywords:"), FALSE);
146 groupedbox_pack(GROUPEDBOX(box), label_list(user->pickws, FALSE), FALSE);
147 return box;
151 static GtkWidget *account_lj_make_ui_friends (ManagerUI *mui, JamAccountLJ *acc) {
152 GtkWidget *vbox, *box, *hbcff, *licff, *bcff;
153 LJUser *user = jam_account_lj_get_user(acc);
155 vbox = gtk_vbox_new(FALSE, 6);
156 gtk_container_set_border_width(GTK_CONTAINER(vbox), 12);
158 /* per-user checkfriends on/off */
159 gtk_box_pack_start(GTK_BOX(vbox),
160 tie_toggle(GTK_TOGGLE_BUTTON(gtk_check_button_new_with_mnemonic(_("_Monitor this user's friends list for updates"))), &user->checkfriends), FALSE, FALSE, 0);
162 gtk_box_pack_start(GTK_BOX(vbox), label_list(user->friendgroups, TRUE), FALSE, FALSE, 0);
164 box = groupedbox_new();
165 groupedbox_set_header(GROUPEDBOX(box), _("Filter for friends list monitor:"), FALSE);
167 hbcff = gtk_hbox_new(FALSE, 5);
169 licff = gtk_label_new(acc->cfmask ? _("active") : _("inactive"));
170 gtk_misc_set_alignment(GTK_MISC(licff), 0.0, 0.5);
171 gtk_box_pack_start(GTK_BOX(hbcff), licff, TRUE, TRUE, 0);
172 mui->filterlabel = licff; /* not the prettiest of hacks */
174 bcff = gtk_button_new_with_mnemonic(_("_Edit filter"));
175 /* guard against a user with no friendgroup info */
176 gtk_widget_set_sensitive(bcff, user->friendgroups != NULL);
177 gtk_box_pack_start(GTK_BOX(hbcff), bcff, FALSE, FALSE, 0);
179 groupedbox_pack(GROUPEDBOX(box), hbcff, FALSE);
181 gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 0);
183 g_signal_connect(G_OBJECT(bcff), "clicked", G_CALLBACK(run_cfmask_manager_dlg), mui);
185 return vbox;
189 static GtkWidget *account_lj_make_ui (ManagerUI *mui, JamAccount *acc) {
190 LJUser *user;
191 GtkWidget *nb, *box;
192 nb = gtk_notebook_new();
193 user = jam_account_lj_get_user(JAM_ACCOUNT_LJ(acc));
194 box = lj_user_make_ui_identity(mui, user), account_add_ui_loginoptions(mui, GTK_BOX(box), acc);
195 gtk_notebook_append_page(GTK_NOTEBOOK(nb), box, gtk_label_new(_("Identity")));
196 gtk_notebook_append_page(GTK_NOTEBOOK(nb), user_make_ui_pictures(mui, user), gtk_label_new(_("Pictures")));
197 gtk_notebook_append_page(GTK_NOTEBOOK(nb), account_lj_make_ui_friends(mui, JAM_ACCOUNT_LJ(acc)), gtk_label_new(_("Friends")));
198 return nb;
202 static GtkWidget *account_make_ui (ManagerUI *mui, JamAccount *acc) {
203 if (JAM_ACCOUNT_IS_LJ(acc)) {
204 return account_lj_make_ui(mui, acc);
205 } else {
206 g_error("unknown account type!");
208 return NULL;
212 typedef struct {
213 GtkTreeIter iter;
214 gpointer searchdata;
215 gboolean found;
216 } SearchData;
219 static gboolean search_model_cb (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) {
220 SearchData *sd = data;
221 gpointer datacol;
222 gtk_tree_model_get(model, iter, COL_DATA, &datacol, -1);
223 if (datacol == sd->searchdata) {
224 sd->found = TRUE;
225 sd->iter = *iter;
226 return TRUE;
228 return FALSE;
232 static GtkTreeIter search_model (ManagerUI *mui, gpointer data) {
233 SearchData sd;
234 sd.found = FALSE;
235 sd.searchdata = data;
236 gtk_tree_model_foreach(GTK_TREE_MODEL(mui->store), search_model_cb, &sd);
237 return sd.iter;
241 static void account_edit (ManagerUI *mui, GtkTreeIter *iter, JamAccount *acc) {
242 GtkWidget *dlg;
243 char *oldname;
244 const char *newname;
245 oldname = g_strdup(jam_account_get_username(acc));
246 dlg = gtk_dialog_new_with_buttons(_("User Properties"), GTK_WINDOW(mui->win), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL);
247 //gtk_window_set_default_size(GTK_WINDOW(dlg), 300, 200);
248 jam_dialog_set_contents(GTK_DIALOG(dlg), account_make_ui(mui, acc));
249 gtk_dialog_run(GTK_DIALOG(dlg));
250 gtk_widget_destroy(dlg);
251 newname = jam_account_get_username(acc);
252 if (strcmp(oldname, newname) != 0) {
253 gtk_tree_store_set(mui->store, iter, COL_TEXT, newname, -1);
255 g_free(oldname);
259 static void account_del (ManagerUI *mui, GtkTreeIter *iter, JamAccount *acc) {
261 GtkTreeIter it;
262 LJUser *user = jam_account_get_user(acc);
263 it = search_model(mui, user);
265 g_warning("unimplemented\n");
270 static void add_entry_cache (ManagerUI *mui, GtkTreeStore *ts, GtkTreeIter *parent, JamAccount *user) {
271 GtkTreeIter ientries;
272 gtk_tree_store_append(ts, &ientries, parent);
273 gtk_tree_store_set(ts, &ientries, COL_TEXT, _("Entries"), -1);
278 static void add_account (ManagerUI *mui, GtkTreeIter *parent, JamAccount *account) {
279 GtkTreeIter iaccount;
280 gtk_tree_store_append(mui->store, &iaccount, parent);
281 gtk_tree_store_set(mui->store, &iaccount,
282 COL_ICON, NULL, COL_TEXT, jam_account_get_username(account), COL_ISHOST, FALSE, COL_DATA, account, -1);
284 add_entry_cache(mui, ts, &iuser, user);
285 gtk_tree_store_append(ts, &idrafts, &iuser);
286 gtk_tree_store_set(ts, &idrafts, COL_TEXT, _("Drafts"), -1);
287 gtk_tree_store_append(ts, &ioutbox, &iuser);
288 gtk_tree_store_set(ts, &ioutbox, COL_TEXT, _("Outbox"), -1);
293 static gboolean entry_tie_cb (GtkEntry *entry, GdkEventFocus *e, char **data) {
294 string_replace(data, gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1));
295 return FALSE;
299 static void entry_tie (GtkEntry *entry, char **data) {
300 if (*data) gtk_entry_set_text(GTK_ENTRY(entry), *data);
301 g_signal_connect(G_OBJECT(entry), "focus-out-event", G_CALLBACK(entry_tie_cb), data);
305 static gboolean host_rename_cb (GtkEntry *entry, GdkEventFocus *e, JamHost *host) {
306 const char *newname;
307 GError *err = NULL;
308 newname = gtk_entry_get_text(entry);
309 if (strcmp(host->name, newname) == 0) return FALSE;
310 if (!conf_rename_host(host, newname, &err)) {
311 jam_warning(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(entry))), _("Unable to rename server: %s."), err->message);
312 g_error_free(err);
313 gtk_entry_set_text(entry, host->name);
314 return FALSE;
316 return FALSE;
320 static GtkWidget *host_make_ui (ManagerUI *mui, JamHost *host) {
321 GtkWidget *vbox, *name;
322 GtkSizeGroup *sg;
323 sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
324 vbox = gtk_vbox_new(FALSE, 6);
325 name = gtk_entry_new();
326 gtk_entry_set_text(GTK_ENTRY(name), host->name);
327 g_signal_connect_after(G_OBJECT(name), "focus-out-event", G_CALLBACK(host_rename_cb), host);
328 gtk_widget_set_usize(name, 200, -1);
329 gtk_box_pack_start(GTK_BOX(vbox), labelled_box_new_sg(_("_Name:"), name, sg), FALSE, FALSE, 0);
330 if (JAM_HOST_IS_LJ(host)) {
331 JamHostLJ *hostlj = JAM_HOST_LJ(host);
332 LJServer *server = jam_host_lj_get_server(hostlj);
333 GtkWidget *eurl, *cprotocol;
334 eurl = gtk_entry_new();
335 entry_tie(GTK_ENTRY(eurl), &server->url);
336 gtk_widget_set_usize(eurl, 200, -1);
337 gtk_box_pack_start(GTK_BOX(vbox), labelled_box_new_sg(_("_URL:"), eurl, sg), FALSE, FALSE, 0);
338 cprotocol = gtk_check_button_new_with_mnemonic(_("_Server supports protocol version 1"));
339 tie_toggle(GTK_TOGGLE_BUTTON(cprotocol), &server->protocolversion);
340 gtk_box_pack_start(GTK_BOX(vbox), cprotocol, FALSE, FALSE, 0);
341 } else {
342 g_error("unknown host type!");
344 return vbox;
348 static void add_host (ManagerUI *mui, JamHost *host);
350 static void host_edit (ManagerUI *mui, GtkTreeIter *iter, JamHost *host) {
351 GtkWidget *dlg;
352 char *oldname;
353 oldname = g_strdup(host->name);
354 dlg = gtk_dialog_new_with_buttons(_("Server Properties"), GTK_WINDOW(mui->win), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL);
355 //gtk_window_set_default_size(GTK_WINDOW(dlg), 300, 200);
356 jam_dialog_set_contents(GTK_DIALOG(dlg), host_make_ui(mui, host));
357 gtk_dialog_run(GTK_DIALOG(dlg));
358 gtk_widget_destroy(dlg);
359 if (strcmp(host->name, oldname) != 0) gtk_tree_store_set(mui->store, iter, COL_TEXT, host->name, -1);
360 g_free(oldname);
364 static void host_del (ManagerUI *mui, GtkTreeIter *iter, JamHost *host) {
365 GtkTreeIter it;
366 it = search_model(mui, host);
367 gtk_tree_store_remove(mui->store, &it);
368 conf.hosts = g_slist_remove(conf.hosts, host);
369 // XXX evan g_free(server);
373 static void add_host (ManagerUI *mui, JamHost *host) {
374 GtkTreeIter ihost;
375 GSList *l;
376 gtk_tree_store_append(mui->store, &ihost, NULL);
377 gtk_tree_store_set(mui->store, &ihost, COL_ICON, get_pixbuf(mui, jam_host_get_stock_icon(host)), COL_TEXT, host->name, COL_ISHOST, TRUE, COL_DATA, host, -1);
378 for (l = host->accounts; l; l = l->next) add_account(mui, &ihost, l->data);
382 static void populate_store (ManagerUI *mui) {
383 GSList *l;
384 gtk_tree_store_clear(mui->store);
385 for (l = conf.hosts; l; l = l->next) if (l->data) add_host(mui, l->data);
389 static GtkTreeStore *make_model (ManagerUI *mui) {
390 mui->store = gtk_tree_store_new(COL_COUNT,
391 /* icon */ GDK_TYPE_PIXBUF,
392 /* name */ G_TYPE_STRING,
393 /* ishost */ G_TYPE_BOOLEAN,
394 /* data */ G_TYPE_POINTER);
395 populate_store(mui);
396 return mui->store;
400 static void selection_changed_cb (GtkTreeSelection *ts, ManagerUI *mui) {
401 GtkTreeModel *model;
402 GtkTreeIter iter;
403 if (!gtk_tree_selection_get_selected(ts, &model, &iter)) {
404 gtk_widget_set_sensitive(mui->edit, FALSE);
405 gtk_widget_set_sensitive(mui->del, FALSE);
406 } else {
407 /* XXX gboolean ishost;
408 gtk_tree_model_get(model, &iter, COL_ISHOST, &ishost, -1); */
409 gtk_widget_set_sensitive(mui->edit, TRUE);
410 gtk_widget_set_sensitive(mui->del, TRUE);
415 static void add_lj_cb (GtkWidget *i, ManagerUI *mui) {
416 JamHost *h;
417 LJServer *s;
418 s = lj_server_new("http://hostname:port");
419 h = JAM_HOST(jam_host_lj_new(s));
420 h->name = g_strdup("New LiveJournal Server");
421 conf.hosts = g_slist_append(conf.hosts, h);
422 gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(GTK_TREE_VIEW(mui->treeview)));
423 add_host(mui, h);
427 static void edit_cb (GtkWidget *b, ManagerUI *mui) {
428 GtkTreeSelection *ts;
429 GtkTreeModel *model;
430 GtkTreeIter iter;
431 gboolean ishost;
432 gpointer data;
433 ts = gtk_tree_view_get_selection(GTK_TREE_VIEW(mui->treeview));
434 if (!gtk_tree_selection_get_selected(ts, &model, &iter)) return;
435 gtk_tree_model_get(model, &iter, COL_ISHOST, &ishost, COL_DATA, &data, -1);
436 mui->selection = data;
437 if (ishost) {
438 host_edit(mui, &iter, JAM_HOST(data));
439 } else {
440 account_edit(mui, &iter, JAM_ACCOUNT(data));
445 static void del_cb (GtkWidget *b, ManagerUI *mui) {
446 GtkTreeSelection *ts;
447 GtkTreeModel *model;
448 GtkTreeIter iter;
449 gboolean ishost;
450 gpointer data;
451 ts = gtk_tree_view_get_selection(GTK_TREE_VIEW(mui->treeview));
452 if (!gtk_tree_selection_get_selected(ts, &model, &iter)) return;
453 gtk_tree_model_get(model, &iter, COL_ISHOST, &ishost, COL_DATA, &data, -1);
454 /* XXX evan this logic is weird; we don't want to delete the "current"
455 * stuff, but there is no real "current" anymore. maybe we should
456 * just unref it?
457 if (mui->selection == c_cur_server() || mui->selection == c_cur_user()) {
458 return;
461 if (ishost) {
462 host_del(mui, &iter, JAM_HOST(data));
463 } else {
464 account_del(mui, &iter, JAM_ACCOUNT(data));
469 static GtkWidget *make_tree (ManagerUI *mui) {
470 GtkCellRenderer *renderer;
471 GtkTreeViewColumn *column;
472 GtkTreeSelection *sel;
473 GtkWidget *sw;
475 mui->treeview = gtk_tree_view_new();
476 gtk_tree_view_set_model(GTK_TREE_VIEW(mui->treeview), GTK_TREE_MODEL(make_model(mui)));
477 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(mui->treeview));
478 g_signal_connect(G_OBJECT(sel), "changed", G_CALLBACK(selection_changed_cb), mui);
479 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(mui->treeview), FALSE);
481 column = gtk_tree_view_column_new();
482 gtk_tree_view_column_set_title(column, "Node");
484 renderer = gtk_cell_renderer_pixbuf_new();
485 gtk_tree_view_column_pack_start(column, renderer, FALSE);
486 gtk_tree_view_column_add_attribute(column, renderer, "pixbuf", COL_ICON);
488 renderer = gtk_cell_renderer_text_new();
489 gtk_tree_view_column_pack_start(column, renderer, FALSE);
490 gtk_tree_view_column_add_attribute(column, renderer, "text", COL_TEXT);
492 gtk_tree_view_append_column(GTK_TREE_VIEW(mui->treeview), column);
494 sw = scroll_wrap(mui->treeview);
495 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
496 return sw;
500 void manager_dialog (GtkWindow *parent) {
501 STACK(ManagerUI, mui);
502 GtkWidget *mainbox, *bbox;
504 mui->win = gtk_dialog_new_with_buttons(_("Servers"), parent, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL);
505 gtk_window_set_default_size(GTK_WINDOW(mui->win), 300, 200);
506 geometry_tie(mui->win, GEOM_MANAGER);
508 mainbox = gtk_hbox_new(FALSE, 6);
510 gtk_box_pack_start(GTK_BOX(mainbox), make_tree(mui), TRUE, TRUE, 0);
512 bbox = gtk_vbox_new(FALSE, 6);
513 mui->add = gtk_button_new_from_stock(GTK_STOCK_ADD);
514 g_signal_connect(G_OBJECT(mui->add), "clicked", G_CALLBACK(add_lj_cb), mui);
515 gtk_box_pack_start(GTK_BOX(bbox), mui->add, FALSE, FALSE, 0);
516 mui->edit = gtk_button_new_from_stock(GTK_STOCK_PROPERTIES);
517 g_signal_connect(G_OBJECT(mui->edit), "clicked", G_CALLBACK(edit_cb), mui);
518 gtk_box_pack_start(GTK_BOX(bbox), mui->edit, FALSE, FALSE, 0);
519 mui->del = gtk_button_new_from_stock(GTK_STOCK_REMOVE);
520 g_signal_connect(G_OBJECT(mui->del), "clicked", G_CALLBACK(del_cb), mui);
521 gtk_box_pack_start(GTK_BOX(bbox), mui->del, FALSE, FALSE, 0);
523 gtk_box_pack_start(GTK_BOX(mainbox), bbox, FALSE, FALSE, 0);
525 jam_dialog_set_contents(GTK_DIALOG(mui->win), mainbox);
527 gtk_dialog_run(GTK_DIALOG(mui->win));
529 conf_verify_a_host_exists();
531 gtk_widget_destroy(mui->win);
532 g_slist_foreach(mui->pixbuf_names, (GFunc) g_free, NULL);
533 g_slist_free(mui->pixbuf_names);
534 g_slist_foreach(mui->pixbufs, (GFunc) g_object_unref, NULL);
535 g_slist_free(mui->pixbufs);