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