fixed GTKHTML detection
[k8lowj.git] / src / jam.c
blob89e78c45934acaa29b6fd208ddf7424435252a99
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 <stdio.h>
10 #include <time.h>
11 #include <stdlib.h>
12 #include <sys/types.h>
13 #include <sys/stat.h>
14 #include <unistd.h>
16 #ifndef G_OS_WIN32
17 #include <unistd.h> /* unlink */
18 #else
19 #include <io.h>
20 #endif /* G_OS_WIN32 */
22 #ifdef USE_DASHBOARD
23 #include "dashboard-frontend.h"
24 #endif
26 #include "network.h"
27 #include "conf.h"
28 #include "util-gtk.h"
29 #include "login.h"
30 #include "jamdoc.h"
31 #include "jamview.h"
32 #include "jam.h"
33 #include "menu.h"
34 #include "security.h"
35 #include "icons.h"
36 #include "checkfriends.h"
37 #include "draftstore.h"
38 #include "remote.h"
39 #include "usejournal.h"
40 #include "userlabel.h"
41 #include "sync.h"
43 #undef USE_STRUCTUREDTEXT
45 #ifdef USE_STRUCTUREDTEXT
46 #include "structuredtext.h"
47 #endif
49 static void jam_update_title(JamWin *jw);
50 static void jam_autosave_delete();
51 static void jam_new_doc(JamWin *jw, JamDoc *doc);
52 static void jam_update_actions(JamWin *jw);
54 static const int JAM_AUTOSAVE_INTERVAL = 5*1000;
56 gboolean
57 jam_confirm_lose_entry(JamWin *jw) {
58 GtkWidget *dlg;
59 int ret;
61 if (!jam_doc_get_dirty(jw->doc))
62 return TRUE;
64 dlg = gtk_message_dialog_new(GTK_WINDOW(jw),
65 GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION,
66 GTK_BUTTONS_NONE,
67 _("Your entry has unsaved changes. Save them now?"));
68 gtk_dialog_add_buttons(GTK_DIALOG(dlg),
69 _("Don't Save"), GTK_RESPONSE_NO,
70 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
71 GTK_STOCK_SAVE, GTK_RESPONSE_YES,
72 NULL);
73 gtk_dialog_set_default_response(GTK_DIALOG(dlg), GTK_RESPONSE_YES);
74 ask:
75 ret = gtk_dialog_run(GTK_DIALOG(dlg));
77 switch (ret) {
78 case GTK_RESPONSE_NO: /* don't save */
79 ret = TRUE;
80 break;
81 case GTK_RESPONSE_YES: /* save */
82 if (!jam_save(jw))
83 goto ask;
84 ret = TRUE;
85 break;
86 case GTK_RESPONSE_CANCEL: /* cancel */
87 default:
88 ret = FALSE;
89 break;
91 gtk_widget_destroy(dlg);
92 return ret;
95 gboolean
96 jam_save_as_file(JamWin *jw) {
97 GtkWidget *filesel;
98 GError *err = NULL;
99 gboolean ret = FALSE;
101 filesel = gtk_file_chooser_dialog_new(_("Save Entry As"), GTK_WINDOW(jw),
102 GTK_FILE_CHOOSER_ACTION_SAVE,
103 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
104 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
105 NULL);
107 while (gtk_dialog_run(GTK_DIALOG(filesel)) == GTK_RESPONSE_ACCEPT) {
108 const gchar *filename;
109 struct stat sbuf;
110 filename = gtk_file_chooser_get_filename(
111 GTK_FILE_CHOOSER(filesel));
112 if (stat(filename, &sbuf) == 0) {
113 if (!jam_confirm(GTK_WINDOW(filesel), _("Save Entry As"),
114 _("File already exists! Overwrite?")))
115 continue;
117 if (!jam_doc_save_as_file(jw->doc, filename, &err)) {
118 jam_warning(GTK_WINDOW(filesel), err->message);
119 g_error_free(err);
120 } else {
121 /* save succeeded. */
122 ret = TRUE;
123 break;
127 gtk_widget_destroy(filesel);
128 return ret;
131 static char*
132 prompt_draftname(GtkWindow *parent, const char *basename) {
133 GtkWidget *dlg, *box, *entry;
135 dlg = gtk_dialog_new_with_buttons(_("New Draft Title"),
136 parent, GTK_DIALOG_MODAL,
137 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
138 GTK_STOCK_OK, GTK_RESPONSE_OK,
139 NULL);
140 gtk_window_set_transient_for(GTK_WINDOW(dlg), parent);
141 gtk_dialog_set_default_response(GTK_DIALOG(dlg), GTK_RESPONSE_OK);
143 entry = gtk_entry_new();
144 if (basename)
145 gtk_entry_set_text(GTK_ENTRY(entry), basename);
146 gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
147 box = labelled_box_new(_("_Draft Title:"), entry);
149 jam_dialog_set_contents(GTK_DIALOG(dlg), box);
150 gtk_widget_grab_focus(entry);
152 if (gtk_dialog_run(GTK_DIALOG(dlg)) == GTK_RESPONSE_OK) {
153 char *text;
154 text = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1);
155 gtk_widget_destroy(dlg);
156 return text;
158 gtk_widget_destroy(dlg);
159 return NULL;
162 gboolean
163 jam_save_as_draft(JamWin *jw) {
164 GError *err = NULL;
165 char *sugg, *title;
166 gboolean ret;
168 sugg = jam_doc_get_draftname(jw->doc);
169 title = prompt_draftname(GTK_WINDOW(jw), sugg);
170 g_free(sugg);
171 if (!title)
172 return FALSE;
174 if (!jam_doc_save_as_draft(jw->doc, title, jw->account, &err)) {
175 jam_warning(GTK_WINDOW(jw), _("Error saving draft: %s."), err->message);
176 g_error_free(err);
177 ret = FALSE;
178 } else {
179 ret = TRUE;
181 g_free(title);
182 jam_update_actions(jw);
183 return ret;
186 gboolean
187 jam_save(JamWin *jw) {
188 GError *err = NULL;
190 if (jam_doc_has_save_target(jw->doc)) {
191 if (jam_doc_would_save_over_nonxml(jw->doc)) {
192 if (!jam_confirm(GTK_WINDOW(jw), _("Saving File"),
193 _("Current file was imported from an non-XML file. "
194 "Saving to this file will overwrite the file with XML content. "
195 "Overwrite the file?"))) {
196 return FALSE;
200 if (!jam_doc_save(jw->doc, jw->account, &err)) {
201 jam_warning(GTK_WINDOW(jw), _("Error saving: %s."), err->message);
202 g_error_free(err);
203 return FALSE;
205 } else {
206 return jam_save_as_file(jw);
208 return TRUE;
212 void
213 jam_clear_entry(JamWin *jw) {
214 JamDoc *doc = jw->doc;
215 JamDoc *newdoc;
216 JamAccount *acc;
218 acc = jam_doc_get_account(doc);
219 g_object_ref(acc); /* keep the JamAccount around... */
220 g_object_unref(G_OBJECT(jw->doc));
221 newdoc = jam_doc_new();
222 jam_doc_set_account(newdoc, acc);
223 g_object_unref(acc); /* ...but don't hang on to it */
225 jam_new_doc(jw, newdoc);
228 static void
229 jam_update_actions(JamWin *jw) {
230 int flags;
232 flags = jam_doc_get_flags(jw->doc);
234 if (flags & LOGJAM_DOC_CAN_SAVE) {
235 gtk_button_set_label(GTK_BUTTON(jw->baction), GTK_STOCK_SAVE);
236 } else if (flags & LOGJAM_DOC_CAN_SUBMIT) {
237 gtk_button_set_label(GTK_BUTTON(jw->baction), "logjam-submit");
238 gtk_widget_hide(jw->msaveserver);
241 jam_widget_set_visible(jw->bdelete, flags & LOGJAM_DOC_CAN_DELETE);
243 jam_widget_set_visible(jw->msaveserver, flags & LOGJAM_DOC_CAN_SAVE);
246 void
247 entry_changed_cb(JamDoc *doc, JamWin *jw) {
248 jam_update_title(jw);
249 jam_update_actions(jw);
252 static void
253 jam_update_title(JamWin *jw) {
254 char *doctitle, *title;
255 doctitle = jam_doc_get_title(jw->doc);
256 title = g_strdup_printf(_("LogJam - %s%s"), doctitle,
257 jam_doc_get_dirty(jw->doc) ? "*" : "");
258 gtk_window_set_title(GTK_WINDOW(jw), title);
259 g_free(title);
260 g_free(doctitle);
263 static void
264 dirty_changed_cb(JamDoc *doc, gpointer foo, JamWin *jw) {
265 jam_update_title(jw);
268 static void
269 usejournal_changed_cb(JamWin *jw) {
270 jam_user_label_set_journal(JAM_USER_LABEL(jw->userlabel),
271 jam_doc_get_usejournal(jw->doc));
274 static void
275 can_undo_cb(UndoMgr *um, gboolean can_undo, JamWin *jw) {
276 gtk_widget_set_sensitive(jw->mundo, can_undo);
279 static void
280 can_redo_cb(UndoMgr *um, gboolean can_redo, JamWin *jw) {
281 gtk_widget_set_sensitive(jw->mredo, can_redo);
284 static void
285 jam_new_doc(JamWin *jw, JamDoc *doc) {
286 jw->doc = doc;
287 g_signal_connect(G_OBJECT(jw->doc), "notify::dirty",
288 G_CALLBACK(dirty_changed_cb), jw);
289 g_signal_connect_swapped(G_OBJECT(jw->doc), "notify::usejournal",
290 G_CALLBACK(usejournal_changed_cb), jw);
291 g_signal_connect(G_OBJECT(jw->doc), "entry_changed",
292 G_CALLBACK(entry_changed_cb), jw);
293 jam_update_title(jw);
295 /* if the ui is already up, we need to let everything
296 * know the doc has changed. */
297 if (jw->view) {
298 jam_view_set_doc(JAM_VIEW(jw->view), jw->doc);
299 menu_new_doc(jw);
300 jam_update_actions(jw);
301 usejournal_changed_cb(jw);
305 void
306 jam_save_entry_server(JamWin *jw) {
307 JamAccount *acc = jam_doc_get_account(jw->doc);
308 NetContext *ctx;
309 ctx = net_ctx_gtk_new(GTK_WINDOW(jw), NULL);
310 if (jam_host_do_edit(jam_account_get_host(acc), ctx, jw->doc, NULL)) {
311 jam_clear_entry(jw);
312 jam_autosave_delete();
314 net_ctx_gtk_free(ctx);
317 static void
318 delete_draft(JamWin *jw) {
319 DraftStore *ds;
320 GError *err = NULL;
322 ds = draft_store_new(jw->account);
324 if (!draft_store_remove_entry(ds,
325 jam_doc_get_entry_itemid(jw->doc), &err)) {
326 jam_warning(GTK_WINDOW(jw), err->message);
327 g_error_free(err);
328 } else {
329 jam_clear_entry(jw);
332 draft_store_free(ds);
335 ///////////////////////////////////////////////////////////////////////////////
337 typedef struct tsCPostItem tCPostItem;
338 struct tsCPostItem {
339 char *fromUser;
340 char *fromServer;
341 char *fromUserServer;
342 char *toUser;
343 char *toServer;
344 char *toUserServer;
345 char *toHtml;
346 tCPostItem *prev;
350 //ketmar@ketmar's lj server
351 static int parseUserServer (const char *e, char **fromUser, char **fromServer, char **fromUserServer) {
352 const char *dp = strchr(e, '@');
353 if (!dp) return 0;
354 *fromUser = calloc(strlen(e)+8, 1);
355 *fromServer = calloc(strlen(e)+8, 1);
356 *fromUserServer = calloc(strlen(e)+8, 1);
357 strcpy(*fromUserServer, e);
358 strcpy(*fromServer, dp+1);
359 strcpy(*fromUser, e);
360 char *xe = strchr(*fromUser, '@');
361 if (xe) *xe = '\0';
362 return 1;
366 char *setUrl (const char *str, const char *url) {
367 char *res = calloc(8192, 1); // k8:FIXME!
368 const char *p = str; char *d = res;
369 while (*p) {
370 if (p[0] == '%' && p[1] == 'u' && p[2] == 'r' && p[3] == 'l' && p[4] == '%') {
371 strcpy(d, url);
372 d += strlen(d);
373 p += 5;
374 } else *d++ = *p++;
376 return res;
380 static void freeCPostConfig (tCPostItem *first) {
381 tCPostItem *prev;
382 while (first) {
383 prev = first->prev;
384 if (first->fromUser) free(first->fromUser);
385 if (first->fromServer) free(first->fromServer);
386 if (first->fromUserServer) free(first->fromUserServer);
387 if (first->toUser) free(first->toUser);
388 if (first->toServer) free(first->toServer);
389 if (first->toUserServer) free(first->toUserServer);
390 if (first->toHtml) free(first->toHtml);
391 free(first);
392 first = prev;
397 static tCPostItem *readCPostConfig (const char *url) {
398 char *text, *ht, *ep;
399 gsize len;
400 GError *err = NULL;
401 char buf[1024], home[1024], *e;
402 tCPostItem *cur = NULL, *first = NULL;
404 e = getenv("HOME");
405 if (!e) {
406 fprintf(stderr, "shit!\n");
407 return NULL;
409 strcpy(buf, e);
410 if (buf[strlen(buf)-1] != '/') strcat(buf, "/");
411 sprintf(home, "%s.logjam/", buf);
413 strcpy(buf, home);
414 strcat(buf, "crosspost.txt");
415 if (!g_file_get_contents(buf, &text, &len, &err)) {
416 fprintf(stderr, "shit!\n");
417 fprintf(stderr, "ERROR: [%s]\n", err->message);
418 g_error_free(err);
419 return NULL;
422 const gchar *end;
423 if (!g_utf8_validate(text, len, &end)) {
424 //g_set_error(err, 0, 0, "Invalid UTF-8 starting at byte %d.", end-text);
425 g_free(text);
426 return NULL;
429 //fprintf(stderr, "[%s]\n", text);
430 //ketmar@ketmar's lj server>ketmar@lj.rossia.org|crosspost_footer.html
431 e = text;
432 while ((ep = strchr(e, '\n'))) {
433 *ep = '\0';
434 while (*e && (unsigned char)(*e) <= ' ') e++;
435 //fprintf(stderr, "[%s]\n", text);
436 if (*e == '#' || !*e) goto gook;
437 char *d = strchr(e, '>');
438 if (!d) goto gook;
439 *d = '\0';
440 if (!cur) cur = calloc(sizeof(tCPostItem), 1); else memset(cur, 0, sizeof(tCPostItem));
441 if (!parseUserServer(e, &cur->fromUser, &cur->fromServer, &cur->fromUserServer)) goto goon;
442 fprintf(stderr, "user: [%s]\nserver: [%s]\nuserserver: [%s]\n", cur->fromUser, cur->fromServer, cur->fromUserServer);
443 e = d+1;
444 d = strchr(e, '|');
445 if (!d) goto goon;
446 *d = '\0';
447 if (!parseUserServer(e, &cur->toUser, &cur->toServer, &cur->toUserServer)) goto goon;
448 fprintf(stderr, "duser: [%s]\ndserver: [%s]\nduserserver: [%s]\n", cur->toUser, cur->toServer, cur->toUserServer);
450 e = d+1;
451 if (!*e) goto goon;
452 sprintf(buf, "%s%s", home, e);
453 if (!g_file_get_contents(buf, &ht, &len, &err)) {
454 fprintf(stderr, " ERROR: [%s]\n", err->message);
455 g_error_free(err);
456 goto goon;
458 if (!g_utf8_validate(ht, len, &end)) {
459 //g_set_error(err, 0, 0, "Invalid UTF-8 starting at byte %d.", end-text);
460 g_free(ht);
461 goto goon;
463 cur->toHtml = calloc(strlen(ht)+1, 1);
464 strcpy(cur->toHtml, ht);
465 g_free(ht);
466 fprintf(stderr, "html: [%s]\n", cur->toHtml);
468 char *ctmp = setUrl(cur->toHtml, url);
469 fprintf(stderr, "2html: [%s]\n", ctmp);
470 free(cur->toHtml);
471 cur->toHtml = ctmp;
473 cur->prev = first; first = cur; cur = NULL;
474 goto gook;
476 goon:
477 if (cur->fromUser) free(cur->fromUser);
478 if (cur->fromServer) free(cur->fromServer);
479 if (cur->fromUserServer) free(cur->fromUserServer);
480 if (cur->toUser) free(cur->toUser);
481 if (cur->toServer) free(cur->toServer);
482 if (cur->toUserServer) free(cur->toUserServer);
483 if (cur->toHtml) free(cur->toHtml);
484 gook:
485 e = ep+1;
487 if (cur) free(cur);
489 g_free(text);
491 return first;
496 ///////////////////////////////////////////////////////////////////////////////
497 void jam_submit_entry (JamWin *jw) {
498 //GError *err = NULL;
499 //gsize len;
500 JamAccount *acc = jam_doc_get_account(jw->doc);
501 NetContext *ctx;
502 ctx = net_ctx_gtk_new(GTK_WINDOW(jw), NULL);
503 if (jam_host_do_post(jam_account_get_host(acc), ctx, jw->doc, NULL)) {
504 sync_run(JAM_ACCOUNT_LJ(jw->account), GTK_WINDOW(jw));
505 /* k8: FIXME */
506 #ifdef HAVE_GTK
507 /* crossposting code was fucked and it's disabled for now */
508 #if 0
509 const char *url = jam_doc_get_url(jw->doc);
510 if (!url) goto done;
511 tCPostItem *first = readCPostConfig(url), *cur;
512 cur = first;
513 while (cur) {
514 if (strcmp(jam_account_get_host(acc)->name, cur->fromServer) ||
515 strcmp(jam_account_get_username(acc), cur->fromUser)) goto cont;
516 char buf[1024];
517 sprintf(buf, "Do crossposting to %s?", cur->toUserServer);
518 if (!jam_confirm(GTK_WINDOW(jw), "Crossposting", buf)) goto cont;
519 /* find dest server */
520 GSList *l;
521 JamHost *h = NULL;
522 for (l = conf.hosts; l != NULL; l = l->next) {
523 h = l->data;
524 if (!strcmp(h->name, cur->toServer)) {
525 break;
527 h = NULL;
529 if (!h) goto cont;
530 /* get it's account */
531 JamAccount *ljracc = jam_host_get_account_by_username(h, cur->toUser, TRUE);
532 if (!ljracc) goto cont;
533 /* crosspost there */
534 jam_doc_set_account(jw->doc, ljracc);
535 /* insert "crossposted" */
536 GtkTextIter spos, epos;
537 GtkTextBuffer *xbuf = jam_doc_get_text_buffer(jw->doc);
538 gtk_text_buffer_get_end_iter(xbuf, &spos);
539 gtk_text_buffer_insert(xbuf, &spos, cur->toHtml, -1);
540 if (jam_host_do_post(jam_account_get_host(acc), ctx, jw->doc, NULL)) {
541 sync_run(ljracc, GTK_WINDOW(jw));
542 /*url = jam_doc_get_url(jw->doc);*/
543 /*fprintf(stderr, "ljr url=[%s]\n", url);*/
544 } else fprintf(stderr, "can't crosspost to %s!\n", cur->toUserServer);
545 gtk_text_buffer_get_end_iter(xbuf, &epos);
546 gtk_text_buffer_delete(xbuf, &spos, &epos);
547 /* restore account */
548 jam_doc_set_account(jw->doc, acc);
549 cont:
550 cur = cur->prev;
552 freeCPostConfig(first);
553 /* k8 */
554 done: ;
555 #endif
556 #endif
557 gint type = jam_doc_get_entry_type(jw->doc);
558 if (type == ENTRY_DRAFT) {
559 if (jam_confirm(GTK_WINDOW(jw), _("Delete"), _("Delete this draft from disk?"))) delete_draft(jw);
561 jam_clear_entry(jw);
562 if (conf.options.revertusejournal) jam_doc_set_usejournal(jw->doc, NULL);
563 jam_autosave_delete();
565 net_ctx_gtk_free(ctx);
566 jam_doc_reset_url(jw->doc);
570 #if 0
571 void jam_submit_entryX2 (JamWin *jw) {
572 JamAccount *acc = jam_doc_get_account(jw->doc);
573 NetContext *ctx;
574 ctx = net_ctx_gtk_new(GTK_WINDOW(jw), NULL);
575 if (jam_host_do_post(jam_account_get_host(acc), ctx, jw->doc, NULL)) {
576 /* k8: FIXME */
577 if (!strcmp(jam_account_get_host(acc)->name, "ketmar's lj server") &&
578 !strcmp(jam_account_get_username(acc), "ketmar")) {
579 while (jam_confirm(GTK_WINDOW(jw), "Crossposting", "Do crossposting to lj.rossia.org?")) {
580 /* find lj.rossia.org host */
581 GSList *l;
582 JamHost *h = NULL;
583 for (l = conf.hosts; l != NULL; l = l->next) {
584 h = l->data;
585 if (!strcmp(h->name, "lj.rossia.org")) break;
586 h = NULL;
588 if (h) {
589 /* get it's account */
590 JamAccount *ljracc = jam_host_get_account_by_username(h, "ketmar", TRUE);
591 if (ljracc) {
592 /* crosspost there */
593 jam_doc_set_account(jw->doc, ljracc);
594 /* insert "crossposted" */
595 #ifdef HAVE_GTK
596 #define CROSSPOST_FOOTER_FILE "~/.logjam/crosspost_footer.html"
597 #define CROSSPOST_FOOTER_FILE_ENCODING "KOI8-U"
598 /*GError *err = NULL;*/
599 const char *url = jam_doc_get_url(jw->doc);
600 /*if (!jam_doc_insert_file(jw->doc, CROSSPOST_FOOTER_FILE, CROSSPOST_FOOTER_FILE_ENCODING, &err)) {
601 jam_warning(GTK_WINDOW(jw), _("Error loading file: %s"), CROSSPOST_FOOTER_FILE);
602 g_error_free(err);
604 if (url) {
605 /*fprintf(stderr, "url=[%s]\n", url);*/
606 jam_doc_append_text(jw->doc, "\n<p align='right'><small>crossposted from <a href='", "UTF-8");
607 jam_doc_append_text(jw->doc, url, "UTF-8");
608 jam_doc_append_text(jw->doc, "'>Vivisector's Home</a></small></p>", "UTF-8");
610 #endif
611 if (jam_host_do_post(jam_account_get_host(acc), ctx, jw->doc, NULL)) {
612 sync_run(ljracc, GTK_WINDOW(jw));
613 url = jam_doc_get_url(jw->doc);
614 /*fprintf(stderr, "ljr url=[%s]\n", url);*/
615 } else fprintf(stderr, "can't crosspost to lj.rossia.org!\n");
616 /* restore account */
617 jam_doc_set_account(jw->doc, acc);
618 } else fprintf(stderr, "can't find account on lj.rossia.org!\n");
619 } else fprintf(stderr, "can't find lj.rossia.org!\n");
620 break;
623 /* k8 */
624 gint type = jam_doc_get_entry_type(jw->doc);
625 if (type == ENTRY_DRAFT) {
626 if (jam_confirm(GTK_WINDOW(jw),
627 _("Delete"), _("Delete this draft from disk?")))
628 delete_draft(jw);
630 jam_clear_entry(jw);
631 if (conf.options.revertusejournal)
632 jam_doc_set_usejournal(jw->doc, NULL);
633 jam_autosave_delete();
635 sync_run(acc, GTK_WINDOW(jw));
637 net_ctx_gtk_free(ctx);
638 jam_doc_reset_url(jw->doc);
640 #endif
641 ////////////////////////////////////////////////////////////////////////////////
644 static void
645 action_cb(GtkWidget *w, JamWin *jw) {
646 int flags = jam_doc_get_flags(jw->doc);
647 if (flags & LOGJAM_DOC_CAN_SAVE) {
648 jam_save_entry_server(jw);
649 } else if (flags & LOGJAM_DOC_CAN_SUBMIT) {
650 jam_submit_entry(jw);
651 } else {
652 g_warning("action callback, but no default action?\n");
656 static void
657 delete_server_entry(JamWin *jw) {
658 /*fprintf(stderr, "id: %i\n", jam_doc_get_entry_itemid(jw->doc));
659 return;*/
660 JamAccount *acc = jam_doc_get_account(jw->doc);
661 NetContext *ctx;
662 ctx = net_ctx_gtk_new(GTK_WINDOW(jw), NULL);
663 if (jam_host_do_delete(jam_account_get_host(acc), ctx, jw->doc, NULL)) {
664 jam_clear_entry(jw);
665 jam_autosave_delete();
667 net_ctx_gtk_free(ctx);
670 static void
671 delete_cb(GtkWidget *w, JamWin *jw) {
672 gint type = jam_doc_get_entry_type(jw->doc); /* defer making a copy */
673 const gchar *confirm_text = type == ENTRY_DRAFT ?
674 _("Delete this draft from disk?") :
675 _("Delete this entry from server?");
677 g_assert(type != ENTRY_NEW);
679 if (!jam_confirm(GTK_WINDOW(jw),
680 _("Delete"), confirm_text))
681 return;
683 if (type == ENTRY_DRAFT) {
684 delete_draft(jw);
685 } else {
686 delete_server_entry(jw);
690 static void
691 jam_autosave_delete(void) {
692 char *path;
693 path = g_build_filename(app.conf_dir, "draft", NULL);
694 if (unlink(path) < 0) {
695 /* FIXME handle error. */
697 g_free(path);
700 static void
701 update_userlabel(JamWin *jw) {
702 jam_user_label_set_account(JAM_USER_LABEL(jw->userlabel),
703 jw->account);
704 jam_user_label_set_journal(JAM_USER_LABEL(jw->userlabel),
705 jam_doc_get_usejournal(jw->doc));
708 /* implicitly works on the *current* document! */
709 static void
710 changeuser(JamWin *jw, JamAccount *acc) {
711 JamDoc *doc = jw->doc;
713 jam_doc_set_account(doc, acc);
714 jw->account = acc;
715 cfmgr_set_account(app.cfmgr, acc);
716 update_userlabel(jw);
718 /*fprintf(stderr, "user: [%s]\n", jam_account_get_username(acc));*/
721 void
722 jam_do_changeuser(JamWin *jw) {
723 JamAccount *acc = login_dlg_run(GTK_WINDOW(jw), NULL, jw->account);
725 if (acc) {
726 /*fprintf(stderr, "acc: [%s]\n", jam_account_get_host(acc)->name);*/
727 changeuser(jw, acc);
731 static gboolean
732 jam_remote_change_user_cb(JamWin *jw, char *username, GError **err) {
733 if (username) {
734 // XXX evan -- we need user@host
735 /*JamAccount *acc;
736 acc = jam_host_get_account_by_username(
737 LJUser *u;
738 u = lj_server_get_user_by_username(jam_account_get_server(jw->account), username);
739 if (!u)
740 return FALSE;
742 changeuser(jw, jam_account_make(u));*/
744 gtk_window_present(GTK_WINDOW(jw));
745 return TRUE;
748 void
749 jam_save_autosave(JamWin *jw) {
750 LJEntry *entry;
751 char *path;
753 path = g_build_filename(app.conf_dir, "draft", NULL);
754 entry = jam_doc_get_entry(jw->doc);
755 if (!lj_entry_to_xml_file(entry, path, NULL)) {
756 /* FIXME handle error. */
758 lj_entry_free(entry);
759 g_free(path);
762 gboolean
763 jam_save_autosave_cb(JamWin *jw) {
764 if (!jam_doc_get_dirty(jw->doc))
765 return TRUE;
767 jam_save_autosave(jw);
769 return TRUE; /* perpetuate timeout */
772 void
773 jam_open_entry(JamWin *jw) {
774 GtkWidget *filesel;
775 GtkWidget *hbox, *filetype;
776 GtkWidget *menu, *item;
777 static struct {
778 char *label;
779 LJEntryFileType type;
780 } filetypes[] = {
781 { N_("Autodetect"), LJ_ENTRY_FILE_AUTODETECT },
782 { N_("XML"), LJ_ENTRY_FILE_XML },
783 { N_("RFC822-Style"), LJ_ENTRY_FILE_RFC822 },
784 { N_("Plain Text"), LJ_ENTRY_FILE_PLAIN },
785 { NULL },
786 }, *ft;
788 if (!jam_confirm_lose_entry(jw)) return;
790 filesel = gtk_file_chooser_dialog_new(_("Open Entry"), GTK_WINDOW(jw),
791 GTK_FILE_CHOOSER_ACTION_OPEN,
792 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
793 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
794 NULL);
796 filetype = gtk_option_menu_new();
797 menu = gtk_menu_new();
798 for (ft = filetypes; ft->label; ft++) {
799 item = gtk_menu_item_new_with_label(_(ft->label));
800 gtk_widget_show(item);
801 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
803 gtk_option_menu_set_menu(GTK_OPTION_MENU(filetype), menu);
805 hbox = gtk_hbox_new(FALSE, 5);
806 gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(_("File Type:")),
807 FALSE, FALSE, 0);
808 gtk_box_pack_start(GTK_BOX(hbox), filetype, TRUE, TRUE, 0);
809 gtk_widget_show_all(hbox);
811 gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(filesel), hbox);
813 while (gtk_dialog_run(GTK_DIALOG(filesel)) == GTK_RESPONSE_ACCEPT) {
814 const gchar *filename;
815 GError *err = NULL;
816 int op = gtk_option_menu_get_history(GTK_OPTION_MENU(filetype));
817 LJEntryFileType type = filetypes[op].type;
819 filename = gtk_file_chooser_get_filename(
820 GTK_FILE_CHOOSER(filesel));
822 if (!jam_doc_load_file(jw->doc, filename, type, &err)) {
823 jam_warning(GTK_WINDOW(filesel), err->message);
824 g_error_free(err);
825 } else {
826 undomgr_reset(UNDOMGR(jam_view_get_undomgr(JAM_VIEW(jw->view))));
827 break;
830 gtk_widget_destroy(filesel);
833 void
834 jam_open_draft(JamWin *jw) {
835 DraftStore *ds;
836 LJEntry *e;
838 if (!jam_confirm_lose_entry(jw)) return;
840 ds = draft_store_new(jw->account);
842 e = draft_store_ui_select(ds, GTK_WINDOW(jw));
843 if (e) {
844 jam_doc_load_draft(jw->doc, e);
845 lj_entry_free(e);
847 undomgr_reset(UNDOMGR(jam_view_get_undomgr(JAM_VIEW(jw->view))));
850 draft_store_free(ds);
853 #ifdef USE_STRUCTUREDTEXT
855 static void
856 textstyle_changed_cb(GtkOptionMenu *om, JamWin *jw) {
857 TextStyle newstyle = gtk_option_menu_get_history(om);
858 GError *err = NULL;
859 JamDoc *doc = jam_get_doc(jw);
861 if (newstyle == doc->textstyle)
862 return;
864 if (!jam_doc_change_textstyle(doc, newstyle, &err)) {
865 jam_message(GTK_WIDGET(jw), JAM_MSG_ERROR, FALSE,
866 _("Error Changing Text Format"), "%s", err->message);
867 g_error_free(err);
868 gtk_option_menu_set_history(om, doc->textstyle);
872 static void
873 make_textmods(JamWin *jw) {
874 GtkWidget *hbox;
875 GtkWidget *omenu, *menu, *item;
876 GtkRequisition req;
878 omenu = gtk_option_menu_new();
879 menu = gtk_menu_new();
880 item = gtk_menu_item_new_with_label(_("Normal"));
881 gtk_widget_show(item);
882 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
884 item = gtk_menu_item_new_with_label(_("Structured"));
885 gtk_widget_show(item);
886 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
888 /*item = gtk_menu_item_new_with_label("Formatted");
889 gtk_widget_set_sensitive(item, FALSE);
890 gtk_widget_show(item);
891 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);*/
893 gtk_option_menu_set_menu(GTK_OPTION_MENU(omenu), menu);
895 g_signal_connect(G_OBJECT(omenu), "changed",
896 G_CALLBACK(textstyle_changed_cb), jw);
898 hbox = gtk_hbox_new(FALSE, 5);
899 gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(_("Entry Format:")),
900 FALSE, FALSE, 0);
901 gtk_box_pack_start(GTK_BOX(hbox), omenu, FALSE, FALSE, 0);
903 gtk_widget_size_request(omenu, &req);
905 gtk_text_view_set_border_window_size(GTK_TEXT_VIEW(jw->eentry),
906 GTK_TEXT_WINDOW_TOP, req.height+10);
907 gtk_text_view_add_child_in_window(GTK_TEXT_VIEW(jw->eentry),
908 hbox, GTK_TEXT_WINDOW_TOP, 5, 5);
910 #endif /* USE_STRUCTUREDTEXT */
912 static GtkWidget*
913 make_main_view(JamWin *jw, JamView *view) {
914 UndoMgr *um = UNDOMGR(jam_view_get_undomgr(view));
916 g_signal_connect(G_OBJECT(um), "can-undo",
917 G_CALLBACK(can_undo_cb), jw);
918 g_signal_connect(G_OBJECT(um), "can-redo",
919 G_CALLBACK(can_redo_cb), jw);
921 return GTK_WIDGET(view);
924 static void
925 usejournal_cb(GtkWidget *w, JamWin *jw) {
926 GtkWidget *menu;
927 JamAccount *acc = jam_doc_get_account(jw->doc);
928 LJUser *u = jam_account_lj_get_user(JAM_ACCOUNT_LJ(acc));
930 menu = usejournal_build_menu(u->username,
931 jam_doc_get_usejournal(jw->doc),
932 u->usejournals,
933 jam_win_get_cur_doc(jw));
934 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
935 1, gtk_get_current_event_time());
938 static GtkWidget*
939 make_action_bar(JamWin *jw) {
940 GtkWidget *actionbox, *buttonbox;
942 actionbox = gtk_hbox_new(FALSE, 18);
944 /* XXX blogger jw->cfi_main = cfindicator_new(fetch_cfmgr(jw), CF_MAIN, GTK_WINDOW(jw));
945 cfmgr_set_state(fetch_cfmgr(jw), CF_DISABLED);
946 gtk_box_pack_start(GTK_BOX(jw->actionbox),
947 cfindicator_box_get(jw->cfi_main),
948 FALSE, FALSE, 0);
950 gtk_box_pack_start(GTK_BOX(jw->actionbox),
951 gtk_label_new(_("Current User:")),
952 FALSE, FALSE, 0);*/
954 jw->userlabel = jam_user_label_new();
955 g_signal_connect(G_OBJECT(jw->userlabel), "clicked",
956 G_CALLBACK(usejournal_cb), jw);
957 gtk_box_pack_start(GTK_BOX(actionbox), jw->userlabel, FALSE, FALSE, 0);
958 update_userlabel(jw);
960 buttonbox = gtk_hbox_new(FALSE, 6);
961 /*buttonbox = gtk_hbutton_box_new();
962 gtk_box_set_spacing(GTK_BOX(buttonbox), 6);*/
964 jw->bdelete = gtk_button_new_from_stock(GTK_STOCK_DELETE);
965 g_signal_connect(G_OBJECT(jw->bdelete), "clicked",
966 G_CALLBACK(delete_cb), jw);
967 gtk_box_pack_start(GTK_BOX(buttonbox), jw->bdelete, FALSE, FALSE, 0);
969 jw->baction = gtk_button_new_from_stock("logjam-submit");
970 g_signal_connect(G_OBJECT(jw->baction), "clicked",
971 G_CALLBACK(action_cb), jw);
972 gtk_box_pack_start(GTK_BOX(buttonbox), jw->baction, FALSE, FALSE, 0);
974 gtk_box_pack_end(GTK_BOX(actionbox), buttonbox, FALSE, FALSE, 0);
976 return actionbox;
979 static GtkWidget*
980 make_app_contents(JamWin *jw, JamDoc *doc) {
981 GtkWidget *vbox;
983 vbox = gtk_vbox_new(FALSE, 6);
984 gtk_container_set_border_width(GTK_CONTAINER(vbox), 6);
986 /* TODO: when we have multiple tabs, we'll create the notebook
987 * and pack the first view into it here */
989 gtk_box_pack_start(GTK_BOX(vbox),
990 make_main_view(jw, JAM_VIEW(jw->view)), TRUE, TRUE, 0);
991 gtk_box_pack_end(GTK_BOX(vbox),
992 make_action_bar(jw), FALSE, FALSE, 0);
994 gtk_widget_show_all(vbox);
995 return vbox;
998 void
999 jam_autosave_init(JamWin* jw) {
1000 if (app.autosave || !conf.options.autosave)
1001 return;
1003 app.autosave = g_timeout_add(JAM_AUTOSAVE_INTERVAL,
1004 (GSourceFunc)jam_save_autosave_cb, jw);
1007 void
1008 jam_autosave_stop(JamWin* jw) {
1009 if (!app.autosave)
1010 return;
1011 jam_autosave_delete();
1012 g_source_remove(app.autosave);
1013 app.autosave = 0;
1016 /* load a saved autosave if there is one and it's appropriate to do so */
1017 static LJEntry*
1018 jam_load_autosave(JamWin* jw) {
1019 if (conf.options.autosave) {
1020 GError *err = NULL;
1021 char *path;
1022 LJEntry *entry;
1024 path = g_build_filename(app.conf_dir, "draft", NULL);
1026 if (!g_file_test(path, G_FILE_TEST_EXISTS)) {
1027 g_free(path);
1028 return FALSE;
1031 entry = lj_entry_new_from_filename(path, LJ_ENTRY_FILE_XML, NULL, &err);
1032 if (entry == NULL) {
1033 jam_warning(GTK_WINDOW(jw), _("Error loading draft: %s."),
1034 err->message);
1035 g_error_free(err);
1037 g_free(path);
1038 return entry;
1040 return NULL; /* didn't read draft */
1043 void
1044 jam_quit(JamWin *jw) {
1045 if (jam_confirm_lose_entry(jw))
1046 gtk_main_quit();
1049 static gboolean
1050 delete_event_cb(JamWin *jw) {
1051 jam_quit(jw);
1052 return TRUE; /* don't ever let this delete the window; quit will do it. */
1055 /* gtk stuff */
1056 static GType
1057 jam_win_get_type(void) {
1058 static GType jw_type = 0;
1059 if (!jw_type) {
1060 const GTypeInfo jw_info = {
1061 sizeof (GtkWindowClass),
1062 NULL,
1063 NULL,
1064 NULL,
1065 NULL,
1066 NULL,
1067 sizeof (JamWin),
1069 //(GInstanceInitFunc) jam_win_init, /* no init func needed since */
1070 NULL, /* GTK_WINDOW_TOPLEVEL is the default GtkWindow:type */
1072 jw_type = g_type_register_static(GTK_TYPE_WINDOW,
1073 "JamWin", &jw_info, 0);
1075 return jw_type;
1078 #define JAM_WIN(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), jam_win_get_type(), JamWin))
1080 static GtkWidget*
1081 jam_win_new(void) {
1082 JamWin *jw = JAM_WIN(g_object_new(jam_win_get_type(), NULL));
1083 return GTK_WIDGET(jw);
1086 void docklet_setup(GtkWindow *win);
1088 void
1089 jam_run(JamDoc *doc) {
1090 GtkWidget *vbox;
1091 LJEntry *draftentry;
1092 JamWin *jw;
1094 jw = JAM_WIN(jam_win_new());
1095 gtk_window_set_default_size(GTK_WINDOW(jw), 400, 300);
1096 geometry_tie(GTK_WIDGET(jw), GEOM_MAIN);
1098 jam_new_doc(jw, doc);
1099 jw->view = jam_view_new(doc);
1100 jw->account = jam_doc_get_account(doc);
1102 g_signal_connect(G_OBJECT(jw), "delete-event",
1103 G_CALLBACK(delete_event_cb), NULL);
1104 g_signal_connect_swapped(G_OBJECT(app.remote), "present",
1105 G_CALLBACK(gtk_window_present), jw);
1106 g_signal_connect_swapped(G_OBJECT(app.remote), "change_user",
1107 G_CALLBACK(jam_remote_change_user_cb), jw);
1109 app.cfmgr = cfmgr_new(jw->account);
1111 vbox = gtk_vbox_new(FALSE, 0);
1113 gtk_box_pack_start(GTK_BOX(vbox),
1114 menu_make_bar(jw), FALSE, FALSE, 0);
1116 gtk_box_pack_start(GTK_BOX(vbox), make_app_contents(jw, doc),
1117 TRUE, TRUE, 0);
1119 gtk_container_add(GTK_CONTAINER(jw), vbox);
1121 jam_autosave_init(jw);
1123 gtk_widget_show(vbox);
1124 jam_update_actions(jw);
1126 gtk_widget_show(GTK_WIDGET(jw));
1128 /* suck a bunch of events in. */
1129 while (gtk_events_pending())
1130 gtk_main_iteration();
1132 if (!conf.options.allowmultipleinstances) {
1133 GError *err = NULL;
1134 if (!logjam_remote_listen(app.remote, &err)) {
1135 if (err) {
1136 jam_warning(GTK_WINDOW(jw),
1137 _("Error initializing remote command socket: %s."),
1138 err->message);
1139 g_error_free(err);
1144 #ifdef USE_DOCK
1145 if (conf.options.docklet)
1146 docklet_setup(GTK_WINDOW(jw));
1147 #endif
1149 draftentry = jam_load_autosave(jw);
1150 if (draftentry) {
1151 if (jam_confirm(GTK_WINDOW(jw), _("Autosaved Draft Found"),
1152 _("An autosaved draft was found, possibly from a previous run of LogJam that crashed. "
1153 "Would you like to recover it?"))) {
1154 jam_doc_load_entry(jw->doc, draftentry);
1156 lj_entry_free(draftentry);
1159 jam_new_doc(jw, doc);
1161 #ifdef USE_DOCK
1162 if (JAM_ACCOUNT_IS_LJ(jw->account))
1163 cf_update_dock(app.cfmgr, GTK_WINDOW(jw));
1164 #endif
1166 /* to prevent flicker, make this GtkWindow immediately after
1167 * pending events have been handled, and immediately handle
1168 its * own event afterwards. * * XXX: Should make_cf_float()
1169 have its own event-sucking loop? */
1170 cf_app_update_float();
1171 while (gtk_events_pending())
1172 gtk_main_iteration();
1174 gtk_main();
1176 jam_autosave_delete();
1179 JamDoc*
1180 jam_win_get_cur_doc(JamWin *jw) {
1181 return jw->doc;
1184 JamView*
1185 jam_win_get_cur_view(JamWin *jw) {
1186 return JAM_VIEW(jw->view);