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