Comments
[colloquium.git] / src / narrative_window.c
1 /*
2  * narrative_window.c
3  *
4  * Copyright © 2014-2015 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
41
42 struct _narrative_window
43 {
44         GtkWidget *window;
45         GtkAdjustment *vadj;
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         struct slide        *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 == nw->p->num_slides-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 exportpdf_sig(GSimpleAction *action, GVariant *parameter,
138                           gpointer vp)
139 {
140 }
141
142
143 static void open_slidesorter_sig(GSimpleAction *action, GVariant *parameter, gpointer vp)
144 {
145 }
146
147
148 static void delete_frame_sig(GSimpleAction *action, GVariant *parameter, gpointer vp)
149 {
150 }
151
152
153 static void add_slide_sig(GSimpleAction *action, GVariant *parameter, gpointer vp)
154 {
155         struct slide *slide;
156         int n_slides;
157         SCBlock *block;
158         SCBlock *nsblock;
159         NarrativeWindow *nw = vp;
160
161         slide = new_slide();
162
163         /* Link it into the SC structure */
164         nsblock = sc_parse("\\slide{}");
165         insert_scblock(nsblock, nw->sceditor);
166
167         /* Iterate over blocks of presentation, counting \slides, until
168          * we reach the block we just added */
169         block = nw->p->scblocks;
170         n_slides = 0;
171         while ( block != NULL ) {
172                 const char *n = sc_block_name(block);
173                 if ( n == NULL ) goto next;
174                 if ( strcmp(n, "slide") == 0 ) {
175                         if ( block == nsblock ) break;
176                         n_slides++;
177                 }
178 next:
179                 block = sc_block_next(block);
180         }
181         slide->scblocks = sc_block_child(nsblock);
182         insert_slide(nw->p, slide, n_slides);
183 }
184
185
186 static void ss_end_show(SlideShow *ss, void *vp)
187 {
188         NarrativeWindow *nw = vp;
189         nw->show = NULL;
190 }
191
192
193 static void ss_next_slide(SlideShow *ss, void *vp)
194 {
195         NarrativeWindow *nw = vp;
196         int cur_slide_number;
197         cur_slide_number = slide_number(nw->p, nw->sel_slide);
198         if ( cur_slide_number == nw->p->num_slides-1 ) return;
199         nw->sel_slide = nw->p->slides[cur_slide_number+1];
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 ss_prev_slide(SlideShow *ss, void *vp)
208 {
209         NarrativeWindow *nw = vp;
210         int cur_slide_number;
211         cur_slide_number = slide_number(nw->p, nw->sel_slide);
212         if ( cur_slide_number == 0 ) return;
213         nw->sel_slide = nw->p->slides[cur_slide_number-1];
214         if ( slideshow_linked(nw->show) ) {
215                 change_proj_slide(nw->show, nw->sel_slide);
216         } /* else leave the slideshow alone */
217         update_toolbar(nw);
218 }
219
220
221 static void first_slide_sig(GSimpleAction *action, GVariant *parameter,
222                            gpointer vp)
223 {
224         //NarrativeWindow *nw = vp;
225 }
226
227
228 static void prev_slide_sig(GSimpleAction *action, GVariant *parameter,
229                            gpointer vp)
230 {
231         //NarrativeWindow *nw = vp;
232         //ss_prev_slide(NULL, vp);
233 }
234
235
236 static void next_slide_sig(GSimpleAction *action, GVariant *parameter,
237                            gpointer vp)
238 {
239         //NarrativeWindow *nw = vp;
240         //ss_next_slide(NULL, vp);
241 }
242
243
244 static void last_slide_sig(GSimpleAction *action, GVariant *parameter,
245                            gpointer vp)
246 {
247         //NarrativeWindow *nw = vp;
248 }
249
250
251 static void ss_changed_link(SlideShow *ss, void *vp)
252 {
253 }
254
255
256 static struct slide *ss_cur_slide(SlideShow *ss, void *vp)
257 {
258         NarrativeWindow *nw = vp;
259         return nw->sel_slide;
260 }
261
262
263 static void start_slideshow_sig(GSimpleAction *action, GVariant *parameter,
264                                 gpointer vp)
265 {
266         NarrativeWindow *nw = vp;
267         struct sscontrolfuncs ssc;
268
269         if ( nw->p->num_slides == 0 ) return;
270
271         ssc.next_slide = ss_next_slide;
272         ssc.prev_slide = ss_prev_slide;
273         ssc.current_slide = ss_cur_slide;
274         ssc.changed_link = ss_changed_link;
275         ssc.end_show = ss_end_show;
276
277         nw->sel_slide = nw->p->slides[0];
278
279         nw->show = try_start_slideshow(nw->p, ssc, nw);
280 }
281
282
283 static void open_notes_sig(GSimpleAction *action, GVariant *parameter, gpointer vp)
284 {
285 }
286
287
288 static void open_clock_sig(GSimpleAction *action, GVariant *parameter, gpointer vp)
289 {
290         NarrativeWindow *nw = vp;
291         open_clock(nw->p);
292 }
293
294
295 static void testcard_sig(GSimpleAction *action, GVariant *parameter,
296                          gpointer vp)
297 {
298         NarrativeWindow *nw = vp;
299         show_testcard(nw->p);
300 }
301
302
303 GActionEntry nw_entries[] = {
304
305         { "save", save_sig, NULL, NULL, NULL },
306         { "saveas", saveas_sig, NULL, NULL, NULL },
307         { "exportpdf", exportpdf_sig, NULL, NULL, NULL  },
308         { "sorter", open_slidesorter_sig, NULL, NULL, NULL },
309         { "deleteframe", delete_frame_sig, NULL, NULL, NULL },
310         { "slide", add_slide_sig, NULL, NULL, NULL },
311         { "startslideshow", start_slideshow_sig, NULL, NULL, NULL },
312         { "notes", open_notes_sig, NULL, NULL, NULL },
313         { "clock", open_clock_sig, NULL, NULL, NULL },
314         { "testcard", testcard_sig, NULL, NULL, NULL },
315         { "first", first_slide_sig, NULL, NULL, NULL },
316         { "prev", prev_slide_sig, NULL, NULL, NULL },
317         { "next", next_slide_sig, NULL, NULL, NULL },
318         { "last", last_slide_sig, NULL, NULL, NULL },
319 };
320
321
322 static gboolean button_press_sig(GtkWidget *da, GdkEventButton *event,
323                                  NarrativeWindow *nw)
324 {
325         if ( event->type == GDK_2BUTTON_PRESS ) {
326                 nw->p->slidewindow = slide_window_open(nw->p, nw->app);
327         }
328
329         return 0;
330 }
331
332
333 static void scroll_down(NarrativeWindow *nw)
334 {
335 /* FIXME: Implement via SCEditor */
336 #if 0
337         gdouble inc, val;
338         inc = gtk_adjustment_get_step_increment(GTK_ADJUSTMENT(nw->vadj));
339         val = gtk_adjustment_get_value(GTK_ADJUSTMENT(nw->vadj));
340         gtk_adjustment_set_value(GTK_ADJUSTMENT(nw->vadj), inc+val);
341 #endif
342 }
343
344
345 static gboolean key_press_sig(GtkWidget *da, GdkEventKey *event,
346                               NarrativeWindow *nw)
347 {
348         switch ( event->keyval ) {
349
350                 case GDK_KEY_B :
351                 case GDK_KEY_b :
352                 if ( nw->show != NULL ) {
353                         scroll_down(nw);
354                         return TRUE;
355                 }
356                 break;
357
358                 case GDK_KEY_Page_Up :
359                 if ( nw->show != NULL ) {
360                         ss_prev_slide(nw->show, nw);
361                         return TRUE;
362                 }
363                 break;
364
365                 case GDK_KEY_Page_Down :
366                 if ( nw->show != NULL) {
367                         ss_next_slide(nw->show, nw);
368                         return TRUE;
369                 }
370                 break;
371
372         }
373
374         return FALSE;
375 }
376
377
378 static void nw_update_titlebar(NarrativeWindow *nw)
379 {
380         get_titlebar_string(nw->p);
381
382         if ( nw->p->slidewindow != NULL ) {
383
384                 char *title;
385
386                 title = malloc(strlen(nw->p->titlebar)+14);
387                 sprintf(title, "%s - Colloquium", nw->p->titlebar);
388                 gtk_window_set_title(GTK_WINDOW(nw->window), title);
389                 free(title);
390
391        }
392
393 }
394
395
396 static SCBlock *narrative_stylesheet()
397 {
398         return sc_parse("\\stylesheet{\\ss[slide]{\\callback[sthumb]}}");
399 }
400
401
402 static int create_thumbnail(SCInterpreter *scin, SCBlock *bl,
403                             double *w, double *h, void **bvp, void *vp)
404 {
405         SCBlock *b;
406
407         *w = 320.0;
408         *h = 256.0;
409         b = sc_interp_get_macro_real_block(scin);
410
411         *bvp = b;
412
413         return 1;
414 }
415
416
417 static cairo_surface_t *render_thumbnail(int w, int h, void *bvp, void *vp)
418 {
419         struct presentation *p = vp;
420         SCBlock *scblocks = bvp;
421         cairo_surface_t *surf;
422         SCBlock *stylesheets[2];
423         struct frame *top;
424
425         scblocks = sc_block_child(scblocks);
426         stylesheets[0] = p->stylesheet;
427         stylesheets[1] = NULL;
428         /* FIXME: Cache like crazy here */
429         surf = render_sc(scblocks, w, h, 1024.0, 768.0, stylesheets, NULL,
430                          p->is, ISZ_THUMBNAIL, 0, &top);
431         frame_free(top);
432
433         return surf;
434 }
435
436
437 NarrativeWindow *narrative_window_new(struct presentation *p, GApplication *app)
438 {
439         NarrativeWindow *nw;
440         GtkWidget *vbox;
441         GtkWidget *scroll;
442         GtkWidget *toolbar;
443         GtkToolItem *button;
444         SCBlock *stylesheets[3];
445         SCCallbackList *cbl;
446         GtkWidget *image;
447
448         if ( p->narrative_window != NULL ) {
449                 fprintf(stderr, "Narrative window is already open!\n");
450                 return NULL;
451         }
452
453         nw = calloc(1, sizeof(NarrativeWindow));
454         if ( nw == NULL ) return NULL;
455
456         nw->app = app;
457         nw->p = p;
458
459         nw->window = gtk_application_window_new(GTK_APPLICATION(app));
460         p->narrative_window = nw;
461
462         g_action_map_add_action_entries(G_ACTION_MAP(nw->window), nw_entries,
463                                         G_N_ELEMENTS(nw_entries), nw);
464
465         nw_update_titlebar(nw);
466
467         vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
468         gtk_container_add(GTK_CONTAINER(nw->window), vbox);
469
470         stylesheets[0] = p->stylesheet;
471         stylesheets[1] = narrative_stylesheet();
472         stylesheets[2] = NULL;
473         nw->sceditor = sc_editor_new(nw->p->scblocks, stylesheets);
474         cbl = sc_callback_list_new();
475         sc_callback_list_add_callback(cbl, "sthumb", create_thumbnail,
476                                       render_thumbnail, p);
477         sc_editor_set_callbacks(nw->sceditor, cbl);
478
479         toolbar = gtk_toolbar_new();
480         gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_ICONS);
481         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(toolbar), FALSE, FALSE, 0);
482
483         /* Fullscreen */
484         image = gtk_image_new_from_icon_name("view-fullscreen",
485                                              GTK_ICON_SIZE_LARGE_TOOLBAR);
486         button = gtk_tool_button_new(image, "Start slideshow");
487         gtk_actionable_set_action_name(GTK_ACTIONABLE(button),
488                                        "win.startslideshow");
489         gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(button));
490
491         button = gtk_separator_tool_item_new();
492         gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(button));
493
494         /* Add slide */
495         image = gtk_image_new_from_icon_name("add",
496                                              GTK_ICON_SIZE_LARGE_TOOLBAR);
497         button = gtk_tool_button_new(image, "Add slide");
498         gtk_actionable_set_action_name(GTK_ACTIONABLE(button),
499                                        "win.slide");
500         gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(button));
501
502         button = gtk_separator_tool_item_new();
503         gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(button));
504
505         /* Change slide.  FIXME: LTR vs RTL */
506         image = gtk_image_new_from_icon_name("gtk-goto-first-ltr",
507                                              GTK_ICON_SIZE_LARGE_TOOLBAR);
508         nw->bfirst = gtk_tool_button_new(image, "First slide");
509         gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(nw->bfirst));
510         gtk_actionable_set_action_name(GTK_ACTIONABLE(nw->bfirst),
511                                        "win.first");
512
513         image = gtk_image_new_from_icon_name("gtk-go-back-ltr",
514                                              GTK_ICON_SIZE_LARGE_TOOLBAR);
515         nw->bprev = gtk_tool_button_new(image, "Previous slide");
516         gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(nw->bprev));
517         gtk_actionable_set_action_name(GTK_ACTIONABLE(nw->bprev),
518                                        "win.prev");
519
520         image = gtk_image_new_from_icon_name("gtk-go-forward-ltr",
521                                              GTK_ICON_SIZE_LARGE_TOOLBAR);
522         nw->bnext = gtk_tool_button_new(image, "Next slide");
523         gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(nw->bnext));
524         gtk_actionable_set_action_name(GTK_ACTIONABLE(nw->bnext),
525                                        "win.next");
526
527         image = gtk_image_new_from_icon_name("gtk-goto-last-ltr",
528                                              GTK_ICON_SIZE_LARGE_TOOLBAR);
529         nw->blast = gtk_tool_button_new(image, "Last slide");
530         gtk_container_add(GTK_CONTAINER(toolbar), GTK_WIDGET(nw->blast));
531         gtk_actionable_set_action_name(GTK_ACTIONABLE(nw->blast),
532                                        "win.last");
533
534         nw->sel_slide = nw->p->slides[0];
535         update_toolbar(nw);
536
537         scroll = gtk_scrolled_window_new(NULL, NULL);
538         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
539                                        GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
540         gtk_container_add(GTK_CONTAINER(scroll), GTK_WIDGET(nw->sceditor));
541
542         sc_editor_set_flow(nw->sceditor, 1);
543         sc_editor_set_background(nw->sceditor, 0.9, 0.9, 0.9);
544         sc_editor_set_min_border(nw->sceditor, 0.0);
545         sc_editor_set_top_frame_editable(nw->sceditor, 1);
546
547         g_signal_connect(G_OBJECT(nw->sceditor), "button-press-event",
548                          G_CALLBACK(button_press_sig), nw);
549         g_signal_connect(G_OBJECT(nw->sceditor), "key-press-event",
550                          G_CALLBACK(key_press_sig), nw);
551
552         gtk_window_set_default_size(GTK_WINDOW(nw->window), 768, 768);
553         gtk_box_pack_start(GTK_BOX(vbox), scroll, TRUE, TRUE, 0);
554
555         gtk_widget_show_all(nw->window);
556
557         return nw;
558 }