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