Update comments
[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         SlideShow           *show;
54         SCBlock             *sel_slide;
55 };
56
57
58 static void update_toolbar(NarrativeWindow *nw)
59 {
60         int cur_slide_number;
61
62         cur_slide_number = slide_number(nw->p, nw->sel_slide);
63         if ( cur_slide_number == 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_slide_number == num_slides(nw->p)-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_frame_sig(GSimpleAction *action, GVariant *parameter,
144                              gpointer vp)
145 {
146 }
147
148
149 static void add_slide_sig(GSimpleAction *action, GVariant *parameter,
150                           gpointer vp)
151 {
152         SCBlock *nsblock;
153         NarrativeWindow *nw = vp;
154
155         /* Split the current paragraph */
156         nsblock = split_paragraph_at_cursor(nw->sceditor);
157
158         /* Link the new SCBlock in */
159         if ( nsblock != NULL ) {
160                 sc_block_append(nsblock, "slide", NULL, NULL, NULL);
161         } else {
162                 fprintf(stderr, "Failed to split paragraph\n");
163         }
164
165         sc_editor_set_scblock(nw->sceditor,
166                               sc_editor_get_scblock(nw->sceditor));
167 }
168
169
170 static void ss_end_show(SlideShow *ss, void *vp)
171 {
172         NarrativeWindow *nw = vp;
173         nw->show = NULL;
174 }
175
176
177 static void ss_next_slide(SlideShow *ss, void *vp)
178 {
179         NarrativeWindow *nw = vp;
180         SCBlock *tt;
181
182         tt = next_slide(nw->p, nw->sel_slide);
183         if ( tt == NULL ) return;  /* Already on last slide */
184         nw->sel_slide = tt;
185         if ( slideshow_linked(nw->show) ) {
186                 change_proj_slide(nw->show, nw->sel_slide);
187         } /* else leave the slideshow alone */
188         update_toolbar(nw);
189 }
190
191
192 static void ss_prev_slide(SlideShow *ss, void *vp)
193 {
194         NarrativeWindow *nw = vp;
195         SCBlock *tt;
196
197         tt = prev_slide(nw->p, nw->sel_slide);
198         if ( tt == NULL ) return;  /* Already on first slide */
199         nw->sel_slide = tt;
200         if ( slideshow_linked(nw->show) ) {
201                 change_proj_slide(nw->show, nw->sel_slide);
202         } /* else leave the slideshow alone */
203         update_toolbar(nw);
204 }
205
206
207 static void first_slide_sig(GSimpleAction *action, GVariant *parameter,
208                             gpointer vp)
209 {
210         NarrativeWindow *nw = vp;
211         SCBlock *tt;
212
213         tt = first_slide(nw->p);
214         if ( tt == NULL ) return;  /* Fail */
215         nw->sel_slide = tt;
216         if ( slideshow_linked(nw->show) ) {
217                 change_proj_slide(nw->show, nw->sel_slide);
218         } /* else leave the slideshow alone */
219         update_toolbar(nw);
220 }
221
222
223 static void prev_slide_sig(GSimpleAction *action, GVariant *parameter,
224                            gpointer vp)
225 {
226         ss_prev_slide(NULL, vp);
227 }
228
229
230 static void next_slide_sig(GSimpleAction *action, GVariant *parameter,
231                            gpointer vp)
232 {
233         ss_next_slide(NULL, vp);
234 }
235
236
237 static void last_slide_sig(GSimpleAction *action, GVariant *parameter,
238                            gpointer vp)
239 {
240         NarrativeWindow *nw = vp;
241         SCBlock *tt;
242
243         tt = last_slide(nw->p);
244         if ( tt == NULL ) return;  /* Fail */
245         nw->sel_slide = tt;
246         if ( slideshow_linked(nw->show) ) {
247                 change_proj_slide(nw->show, nw->sel_slide);
248         } /* else leave the slideshow alone */
249         update_toolbar(nw);
250 }
251
252
253 static void ss_changed_link(SlideShow *ss, void *vp)
254 {
255 }
256
257
258 static SCBlock *ss_cur_slide(SlideShow *ss, void *vp)
259 {
260         NarrativeWindow *nw = vp;
261         return nw->sel_slide;
262 }
263
264
265 static void start_slideshow_sig(GSimpleAction *action, GVariant *parameter,
266                                 gpointer vp)
267 {
268         NarrativeWindow *nw = vp;
269         struct sscontrolfuncs ssc;
270
271         if ( num_slides(nw->p) == 0 ) return;
272
273         ssc.next_slide = ss_next_slide;
274         ssc.prev_slide = ss_prev_slide;
275         ssc.current_slide = ss_cur_slide;
276         ssc.changed_link = ss_changed_link;
277         ssc.end_show = ss_end_show;
278
279         nw->sel_slide = first_slide(nw->p);
280
281         nw->show = try_start_slideshow(nw->p, ssc, nw);
282 }
283
284
285 static void open_notes_sig(GSimpleAction *action, GVariant *parameter, gpointer vp)
286 {
287 }
288
289
290 static void open_clock_sig(GSimpleAction *action, GVariant *parameter, gpointer vp)
291 {
292         NarrativeWindow *nw = vp;
293         open_clock(nw->p);
294 }
295
296
297 static void testcard_sig(GSimpleAction *action, GVariant *parameter,
298                          gpointer vp)
299 {
300         NarrativeWindow *nw = vp;
301         show_testcard(nw->p);
302 }
303
304
305 static gint export_pdf_response_sig(GtkWidget *d, gint response,
306                                     struct presentation *p)
307 {
308        if ( response == GTK_RESPONSE_ACCEPT ) {
309                char *filename;
310                filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(d));
311                export_pdf(p, filename);
312                g_free(filename);
313        }
314
315        gtk_widget_destroy(d);
316
317        return 0;
318 }
319
320
321 static void exportpdf_sig(GSimpleAction *action, GVariant *parameter,
322                           gpointer vp)
323 {
324        struct presentation *p = vp;
325        GtkWidget *d;
326
327        d = gtk_file_chooser_dialog_new("Export PDF",
328                                        NULL,
329                                        GTK_FILE_CHOOSER_ACTION_SAVE,
330                                        "_Cancel", GTK_RESPONSE_CANCEL,
331                                        "_Export", GTK_RESPONSE_ACCEPT,
332                                        NULL);
333        gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(d),
334                                                       TRUE);
335
336        g_signal_connect(G_OBJECT(d), "response",
337                         G_CALLBACK(export_pdf_response_sig), p);
338
339        gtk_widget_show_all(d);
340 }
341
342
343
344 GActionEntry nw_entries[] = {
345
346         { "save", save_sig, NULL, NULL, NULL },
347         { "saveas", saveas_sig, NULL, NULL, NULL },
348         { "sorter", open_slidesorter_sig, NULL, NULL, NULL },
349         { "deleteframe", delete_frame_sig, NULL, NULL, NULL },
350         { "slide", add_slide_sig, NULL, NULL, NULL },
351         { "startslideshow", start_slideshow_sig, NULL, NULL, NULL },
352         { "notes", open_notes_sig, NULL, NULL, NULL },
353         { "clock", open_clock_sig, NULL, NULL, NULL },
354         { "testcard", testcard_sig, NULL, NULL, NULL },
355         { "first", first_slide_sig, NULL, NULL, NULL },
356         { "prev", prev_slide_sig, NULL, NULL, NULL },
357         { "next", next_slide_sig, NULL, NULL, NULL },
358         { "last", last_slide_sig, NULL, NULL, NULL },
359 };
360
361
362 GActionEntry nw_entries_p[] = {
363         { "print", print_sig, NULL, NULL, NULL  },
364         { "exportpdf", exportpdf_sig, NULL, NULL, NULL  },
365 };
366
367
368 static gboolean button_press_sig(GtkWidget *da, GdkEventButton *event,
369                                  NarrativeWindow *nw)
370 {
371         return 0;
372 }
373
374
375 static void scroll_down(NarrativeWindow *nw)
376 {
377         gdouble inc, val;
378         GtkAdjustment *vadj;
379
380         vadj = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(nw->sceditor));
381         inc = gtk_adjustment_get_step_increment(GTK_ADJUSTMENT(vadj));
382         val = gtk_adjustment_get_value(GTK_ADJUSTMENT(vadj));
383         gtk_adjustment_set_value(GTK_ADJUSTMENT(vadj), inc+val);
384 }
385
386
387 static gboolean destroy_sig(GtkWidget *da, NarrativeWindow *nw)
388 {
389         g_application_release(nw->app);
390         return FALSE;
391 }
392
393
394 static gboolean key_press_sig(GtkWidget *da, GdkEventKey *event,
395                               NarrativeWindow *nw)
396 {
397         switch ( event->keyval ) {
398
399                 case GDK_KEY_B :
400                 case GDK_KEY_b :
401                 if ( nw->show != NULL ) {
402                         scroll_down(nw);
403                         return TRUE;
404                 }
405                 break;
406
407                 case GDK_KEY_Page_Up :
408                 if ( nw->show != NULL ) {
409                         ss_prev_slide(nw->show, nw);
410                         return TRUE;
411                 }
412                 break;
413
414                 case GDK_KEY_Page_Down :
415                 if ( nw->show != NULL) {
416                         ss_next_slide(nw->show, nw);
417                         return TRUE;
418                 }
419                 break;
420
421         }
422
423         return FALSE;
424 }
425
426
427 static void nw_update_titlebar(NarrativeWindow *nw)
428 {
429         get_titlebar_string(nw->p);
430
431         if ( nw->p->slidewindow != NULL ) {
432
433                 char *title;
434
435                 title = malloc(strlen(nw->p->titlebar)+14);
436                 sprintf(title, "%s - Colloquium", nw->p->titlebar);
437                 gtk_window_set_title(GTK_WINDOW(nw->window), title);
438                 free(title);
439
440        }
441
442 }
443
444
445 static SCBlock *narrative_stylesheet()
446 {
447         return sc_parse("\\stylesheet{"
448                         "\\ss[slide]{\\callback[sthumb]}"
449                         "}");
450 }
451
452
453 static int create_thumbnail(SCInterpreter *scin, SCBlock *bl,
454                             double *w, double *h, void **bvp, void *vp)
455 {
456         SCBlock *b;
457
458         *w = 320.0;
459         *h = 256.0;
460         b = sc_interp_get_macro_real_block(scin);
461
462         *bvp = b;
463
464         return 1;
465 }
466
467
468 static cairo_surface_t *render_thumbnail(int w, int h, void *bvp, void *vp)
469 {
470         struct presentation *p = vp;
471         SCBlock *scblocks = bvp;
472         cairo_surface_t *surf;
473         SCBlock *stylesheets[2];
474         struct frame *top;
475
476         scblocks = sc_block_child(scblocks);
477         stylesheets[0] = p->stylesheet;
478         stylesheets[1] = NULL;
479         /* FIXME: Cache like crazy here */
480         surf = render_sc(scblocks, w, h, 1024.0, 768.0, stylesheets, NULL,
481                          p->is, ISZ_THUMBNAIL, 0, &top, p->lang);
482         frame_free(top);
483
484         return surf;
485 }
486
487
488 static int click_thumbnail(double x, double y, void *bvp, void *vp)
489 {
490         struct presentation *p = vp;
491         SCBlock *scblocks = bvp;
492
493         slide_window_open(p, scblocks, p->narrative_window->app);
494
495         return 0;
496 }
497
498
499 NarrativeWindow *narrative_window_new(struct presentation *p, GApplication *app)
500 {
501         NarrativeWindow *nw;
502         GtkWidget *vbox;
503         GtkWidget *scroll;
504         GtkWidget *toolbar;
505         GtkToolItem *button;
506         SCBlock *stylesheets[3];
507         SCCallbackList *cbl;
508         GtkWidget *image;
509
510         if ( p->narrative_window != NULL ) {
511                 fprintf(stderr, "Narrative window is already open!\n");
512                 return NULL;
513         }
514
515         nw = calloc(1, sizeof(NarrativeWindow));
516         if ( nw == NULL ) return NULL;
517
518         nw->app = app;
519         nw->p = p;
520
521         nw->window = gtk_application_window_new(GTK_APPLICATION(app));
522         p->narrative_window = nw;
523
524         g_action_map_add_action_entries(G_ACTION_MAP(nw->window), nw_entries,
525                                         G_N_ELEMENTS(nw_entries), nw);
526         g_action_map_add_action_entries(G_ACTION_MAP(nw->window), nw_entries_p,
527                                         G_N_ELEMENTS(nw_entries_p), p);
528
529         nw_update_titlebar(nw);
530
531         vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
532         gtk_container_add(GTK_CONTAINER(nw->window), vbox);
533
534         if ( p->stylesheet != NULL ) {
535                 stylesheets[0] = p->stylesheet;
536                 stylesheets[1] = narrative_stylesheet();
537                 stylesheets[2] = NULL;
538         } else {
539                 stylesheets[0] = narrative_stylesheet();
540                 stylesheets[1] = NULL;
541         }
542
543         if ( nw->p->scblocks == NULL ) {
544                 nw->p->scblocks = sc_parse("");
545         }
546
547         nw->sceditor = sc_editor_new(nw->p->scblocks, stylesheets, p->lang);
548         cbl = sc_callback_list_new();
549         sc_callback_list_add_callback(cbl, "sthumb", create_thumbnail,
550                                       render_thumbnail, click_thumbnail, p);
551         sc_editor_set_callbacks(nw->sceditor, cbl);
552
553         toolbar = gtk_toolbar_new();
554         gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_ICONS);
555         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(toolbar), FALSE, FALSE, 0);
556
557         /* Fullscreen */
558         image = gtk_image_new_from_icon_name("view-fullscreen",
559                                              GTK_ICON_SIZE_LARGE_TOOLBAR);
560         button = gtk_tool_button_new(image, "Start slideshow");
561         gtk_actionable_set_action_name(GTK_ACTIONABLE(button),
562                                        "win.startslideshow");
563         gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(button));
564
565         button = gtk_separator_tool_item_new();
566         gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(button));
567
568         /* Add slide */
569         image = gtk_image_new_from_icon_name("add",
570                                              GTK_ICON_SIZE_LARGE_TOOLBAR);
571         button = gtk_tool_button_new(image, "Add slide");
572         gtk_actionable_set_action_name(GTK_ACTIONABLE(button),
573                                        "win.slide");
574         gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(button));
575
576         button = gtk_separator_tool_item_new();
577         gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(button));
578
579         /* Change slide.  FIXME: LTR vs RTL */
580         image = gtk_image_new_from_icon_name("gtk-goto-first-ltr",
581                                              GTK_ICON_SIZE_LARGE_TOOLBAR);
582         nw->bfirst = gtk_tool_button_new(image, "First slide");
583         gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(nw->bfirst));
584         gtk_actionable_set_action_name(GTK_ACTIONABLE(nw->bfirst),
585                                        "win.first");
586
587         image = gtk_image_new_from_icon_name("gtk-go-back-ltr",
588                                              GTK_ICON_SIZE_LARGE_TOOLBAR);
589         nw->bprev = gtk_tool_button_new(image, "Previous slide");
590         gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(nw->bprev));
591         gtk_actionable_set_action_name(GTK_ACTIONABLE(nw->bprev),
592                                        "win.prev");
593
594         image = gtk_image_new_from_icon_name("gtk-go-forward-ltr",
595                                              GTK_ICON_SIZE_LARGE_TOOLBAR);
596         nw->bnext = gtk_tool_button_new(image, "Next slide");
597         gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(nw->bnext));
598         gtk_actionable_set_action_name(GTK_ACTIONABLE(nw->bnext),
599                                        "win.next");
600
601         image = gtk_image_new_from_icon_name("gtk-goto-last-ltr",
602                                              GTK_ICON_SIZE_LARGE_TOOLBAR);
603         nw->blast = gtk_tool_button_new(image, "Last slide");
604         gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(nw->blast));
605         gtk_actionable_set_action_name(GTK_ACTIONABLE(nw->blast),
606                                        "win.last");
607
608         nw->sel_slide = NULL;
609         update_toolbar(nw);
610
611         scroll = gtk_scrolled_window_new(NULL, NULL);
612         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
613                                        GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
614         gtk_container_add(GTK_CONTAINER(scroll), GTK_WIDGET(nw->sceditor));
615
616         sc_editor_set_flow(nw->sceditor, 1);
617         sc_editor_set_background(nw->sceditor, 0.9, 0.9, 0.9);
618         sc_editor_set_min_border(nw->sceditor, 0.0);
619         sc_editor_set_top_frame_editable(nw->sceditor, 1);
620
621         g_signal_connect(G_OBJECT(nw->sceditor), "button-press-event",
622                          G_CALLBACK(button_press_sig), nw);
623         g_signal_connect(G_OBJECT(nw->sceditor), "key-press-event",
624                          G_CALLBACK(key_press_sig), nw);
625         g_signal_connect(G_OBJECT(nw->window), "destroy",
626                          G_CALLBACK(destroy_sig), nw);
627
628         gtk_window_set_default_size(GTK_WINDOW(nw->window), 768, 768);
629         gtk_box_pack_start(GTK_BOX(vbox), scroll, TRUE, TRUE, 0);
630
631         gtk_widget_show_all(nw->window);
632         nw->app = app;
633         g_application_hold(app);
634
635         return nw;
636 }