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