Trap F5 in narrative window during slide show
[colloquium.git] / src / narrative_window.c
1 /*
2  * narrative_window.c
3  *
4  * Copyright © 2014-2016 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 "presentation.h"
34 #include "narrative_window.h"
35 #include "sc_editor.h"
36 #include "sc_parse.h"
37 #include "render.h"
38 #include "testcard.h"
39 #include "pr_clock.h"
40 #include "print.h"
41
42
43 struct _narrative_window
44 {
45         GtkWidget *window;
46         GtkToolItem         *bfirst;
47         GtkToolItem         *bprev;
48         GtkToolItem         *bnext;
49         GtkToolItem         *blast;
50         SCEditor *sceditor;
51         GApplication *app;
52         struct presentation *p;
53         SCSlideshow         *show;
54         PRClock             *pr_clock;
55 };
56
57
58 static void update_toolbar(NarrativeWindow *nw)
59 {
60         int cur_para;
61
62         cur_para = sc_editor_get_cursor_para(nw->sceditor);
63         if ( cur_para == 0 ) {
64                 gtk_widget_set_sensitive(GTK_WIDGET(nw->bfirst), FALSE);
65                 gtk_widget_set_sensitive(GTK_WIDGET(nw->bprev), FALSE);
66         } else {
67                 gtk_widget_set_sensitive(GTK_WIDGET(nw->bfirst), TRUE);
68                 gtk_widget_set_sensitive(GTK_WIDGET(nw->bprev), TRUE);
69         }
70
71         if ( cur_para == sc_editor_get_num_paras(nw->sceditor)-1 ) {
72                 gtk_widget_set_sensitive(GTK_WIDGET(nw->bnext), FALSE);
73                 gtk_widget_set_sensitive(GTK_WIDGET(nw->blast), FALSE);
74         } else {
75                 gtk_widget_set_sensitive(GTK_WIDGET(nw->bnext), TRUE);
76                 gtk_widget_set_sensitive(GTK_WIDGET(nw->blast), TRUE);
77         }
78 }
79
80
81 static gint saveas_response_sig(GtkWidget *d, gint response,
82                                 NarrativeWindow *nw)
83 {
84         if ( response == GTK_RESPONSE_ACCEPT ) {
85
86                 char *filename;
87
88                 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(d));
89
90                 if ( save_presentation(nw->p, filename) ) {
91                         //show_error(sw, "Failed to save presentation");
92                 }
93
94                 g_free(filename);
95
96         }
97
98         gtk_widget_destroy(d);
99
100         return 0;
101 }
102
103
104 static void saveas_sig(GSimpleAction *action, GVariant *parameter, gpointer vp)
105 {
106         GtkWidget *d;
107         NarrativeWindow *nw = vp;
108
109         d = gtk_file_chooser_dialog_new("Save Presentation",
110                                         GTK_WINDOW(nw->window),
111                                         GTK_FILE_CHOOSER_ACTION_SAVE,
112                                         "_Cancel", GTK_RESPONSE_CANCEL,
113                                         "_Open", GTK_RESPONSE_ACCEPT,
114                                         NULL);
115         gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(d),
116                                                        TRUE);
117
118         g_signal_connect(G_OBJECT(d), "response",
119                          G_CALLBACK(saveas_response_sig), nw);
120
121         gtk_widget_show_all(d);
122 }
123
124
125 static void save_sig(GSimpleAction *action, GVariant *parameter, gpointer vp)
126 {
127         NarrativeWindow *nw = vp;
128
129         if ( nw->p->filename == NULL ) {
130                 return saveas_sig(NULL, NULL, nw);
131         }
132
133         save_presentation(nw->p, nw->p->filename);
134 }
135
136
137 static void open_slidesorter_sig(GSimpleAction *action, GVariant *parameter,
138                                  gpointer vp)
139 {
140 }
141
142
143 static void delete_slide_sig(GSimpleAction *action, GVariant *parameter,
144                               gpointer vp)
145 {
146         SCBlock *ns;
147         NarrativeWindow *nw = vp;
148
149         /* Get the SCBlock corresponding to the slide */
150         ns = sc_editor_get_cursor_bvp(nw->sceditor);
151         if ( ns == NULL ) {
152                 fprintf(stderr, "Not a slide!\n");
153                 return;
154         }
155
156         sc_block_delete(nw->p->scblocks, ns);
157
158         /* Full rerender */
159         sc_editor_set_scblock(nw->sceditor,
160                               sc_editor_get_scblock(nw->sceditor));
161 }
162
163
164 static void add_slide_sig(GSimpleAction *action, GVariant *parameter,
165                           gpointer vp)
166 {
167         SCBlock *nsblock;
168         NarrativeWindow *nw = vp;
169
170         /* Split the current paragraph */
171         nsblock = split_paragraph_at_cursor(nw->sceditor);
172
173         /* Link the new SCBlock in */
174         if ( nsblock != NULL ) {
175                 sc_block_append(nsblock, "slide", NULL, NULL, NULL);
176         } else {
177                 fprintf(stderr, "Failed to split paragraph\n");
178         }
179
180         sc_editor_set_scblock(nw->sceditor,
181                               sc_editor_get_scblock(nw->sceditor));
182 }
183
184
185 static void first_para_sig(GSimpleAction *action, GVariant *parameter,
186                            gpointer vp)
187 {
188         NarrativeWindow *nw = vp;
189         sc_editor_set_cursor_para(nw->sceditor, 0);
190         pr_clock_set_pos(nw->pr_clock, sc_editor_get_cursor_para(nw->sceditor),
191                                        sc_editor_get_num_paras(nw->sceditor));
192         update_toolbar(nw);
193 }
194
195
196 static void ss_prev_para(SCSlideshow *ss, void *vp)
197 {
198         NarrativeWindow *nw = vp;
199         if ( sc_editor_get_cursor_para(nw->sceditor) == 0 ) return;
200         sc_editor_set_cursor_para(nw->sceditor,
201                                   sc_editor_get_cursor_para(nw->sceditor)-1);
202         pr_clock_set_pos(nw->pr_clock, sc_editor_get_cursor_para(nw->sceditor),
203                                        sc_editor_get_num_paras(nw->sceditor));
204         update_toolbar(nw);
205 }
206
207
208 static void prev_para_sig(GSimpleAction *action, GVariant *parameter,
209                           gpointer vp)
210 {
211         NarrativeWindow *nw = vp;
212         ss_prev_para(nw->show, nw);
213 }
214
215
216 static void ss_next_para(SCSlideshow *ss, void *vp)
217 {
218         NarrativeWindow *nw = vp;
219         SCBlock *ns;
220
221         sc_editor_set_cursor_para(nw->sceditor,
222                                   sc_editor_get_cursor_para(nw->sceditor)+1);
223         pr_clock_set_pos(nw->pr_clock, sc_editor_get_cursor_para(nw->sceditor),
224                                        sc_editor_get_num_paras(nw->sceditor));
225         ns = sc_editor_get_cursor_bvp(nw->sceditor);
226         if ( ns != NULL ) {
227                 sc_slideshow_set_slide(nw->show, ns);
228         }
229         update_toolbar(nw);
230 }
231
232
233 static void next_para_sig(GSimpleAction *action, GVariant *parameter,
234                           gpointer vp)
235 {
236         NarrativeWindow *nw = vp;
237         ss_next_para(nw->show, nw);
238 }
239
240
241 static void last_para_sig(GSimpleAction *action, GVariant *parameter,
242                           gpointer vp)
243 {
244         NarrativeWindow *nw = vp;
245         sc_editor_set_cursor_para(nw->sceditor, -1);
246         pr_clock_set_pos(nw->pr_clock, sc_editor_get_cursor_para(nw->sceditor),
247                                        sc_editor_get_num_paras(nw->sceditor));
248         update_toolbar(nw);
249 }
250
251
252 static void open_notes_sig(GSimpleAction *action, GVariant *parameter, gpointer vp)
253 {
254 }
255
256
257 static void open_clock_sig(GSimpleAction *action, GVariant *parameter, gpointer vp)
258 {
259         NarrativeWindow *nw = vp;
260         nw->pr_clock = pr_clock_new();
261 }
262
263
264 static void testcard_sig(GSimpleAction *action, GVariant *parameter,
265                          gpointer vp)
266 {
267         NarrativeWindow *nw = vp;
268         show_testcard(nw->p);
269 }
270
271
272 static gint export_pdf_response_sig(GtkWidget *d, gint response,
273                                     struct presentation *p)
274 {
275        if ( response == GTK_RESPONSE_ACCEPT ) {
276                char *filename;
277                filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(d));
278                export_pdf(p, filename);
279                g_free(filename);
280        }
281
282        gtk_widget_destroy(d);
283
284        return 0;
285 }
286
287
288 static void exportpdf_sig(GSimpleAction *action, GVariant *parameter,
289                           gpointer vp)
290 {
291        struct presentation *p = vp;
292        GtkWidget *d;
293
294        d = gtk_file_chooser_dialog_new("Export PDF",
295                                        NULL,
296                                        GTK_FILE_CHOOSER_ACTION_SAVE,
297                                        "_Cancel", GTK_RESPONSE_CANCEL,
298                                        "_Export", GTK_RESPONSE_ACCEPT,
299                                        NULL);
300        gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(d),
301                                                       TRUE);
302
303        g_signal_connect(G_OBJECT(d), "response",
304                         G_CALLBACK(export_pdf_response_sig), p);
305
306        gtk_widget_show_all(d);
307 }
308
309
310
311 static gboolean button_press_sig(GtkWidget *da, GdkEventButton *event,
312                                  NarrativeWindow *nw)
313 {
314         return 0;
315 }
316
317
318 static void scroll_down(NarrativeWindow *nw)
319 {
320         gdouble inc, val;
321         GtkAdjustment *vadj;
322
323         vadj = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(nw->sceditor));
324         inc = gtk_adjustment_get_step_increment(GTK_ADJUSTMENT(vadj));
325         val = gtk_adjustment_get_value(GTK_ADJUSTMENT(vadj));
326         gtk_adjustment_set_value(GTK_ADJUSTMENT(vadj), inc+val);
327 }
328
329
330 static gboolean destroy_sig(GtkWidget *da, NarrativeWindow *nw)
331 {
332         g_application_release(nw->app);
333         return FALSE;
334 }
335
336
337 static gboolean key_press_sig(GtkWidget *da, GdkEventKey *event,
338                               NarrativeWindow *nw)
339 {
340         switch ( event->keyval ) {
341
342                 case GDK_KEY_B :
343                 case GDK_KEY_b :
344                 if ( nw->show != NULL ) {
345                         scroll_down(nw);
346                         return TRUE;
347                 }
348                 break;
349
350                 case GDK_KEY_Page_Up :
351                 if ( nw->show != NULL ) {
352                         ss_prev_para(nw->show, nw);
353                         return TRUE;
354                 }
355                 break;
356
357                 case GDK_KEY_Page_Down :
358                 if ( nw->show != NULL) {
359                         ss_next_para(nw->show, nw);
360                         return TRUE;
361                 }
362                 break;
363
364                 case GDK_KEY_Escape :
365                 if ( nw->show != NULL ) {
366                         gtk_widget_destroy(GTK_WIDGET(nw->show));
367                         return TRUE;
368                 }
369                 break;
370
371                 case GDK_KEY_F5 :
372                 if ( nw->show != NULL ) {
373                         /* Trap F5 so that full rerender does NOT happen */
374                         return TRUE;
375                 }
376
377         }
378
379         return FALSE;
380 }
381
382
383 static gboolean ss_destroy_sig(GtkWidget *da, NarrativeWindow *nw)
384 {
385         nw->show = NULL;
386         sc_editor_set_para_highlight(nw->sceditor, 0);
387         return FALSE;
388 }
389
390
391 static void start_slideshow_sig(GSimpleAction *action, GVariant *parameter,
392                                 gpointer vp)
393 {
394         NarrativeWindow *nw = vp;
395
396         if ( num_slides(nw->p) == 0 ) return;
397
398         nw->show = sc_slideshow_new(nw->p);
399
400         if ( nw->show == NULL ) return;
401
402         g_signal_connect(G_OBJECT(nw->show), "key-press-event",
403                  G_CALLBACK(key_press_sig), nw);
404         g_signal_connect(G_OBJECT(nw->show), "destroy",
405                  G_CALLBACK(ss_destroy_sig), nw);
406         sc_slideshow_set_slide(nw->show, first_slide(nw->p));
407         sc_editor_set_para_highlight(nw->sceditor, 1);
408         sc_editor_set_cursor_para(nw->sceditor, 0);
409         update_toolbar(nw);
410 }
411
412
413 static void nw_update_titlebar(NarrativeWindow *nw)
414 {
415         get_titlebar_string(nw->p);
416
417         if ( nw->p->slidewindow != NULL ) {
418
419                 char *title;
420
421                 title = malloc(strlen(nw->p->titlebar)+14);
422                 sprintf(title, "%s - Colloquium", nw->p->titlebar);
423                 gtk_window_set_title(GTK_WINDOW(nw->window), title);
424                 free(title);
425
426        }
427
428 }
429
430
431 static SCBlock *narrative_stylesheet()
432 {
433         return sc_parse("\\stylesheet{"
434                         "\\ss[slide]{\\callback[sthumb]}"
435                         "}");
436 }
437
438
439 static int create_thumbnail(SCInterpreter *scin, SCBlock *bl,
440                             double *w, double *h, void **bvp, void *vp)
441 {
442         SCBlock *b;
443
444         *w = 320.0;
445         *h = 256.0;
446         b = sc_interp_get_macro_real_block(scin);
447
448         *bvp = b;
449
450         return 1;
451 }
452
453
454 static cairo_surface_t *render_thumbnail(int w, int h, void *bvp, void *vp)
455 {
456         struct presentation *p = vp;
457         SCBlock *scblocks = bvp;
458         cairo_surface_t *surf;
459         SCBlock *stylesheets[2];
460         struct frame *top;
461
462         scblocks = sc_block_child(scblocks);
463         stylesheets[0] = p->stylesheet;
464         stylesheets[1] = NULL;
465         /* FIXME: Cache like crazy here */
466         surf = render_sc(scblocks, w, h, 1024.0, 768.0, stylesheets, NULL,
467                          p->is, ISZ_THUMBNAIL, 0, &top, p->lang);
468         frame_free(top);
469
470         return surf;
471 }
472
473
474 static int click_thumbnail(double x, double y, void *bvp, void *vp)
475 {
476         struct presentation *p = vp;
477         SCBlock *scblocks = bvp;
478
479         slide_window_open(p, scblocks, p->narrative_window->app);
480
481         return 0;
482 }
483
484
485 GActionEntry nw_entries[] = {
486
487         { "save", save_sig, NULL, NULL, NULL },
488         { "saveas", saveas_sig, NULL, NULL, NULL },
489         { "sorter", open_slidesorter_sig, NULL, NULL, NULL },
490         { "deleteslide", delete_slide_sig, NULL, NULL, NULL },
491         { "slide", add_slide_sig, NULL, NULL, NULL },
492         { "startslideshow", start_slideshow_sig, NULL, NULL, NULL },
493         { "notes", open_notes_sig, NULL, NULL, NULL },
494         { "clock", open_clock_sig, NULL, NULL, NULL },
495         { "testcard", testcard_sig, NULL, NULL, NULL },
496         { "first", first_para_sig, NULL, NULL, NULL },
497         { "prev", prev_para_sig, NULL, NULL, NULL },
498         { "next", next_para_sig, NULL, NULL, NULL },
499         { "last", last_para_sig, NULL, NULL, NULL },
500 };
501
502
503 GActionEntry nw_entries_p[] = {
504         { "print", print_sig, NULL, NULL, NULL  },
505         { "exportpdf", exportpdf_sig, NULL, NULL, NULL  },
506 };
507
508
509 NarrativeWindow *narrative_window_new(struct presentation *p, GApplication *app)
510 {
511         NarrativeWindow *nw;
512         GtkWidget *vbox;
513         GtkWidget *scroll;
514         GtkWidget *toolbar;
515         GtkToolItem *button;
516         SCBlock *stylesheets[3];
517         SCCallbackList *cbl;
518         GtkWidget *image;
519
520         if ( p->narrative_window != NULL ) {
521                 fprintf(stderr, "Narrative window is already open!\n");
522                 return NULL;
523         }
524
525         nw = calloc(1, sizeof(NarrativeWindow));
526         if ( nw == NULL ) return NULL;
527
528         nw->app = app;
529         nw->p = p;
530
531         nw->window = gtk_application_window_new(GTK_APPLICATION(app));
532         p->narrative_window = nw;
533
534         g_action_map_add_action_entries(G_ACTION_MAP(nw->window), nw_entries,
535                                         G_N_ELEMENTS(nw_entries), nw);
536         g_action_map_add_action_entries(G_ACTION_MAP(nw->window), nw_entries_p,
537                                         G_N_ELEMENTS(nw_entries_p), p);
538
539         nw_update_titlebar(nw);
540
541         vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
542         gtk_container_add(GTK_CONTAINER(nw->window), vbox);
543
544         if ( p->stylesheet != NULL ) {
545                 stylesheets[0] = p->stylesheet;
546                 stylesheets[1] = narrative_stylesheet();
547                 stylesheets[2] = NULL;
548         } else {
549                 stylesheets[0] = narrative_stylesheet();
550                 stylesheets[1] = NULL;
551         }
552
553         if ( nw->p->scblocks == NULL ) {
554                 nw->p->scblocks = sc_parse("");
555         }
556
557         nw->sceditor = sc_editor_new(nw->p->scblocks, stylesheets, p->lang);
558         cbl = sc_callback_list_new();
559         sc_callback_list_add_callback(cbl, "sthumb", create_thumbnail,
560                                       render_thumbnail, click_thumbnail, p);
561         sc_editor_set_callbacks(nw->sceditor, cbl);
562
563         toolbar = gtk_toolbar_new();
564         gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_ICONS);
565         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(toolbar), FALSE, FALSE, 0);
566
567         /* Fullscreen */
568         image = gtk_image_new_from_icon_name("view-fullscreen",
569                                              GTK_ICON_SIZE_LARGE_TOOLBAR);
570         button = gtk_tool_button_new(image, "Start slideshow");
571         gtk_actionable_set_action_name(GTK_ACTIONABLE(button),
572                                        "win.startslideshow");
573         gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(button));
574
575         button = gtk_separator_tool_item_new();
576         gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(button));
577
578         /* Add slide */
579         image = gtk_image_new_from_icon_name("list-add",
580                                              GTK_ICON_SIZE_LARGE_TOOLBAR);
581         button = gtk_tool_button_new(image, "Add slide");
582         gtk_actionable_set_action_name(GTK_ACTIONABLE(button),
583                                        "win.slide");
584         gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(button));
585
586         button = gtk_separator_tool_item_new();
587         gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(button));
588
589         image = gtk_image_new_from_icon_name("go-top",
590                                              GTK_ICON_SIZE_LARGE_TOOLBAR);
591         nw->bfirst = gtk_tool_button_new(image, "First slide");
592         gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(nw->bfirst));
593         gtk_actionable_set_action_name(GTK_ACTIONABLE(nw->bfirst),
594                                        "win.first");
595
596         image = gtk_image_new_from_icon_name("go-up",
597                                              GTK_ICON_SIZE_LARGE_TOOLBAR);
598         nw->bprev = gtk_tool_button_new(image, "Previous slide");
599         gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(nw->bprev));
600         gtk_actionable_set_action_name(GTK_ACTIONABLE(nw->bprev),
601                                        "win.prev");
602
603         image = gtk_image_new_from_icon_name("go-down",
604                                              GTK_ICON_SIZE_LARGE_TOOLBAR);
605         nw->bnext = gtk_tool_button_new(image, "Next slide");
606         gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(nw->bnext));
607         gtk_actionable_set_action_name(GTK_ACTIONABLE(nw->bnext),
608                                        "win.next");
609
610         image = gtk_image_new_from_icon_name("go-bottom",
611                                              GTK_ICON_SIZE_LARGE_TOOLBAR);
612         nw->blast = gtk_tool_button_new(image, "Last slide");
613         gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(nw->blast));
614         gtk_actionable_set_action_name(GTK_ACTIONABLE(nw->blast),
615                                        "win.last");
616
617         update_toolbar(nw);
618
619         scroll = gtk_scrolled_window_new(NULL, NULL);
620         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
621                                        GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
622         gtk_container_add(GTK_CONTAINER(scroll), GTK_WIDGET(nw->sceditor));
623
624         sc_editor_set_flow(nw->sceditor, 1);
625         sc_editor_set_background(nw->sceditor, 0.9, 0.9, 0.9);
626         sc_editor_set_min_border(nw->sceditor, 0.0);
627         sc_editor_set_top_frame_editable(nw->sceditor, 1);
628
629         g_signal_connect(G_OBJECT(nw->sceditor), "button-press-event",
630                          G_CALLBACK(button_press_sig), nw);
631         g_signal_connect(G_OBJECT(nw->sceditor), "key-press-event",
632                          G_CALLBACK(key_press_sig), nw);
633         g_signal_connect(G_OBJECT(nw->window), "destroy",
634                          G_CALLBACK(destroy_sig), nw);
635
636         gtk_window_set_default_size(GTK_WINDOW(nw->window), 768, 768);
637         gtk_box_pack_start(GTK_BOX(vbox), scroll, TRUE, TRUE, 0);
638         gtk_container_set_focus_child(GTK_CONTAINER(nw->window),
639                                       GTK_WIDGET(nw->sceditor));
640
641         gtk_widget_show_all(nw->window);
642         nw->app = app;
643         g_application_hold(app);
644
645         return nw;
646 }