cefbd0df474c377d11dc977d679bf8bdf135a3ca
[colloquium.git] / libstorycode / narrative.c
1 /*
2  * narrative.c
3  *
4  * Copyright © 2019 Thomas White <taw@bitwiz.org.uk>
5  *
6  * This file is part of Colloquium.
7  *
8  * Colloquium is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation, either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
20  *
21  */
22
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include <stdlib.h>
29 #include <string.h>
30 #include <assert.h>
31 #include <stdio.h>
32 #include <gio/gio.h>
33
34 #include <libintl.h>
35 #define _(x) gettext(x)
36
37 #ifdef HAVE_PANGO
38 #include <pango/pango.h>
39 #endif
40
41 #ifdef HAVE_CAIRO
42 #include <cairo.h>
43 #endif
44
45 #include "stylesheet.h"
46 #include "slide.h"
47 #include "narrative.h"
48 #include "narrative_priv.h"
49 #include "imagestore.h"
50 #include "storycode.h"
51
52 Narrative *narrative_new()
53 {
54         Narrative *n;
55         n = malloc(sizeof(*n));
56         if ( n == NULL ) return NULL;
57         n->n_items = 0;
58         n->items = NULL;
59         n->stylesheet = stylesheet_new();
60         n->imagestore = imagestore_new("."); /* FIXME: From app config */
61         n->saved = 1;
62 #ifdef HAVE_PANGO
63         n->language = pango_language_to_string(pango_language_get_default());
64 #else
65         n->language = NULL;
66 #endif
67         return n;
68 }
69
70
71 static void narrative_item_destroy(struct narrative_item *item)
72 {
73         free(item->text);
74 #ifdef HAVE_PANGO
75         if ( item->layout != NULL ) {
76                 g_object_unref(item->layout);
77         }
78 #endif
79 #ifdef HAVE_CAIRO
80         if ( item->slide_thumbnail != NULL ) {
81                 cairo_surface_destroy(item->slide_thumbnail);
82         }
83 #endif
84 }
85
86
87 /* Free the narrative and all contents, but not the stylesheet */
88 void narrative_free(Narrative *n)
89 {
90         int i;
91         for ( i=0; i<n->n_items; i++ ) {
92                 narrative_item_destroy(&n->items[i]);
93         }
94         free(n->items);
95         free(n);
96 }
97
98
99 Narrative *narrative_load(GFile *file)
100 {
101         GBytes *bytes;
102         const char *text;
103         size_t len;
104         Narrative *n;
105
106         bytes = g_file_load_bytes(file, NULL, NULL, NULL);
107         if ( bytes == NULL ) return NULL;
108
109         text = g_bytes_get_data(bytes, &len);
110         n = storycode_parse_presentation(text);
111         g_bytes_unref(bytes);
112         if ( n == NULL ) return NULL;
113
114         if ( n->n_items == 0 ) {
115                 /* Presentation is empty.  Add a dummy to start things off */
116                 narrative_add_text(n, strdup(""));
117         }
118
119         imagestore_set_parent(n->imagestore, g_file_get_parent(file));
120         return n;
121 }
122
123
124 int narrative_save(Narrative *n, GFile *file)
125 {
126         GFileOutputStream *fh;
127         int r;
128         GError *error = NULL;
129
130         if ( file == NULL ) {
131                 fprintf(stderr, "Saving to NULL!\n");
132                 return 1;
133         }
134
135         fh = g_file_replace(file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, &error);
136         if ( fh == NULL ) {
137                 fprintf(stderr, _("Open failed: %s\n"), error->message);
138                 return 1;
139         }
140         r = storycode_write_presentation(n, G_OUTPUT_STREAM(fh));
141         if ( r ) {
142                 fprintf(stderr, _("Couldn't save presentation\n"));
143         }
144         g_object_unref(fh);
145
146         imagestore_set_parent(n->imagestore, g_file_get_parent(file));
147
148         n->saved = 1;
149         return 0;
150 }
151
152
153 void narrative_set_unsaved(Narrative *n)
154 {
155         n->saved = 0;
156 }
157
158
159 int narrative_get_unsaved(Narrative *n)
160 {
161         return !n->saved;
162 }
163
164
165 int narrative_item_is_text(Narrative *n, int item)
166 {
167         if ( n->items[item].type == NARRATIVE_ITEM_SLIDE ) return 0;
168         return 1;
169 }
170
171
172 void narrative_add_stylesheet(Narrative *n, Stylesheet *ss)
173 {
174         assert(n->stylesheet == NULL);
175         n->stylesheet = ss;
176 }
177
178
179 Stylesheet *narrative_get_stylesheet(Narrative *n)
180 {
181         if ( n == NULL ) return NULL;
182         return n->stylesheet;
183 }
184
185
186 const char *narrative_get_language(Narrative *n)
187 {
188         if ( n == NULL ) return NULL;
189         return n->language;
190 }
191
192
193 ImageStore *narrative_get_imagestore(Narrative *n)
194 {
195         if ( n == NULL ) return NULL;
196         return n->imagestore;
197 }
198
199
200 static void init_item(struct narrative_item *item)
201 {
202         item->layout = NULL;
203         item->text = NULL;
204         item->slide = NULL;
205         item->slide_thumbnail = NULL;
206 }
207
208
209 static struct narrative_item *add_item(Narrative *n)
210 {
211         struct narrative_item *new_items;
212         struct narrative_item *item;
213         new_items = realloc(n->items, (n->n_items+1)*sizeof(struct narrative_item));
214         if ( new_items == NULL ) return NULL;
215         n->items = new_items;
216         item = &n->items[n->n_items++];
217         init_item(item);
218         return item;
219 }
220
221
222 static struct narrative_item *insert_item(Narrative *n, int pos)
223 {
224         add_item(n);
225         memmove(&n->items[pos+1], &n->items[pos],
226                 (n->n_items-pos-1)*sizeof(struct narrative_item));
227         init_item(&n->items[pos+1]);
228         return &n->items[pos+1];
229 }
230
231
232 void narrative_add_prestitle(Narrative *n, char *text)
233 {
234         struct narrative_item *item;
235
236         item = add_item(n);
237         if ( item == NULL ) return;
238
239         item->type = NARRATIVE_ITEM_PRESTITLE;
240         item->text = text;
241         item->align = ALIGN_INHERIT;
242         item->layout = NULL;
243 }
244
245
246 void narrative_add_bp(Narrative *n, char *text)
247 {
248         struct narrative_item *item;
249
250         item = add_item(n);
251         if ( item == NULL ) return;
252
253         item->type = NARRATIVE_ITEM_BP;
254         item->text = text;
255         item->align = ALIGN_INHERIT;
256         item->layout = NULL;
257 }
258
259
260 void narrative_add_text(Narrative *n, char *text)
261 {
262         struct narrative_item *item;
263
264         item = add_item(n);
265         if ( item == NULL ) return;
266
267         item->type = NARRATIVE_ITEM_TEXT;
268         item->text = text;
269         item->align = ALIGN_INHERIT;
270         item->layout = NULL;
271 }
272
273
274 void narrative_add_slide(Narrative *n, Slide *slide)
275 {
276         struct narrative_item *item;
277
278         item = add_item(n);
279         if ( item == NULL ) return;
280
281         item->type = NARRATIVE_ITEM_SLIDE;
282         item->slide = slide;
283         item->slide_thumbnail = NULL;
284 }
285
286
287 void narrative_insert_slide(Narrative *n, Slide *slide, int pos)
288 {
289         struct narrative_item *item = insert_item(n, pos-1);
290         item->type = NARRATIVE_ITEM_SLIDE;
291         item->slide = slide;
292         item->slide_thumbnail = NULL;
293 }
294
295
296 static void delete_item(Narrative *n, int del)
297 {
298         int i;
299         narrative_item_destroy(&n->items[del]);
300         for ( i=del; i<n->n_items-1; i++ ) {
301                 n->items[i] = n->items[i+1];
302         }
303         n->n_items--;
304 }
305
306
307 /* Delete from item i1 offset o1 to item i2 offset o2, inclusive */
308 void narrative_delete_block(Narrative *n, int i1, size_t o1, int i2, size_t o2)
309 {
310         int i;
311         int n_del = 0;
312         int merge = 1;
313         int middle;    /* This is where the "middle deletion" will begin */
314
315         /* Starting item */
316         if ( n->items[i1].type == NARRATIVE_ITEM_SLIDE ) {
317                 delete_item(n, i1);
318                 if ( i1 == i2 ) return;  /* only one slide to delete */
319                 middle = i1; /* ... which is now the item just after the slide */
320                 i2--;
321                 merge = 0;
322         } else {
323                 if ( i1 == i2 ) {
324                         memmove(&n->items[i1].text[o1],
325                                 &n->items[i1].text[o2],
326                                 strlen(n->items[i1].text)-o2+1);
327                         return;  /* easy case */
328                 } else {
329                         n->items[i1].text[o1] = '\0';
330                         middle = i1+1;
331                 }
332         }
333
334         /* Middle items */
335         for ( i=middle; i<i2; i++ ) {
336                 /* Deleting the item moves all the subsequent items up, so the
337                  * index to be deleted doesn't change. */
338                 delete_item(n, middle);
339                 n_del++;
340         }
341         i2 -= n_del;
342
343         /* Last item */
344         if ( n->items[i2].type == NARRATIVE_ITEM_SLIDE ) {
345                 delete_item(n, i2);
346                 return;
347         }
348
349         /* We end with a text item */
350         memmove(&n->items[i2].text[0],
351                 &n->items[i2].text[o2],
352                 strlen(&n->items[i2].text[o2])+1);
353
354         /* If the start and end points are in different paragraphs, and both
355          * of them are text (any kind), merge them.  Note that at this point,
356          * we know that i1 and i2 are different because otherwise we
357          * would've returned earlier ("easy case"). */
358         if ( merge ) {
359                 char *new_text;
360                 size_t len = strlen(n->items[i1].text);
361                 assert(i1 != i2);
362                 len += strlen(n->items[i2].text);
363                 new_text = malloc(len+1);
364                 if ( new_text == NULL ) return;
365                 strcpy(new_text, n->items[i1].text);
366                 strcat(new_text, n->items[i2].text);
367                 free(n->items[i1].text);
368                 n->items[i1].text = new_text;
369                 delete_item(n, i2);
370         }
371 }
372
373
374 void narrative_split_item(Narrative *n, int i1, size_t o1)
375 {
376         struct narrative_item *item1;
377         struct narrative_item *item2;
378
379         item1 = &n->items[i1];
380         item2 = insert_item(n, i1);
381
382         if ( item1->type != NARRATIVE_ITEM_SLIDE ) {
383                 item2->text = strdup(&item1->text[o1]);
384                 item1->text[o1] = '\0';
385         } else {
386                 item2->text = strdup("");
387         }
388
389         item2->type = NARRATIVE_ITEM_TEXT;
390 }
391
392
393 int narrative_get_num_items(Narrative *n)
394 {
395         return n->n_items;
396 }
397
398
399 int narrative_get_num_slides(Narrative *n)
400 {
401         int i;
402         int ns = 0;
403         for ( i=0; i<n->n_items; i++ ) {
404                 if ( n->items[i].type == NARRATIVE_ITEM_SLIDE ) ns++;
405         }
406         return ns;
407 }
408
409
410 Slide *narrative_get_slide(Narrative *n, int para)
411 {
412         if ( para >= n->n_items ) return NULL;
413         if ( n->items[para].type != NARRATIVE_ITEM_SLIDE ) return NULL;
414         return n->items[para].slide;
415 }
416
417
418 int narrative_get_slide_number_for_para(Narrative *n, int para)
419 {
420         int i;
421         int ns = 0;
422         for ( i=0; i<para; i++ ) {
423                 if ( n->items[i].type == NARRATIVE_ITEM_SLIDE ) ns++;
424         }
425         return ns;
426 }
427
428
429 int narrative_get_slide_number_for_slide(Narrative *n, Slide *s)
430 {
431         int i;
432         int ns = 0;
433         for ( i=0; i<n->n_items; i++ ) {
434                 if ( n->items[i].type == NARRATIVE_ITEM_SLIDE ) {
435                         if ( n->items[i].slide == s ) return ns;
436                         ns++;
437                 }
438         }
439         return n->n_items;
440 }
441
442
443 Slide *narrative_get_slide_by_number(Narrative *n, int pos)
444 {
445         int i;
446         int ns = 0;
447         for ( i=0; i<n->n_items; i++ ) {
448                 if ( n->items[i].type == NARRATIVE_ITEM_SLIDE ) {
449                         if ( ns == pos ) return n->items[i].slide;
450                         ns++;
451                 }
452         }
453         return NULL;
454 }