Add preliminary stylesheet editor
[colloquium.git] / src / narrative_window.c
1 /*
2  * narrative_window.c
3  *
4  * Copyright © 2014-2018 Thomas White <taw@bitwiz.org.uk>
5  *
6  * This file is part of Colloquium.
7  *
8  * Colloquium is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation, either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
20  *
21  */
22
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include <gtk/gtk.h>
29 #include <assert.h>
30 #include <string.h>
31 #include <stdlib.h>
32
33 #include "colloquium.h"
34 #include "presentation.h"
35 #include "narrative_window.h"
36 #include "sc_editor.h"
37 #include "sc_parse.h"
38 #include "render.h"
39 #include "testcard.h"
40 #include "pr_clock.h"
41 #include "print.h"
42 #include "utils.h"
43 #include "stylesheet_editor.h"
44
45
46 struct _narrative_window
47 {
48         GtkWidget *window;
49         GtkToolItem         *bfirst;
50         GtkToolItem         *bprev;
51         GtkToolItem         *bnext;
52         GtkToolItem         *blast;
53         SCEditor *sceditor;
54         GApplication *app;
55         struct presentation *p;
56         SCBlock             *dummy_top;
57         SCSlideshow         *show;
58         PRClock             *pr_clock;
59 };
60
61
62 static void show_error(NarrativeWindow *nw, const char *err)
63 {
64         GtkWidget *mw;
65
66         mw = gtk_message_dialog_new(GTK_WINDOW(nw->window),
67                                     GTK_DIALOG_DESTROY_WITH_PARENT,
68                                     GTK_MESSAGE_ERROR,
69                                     GTK_BUTTONS_CLOSE, err);
70
71         g_signal_connect_swapped(mw, "response",
72                                  G_CALLBACK(gtk_widget_destroy), mw);
73
74         gtk_widget_show(mw);
75 }
76
77
78 static void update_toolbar(NarrativeWindow *nw)
79 {
80         int cur_para;
81
82         cur_para = sc_editor_get_cursor_para(nw->sceditor);
83         if ( cur_para == 0 ) {
84                 gtk_widget_set_sensitive(GTK_WIDGET(nw->bfirst), FALSE);
85                 gtk_widget_set_sensitive(GTK_WIDGET(nw->bprev), FALSE);
86         } else {
87                 gtk_widget_set_sensitive(GTK_WIDGET(nw->bfirst), TRUE);
88                 gtk_widget_set_sensitive(GTK_WIDGET(nw->bprev), TRUE);
89         }
90
91         if ( cur_para == sc_editor_get_num_paras(nw->sceditor)-1 ) {
92                 gtk_widget_set_sensitive(GTK_WIDGET(nw->bnext), FALSE);
93                 gtk_widget_set_sensitive(GTK_WIDGET(nw->blast), FALSE);
94         } else {
95                 gtk_widget_set_sensitive(GTK_WIDGET(nw->bnext), TRUE);
96                 gtk_widget_set_sensitive(GTK_WIDGET(nw->blast), TRUE);
97         }
98 }
99
100
101 static gint saveas_response_sig(GtkWidget *d, gint response,
102                                 NarrativeWindow *nw)
103 {
104         if ( response == GTK_RESPONSE_ACCEPT ) {
105
106                 GFile *file = gtk_file_chooser_get_file(GTK_FILE_CHOOSER(d));
107
108                 if ( save_presentation(nw->p, file) ) {
109                         show_error(nw, _("Failed to save presentation"));
110                 }
111
112                 g_object_unref(file);
113
114         }
115
116         gtk_widget_destroy(d);
117
118         return 0;
119 }
120
121
122 static void saveas_sig(GSimpleAction *action, GVariant *parameter, gpointer vp)
123 {
124         GtkWidget *d;
125         NarrativeWindow *nw = vp;
126
127         d = gtk_file_chooser_dialog_new(_("Save Presentation"),
128                                         GTK_WINDOW(nw->window),
129                                         GTK_FILE_CHOOSER_ACTION_SAVE,
130                                         _("_Cancel"), GTK_RESPONSE_CANCEL,
131                                         _("_Save"), GTK_RESPONSE_ACCEPT,
132                                         NULL);
133         gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(d),
134                                                        TRUE);
135
136         g_signal_connect(G_OBJECT(d), "response",
137                          G_CALLBACK(saveas_response_sig), nw);
138
139         gtk_widget_show_all(d);
140 }
141
142
143 static void about_sig(GSimpleAction *action, GVariant *parameter, gpointer vp)
144 {
145         NarrativeWindow *nw = vp;
146         open_about_dialog(nw->window);
147 }
148
149
150 static void save_sig(GSimpleAction *action, GVariant *parameter, gpointer vp)
151 {
152         NarrativeWindow *nw = vp;
153         GFile *file;
154
155         if ( nw->p->uri == NULL ) {
156                 return saveas_sig(NULL, NULL, nw);
157         }
158
159         file = g_file_new_for_uri(nw->p->uri);
160         save_presentation(nw->p, file);
161         g_object_unref(file);
162 }
163
164
165 static void delete_slide_sig(GSimpleAction *action, GVariant *parameter,
166                               gpointer vp)
167 {
168         SCBlock *ns;
169         NarrativeWindow *nw = vp;
170
171         /* Get the SCBlock corresponding to the slide */
172         ns = sc_editor_get_cursor_bvp(nw->sceditor);
173         if ( ns == NULL ) {
174                 fprintf(stderr, _("Not a slide!\n"));
175                 return;
176         }
177
178         sc_block_delete(&nw->dummy_top, ns);
179
180         /* Full rerender */
181         sc_editor_set_scblock(nw->sceditor, nw->dummy_top);
182         nw->p->saved = 0;
183         update_titlebar(nw);
184 }
185
186
187 static struct template_id *get_templates(SCBlock *ss, int *n)
188 {
189         struct template_id *list;
190         SCInterpreter *scin;
191
192         scin = sc_interp_new(NULL, NULL, NULL, NULL);
193         sc_interp_run_stylesheet(scin, ss);  /* ss == NULL is OK */
194         list = sc_interp_get_templates(scin, n);
195         sc_interp_destroy(scin);
196         return list;
197 }
198
199
200 static void update_template_menus(NarrativeWindow *nw)
201 {
202         struct template_id *templates;
203         int i, n_templates;
204
205         templates = get_templates(nw->p->stylesheet, &n_templates);
206
207         for ( i=0; i<n_templates; i++ ) {
208                 printf("%2i: %s %s\n", i, templates[i].name,
209                        templates[i].friendlyname);
210                 free(templates[i].name);
211                 free(templates[i].friendlyname);
212                 sc_block_free(templates[i].scblock);
213         }
214
215         free(templates);
216 }
217
218
219 static SCBlock *get_slide_template(SCBlock *ss)
220 {
221         struct template_id *templates;
222         int i, n_templates;
223         SCBlock *ret = NULL;
224
225         templates = get_templates(ss, &n_templates);
226
227         for ( i=0; i<n_templates; i++ ) {
228                 if ( strcmp(templates[i].name, "slide") == 0 ) {
229                         ret = templates[i].scblock;
230                 } else {
231                         sc_block_free(templates[i].scblock);
232                 }
233                 free(templates[i].name);
234                 free(templates[i].friendlyname);
235         }
236         free(templates);
237
238         /* No template? */
239         if ( ret == NULL ) {
240                 ret = sc_parse("\\slide{}");
241         }
242
243         return ret;  /* NB this is a copy of the one owned by the interpreter */
244 }
245
246
247 static SCBlock **get_ss_list(struct presentation *p)
248 {
249         SCBlock **stylesheets;
250
251         stylesheets = malloc(3 * sizeof(SCBlock *));
252         if ( stylesheets == NULL ) return NULL;
253
254         if ( p->stylesheet != NULL ) {
255                 stylesheets[0] = p->stylesheet;
256                 stylesheets[1] = NULL;
257         } else {
258                 stylesheets[0] = NULL;
259         }
260
261         return stylesheets;
262 }
263
264
265 static gint load_ss_response_sig(GtkWidget *d, gint response,
266                                  NarrativeWindow *nw)
267 {
268         if ( response == GTK_RESPONSE_ACCEPT ) {
269
270                 char *filename;
271                 char *stext;
272
273                 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(d));
274                 printf("Loading %s\n",filename);
275
276                 stext = load_everything(filename);
277                 if ( stext != NULL ) {
278                         SCBlock *bl;
279                         SCBlock *ss;
280                         bl = sc_parse(stext);
281                         free(stext);
282                         ss = find_stylesheet(bl);
283                         if ( ss != NULL ) {
284
285                                 SCBlock **stylesheets;
286
287                                 /* Substitute the style sheet */
288                                 replace_stylesheet(nw->p, ss);
289
290                                 stylesheets = get_ss_list(nw->p);
291                                 sc_editor_set_stylesheets(nw->sceditor,
292                                                           stylesheets);
293                                 free(stylesheets);
294
295                                 /* Full rerender, first block may have
296                                  * changed */
297                                 sc_editor_set_scblock(nw->sceditor,
298                                                       nw->dummy_top);
299
300                         } else {
301                                 fprintf(stderr, _("Not a style sheet\n"));
302                         }
303                 } else {
304                         fprintf(stderr, _("Failed to load\n"));
305                 }
306
307                 g_free(filename);
308
309         }
310
311         gtk_widget_destroy(d);
312
313         return 0;
314 }
315
316
317 static void edit_ss_sig(GSimpleAction *action, GVariant *parameter,
318                         gpointer vp)
319 {
320         NarrativeWindow *nw = vp;
321         StylesheetEditor *se;
322
323         se = stylesheet_editor_new(nw->p);
324         gtk_window_set_transient_for(GTK_WINDOW(se), GTK_WINDOW(nw->window));
325         gtk_widget_show_all(GTK_WIDGET(se));
326 }
327
328
329 static void load_ss_sig(GSimpleAction *action, GVariant *parameter,
330                         gpointer vp)
331 {
332         //SCBlock *nsblock;
333         //SCBlock *templ;
334         NarrativeWindow *nw = vp;
335         GtkWidget *d;
336
337         d = gtk_file_chooser_dialog_new(_("Load stylesheet"),
338                                         GTK_WINDOW(nw->window),
339                                         GTK_FILE_CHOOSER_ACTION_OPEN,
340                                         _("_Cancel"), GTK_RESPONSE_CANCEL,
341                                         _("_Open"), GTK_RESPONSE_ACCEPT,
342                                         NULL);
343
344         g_signal_connect(G_OBJECT(d), "response",
345                          G_CALLBACK(load_ss_response_sig), nw);
346
347         gtk_widget_show_all(d);
348 }
349
350
351 static void add_slide_sig(GSimpleAction *action, GVariant *parameter,
352                           gpointer vp)
353 {
354         SCBlock *nsblock;
355         SCBlock *templ;
356         NarrativeWindow *nw = vp;
357
358         sc_editor_ensure_cursor(nw->sceditor);
359
360         /* Split the current paragraph */
361         nsblock = split_paragraph_at_cursor(nw->sceditor);
362
363         /* Get the template */
364         templ = get_slide_template(nw->p->stylesheet); /* our copy */
365         show_sc_blocks(templ);
366
367         /* Link the new SCBlock in */
368         if ( nsblock != NULL ) {
369                 sc_block_append_p(nsblock, templ);
370         } else {
371                 fprintf(stderr, _("Failed to split paragraph\n"));
372         }
373
374         sc_editor_set_scblock(nw->sceditor, nw->dummy_top);
375         nw->p->saved = 0;
376         update_titlebar(nw);
377 }
378
379
380 static void first_para_sig(GSimpleAction *action, GVariant *parameter,
381                            gpointer vp)
382 {
383         NarrativeWindow *nw = vp;
384         sc_editor_set_cursor_para(nw->sceditor, 0);
385         pr_clock_set_pos(nw->pr_clock, sc_editor_get_cursor_para(nw->sceditor),
386                                        sc_editor_get_num_paras(nw->sceditor));
387         update_toolbar(nw);
388 }
389
390
391 static void ss_prev_para(SCSlideshow *ss, void *vp)
392 {
393         NarrativeWindow *nw = vp;
394         if ( sc_editor_get_cursor_para(nw->sceditor) == 0 ) return;
395         sc_editor_set_cursor_para(nw->sceditor,
396                                   sc_editor_get_cursor_para(nw->sceditor)-1);
397         pr_clock_set_pos(nw->pr_clock, sc_editor_get_cursor_para(nw->sceditor),
398                                        sc_editor_get_num_paras(nw->sceditor));
399         update_toolbar(nw);
400 }
401
402
403 static void prev_para_sig(GSimpleAction *action, GVariant *parameter,
404                           gpointer vp)
405 {
406         NarrativeWindow *nw = vp;
407         ss_prev_para(nw->show, nw);
408 }
409
410
411 static void ss_next_para(SCSlideshow *ss, void *vp)
412 {
413         NarrativeWindow *nw = vp;
414         SCBlock *ns;
415
416         sc_editor_set_cursor_para(nw->sceditor,
417                                   sc_editor_get_cursor_para(nw->sceditor)+1);
418
419         /* If we only have one monitor, don't try to do paragraph counting */
420         if ( ss->single_monitor ) {
421                 int i, max;
422                 max = sc_editor_get_num_paras(nw->sceditor);
423                 for ( i=sc_editor_get_cursor_para(nw->sceditor); i<max; i++ ) {
424                         SCBlock *ns;
425                         sc_editor_set_cursor_para(nw->sceditor, i);
426                         ns = sc_editor_get_cursor_bvp(nw->sceditor);
427                         if ( ns != NULL ) break;
428                 }
429         }
430
431         pr_clock_set_pos(nw->pr_clock, sc_editor_get_cursor_para(nw->sceditor),
432                                        sc_editor_get_num_paras(nw->sceditor));
433         ns = sc_editor_get_cursor_bvp(nw->sceditor);
434         if ( ns != NULL ) {
435                 sc_slideshow_set_slide(nw->show, ns);
436         }
437         update_toolbar(nw);
438 }
439
440
441 static void next_para_sig(GSimpleAction *action, GVariant *parameter,
442                           gpointer vp)
443 {
444         NarrativeWindow *nw = vp;
445         ss_next_para(nw->show, nw);
446 }
447
448
449 static void last_para_sig(GSimpleAction *action, GVariant *parameter,
450                           gpointer vp)
451 {
452         NarrativeWindow *nw = vp;
453         sc_editor_set_cursor_para(nw->sceditor, -1);
454         pr_clock_set_pos(nw->pr_clock, sc_editor_get_cursor_para(nw->sceditor),
455                                        sc_editor_get_num_paras(nw->sceditor));
456         update_toolbar(nw);
457 }
458
459
460 static void open_clock_sig(GSimpleAction *action, GVariant *parameter, gpointer vp)
461 {
462         NarrativeWindow *nw = vp;
463         nw->pr_clock = pr_clock_new();
464 }
465
466
467 static void testcard_sig(GSimpleAction *action, GVariant *parameter,
468                          gpointer vp)
469 {
470         NarrativeWindow *nw = vp;
471         show_testcard(nw->p);
472 }
473
474
475 static gint export_pdf_response_sig(GtkWidget *d, gint response,
476                                     struct presentation *p)
477 {
478        if ( response == GTK_RESPONSE_ACCEPT ) {
479                char *filename;
480                filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(d));
481                export_pdf(p, filename);
482                g_free(filename);
483        }
484
485        gtk_widget_destroy(d);
486
487        return 0;
488 }
489
490
491 static void print_sig(GSimpleAction *action, GVariant *parameter, gpointer vp)
492 {
493         NarrativeWindow *nw = vp;
494         run_printing(nw->p, nw->window);
495 }
496
497
498 static void exportpdf_sig(GSimpleAction *action, GVariant *parameter,
499                           gpointer vp)
500 {
501         NarrativeWindow *nw = vp;
502        GtkWidget *d;
503
504        d = gtk_file_chooser_dialog_new(_("Export PDF"),
505                                        NULL,
506                                        GTK_FILE_CHOOSER_ACTION_SAVE,
507                                        _("_Cancel"), GTK_RESPONSE_CANCEL,
508                                        _("_Export"), GTK_RESPONSE_ACCEPT,
509                                        NULL);
510        gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(d),
511                                                       TRUE);
512
513        g_signal_connect(G_OBJECT(d), "response",
514                         G_CALLBACK(export_pdf_response_sig), nw->p);
515
516        gtk_widget_show_all(d);
517 }
518
519
520
521 static gboolean nw_button_press_sig(GtkWidget *da, GdkEventButton *event,
522                                     NarrativeWindow *nw)
523 {
524         return 0;
525 }
526
527
528 static void changed_sig(GtkWidget *da, NarrativeWindow *nw)
529 {
530         nw->p->saved = 0;
531         update_titlebar(nw);
532 }
533
534
535 static void scroll_down(NarrativeWindow *nw)
536 {
537         gdouble inc, val;
538         GtkAdjustment *vadj;
539
540         vadj = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(nw->sceditor));
541         inc = gtk_adjustment_get_step_increment(GTK_ADJUSTMENT(vadj));
542         val = gtk_adjustment_get_value(GTK_ADJUSTMENT(vadj));
543         gtk_adjustment_set_value(GTK_ADJUSTMENT(vadj), inc+val);
544 }
545
546
547 static gboolean nw_destroy_sig(GtkWidget *da, NarrativeWindow *nw)
548 {
549         g_application_release(nw->app);
550         return FALSE;
551 }
552
553
554 static gboolean nw_key_press_sig(GtkWidget *da, GdkEventKey *event,
555                                  NarrativeWindow *nw)
556 {
557         switch ( event->keyval ) {
558
559                 case GDK_KEY_B :
560                 case GDK_KEY_b :
561                 if ( nw->show != NULL ) {
562                         scroll_down(nw);
563                         return TRUE;
564                 }
565                 break;
566
567                 case GDK_KEY_Page_Up :
568                 if ( nw->show != NULL ) {
569                         ss_prev_para(nw->show, nw);
570                         return TRUE;
571                 }
572                 break;
573
574                 case GDK_KEY_Page_Down :
575                 if ( nw->show != NULL) {
576                         ss_next_para(nw->show, nw);
577                         return TRUE;
578                 }
579                 break;
580
581                 case GDK_KEY_Escape :
582                 if ( nw->show != NULL ) {
583                         gtk_widget_destroy(GTK_WIDGET(nw->show));
584                         return TRUE;
585                 }
586                 break;
587
588                 case GDK_KEY_F5 :
589                 if ( nw->show != NULL ) {
590                         /* Trap F5 so that full rerender does NOT happen */
591                         return TRUE;
592                 }
593
594         }
595
596         return FALSE;
597 }
598
599
600 static gboolean ss_destroy_sig(GtkWidget *da, NarrativeWindow *nw)
601 {
602         nw->show = NULL;
603         sc_editor_set_para_highlight(nw->sceditor, 0);
604
605         gtk_widget_set_sensitive(GTK_WIDGET(nw->bfirst), FALSE);
606         gtk_widget_set_sensitive(GTK_WIDGET(nw->bprev), FALSE);
607         gtk_widget_set_sensitive(GTK_WIDGET(nw->bnext), FALSE);
608         gtk_widget_set_sensitive(GTK_WIDGET(nw->blast), FALSE);
609
610         return FALSE;
611 }
612
613
614 static void start_slideshow_here_sig(GSimpleAction *action, GVariant *parameter,
615                                      gpointer vp)
616 {
617         NarrativeWindow *nw = vp;
618         void *bvp;
619
620         if ( num_slides(nw->p) == 0 ) return;
621
622         bvp = sc_editor_get_cursor_bvp(nw->sceditor);
623         if ( bvp == NULL ) return;
624
625         nw->show = sc_slideshow_new(nw->p, GTK_APPLICATION(nw->app));
626         if ( nw->show == NULL ) return;
627
628         g_signal_connect(G_OBJECT(nw->show), "key-press-event",
629                  G_CALLBACK(nw_key_press_sig), nw);
630         g_signal_connect(G_OBJECT(nw->show), "destroy",
631                  G_CALLBACK(ss_destroy_sig), nw);
632         sc_slideshow_set_slide(nw->show, bvp);
633         sc_editor_set_para_highlight(nw->sceditor, 1);
634         gtk_widget_show_all(GTK_WIDGET(nw->show));
635         update_toolbar(nw);
636 }
637
638
639 static void start_slideshow_noslides_sig(GSimpleAction *action, GVariant *parameter,
640                                          gpointer vp)
641 {
642         NarrativeWindow *nw = vp;
643
644         if ( num_slides(nw->p) == 0 ) return;
645
646         nw->show = sc_slideshow_new(nw->p, GTK_APPLICATION(nw->app));
647         if ( nw->show == NULL ) return;
648
649         g_signal_connect(G_OBJECT(nw->show), "key-press-event",
650                  G_CALLBACK(nw_key_press_sig), nw);
651         g_signal_connect(G_OBJECT(nw->show), "destroy",
652                  G_CALLBACK(ss_destroy_sig), nw);
653         sc_slideshow_set_slide(nw->show, first_slide(nw->p));
654         sc_editor_set_para_highlight(nw->sceditor, 1);
655         sc_editor_set_cursor_para(nw->sceditor, 0);
656         update_toolbar(nw);
657 }
658
659
660 static void start_slideshow_sig(GSimpleAction *action, GVariant *parameter,
661                                 gpointer vp)
662 {
663         NarrativeWindow *nw = vp;
664
665         if ( num_slides(nw->p) == 0 ) return;
666
667         nw->show = sc_slideshow_new(nw->p, GTK_APPLICATION(nw->app));
668         if ( nw->show == NULL ) return;
669
670         g_signal_connect(G_OBJECT(nw->show), "key-press-event",
671                  G_CALLBACK(nw_key_press_sig), nw);
672         g_signal_connect(G_OBJECT(nw->show), "destroy",
673                  G_CALLBACK(ss_destroy_sig), nw);
674         sc_slideshow_set_slide(nw->show, first_slide(nw->p));
675         sc_editor_set_para_highlight(nw->sceditor, 1);
676         sc_editor_set_cursor_para(nw->sceditor, 0);
677         gtk_widget_show_all(GTK_WIDGET(nw->show));
678         update_toolbar(nw);
679 }
680
681
682 static void nw_update_titlebar(NarrativeWindow *nw)
683 {
684         char *tb = get_titlebar_string(nw->p);
685
686         if ( nw->p->slidewindow != NULL ) {
687
688                 char *title;
689
690                 title = malloc(strlen(tb)+14);
691                 sprintf(title, "%s - Colloquium", tb);
692                 gtk_window_set_title(GTK_WINDOW(nw->window), title);
693                 free(title);
694
695        }
696
697 }
698
699
700 static int create_thumbnail(SCInterpreter *scin, SCBlock *bl,
701                             double *w, double *h, void **bvp, void *vp)
702 {
703         struct presentation *p = vp;
704
705         *w = 270.0*(p->slide_width / p->slide_height);
706         *h = 270.0;
707         *bvp = bl;
708
709         return 1;
710 }
711
712
713 static cairo_surface_t *render_thumbnail(int w, int h, void *bvp, void *vp)
714 {
715         struct presentation *p = vp;
716         SCBlock *scblocks = bvp;
717         cairo_surface_t *surf;
718         SCBlock *stylesheets[2];
719         struct frame *top;
720         int sn = slide_number(p, scblocks);
721
722         stylesheets[0] = p->stylesheet;
723         stylesheets[1] = NULL;
724
725         /* FIXME: Cache like crazy here */
726         surf = render_sc(scblocks, w, h, p->slide_width, p->slide_height,
727                          stylesheets, NULL, p->is, sn, &top, p->lang);
728         frame_free(top);
729
730         return surf;
731 }
732
733
734 static int click_thumbnail(double x, double y, void *bvp, void *vp)
735 {
736         struct presentation *p = vp;
737         SCBlock *scblocks = bvp;
738
739         if ( p->narrative_window->show != NULL ) {
740                 sc_slideshow_set_slide(p->narrative_window->show, scblocks);
741         } else {
742                 slide_window_open(p, scblocks, p->narrative_window->app);
743         }
744
745         return 0;
746 }
747
748
749 GActionEntry nw_entries[] = {
750
751         { "about", about_sig, NULL, NULL, NULL },
752         { "save", save_sig, NULL, NULL, NULL },
753         { "saveas", saveas_sig, NULL, NULL, NULL },
754         { "deleteslide", delete_slide_sig, NULL, NULL, NULL },
755         { "slide", add_slide_sig, NULL, NULL, NULL },
756         { "loadstylesheet", load_ss_sig, NULL, NULL, NULL },
757         { "stylesheet", edit_ss_sig, NULL, NULL, NULL },
758         { "startslideshow", start_slideshow_sig, NULL, NULL, NULL },
759         { "startslideshowhere", start_slideshow_here_sig, NULL, NULL, NULL },
760         { "startslideshownoslides", start_slideshow_noslides_sig, NULL, NULL, NULL },
761         { "clock", open_clock_sig, NULL, NULL, NULL },
762         { "testcard", testcard_sig, NULL, NULL, NULL },
763         { "first", first_para_sig, NULL, NULL, NULL },
764         { "prev", prev_para_sig, NULL, NULL, NULL },
765         { "next", next_para_sig, NULL, NULL, NULL },
766         { "last", last_para_sig, NULL, NULL, NULL },
767         { "print", print_sig, NULL, NULL, NULL  },
768         { "exportpdf", exportpdf_sig, NULL, NULL, NULL  },
769 };
770
771
772 void update_titlebar(NarrativeWindow *nw)
773 {
774         char *title;
775
776         title = get_titlebar_string(nw->p);
777         title = realloc(title, strlen(title)+16);
778         if ( title == NULL ) return;
779
780         strcat(title, " - Colloquium");
781         if ( !nw->p->saved ) {
782                 strcat(title, " *");
783         }
784         gtk_window_set_title(GTK_WINDOW(nw->window), title);
785         free(title);
786 }
787
788
789 NarrativeWindow *narrative_window_new(struct presentation *p, GApplication *papp)
790 {
791         NarrativeWindow *nw;
792         GtkWidget *vbox;
793         GtkWidget *scroll;
794         GtkWidget *toolbar;
795         GtkToolItem *button;
796         SCBlock **stylesheets;
797         SCCallbackList *cbl;
798         GtkWidget *image;
799         Colloquium *app = COLLOQUIUM(papp);
800
801         if ( p->narrative_window != NULL ) {
802                 fprintf(stderr, _("Narrative window is already open!\n"));
803                 return NULL;
804         }
805
806         nw = calloc(1, sizeof(NarrativeWindow));
807         if ( nw == NULL ) return NULL;
808
809         nw->app = papp;
810         nw->p = p;
811
812         nw->window = gtk_application_window_new(GTK_APPLICATION(app));
813         p->narrative_window = nw;
814         update_titlebar(nw);
815
816         g_action_map_add_action_entries(G_ACTION_MAP(nw->window), nw_entries,
817                                         G_N_ELEMENTS(nw_entries), nw);
818
819         nw_update_titlebar(nw);
820
821         vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
822         gtk_container_add(GTK_CONTAINER(nw->window), vbox);
823
824         stylesheets = get_ss_list(p);
825
826         /* If the presentation is completely empty, give ourselves at least
827          * something to work with */
828         if ( nw->p->scblocks == NULL ) {
829                 nw->p->scblocks = sc_parse("");
830         }
831
832         /* Put everything we have inside \presentation{}.
833          * SCEditor will start processing one level down */
834         nw->dummy_top = sc_block_new_parent(nw->p->scblocks, "presentation");
835
836         nw->sceditor = sc_editor_new(nw->dummy_top, stylesheets, p->lang,
837                                      colloquium_get_imagestore(app));
838         free(stylesheets);
839         cbl = sc_callback_list_new();
840         sc_callback_list_add_callback(cbl, "slide", create_thumbnail,
841                                       render_thumbnail, click_thumbnail, p);
842         sc_editor_set_callbacks(nw->sceditor, cbl);
843         sc_editor_set_imagestore(nw->sceditor, p->is);
844
845         toolbar = gtk_toolbar_new();
846         gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_ICONS);
847         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(toolbar), FALSE, FALSE, 0);
848
849         /* Fullscreen */
850         image = gtk_image_new_from_icon_name("view-fullscreen",
851                                              GTK_ICON_SIZE_LARGE_TOOLBAR);
852         button = gtk_tool_button_new(image, _("Start slideshow"));
853         gtk_actionable_set_action_name(GTK_ACTIONABLE(button),
854                                        "win.startslideshow");
855         gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(button));
856
857         button = gtk_separator_tool_item_new();
858         gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(button));
859
860         /* Add slide */
861         image = gtk_image_new_from_icon_name("list-add",
862                                              GTK_ICON_SIZE_LARGE_TOOLBAR);
863         button = gtk_tool_button_new(image, _("Add slide"));
864         gtk_actionable_set_action_name(GTK_ACTIONABLE(button),
865                                        "win.slide");
866         gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(button));
867
868         button = gtk_separator_tool_item_new();
869         gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(button));
870
871         image = gtk_image_new_from_icon_name("go-top",
872                                              GTK_ICON_SIZE_LARGE_TOOLBAR);
873         nw->bfirst = gtk_tool_button_new(image, _("First slide"));
874         gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(nw->bfirst));
875         gtk_actionable_set_action_name(GTK_ACTIONABLE(nw->bfirst),
876                                        "win.first");
877
878         image = gtk_image_new_from_icon_name("go-up",
879                                              GTK_ICON_SIZE_LARGE_TOOLBAR);
880         nw->bprev = gtk_tool_button_new(image, _("Previous slide"));
881         gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(nw->bprev));
882         gtk_actionable_set_action_name(GTK_ACTIONABLE(nw->bprev),
883                                        "win.prev");
884
885         image = gtk_image_new_from_icon_name("go-down",
886                                              GTK_ICON_SIZE_LARGE_TOOLBAR);
887         nw->bnext = gtk_tool_button_new(image, _("Next slide"));
888         gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(nw->bnext));
889         gtk_actionable_set_action_name(GTK_ACTIONABLE(nw->bnext),
890                                        "win.next");
891
892         image = gtk_image_new_from_icon_name("go-bottom",
893                                              GTK_ICON_SIZE_LARGE_TOOLBAR);
894         nw->blast = gtk_tool_button_new(image, _("Last slide"));
895         gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(nw->blast));
896         gtk_actionable_set_action_name(GTK_ACTIONABLE(nw->blast),
897                                        "win.last");
898
899         update_toolbar(nw);
900         update_template_menus(nw);
901
902         scroll = gtk_scrolled_window_new(NULL, NULL);
903         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
904                                        GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
905         gtk_container_add(GTK_CONTAINER(scroll), GTK_WIDGET(nw->sceditor));
906
907         sc_editor_set_flow(nw->sceditor, 1);
908         sc_editor_set_background(nw->sceditor, 0.9, 0.9, 0.9);
909         sc_editor_set_min_border(nw->sceditor, 0.0);
910         sc_editor_set_top_frame_editable(nw->sceditor, 1);
911
912         g_signal_connect(G_OBJECT(nw->sceditor), "button-press-event",
913                          G_CALLBACK(nw_button_press_sig), nw);
914         g_signal_connect(G_OBJECT(nw->sceditor), "changed",
915                          G_CALLBACK(changed_sig), nw);
916         g_signal_connect(G_OBJECT(nw->sceditor), "key-press-event",
917                          G_CALLBACK(nw_key_press_sig), nw);
918         g_signal_connect(G_OBJECT(nw->window), "destroy",
919                          G_CALLBACK(nw_destroy_sig), nw);
920
921         gtk_window_set_default_size(GTK_WINDOW(nw->window), 768, 768);
922         gtk_box_pack_start(GTK_BOX(vbox), scroll, TRUE, TRUE, 0);
923         gtk_container_set_focus_child(GTK_CONTAINER(nw->window),
924                                       GTK_WIDGET(nw->sceditor));
925
926         gtk_widget_show_all(nw->window);
927         g_application_hold(papp);
928
929         return nw;
930 }