7eed8ca53c69585bfc3fe1a32a851b91ec6f1c1a
[colloquium.git] / src / narrative_window.c
1 /*
2  * narrative_window.c
3  *
4  * Copyright © 2014-2019 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 <libintl.h>
34 #define _(x) gettext(x)
35
36 #include <narrative.h>
37 #include <gtk/gtknarrativeview.h>
38
39 #include "colloquium.h"
40 #include "narrative_window.h"
41 #include "slide_window.h"
42 #include "slide_render_cairo.h"
43 #include "testcard.h"
44 #include "pr_clock.h"
45 #include "slideshow.h"
46 #include "print.h"
47 #include "stylesheet_editor.h"
48
49 struct _narrative_window
50 {
51         GtkWidget           *window;
52         GtkToolItem         *bfirst;
53         GtkToolItem         *bprev;
54         GtkToolItem         *bnext;
55         GtkToolItem         *blast;
56         GtkWidget           *nv;
57         GApplication        *app;
58         Narrative           *n;
59         GFile               *file;
60         SCSlideshow         *show;
61         int                  show_no_slides;
62         PRClock             *pr_clock;
63         SlideWindow         *slidewindows[16];
64         int                  n_slidewindows;
65 };
66
67
68 static void show_error(NarrativeWindow *nw, const char *err)
69 {
70         GtkWidget *mw;
71
72         mw = gtk_message_dialog_new(GTK_WINDOW(nw->window),
73                                     GTK_DIALOG_DESTROY_WITH_PARENT,
74                                     GTK_MESSAGE_ERROR,
75                                     GTK_BUTTONS_CLOSE, "%s", err);
76
77         g_signal_connect_swapped(mw, "response",
78                                  G_CALLBACK(gtk_widget_destroy), mw);
79
80         gtk_widget_show(mw);
81 }
82
83
84 char *narrative_window_get_filename(NarrativeWindow *nw)
85 {
86         char *filename;
87         if ( nw == NULL || nw->file == NULL ) {
88                 filename = strdup(_("(untitled)"));
89         } else {
90                 filename = g_file_get_basename(nw->file);
91         }
92         return filename;
93 }
94
95
96 static void update_titlebar(NarrativeWindow *nw)
97 {
98         char title[1026];
99         char *filename;
100         int i;
101
102         filename = narrative_window_get_filename(nw);
103         snprintf(title, 1024, "%s - Colloquium", filename);
104         if ( narrative_get_unsaved(nw->n) ) strcat(title, " *");
105         free(filename);
106
107         gtk_window_set_title(GTK_WINDOW(nw->window), title);
108
109         for ( i=0; i<nw->n_slidewindows; i++ ) {
110                 slide_window_update_titlebar(nw->slidewindows[i]);
111         }
112 }
113
114
115 void narrative_window_sw_closed(NarrativeWindow *nw, SlideWindow *sw)
116 {
117         int i;
118         int found = 0;
119
120         for ( i=0; i<nw->n_slidewindows; i++ ) {
121                 if ( nw->slidewindows[i] == sw ) {
122                         int j;
123                         for ( j=i; j<nw->n_slidewindows-1; j++ ) {
124                                 nw->slidewindows[j] = nw->slidewindows[j+1];
125                         }
126                         nw->n_slidewindows--;
127                         found = 1;
128                 }
129         }
130
131         if ( !found ) {
132                 fprintf(stderr, "Couldn't find slide window in narrative record\n");
133         }
134 }
135
136
137 static void update_toolbar(NarrativeWindow *nw)
138 {
139         int cur_para, n_para;
140
141         if ( nw->show == NULL ) return;
142
143         cur_para = gtk_narrative_view_get_cursor_para(GTK_NARRATIVE_VIEW(nw->nv));
144         if ( cur_para == 0 ) {
145                 gtk_widget_set_sensitive(GTK_WIDGET(nw->bfirst), FALSE);
146                 gtk_widget_set_sensitive(GTK_WIDGET(nw->bprev), FALSE);
147         } else {
148                 gtk_widget_set_sensitive(GTK_WIDGET(nw->bfirst), TRUE);
149                 gtk_widget_set_sensitive(GTK_WIDGET(nw->bprev), TRUE);
150         }
151
152         n_para = narrative_get_num_items(nw->n);
153         if ( cur_para == n_para - 1 ) {
154                 gtk_widget_set_sensitive(GTK_WIDGET(nw->bnext), FALSE);
155                 gtk_widget_set_sensitive(GTK_WIDGET(nw->blast), FALSE);
156         } else {
157                 gtk_widget_set_sensitive(GTK_WIDGET(nw->bnext), TRUE);
158                 gtk_widget_set_sensitive(GTK_WIDGET(nw->blast), TRUE);
159         }
160 }
161
162
163 static gint saveas_response_sig(GtkWidget *d, gint response,
164                                 NarrativeWindow *nw)
165 {
166         if ( response == GTK_RESPONSE_ACCEPT ) {
167
168                 GFile *file = gtk_file_chooser_get_file(GTK_FILE_CHOOSER(d));
169
170                 if ( narrative_save(nw->n, file) ) {
171                         show_error(nw, _("Failed to save presentation"));
172                 } else {
173                         update_titlebar(nw);
174                 }
175
176                 if ( nw->file != file ) {
177                         if ( nw->file != NULL ) g_object_unref(nw->file);
178                         nw->file = file;
179                         g_object_ref(nw->file);
180                 }
181                 g_object_unref(file);
182
183         }
184         gtk_widget_destroy(d);
185         return 0;
186 }
187
188
189 static void saveas_sig(GSimpleAction *action, GVariant *parameter, gpointer vp)
190 {
191         GtkWidget *d;
192         GtkWidget *box;
193         NarrativeWindow *nw = vp;
194
195         d = gtk_file_chooser_dialog_new(_("Save presentation"),
196                                         GTK_WINDOW(nw->window),
197                                         GTK_FILE_CHOOSER_ACTION_SAVE,
198                                         _("_Cancel"), GTK_RESPONSE_CANCEL,
199                                         _("_Save"), GTK_RESPONSE_ACCEPT,
200                                         NULL);
201         gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(d),
202                                                        TRUE);
203
204         box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
205         gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(d), box);
206
207         g_signal_connect(G_OBJECT(d), "response",
208                          G_CALLBACK(saveas_response_sig), nw);
209
210         gtk_widget_show_all(d);
211 }
212
213
214 static void about_sig(GSimpleAction *action, GVariant *parameter, gpointer vp)
215 {
216         NarrativeWindow *nw = vp;
217         open_about_dialog(nw->window);
218 }
219
220
221 static void save_sig(GSimpleAction *action, GVariant *parameter, gpointer vp)
222 {
223         NarrativeWindow *nw = vp;
224
225         if ( nw->file == NULL ) {
226                 return saveas_sig(NULL, NULL, nw);
227         }
228
229         if ( narrative_save(nw->n, nw->file) ) {
230                 show_error(nw, _("Failed to save presentation"));
231         } else {
232                 update_titlebar(nw);
233         }
234 }
235
236
237 static gint load_ss_response_sig(GtkWidget *d, gint response,
238                                  NarrativeWindow *nw)
239 {
240 //      if ( response == GTK_RESPONSE_ACCEPT ) {
241 //
242 //              GFile *file;
243 //              Stylesheet *new_ss;
244 //
245 //              file = gtk_file_chooser_get_file(GTK_FILE_CHOOSER(d));
246 //
247 //              new_ss = stylesheet_load(file);
248 //              if ( new_ss != NULL ) {
249 //
250 //                      stylesheet_free(nw->p->stylesheet);
251 //                      nw->n->stylesheet = new_ss;
252 //                      sc_editor_set_stylesheet(nw->nv, new_ss);
253 //
254 //                      /* Full rerender */
255 //                      sc_editor_set_scblock(nw->nv, nw->dummy_top);
256 //
257 //              } else {
258 //                      fprintf(stderr, _("Failed to load stylesheet\n"));
259 //              }
260 //
261 //              g_object_unref(file);
262 //
263 //      }
264 //
265 //      gtk_widget_destroy(d);
266
267         return 0;
268 }
269
270
271 static void stylesheet_changed_sig(GtkWidget *da, NarrativeWindow *nw)
272 {
273         int i;
274
275         /* Full rerender */
276         gtk_narrative_view_redraw(GTK_NARRATIVE_VIEW(nw->nv));
277
278         /* Full rerender of all slide windows */
279         for ( i=0; i<nw->n_slidewindows; i++ ) {
280                 slide_window_update(nw->slidewindows[i]);
281         }
282 }
283
284
285 static void edit_ss_sig(GSimpleAction *action, GVariant *parameter,
286                         gpointer vp)
287 {
288         NarrativeWindow *nw = vp;
289         StylesheetEditor *se;
290
291         se = stylesheet_editor_new(narrative_get_stylesheet(nw->n));
292         g_signal_connect(G_OBJECT(se), "changed",
293                          G_CALLBACK(stylesheet_changed_sig), nw);
294         gtk_widget_show_all(GTK_WIDGET(se));
295 }
296
297
298 static void load_ss_sig(GSimpleAction *action, GVariant *parameter,
299                         gpointer vp)
300 {
301 //      //SCBlock *nsblock;
302 //      //SCBlock *templ;
303 //      NarrativeWindow *nw = vp;
304 //      GtkWidget *d;
305 //
306 //      d = gtk_file_chooser_dialog_new(_("Load stylesheet"),
307 //                                      GTK_WINDOW(nw->window),
308 //                                      GTK_FILE_CHOOSER_ACTION_OPEN,
309 //                                      _("_Cancel"), GTK_RESPONSE_CANCEL,
310 //                                      _("_Open"), GTK_RESPONSE_ACCEPT,
311 //                                      NULL);
312 //
313 //      g_signal_connect(G_OBJECT(d), "response",
314 //                       G_CALLBACK(load_ss_response_sig), nw);
315 //
316 //      gtk_widget_show_all(d);
317 }
318
319
320 static void add_slide_sig(GSimpleAction *action, GVariant *parameter,
321                           gpointer vp)
322 {
323         NarrativeWindow *nw = vp;
324         gtk_narrative_view_add_slide_at_cursor(GTK_NARRATIVE_VIEW(nw->nv));
325         narrative_set_unsaved(nw->n);
326         update_titlebar(nw);
327 }
328
329
330 static void set_clock_pos(NarrativeWindow *nw)
331 {
332         int pos = gtk_narrative_view_get_cursor_para(GTK_NARRATIVE_VIEW(nw->nv));
333         int end = narrative_get_num_items_to_eop(nw->n);
334         if ( pos >= end ) pos = end-1;
335         pr_clock_set_pos(nw->pr_clock, pos, end);
336 }
337
338
339 static void first_para_sig(GSimpleAction *action, GVariant *parameter,
340                            gpointer vp)
341 {
342         NarrativeWindow *nw = vp;
343         gtk_narrative_view_set_cursor_para(GTK_NARRATIVE_VIEW(nw->nv), 0);
344         set_clock_pos(nw);
345         update_toolbar(nw);
346 }
347
348
349 static void ss_prev_para(SCSlideshow *ss, void *vp)
350 {
351         NarrativeWindow *nw = vp;
352         if ( gtk_narrative_view_get_cursor_para(GTK_NARRATIVE_VIEW(nw->nv)) == 0 ) return;
353         gtk_narrative_view_set_cursor_para(GTK_NARRATIVE_VIEW(nw->nv),
354                                   gtk_narrative_view_get_cursor_para(GTK_NARRATIVE_VIEW(nw->nv))-1);
355                          set_clock_pos(nw);
356         update_toolbar(nw);
357 }
358
359
360 static void prev_para_sig(GSimpleAction *action, GVariant *parameter,
361                           gpointer vp)
362 {
363         NarrativeWindow *nw = vp;
364         ss_prev_para(nw->show, nw);
365 }
366
367
368 static void ss_next_para(SCSlideshow *ss, void *vp)
369 {
370         NarrativeWindow *nw = vp;
371         Slide *ns;
372         GtkNarrativeView *nv;
373         int n_paras;
374
375         n_paras = narrative_get_num_items(nw->n);
376         nv = GTK_NARRATIVE_VIEW(nw->nv);
377
378         if ( gtk_narrative_view_get_cursor_para(nv) == n_paras - 1 ) return;
379         gtk_narrative_view_set_cursor_para(nv, gtk_narrative_view_get_cursor_para(nv)+1);
380
381         /* If we only have one monitor, skip to next slide */
382         if ( ss != NULL && ss->single_monitor && !nw->show_no_slides ) {
383                 int i;
384                 for ( i=gtk_narrative_view_get_cursor_para(nv); i<n_paras; i++ )
385                 {
386                         Slide *ns;
387                         gtk_narrative_view_set_cursor_para(nv, i);
388                         ns = narrative_get_slide(nw->n, i);
389                         if ( ns != NULL ) break;
390                 }
391         }
392
393         set_clock_pos(nw);
394         ns = narrative_get_slide(nw->n, gtk_narrative_view_get_cursor_para(nv));
395         if ( nw->show != NULL && 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         gtk_narrative_view_set_cursor_para(GTK_NARRATIVE_VIEW(nw->nv), -1);
415         set_clock_pos(nw);
416         update_toolbar(nw);
417 }
418
419
420 static void open_clock_sig(GSimpleAction *action, GVariant *parameter, gpointer vp)
421 {
422         NarrativeWindow *nw = vp;
423         if ( nw->pr_clock != NULL ) return;
424         nw->pr_clock = pr_clock_new(&nw->pr_clock);
425 }
426
427
428 static void testcard_sig(GSimpleAction *action, GVariant *parameter,
429                          gpointer vp)
430 {
431         NarrativeWindow *nw = vp;
432         show_testcard(nw->n);
433 }
434
435
436 static gint export_pdf_response_sig(GtkWidget *d, gint response,
437                                     Narrative *n)
438 {
439         if ( response == GTK_RESPONSE_ACCEPT ) {
440                 char *filename;
441                 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(d));
442                 render_slides_to_pdf(n, narrative_get_imagestore(n), 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->n, nw->window);
456         gtk_narrative_view_redraw(GTK_NARRATIVE_VIEW(nw->nv));
457 }
458
459
460 static void exportpdf_sig(GSimpleAction *action, GVariant *parameter,
461                           gpointer vp)
462 {
463         NarrativeWindow *nw = vp;
464         GtkWidget *d;
465
466         d = gtk_file_chooser_dialog_new(_("Export PDF"),
467                                         NULL,
468                                         GTK_FILE_CHOOSER_ACTION_SAVE,
469                                         _("_Cancel"), GTK_RESPONSE_CANCEL,
470                                         _("_Export"), GTK_RESPONSE_ACCEPT,
471                                         NULL);
472         gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(d),
473                                                        TRUE);
474
475         g_signal_connect(G_OBJECT(d), "response",
476                          G_CALLBACK(export_pdf_response_sig), nw->n);
477
478         gtk_widget_show_all(d);
479 }
480
481
482
483 static gboolean nw_button_press_sig(GtkWidget *da, GdkEventButton *event,
484                                     NarrativeWindow *nw)
485 {
486         return 0;
487 }
488
489
490 static void changed_sig(GtkWidget *da, NarrativeWindow *nw)
491 {
492         narrative_set_unsaved(nw->n);
493         update_titlebar(nw);
494 }
495
496
497 static void scroll_down(NarrativeWindow *nw)
498 {
499         gdouble inc, val;
500         GtkAdjustment *vadj;
501
502         vadj = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(nw->nv));
503         inc = gtk_adjustment_get_step_increment(GTK_ADJUSTMENT(vadj));
504         val = gtk_adjustment_get_value(GTK_ADJUSTMENT(vadj));
505         gtk_adjustment_set_value(GTK_ADJUSTMENT(vadj), inc+val);
506 }
507
508
509 static gboolean nw_destroy_sig(GtkWidget *da, NarrativeWindow *nw)
510 {
511         if ( nw->pr_clock != NULL ) pr_clock_destroy(nw->pr_clock);
512         while ( nw->n_slidewindows > 0 ) {
513                 slide_window_destroy(nw->slidewindows[nw->n_slidewindows-1]);
514         }
515         g_application_release(nw->app);
516         return FALSE;
517 }
518
519
520 static gboolean nw_double_click_sig(GtkWidget *da, gpointer *pslide,
521                                     NarrativeWindow *nw)
522 {
523         Slide *slide = (Slide *)pslide;
524         if ( nw->show == NULL ) {
525                 if ( nw->n_slidewindows < 16 ) {
526                         nw->slidewindows[nw->n_slidewindows++] = slide_window_open(nw->n, slide,
527                                                                                    nw, nw->app);
528                 } else {
529                         fprintf(stderr, _("Too many slide windows\n"));
530                 }
531         } else {
532                 sc_slideshow_set_slide(nw->show, slide);
533         }
534         return FALSE;
535 }
536
537
538 static gboolean nw_key_press_sig(GtkWidget *da, GdkEventKey *event,
539                                  NarrativeWindow *nw)
540 {
541         switch ( event->keyval ) {
542
543                 case GDK_KEY_B :
544                 case GDK_KEY_b :
545                 if ( nw->show != NULL ) {
546                         scroll_down(nw);
547                         return TRUE;
548                 }
549                 break;
550
551                 case GDK_KEY_Page_Up :
552                 if ( nw->show != NULL ) {
553                         ss_prev_para(nw->show, nw);
554                         return TRUE;
555                 }
556                 break;
557
558                 case GDK_KEY_Page_Down :
559                 if ( nw->show != NULL) {
560                         ss_next_para(nw->show, nw);
561                         return TRUE;
562                 }
563                 break;
564
565                 case GDK_KEY_Escape :
566                 if ( nw->show != NULL ) {
567                         gtk_widget_destroy(GTK_WIDGET(nw->show));
568                         return TRUE;
569                 }
570                 break;
571
572                 case GDK_KEY_F5 :
573                 if ( nw->show != NULL ) {
574                         /* Trap F5 so that full rerender does NOT happen */
575                         return TRUE;
576                 }
577
578         }
579
580         return FALSE;
581 }
582
583
584 static gboolean ss_destroy_sig(GtkWidget *da, NarrativeWindow *nw)
585 {
586         nw->show = NULL;
587         gtk_narrative_view_set_para_highlight(GTK_NARRATIVE_VIEW(nw->nv), 0);
588
589         gtk_widget_set_sensitive(GTK_WIDGET(nw->bfirst), FALSE);
590         gtk_widget_set_sensitive(GTK_WIDGET(nw->bprev), FALSE);
591         gtk_widget_set_sensitive(GTK_WIDGET(nw->bnext), FALSE);
592         gtk_widget_set_sensitive(GTK_WIDGET(nw->blast), FALSE);
593
594         return FALSE;
595 }
596
597
598 static void start_slideshow_here_sig(GSimpleAction *action, GVariant *parameter,
599                                      gpointer vp)
600 {
601         NarrativeWindow *nw = vp;
602         Slide *slide;
603
604         if ( narrative_get_num_slides(nw->n) == 0 ) return;
605
606         slide = narrative_get_slide(nw->n,
607                                     gtk_narrative_view_get_cursor_para(GTK_NARRATIVE_VIEW(nw->nv)));
608         if ( slide == NULL ) return;
609
610         nw->show = sc_slideshow_new(nw->n, GTK_APPLICATION(nw->app));
611         if ( nw->show == NULL ) return;
612
613         nw->show_no_slides = 0;
614
615         g_signal_connect(G_OBJECT(nw->show), "key-press-event",
616                  G_CALLBACK(nw_key_press_sig), nw);
617         g_signal_connect(G_OBJECT(nw->show), "destroy",
618                  G_CALLBACK(ss_destroy_sig), nw);
619         sc_slideshow_set_slide(nw->show, slide);
620         gtk_narrative_view_set_para_highlight(GTK_NARRATIVE_VIEW(nw->nv), 1);
621         gtk_widget_show_all(GTK_WIDGET(nw->show));
622         update_toolbar(nw);
623 }
624
625
626 static void start_slideshow_noslides_sig(GSimpleAction *action, GVariant *parameter,
627                                          gpointer vp)
628 {
629         NarrativeWindow *nw = vp;
630
631         if ( narrative_get_num_slides(nw->n) == 0 ) return;
632
633         nw->show = sc_slideshow_new(nw->n, GTK_APPLICATION(nw->app));
634         if ( nw->show == NULL ) return;
635
636         nw->show_no_slides = 1;
637
638         g_signal_connect(G_OBJECT(nw->show), "key-press-event",
639                  G_CALLBACK(nw_key_press_sig), nw);
640         g_signal_connect(G_OBJECT(nw->show), "destroy",
641                  G_CALLBACK(ss_destroy_sig), nw);
642         sc_slideshow_set_slide(nw->show, narrative_get_slide_by_number(nw->n, 0));
643         gtk_narrative_view_set_para_highlight(GTK_NARRATIVE_VIEW(nw->nv), 1);
644         gtk_narrative_view_set_cursor_para(GTK_NARRATIVE_VIEW(nw->nv), 0);
645         update_toolbar(nw);
646 }
647
648
649 static void start_slideshow_sig(GSimpleAction *action, GVariant *parameter,
650                                 gpointer vp)
651 {
652         NarrativeWindow *nw = vp;
653
654         if ( narrative_get_num_slides(nw->n) == 0 ) return;
655
656         nw->show = sc_slideshow_new(nw->n, GTK_APPLICATION(nw->app));
657         if ( nw->show == NULL ) return;
658
659         nw->show_no_slides = 0;
660
661         g_signal_connect(G_OBJECT(nw->show), "key-press-event",
662                  G_CALLBACK(nw_key_press_sig), nw);
663         g_signal_connect(G_OBJECT(nw->show), "destroy",
664                  G_CALLBACK(ss_destroy_sig), nw);
665         sc_slideshow_set_slide(nw->show, narrative_get_slide_by_number(nw->n, 0));
666         gtk_narrative_view_set_para_highlight(GTK_NARRATIVE_VIEW(nw->nv), 1);
667         gtk_narrative_view_set_cursor_para(GTK_NARRATIVE_VIEW(nw->nv), 0);
668         gtk_widget_show_all(GTK_WIDGET(nw->show));
669         update_toolbar(nw);
670 }
671
672
673 GActionEntry nw_entries[] = {
674
675         { "about", about_sig, NULL, NULL, NULL },
676         { "save", save_sig, NULL, NULL, NULL },
677         { "saveas", saveas_sig, NULL, NULL, NULL },
678         { "slide", add_slide_sig, NULL, NULL, NULL },
679         { "loadstylesheet", load_ss_sig, NULL, NULL, NULL },
680         { "stylesheet", edit_ss_sig, NULL, NULL, NULL },
681         { "startslideshow", start_slideshow_sig, NULL, NULL, NULL },
682         { "startslideshowhere", start_slideshow_here_sig, NULL, NULL, NULL },
683         { "startslideshownoslides", start_slideshow_noslides_sig, NULL, NULL, NULL },
684         { "clock", open_clock_sig, NULL, NULL, NULL },
685         { "testcard", testcard_sig, NULL, NULL, NULL },
686         { "first", first_para_sig, NULL, NULL, NULL },
687         { "prev", prev_para_sig, NULL, NULL, NULL },
688         { "next", next_para_sig, NULL, NULL, NULL },
689         { "last", last_para_sig, NULL, NULL, NULL },
690         { "print", print_sig, NULL, NULL, NULL  },
691         { "exportpdf", exportpdf_sig, NULL, NULL, NULL  },
692 };
693
694
695 //void narrative_window_sw_closed(NarrativeWindow *nw, SlideWindow *sw)
696 //{
697 //      int i;
698 //      int found = 0;
699 //
700 //      for ( i=0; i<nw->n_slidewindows; i++ ) {
701 //              if ( nw->slidewindows[i] == sw ) {
702 //
703 //                      int j;
704 //                      for ( j=i; j<nw->n_slidewindows-1; j++ ) {
705 //                              nw->slidewindows[j] = nw->slidewindows[j+1];
706 //                      }
707 //                      nw->n_slidewindows--;
708 //                      found = 1;
709 //              }
710 //      }
711 //
712 //      if ( !found ) {
713 //              fprintf(stderr, "Couldn't find slide window in narrative record\n");
714 //      }
715 //}
716
717
718 NarrativeWindow *narrative_window_new(Narrative *n, GFile *file, GApplication *papp)
719 {
720         NarrativeWindow *nw;
721         GtkWidget *vbox;
722         GtkWidget *scroll;
723         GtkWidget *toolbar;
724         GtkToolItem *button;
725         GtkWidget *image;
726         Colloquium *app = COLLOQUIUM(papp);
727
728         nw = calloc(1, sizeof(NarrativeWindow));
729         if ( nw == NULL ) return NULL;
730
731         nw->app = papp;
732         nw->n = n;
733         nw->n_slidewindows = 0;
734         nw->file = file;
735         if ( file != NULL ) g_object_ref(file);
736
737         nw->window = gtk_application_window_new(GTK_APPLICATION(app));
738         update_titlebar(nw);
739
740         g_action_map_add_action_entries(G_ACTION_MAP(nw->window), nw_entries,
741                                         G_N_ELEMENTS(nw_entries), nw);
742
743         vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
744         gtk_container_add(GTK_CONTAINER(nw->window), vbox);
745
746         nw->nv = gtk_narrative_view_new(n);
747
748         toolbar = gtk_toolbar_new();
749         gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_ICONS);
750         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(toolbar), FALSE, FALSE, 0);
751
752         /* Fullscreen */
753         image = gtk_image_new_from_icon_name("view-fullscreen",
754                                              GTK_ICON_SIZE_LARGE_TOOLBAR);
755         button = gtk_tool_button_new(image, _("Start slideshow"));
756         gtk_actionable_set_action_name(GTK_ACTIONABLE(button),
757                                        "win.startslideshow");
758         gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(button));
759
760         button = gtk_separator_tool_item_new();
761         gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(button));
762
763         /* Add slide */
764         image = gtk_image_new_from_icon_name("list-add",
765                                              GTK_ICON_SIZE_LARGE_TOOLBAR);
766         button = gtk_tool_button_new(image, _("Add slide"));
767         gtk_actionable_set_action_name(GTK_ACTIONABLE(button),
768                                        "win.slide");
769         gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(button));
770
771         button = gtk_separator_tool_item_new();
772         gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(button));
773
774         image = gtk_image_new_from_icon_name("go-top",
775                                              GTK_ICON_SIZE_LARGE_TOOLBAR);
776         nw->bfirst = gtk_tool_button_new(image, _("First slide"));
777         gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(nw->bfirst));
778         gtk_actionable_set_action_name(GTK_ACTIONABLE(nw->bfirst),
779                                        "win.first");
780
781         image = gtk_image_new_from_icon_name("go-up",
782                                              GTK_ICON_SIZE_LARGE_TOOLBAR);
783         nw->bprev = gtk_tool_button_new(image, _("Previous slide"));
784         gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(nw->bprev));
785         gtk_actionable_set_action_name(GTK_ACTIONABLE(nw->bprev),
786                                        "win.prev");
787
788         image = gtk_image_new_from_icon_name("go-down",
789                                              GTK_ICON_SIZE_LARGE_TOOLBAR);
790         nw->bnext = gtk_tool_button_new(image, _("Next slide"));
791         gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(nw->bnext));
792         gtk_actionable_set_action_name(GTK_ACTIONABLE(nw->bnext),
793                                        "win.next");
794
795         image = gtk_image_new_from_icon_name("go-bottom",
796                                              GTK_ICON_SIZE_LARGE_TOOLBAR);
797         nw->blast = gtk_tool_button_new(image, _("Last slide"));
798         gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(nw->blast));
799         gtk_actionable_set_action_name(GTK_ACTIONABLE(nw->blast),
800                                        "win.last");
801
802         gtk_widget_set_sensitive(GTK_WIDGET(nw->bfirst), FALSE);
803         gtk_widget_set_sensitive(GTK_WIDGET(nw->bprev), FALSE);
804         gtk_widget_set_sensitive(GTK_WIDGET(nw->bnext), FALSE);
805         gtk_widget_set_sensitive(GTK_WIDGET(nw->blast), FALSE);
806
807         scroll = gtk_scrolled_window_new(NULL, NULL);
808         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
809                                        GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
810         gtk_container_add(GTK_CONTAINER(scroll), GTK_WIDGET(nw->nv));
811
812         g_signal_connect(G_OBJECT(nw->nv), "button-press-event",
813                          G_CALLBACK(nw_button_press_sig), nw);
814         g_signal_connect(G_OBJECT(nw->nv), "changed",
815                          G_CALLBACK(changed_sig), nw);
816         g_signal_connect(G_OBJECT(nw->nv), "key-press-event",
817                          G_CALLBACK(nw_key_press_sig), nw);
818         g_signal_connect(G_OBJECT(nw->window), "destroy",
819                          G_CALLBACK(nw_destroy_sig), nw);
820         g_signal_connect(G_OBJECT(nw->nv), "slide-double-clicked",
821                          G_CALLBACK(nw_double_click_sig), nw);
822
823         gtk_window_set_default_size(GTK_WINDOW(nw->window), 768, 768);
824         gtk_box_pack_start(GTK_BOX(vbox), scroll, TRUE, TRUE, 0);
825         gtk_container_set_focus_child(GTK_CONTAINER(nw->window),
826                                       GTK_WIDGET(nw->nv));
827
828         gtk_widget_show_all(nw->window);
829         g_application_hold(papp);
830
831         return nw;
832 }