'smart qotes' now will replace '<', '>' and '&' inside 'pre' and 'code' tags
[k8lowj.git] / src / jamview.c
blobc0a6a7a447ce4be64f3a288cd933ad33f652eb7f
1 /* logjam - a GTK client for LiveJournal.
2 * Copyright (C) 2000-2003 Gaal Yahas <gaal@forum2.org>
4 * vim: tabstop=4 shiftwidth=4 noexpandtab :
5 */
7 #include "gtk-all.h"
9 #ifdef HAVE_GTKSPELL
10 #include <gtkspell/gtkspell.h>
11 #endif /* HAVE_GTKSPELL */
12 #include "util-gtk.h"
13 #include "jamdoc.h"
14 #include "music.h"
15 #include "undo.h"
16 #include "jamview.h"
17 #include "security.h"
18 #include "marshalers.h"
19 #include "datesel.h"
21 #define KEY_PICTUREKEYWORD "logjam-picturekeyword"
23 enum {
24 META_TOGGLE,
25 LAST_SIGNAL
28 static guint signals[LAST_SIGNAL] = { 0 };
30 struct _JamView {
31 GtkVBox box;
33 GtkWidget *subjectbar;
34 GtkWidget *subject, *security;
36 GtkWidget *moodpicbar;
37 GtkWidget *moodbox, *moodbutton, *moodcombo;
38 GtkWidget *picbox, *pic;
40 GtkWidget *musicbar;
41 GtkWidget *musicbutton, *music;
43 GtkWidget *locationbar;
44 GtkWidget *location;
46 GtkWidget *tagsbar;
47 GtkWidget *tags;
49 GtkWidget *entry;
51 GtkWidget *optionbar;
52 GtkWidget *preformatted;
53 GtkWidget *datesel;
54 GtkWidget *commentsbox, *comments, *screeningbox, *screening;
56 GtkSizeGroup *sizegroup;
57 UndoMgr *undomgr;
58 JamAccount *account;
59 JamDoc *doc;
62 struct _JamViewClass {
63 GtkVBoxClass parent_class;
64 void (*meta_toggle)(JamView *view, JamViewMeta meta);
67 static void
68 show_meta(JamView *view, JamViewMeta meta) {
69 if (jam_view_get_meta_visible(view, meta))
70 return;
71 jam_view_toggle_meta(view, meta, TRUE);
72 g_signal_emit_by_name(view, "meta_toggle", meta, TRUE);
75 static void
76 subject_add(JamView *view) {
77 /* subject is always visible. */
78 view->subjectbar = gtk_hbox_new(FALSE, 12);
80 view->subject = gtk_entry_new();
81 gtk_box_pack_start(GTK_BOX(view->subjectbar),
82 labelled_box_new_sg(_("_Subject:"), view->subject, view->sizegroup),
83 TRUE, TRUE, 0);
85 gtk_box_pack_start(GTK_BOX(view), view->subjectbar, FALSE, FALSE, 0);
87 static void
88 subject_load(JamView *view) {
89 const char *subject = jam_doc_get_subject(view->doc);
90 gtk_entry_set_text(GTK_ENTRY(view->subject), subject ? subject : "");
92 static void
93 subject_store(JamView *view) {
94 const char *subject = gtk_entry_get_text(GTK_ENTRY(view->subject));
95 if (subject[0] == 0) subject = NULL;
96 jam_doc_set_subject(view->doc, subject);
99 static void
100 security_add(JamView *view) {
101 view->security = secmgr_new(TRUE);
102 secmgr_set_account(SECMGR(view->security), JAM_ACCOUNT_LJ(view->account));
103 gtk_widget_show(view->security);
104 gtk_box_pack_start(GTK_BOX(view->subjectbar), view->security, FALSE, FALSE, 0);
105 gtk_box_reorder_child(GTK_BOX(view->subjectbar), view->security, 1);
107 static void
108 security_remove(JamView *view) {
109 LJSecurity sec = {0};
110 jam_doc_set_security(view->doc, &sec);
111 gtk_widget_destroy(view->security);
112 view->security = NULL;
114 static gboolean
115 security_visible(JamView *view) {
116 return view->security != NULL;
118 static void
119 security_account_changed(JamView *view) {
120 /* security shouldn't even be around for non-lj accounts, so the downcast to
121 * account_lj is ok. */
122 secmgr_set_account(SECMGR(view->security), JAM_ACCOUNT_LJ(view->account));
124 static void
125 security_load(JamView *view) {
126 LJSecurity sec = jam_doc_get_security(view->doc);
127 if (sec.type != LJ_SECURITY_PUBLIC)
128 show_meta(view, JAM_VIEW_SECURITY);
129 if (security_visible(view))
130 secmgr_security_set_force(SECMGR(view->security), &sec);
132 static void
133 security_store(JamView *view) {
134 LJSecurity sec;
135 secmgr_security_get(SECMGR(view->security), &sec);
136 jam_doc_set_security(view->doc, &sec);
139 static void
140 datesel_add(JamView *view) {
141 view->datesel = datesel_new();
142 gtk_box_pack_start(GTK_BOX(view->subjectbar), view->datesel, FALSE, FALSE, 0);
143 gtk_widget_show_all(view->datesel);
145 static void
146 datesel_remove(JamView *view) {
147 jam_doc_set_time(view->doc, NULL);
148 gtk_widget_destroy(view->datesel);
149 view->datesel = NULL;
151 static gboolean
152 datesel_visible(JamView *view) {
153 return view->datesel != NULL;
155 static void
156 datesel_load(JamView *view) {
157 struct tm vtm = {0};
158 gboolean backdated;
159 jam_doc_get_time(view->doc, &vtm);
160 backdated = jam_doc_get_backdated(view->doc);
161 if (vtm.tm_year > 0 || backdated)
162 show_meta(view, JAM_VIEW_DATESEL);
163 if (datesel_visible(view)) {
164 datesel_set_tm(DATESEL(view->datesel), &vtm);
165 datesel_set_backdated(DATESEL(view->datesel), backdated);
168 static void
169 datesel_store(JamView *view) {
170 struct tm vtm = {0};
171 datesel_get_tm(DATESEL(view->datesel), &vtm);
172 jam_doc_set_time(view->doc, &vtm);
173 jam_doc_set_backdated(view->doc,
174 datesel_get_backdated(DATESEL(view->datesel)));
178 static void
179 moodpic_add(JamView *view) {
180 if (view->moodpicbar)
181 return;
182 view->moodpicbar = gtk_hbox_new(FALSE, 12);
183 gtk_box_pack_start(GTK_BOX(view), view->moodpicbar, FALSE, FALSE, 0);
184 gtk_box_reorder_child(GTK_BOX(view), view->moodpicbar, 1);
185 gtk_widget_show(view->moodpicbar);
187 static void
188 moodpic_remove(JamView *view) {
189 if (!view->moodpicbar)
190 return;
191 if (view->moodbox || view->pic)
192 return;
193 gtk_widget_destroy(view->moodpicbar);
194 view->moodpicbar = NULL;
197 static void
198 mood_populate(JamView *view) {
199 if (JAM_ACCOUNT_IS_LJ(view->account)) {
200 /* load moods. */
201 LJServer *server = jam_host_lj_get_server(JAM_HOST_LJ(jam_account_get_host(view->account)));
202 LJMood *m;
203 char *text;
204 GList *strings = NULL;
205 GSList *l;
207 text = gtk_editable_get_chars(GTK_EDITABLE(GTK_COMBO(view->moodcombo)->entry), 0, -1);
209 for (l = server->moods; l; l = l->next) {
210 m = l->data;
211 strings = g_list_insert_sorted(strings, m->name,
212 (GCompareFunc)g_ascii_strcasecmp);
214 if (strings)
215 gtk_combo_set_popdown_strings(GTK_COMBO(view->moodcombo), strings);
217 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(view->moodcombo)->entry), text);
218 g_free(text);
221 static void
222 mood_add(JamView *view) {
223 moodpic_add(view);
225 /*view->moodbutton = gtk_button_new_with_label(_("Mood"));
226 gtk_size_group_add_widget(view->sizegroup, view->moodbutton);*/
228 view->moodcombo = gtk_combo_new();
229 gtk_widget_set_usize(GTK_COMBO(view->moodcombo)->entry, 100, -1);
230 mood_populate(view);
232 /*view->moodbox = gtk_hbox_new(FALSE, 12);
233 gtk_box_pack_start(GTK_BOX(view->moodbox), view->moodbutton, FALSE, FALSE, 0);
234 gtk_box_pack_start(GTK_BOX(view->moodbox), view->moodcombo, TRUE, TRUE, 0);*/
235 view->moodbox = labelled_box_new_sg(_("_Mood:"), view->moodcombo, view->sizegroup),
236 gtk_widget_show_all(view->moodbox);
238 gtk_box_pack_start(GTK_BOX(view->moodpicbar), view->moodbox, TRUE, TRUE, 0);
239 if (view->pic)
240 gtk_box_reorder_child(GTK_BOX(view->moodpicbar), view->moodbox, 0);
242 static void
243 mood_remove(JamView *view) {
244 jam_doc_set_mood(view->doc, NULL);
245 gtk_widget_destroy(view->moodbox);
246 view->moodbox = view->moodbutton = view->moodcombo = NULL;
247 moodpic_remove(view);
249 static gboolean
250 mood_visible(JamView *view) {
251 return view->moodbox != NULL;
253 static void
254 mood_account_changed(JamView *view) {
255 mood_populate(view);
257 static void
258 mood_load(JamView *view) {
259 const char *mood = jam_doc_get_mood(view->doc);
260 if (mood)
261 show_meta(view, JAM_VIEW_MOOD);
262 if (mood_visible(view))
263 gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(view->moodcombo)->entry),
264 mood ? mood : "");
266 static void
267 mood_store(JamView *view) {
268 const char *mood = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(view->moodcombo)->entry));
269 /* XXX temporary: set moodid based on mood string. */
270 int id = lj_mood_id_from_name(
271 jam_host_lj_get_server(JAM_HOST_LJ(jam_account_get_host(view->account))),
272 mood);
274 jam_doc_set_mood(view->doc, mood);
275 if (id < 0) id = 0;
276 jam_doc_set_moodid(view->doc, id);
279 static void
280 picture_populate(JamView *view) {
281 GtkWidget *menu;
282 GtkWidget *item;
283 GSList *l;
284 LJUser *user = jam_account_lj_get_user(JAM_ACCOUNT_LJ(view->account));
286 menu = gtk_menu_new();
287 item = gtk_menu_item_new_with_label(_("[default]"));
288 gtk_widget_show(item);
289 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
291 for (l = user->pickws; l; l = l->next) {
292 item = gtk_menu_item_new_with_label(l->data);
293 g_object_set_data(G_OBJECT(item), KEY_PICTUREKEYWORD, l->data);
294 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
295 gtk_widget_show(item);
298 gtk_option_menu_set_menu(GTK_OPTION_MENU(view->pic), menu);
300 static void
301 picture_add(JamView *view) {
302 moodpic_add(view);
303 view->pic = gtk_option_menu_new();
304 picture_populate(view);
305 view->picbox = labelled_box_new_sg(_("_Picture:"), view->pic, view->sizegroup);
306 gtk_box_pack_start(GTK_BOX(view->moodpicbar), view->picbox, FALSE, FALSE, 0);
307 gtk_widget_show_all(view->picbox);
309 static void
310 picture_remove(JamView *view) {
311 jam_doc_set_picture(view->doc, NULL);
312 gtk_widget_destroy(view->picbox);
313 view->picbox = view->pic = NULL;
314 moodpic_remove(view);
316 static gboolean
317 picture_visible(JamView *view) {
318 return view->picbox != NULL;
320 static void
321 picture_account_changed(JamView *view) {
322 picture_populate(view);
324 static void
325 picture_load(JamView *view) {
326 const char *pic = jam_doc_get_picture(view->doc);
327 GSList *l;
328 int sel = 0;
329 LJUser *user = jam_account_lj_get_user(JAM_ACCOUNT_LJ(view->account));
331 if (pic) {
332 for (l = user->pickws, sel = 1; l; l = l->next, sel++) {
333 if (strcmp(pic, l->data) == 0)
334 break;
336 if (l == NULL) sel = 0;
338 if (sel)
339 show_meta(view, JAM_VIEW_PIC);
340 if (picture_visible(view))
341 gtk_option_menu_set_history(GTK_OPTION_MENU(view->pic), sel);
344 static void
345 picture_store(JamView *view) {
346 int sel = gtk_option_menu_get_history(GTK_OPTION_MENU(view->pic));
347 LJUser *user = jam_account_lj_get_user(JAM_ACCOUNT_LJ(view->account));
348 const char *pickw = NULL;
350 if (sel > 0)
351 pickw = g_slist_nth(user->pickws, sel-1)->data;
353 jam_doc_set_picture(view->doc, pickw);
356 static void
357 music_refresh_cb(JamView *view) {
358 GError *err = NULL;
359 gchar *music = music_detect(&err);
361 if (music) {
362 gtk_entry_set_text(GTK_ENTRY(view->music), music);
363 g_free(music);
364 } else if (err) {
365 GtkWindow *toplevel;
366 toplevel = GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(view)));
367 jam_warning(toplevel, _("Error detecting music: %s"), err->message);
368 g_error_free(err);
372 static void
373 music_add(JamView *view) {
374 view->music = gtk_entry_new();
375 view->musicbar = labelled_box_new_sg(_("_Music:"), view->music, view->sizegroup);
376 if (music_can_detect(NULL)) {
377 GtkWidget *refresh = gtk_button_new_from_stock(GTK_STOCK_REFRESH);
378 g_signal_connect_swapped(G_OBJECT(refresh), "clicked",
379 G_CALLBACK(music_refresh_cb), view);
380 gtk_box_pack_start(GTK_BOX(view->musicbar),
381 refresh, FALSE, FALSE, 0);
383 gtk_box_pack_start(GTK_BOX(view), view->musicbar, FALSE, FALSE, 0);
384 gtk_box_reorder_child(GTK_BOX(view), view->musicbar, view->moodpicbar ? 2 : 1);
385 gtk_widget_show_all(view->musicbar);
387 static void
388 music_remove(JamView *view) {
389 jam_doc_set_music(view->doc, NULL);
390 gtk_widget_destroy(view->musicbar);
391 view->musicbar = view->music = NULL;
393 static gboolean
394 music_visible(JamView *view) {
395 return view->musicbar != NULL;
397 static void
398 music_load(JamView *view) {
399 const char *music = jam_doc_get_music(view->doc);
400 if (music)
401 show_meta(view, JAM_VIEW_MUSIC);
402 if (music_visible(view))
403 gtk_entry_set_text(GTK_ENTRY(view->music), music ? music : "");
405 static void
406 music_store(JamView *view) {
407 const char *music = gtk_entry_get_text(GTK_ENTRY(view->music));
408 if (music[0] == 0) music = NULL;
409 jam_doc_set_music(view->doc, music);
412 static void
413 location_add(JamView *view) {
414 view->location = gtk_entry_new();
415 view->locationbar = labelled_box_new_sg(_("_Location:"), view->location, view->sizegroup);
416 gtk_box_pack_start(GTK_BOX(view), view->locationbar, FALSE, FALSE, 0);
417 gtk_box_reorder_child(GTK_BOX(view), view->locationbar, view->moodpicbar ? 2 : 1);
418 gtk_widget_show_all(view->locationbar);
420 static void
421 location_remove(JamView *view) {
422 jam_doc_set_location(view->doc, NULL);
423 gtk_widget_destroy(view->locationbar);
424 view->locationbar = view->location = NULL;
426 static gboolean
427 location_visible(JamView *view) {
428 return view->locationbar != NULL;
430 static void
431 location_load(JamView *view) {
432 const char *location = jam_doc_get_location(view->doc);
433 if (location)
434 show_meta(view, JAM_VIEW_LOCATION);
435 if (location_visible(view))
436 gtk_entry_set_text(GTK_ENTRY(view->location), location ? location : "");
438 static void
439 location_store(JamView *view) {
440 const char *location = gtk_entry_get_text(GTK_ENTRY(view->location));
441 if (location[0] == 0) location = NULL;
442 jam_doc_set_location(view->doc, location);
445 static void
446 tags_add(JamView *view) {
447 view->tags = gtk_entry_new();
448 view->tagsbar = labelled_box_new_sg(_("_Tags:"), view->tags, view->sizegroup);
449 gtk_box_pack_start(GTK_BOX(view), view->tagsbar, FALSE, FALSE, 0);
450 gtk_box_reorder_child(GTK_BOX(view), view->tagsbar, view->musicbar ? 2 : 1);
451 gtk_widget_show_all(view->tagsbar);
453 static void
454 tags_remove(JamView *view) {
455 jam_doc_set_taglist(view->doc, NULL);
456 gtk_widget_destroy(view->tagsbar);
457 view->tagsbar = view->tags = NULL;
459 static gboolean
460 tags_visible(JamView *view) {
461 return view->tagsbar != NULL;
463 static void
464 tags_load(JamView *view) {
465 const char *tags = jam_doc_get_taglist(view->doc);
466 if (tags)
467 show_meta(view, JAM_VIEW_TAGS);
468 if (tags_visible(view))
469 gtk_entry_set_text(GTK_ENTRY(view->tags), tags ? tags : "");
471 static void
472 tags_store(JamView *view) {
473 const char *tags = gtk_entry_get_text(GTK_ENTRY(view->tags));
474 if (tags[0] == 0) tags = NULL;
475 jam_doc_set_taglist(view->doc, tags);
479 static void
480 option_add(JamView *view) {
481 if (view->optionbar)
482 return;
483 view->optionbar = gtk_hbox_new(FALSE, 12);
484 gtk_box_pack_end(GTK_BOX(view), view->optionbar, FALSE, FALSE, 0);
485 gtk_widget_show(view->optionbar);
487 static void
488 option_remove(JamView *view) {
489 if (!view->optionbar)
490 return;
491 if (view->preformatted || view->datesel || view->commentsbox || view->screeningbox)
492 return;
493 gtk_widget_destroy(view->optionbar);
494 view->optionbar = NULL;
497 static void
498 preformatted_add(JamView *view) {
499 option_add(view);
500 view->preformatted = gtk_check_button_new_with_mnemonic(_("_Preformatted"));
501 gtk_box_pack_start(GTK_BOX(view->optionbar), view->preformatted, FALSE, FALSE, 0);
502 gtk_widget_show_all(view->preformatted);
504 static void
505 preformatted_remove(JamView *view) {
506 jam_doc_set_preformatted(view->doc, FALSE);
507 gtk_widget_destroy(view->preformatted);
508 view->preformatted = NULL;
509 option_remove(view);
511 static gboolean
512 preformatted_visible(JamView *view) {
513 return view->preformatted != NULL;
515 static void
516 preformatted_load(JamView *view) {
517 gboolean pre = jam_doc_get_preformatted(view->doc);
518 if (pre)
519 show_meta(view, JAM_VIEW_PREFORMATTED);
520 if (preformatted_visible(view))
521 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(view->preformatted),
522 pre);
524 static void
525 preformatted_store(JamView *view) {
526 jam_doc_set_preformatted(view->doc,
527 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(view->preformatted)));
530 static void
531 comments_add(JamView *view) {
532 GtkWidget *menu, *item;
533 static const char * items[] = {
534 N_("Default"),
535 N_("Don't Email"),
536 N_("Disable")
538 int i;
540 option_add(view);
541 view->comments = gtk_option_menu_new();
543 menu = gtk_menu_new();
544 for (i = 0; i < sizeof(items)/sizeof(char*); i++) {
545 item = gtk_menu_item_new_with_label(_(items[i]));
546 gtk_widget_show(item);
547 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
549 gtk_option_menu_set_menu(GTK_OPTION_MENU(view->comments), menu);
551 view->commentsbox = labelled_box_new(_("_Comments:"), view->comments);
552 gtk_box_pack_start(GTK_BOX(view->optionbar), view->commentsbox, FALSE, FALSE, 0);
553 gtk_widget_show_all(view->commentsbox);
555 static void
556 comments_remove(JamView *view) {
557 jam_doc_set_comments(view->doc, LJ_COMMENTS_DEFAULT);
558 gtk_widget_destroy(view->commentsbox);
559 view->commentsbox = view->comments = NULL;
560 option_remove(view);
562 static gboolean
563 comments_visible(JamView *view) {
564 return view->commentsbox != NULL;
566 static void
567 comments_load(JamView *view) {
568 LJCommentsType type = jam_doc_get_comments(view->doc);
569 if (type != LJ_COMMENTS_DEFAULT)
570 show_meta(view, JAM_VIEW_COMMENTS);
571 if (comments_visible(view))
572 gtk_option_menu_set_history(GTK_OPTION_MENU(view->comments), type);
574 static void
575 comments_store(JamView *view) {
576 LJCommentsType type = gtk_option_menu_get_history(GTK_OPTION_MENU(view->comments));
577 jam_doc_set_comments(view->doc, type);
580 static void
581 screening_add(JamView *view) {
582 GtkWidget *menu, *item;
583 static const char * items[] = {
584 N_("Default"),
585 N_("None"),
586 N_("Anonymous Only"),
587 N_("Non-Friends"),
588 N_("All")
590 int i;
592 option_add(view);
593 view->screening = gtk_option_menu_new();
595 menu = gtk_menu_new();
596 for (i = 0; i < sizeof(items)/sizeof(char*); i++) {
597 item = gtk_menu_item_new_with_label(_(items[i]));
598 gtk_widget_show(item);
599 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
601 gtk_option_menu_set_menu(GTK_OPTION_MENU(view->screening), menu);
603 view->screeningbox = labelled_box_new(_("Scr_eening:"), view->screening);
604 gtk_box_pack_end(GTK_BOX(view->optionbar), view->screeningbox, FALSE,FALSE, 0);
605 gtk_widget_show_all(view->screeningbox);
607 static void
608 screening_remove(JamView *view) {
609 jam_doc_set_screening(view->doc, LJ_SCREENING_DEFAULT);
610 gtk_widget_destroy(view->screeningbox);
611 view->screeningbox = view->screening = NULL;
612 option_remove(view);
614 static gboolean
615 screening_visible(JamView *view) {
616 return view->screeningbox != NULL;
618 static void
619 screening_load(JamView *view) {
620 LJScreeningType type = jam_doc_get_screening(view->doc);
621 if (type != LJ_SCREENING_DEFAULT)
622 show_meta(view, JAM_VIEW_SCREENING);
623 if (screening_visible(view))
624 gtk_option_menu_set_history(GTK_OPTION_MENU(view->screening), type);
626 static void
627 screening_store(JamView *view) {
628 LJCommentsType type = gtk_option_menu_get_history(GTK_OPTION_MENU(view->screening));
629 jam_doc_set_screening(view->doc, type);
632 static struct {
633 char *name;
634 gboolean lj_only;
636 /* standard for all. */
637 void (*add)(JamView *view);
638 void (*remove)(JamView *view);
639 gboolean (*visible)(JamView *view);
641 void (*load)(JamView *view);
642 void (*store)(JamView *view);
644 /* only defined if it matters when the account changes. */
645 void (*account_changed)(JamView *view);
646 } metas[] = {
647 /* these must match JamViewMeta in jamview.h. */
648 #define STD(x) x##_add, x##_remove, x##_visible, x##_load, x##_store
649 { "subject", TRUE, NULL, NULL, NULL, subject_load, subject_store, NULL },
650 { "security", TRUE, STD(security), security_account_changed },
651 { "mood", TRUE, STD(mood), mood_account_changed },
652 { "picture", TRUE, STD(picture), picture_account_changed },
653 { "music", TRUE, STD(music), NULL },
654 { "location", TRUE, STD(location), NULL },
655 { "tags", TRUE, STD(tags), NULL },
656 { "preformatted", TRUE, STD(preformatted), NULL },
657 { "datesel", TRUE, STD(datesel), NULL },
658 { "comments", TRUE, STD(comments), NULL },
659 { "screening", TRUE, STD(screening), NULL },
660 { 0 }
663 gboolean
664 jam_view_get_meta_visible(JamView *view, JamViewMeta meta) {
665 if (!metas[meta].visible) return TRUE;
666 return metas[meta].visible(view);
669 void
670 jam_view_toggle_meta(JamView *view, JamViewMeta meta, gboolean show) {
671 if (show == metas[meta].visible(view)) return;
672 if (show) metas[meta].add(view);
673 else metas[meta].remove(view);
676 const char *
677 jam_view_meta_to_name(JamViewMeta meta) {
678 return metas[meta].name;
681 gboolean
682 jam_view_meta_from_name(const char *name, JamViewMeta *meta) {
683 int i;
684 for (i = 0; i <= JAM_VIEW_META_LAST; i++) {
685 if (strcmp(metas[i].name, name) == 0) {
686 *meta = i;
687 return TRUE;
690 return FALSE;
693 void
694 jam_view_emit_conf(JamView *view) {
695 int i;
696 for (i = JAM_VIEW_META_FIRST; i <= JAM_VIEW_META_LAST; i++)
697 g_signal_emit_by_name(view, "meta_toggle", i, metas[i].visible(view));
700 static void
701 jam_view_load_conf(JamView *view) {
702 int meta;
703 for (meta = JAM_VIEW_META_FIRST; meta <= JAM_VIEW_META_LAST; meta++) {
704 if (conf.options.showmeta[meta] && !metas[meta].visible(view)) {
705 if (JAM_ACCOUNT_IS_LJ(view->account) || !metas[meta].lj_only) {
706 jam_view_toggle_meta(view, meta, TRUE);
707 g_signal_emit_by_name(view, "meta_toggle", meta, TRUE);
709 } else if (!conf.options.showmeta[meta] && metas[meta].visible(view)) {
710 jam_view_toggle_meta(view, meta, FALSE);
711 g_signal_emit_by_name(view, "meta_toggle", meta, FALSE);
716 static void
717 jam_view_class_init(JamViewClass *viewclass) {
718 signals[META_TOGGLE] = g_signal_new("meta_toggle",
719 G_OBJECT_CLASS_TYPE(viewclass),
720 G_SIGNAL_RUN_LAST,
721 G_STRUCT_OFFSET(JamViewClass, meta_toggle),
722 NULL, NULL,
723 logjam_marshal_VOID__INT_BOOLEAN,
724 G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_BOOLEAN);
727 static void
728 undo_cb(JamView *view) {
729 if (undomgr_can_undo(view->undomgr))
730 undomgr_undo(view->undomgr);
732 static void
733 redo_cb(JamView *view) {
734 if (undomgr_can_redo(view->undomgr))
735 undomgr_redo(view->undomgr);
738 static void
739 populate_entry_popup(GtkTextView *text, GtkMenu *menu, JamView *view) {
740 GtkWidget *menu_item;
742 g_return_if_fail(menu != NULL);
743 g_return_if_fail(GTK_IS_MENU_SHELL(menu));
745 menu_item = gtk_separator_menu_item_new();
746 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menu_item);
747 gtk_widget_show(menu_item);
749 menu_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_REDO, NULL);
750 gtk_widget_set_sensitive(menu_item,
751 undomgr_can_redo(view->undomgr));
752 g_signal_connect_swapped(G_OBJECT(menu_item), "activate",
753 G_CALLBACK(redo_cb), view);
754 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menu_item);
755 gtk_widget_show(menu_item);
757 menu_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_UNDO, NULL);
758 gtk_widget_set_sensitive(menu_item,
759 undomgr_can_undo(view->undomgr));
760 g_signal_connect_swapped(G_OBJECT(menu_item), "activate",
761 G_CALLBACK(undo_cb), view);
762 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), menu_item);
763 gtk_widget_show(menu_item);
766 static void
767 jam_view_init(JamView *view) {
768 gtk_box_set_spacing(GTK_BOX(view), 6);
770 view->entry = gtk_text_view_new();
771 view->undomgr = UNDOMGR(undomgr_new());
773 gtk_text_view_set_editable(GTK_TEXT_VIEW(view->entry), TRUE);
774 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(view->entry), GTK_WRAP_WORD);
775 g_signal_connect(G_OBJECT(view->entry), "populate-popup",
776 G_CALLBACK(populate_entry_popup), view);
777 if (conf.uifont)
778 jam_widget_set_font(view->entry, conf.uifont);
780 undomgr_attach(UNDOMGR(view->undomgr), view->entry);
782 view->sizegroup = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
784 subject_add(view);
785 jam_view_load_conf(view);
786 gtk_box_pack_start(GTK_BOX(view), scroll_wrap(view->entry),
787 TRUE, TRUE, 0);
790 GtkWidget*
791 jam_view_new(JamDoc *doc) {
792 JamView *view = JAM_VIEW(g_object_new(jam_view_get_type(), NULL));
794 jam_view_set_doc(view, doc);
796 return GTK_WIDGET(view);
799 GObject*
800 jam_view_get_undomgr(JamView *view) {
801 return G_OBJECT(view->undomgr);
804 static void
805 jam_view_account_changed(JamView *view) {
806 int i;
808 view->account = jam_doc_get_account(view->doc);
810 /* notify all visible meta views that the account
811 * has changed, and hide those that shouldn't be shown. */
812 for (i = JAM_VIEW_META_FIRST; i <= JAM_VIEW_META_LAST; i++) {
813 if (!metas[i].visible(view))
814 continue;
815 if (!JAM_ACCOUNT_IS_LJ(view->account) && metas[i].lj_only) {
816 jam_view_toggle_meta(view, i, FALSE);
817 } else {
818 if (metas[i].account_changed && metas[i].visible(view))
819 metas[i].account_changed(view);
824 static void
825 jam_view_load_doc(JamView *view) {
826 int i;
827 /* if the current account type doesn't support some meta views,
828 * they should've already been hidden by _account_changed. */
829 for (i = 0; i < JAM_VIEW_META_COUNT; i++) {
830 if (!metas[i].lj_only || JAM_ACCOUNT_IS_LJ(view->account))
831 metas[i].load(view);
835 static void
836 jam_view_store_doc(JamView *view) {
837 GtkTextBuffer *buffer;
838 GtkTextIter start, end;
839 char *event;
840 int i;
842 for (i = 0; i < JAM_VIEW_META_COUNT; i++) {
843 if (jam_view_get_meta_visible(view, i)) {
844 if (metas[i].store)
845 metas[i].store(view);
849 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view->entry));
850 gtk_text_buffer_get_bounds(buffer, &start, &end);
851 event = gtk_text_buffer_get_text(buffer, &start, &end, TRUE);
853 jam_doc_set_event(view->doc, event);
854 g_free(event);
857 void
858 jam_view_set_doc(JamView *view, JamDoc *doc) {
859 view->doc = doc;
861 undomgr_detach(view->undomgr, view->entry);
863 if (view->account != jam_doc_get_account(doc))
864 jam_view_account_changed(view);
865 g_signal_connect_swapped(G_OBJECT(doc), "notify::account",
866 G_CALLBACK(jam_view_account_changed), view);
867 g_signal_connect_swapped(G_OBJECT(doc), "update_doc",
868 G_CALLBACK(jam_view_store_doc), view);
869 g_signal_connect_swapped(G_OBJECT(doc), "entry_changed",
870 G_CALLBACK(jam_view_load_doc), view);
872 jam_view_load_conf(view);
873 jam_view_load_doc(view);
875 /* XXX: gtkspell gets confused if you detach its document,
876 * so we detach the spell checker completely, then reattach
877 * it at the end. */
878 #ifdef HAVE_GTKSPELL
879 if (conf.options.usespellcheck && view->entry) {
880 GtkSpell *spell;
881 spell = gtkspell_get_from_text_view(GTK_TEXT_VIEW(view->entry));
882 if (spell)
883 gtkspell_detach(spell);
885 #endif /* HAVE_GTKSPELL */
887 gtk_text_view_set_buffer(GTK_TEXT_VIEW(view->entry),
888 jam_doc_get_text_buffer(doc));
890 #ifdef HAVE_GTKSPELL
891 if (conf.options.usespellcheck) {
892 GError *err = NULL;
893 if (gtkspell_new_attach(GTK_TEXT_VIEW(view->entry),
894 conf.spell_language, &err) == NULL) {
895 jam_warning(NULL, // XXX GTK_WINDOW(view->jw),
896 _("GtkSpell error: %s"), err->message);
897 g_error_free(err);
900 #endif /* HAVE_GTKSPELL */
902 undomgr_attach(view->undomgr, view->entry);
905 void
906 jam_view_settings_changed(JamView *view) {
907 #if 0
908 XXX make this work
909 #ifdef HAVE_GTKSPELL
910 if (conf.options.usespellcheck != hadspell) {
911 GtkSpell *spell;
912 if (conf.options.usespellcheck) {
913 GError *err = NULL;
914 if (gtkspell_new_attach(GTK_TEXT_VIEW(fetcheentry(jw)), conf.spell_language , &err) == NULL) {
915 jam_warning(GTK_WINDOW(jw),
916 _("GtkSpell error: %s"), err->message);
917 conf.options.usespellcheck = FALSE;
918 g_error_free(err);
920 } else {
921 spell = gtkspell_get_from_text_view(GTK_TEXT_VIEW(fetcheentry(jw)));
922 if (spell)
923 gtkspell_detach(spell);
926 #endif
927 #endif
928 if (conf.uifont)
929 jam_widget_set_font(view->entry, conf.uifont);
931 if (view->musicbar) {
932 /* There are many cleaner ways to add/remove the Refresh button when
933 * the music preference has changed, but this works fine. */
934 music_remove(view);
935 music_add(view);
939 GType
940 jam_view_get_type(void) {
941 static GType new_type = 0;
942 if (!new_type) {
943 const GTypeInfo new_info = {
944 sizeof(JamViewClass),
945 NULL,
946 NULL,
947 (GClassInitFunc) jam_view_class_init,
948 NULL,
949 NULL,
950 sizeof(JamView),
952 (GInstanceInitFunc) jam_view_init,
954 new_type = g_type_register_static(GTK_TYPE_VBOX,
955 "JamView", &new_info, 0);
957 return new_type;