Make symbol names unique across all files
[colloquium.git] / libstorycode / gtk / gtknarrativeview.c
1 /*
2  * gtknarrativeview.c
3  *
4  * Copyright © 2013-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 <gtk/gtk.h>
31 #include <assert.h>
32 #include <gdk/gdkkeysyms.h>
33 #include <gdk-pixbuf/gdk-pixbuf.h>
34 #include <math.h>
35
36 #include <narrative.h>
37 #include <narrative_render_cairo.h>
38
39 #include "gtknarrativeview.h"
40 #include "narrative_priv.h"
41
42
43 static void scroll_interface_init(GtkScrollable *iface)
44 {
45 }
46
47
48 enum
49 {
50         GTKNARRATIVEVIEW_0,
51         GTKNARRATIVEVIEW_VADJ,
52         GTKNARRATIVEVIEW_HADJ,
53         GTKNARRATIVEVIEW_VPOL,
54         GTKNARRATIVEVIEW_HPOL,
55 };
56
57
58 G_DEFINE_TYPE_WITH_CODE(GtkNarrativeView, gtk_narrative_view,
59                         GTK_TYPE_DRAWING_AREA,
60                         G_IMPLEMENT_INTERFACE(GTK_TYPE_SCROLLABLE,
61                                               scroll_interface_init))
62
63 static void gtknv_redraw(GtkNarrativeView *e)
64 {
65         gint w, h;
66         w = gtk_widget_get_allocated_width(GTK_WIDGET(e));
67         h = gtk_widget_get_allocated_height(GTK_WIDGET(e));
68         gtk_widget_queue_draw_area(GTK_WIDGET(e), 0, 0, w, h);
69 }
70
71
72 static void horizontal_adjust(GtkAdjustment *adj, GtkNarrativeView *e)
73 {
74         e->h_scroll_pos = gtk_adjustment_get_value(adj);
75         gtknv_redraw(e);
76 }
77
78
79 static void set_horizontal_params(GtkNarrativeView *e)
80 {
81         if ( e->hadj == NULL ) return;
82         gtk_adjustment_configure(e->hadj, e->h_scroll_pos, 0, e->w, 100,
83                                  e->visible_width, e->visible_width);
84 }
85
86
87 static void vertical_adjust(GtkAdjustment *adj, GtkNarrativeView *e)
88 {
89         e->scroll_pos = gtk_adjustment_get_value(adj);
90         gtknv_redraw(e);
91 }
92
93
94 static void set_vertical_params(GtkNarrativeView *e)
95 {
96         double page;
97
98         if ( e->vadj == NULL ) return;
99
100         /* Ensure we do not scroll off the top of the document */
101         if ( e->scroll_pos < 0.0 ) e->scroll_pos = 0.0;
102
103         /* Ensure we do not scroll off the bottom of the document */
104         if ( e->scroll_pos > e->h - e->visible_height ) {
105                 e->scroll_pos = e->h - e->visible_height;
106         }
107
108         /* If we can show the whole document, show it at the top */
109         if ( e->h < e->visible_height ) {
110                 e->scroll_pos = 0.0;
111         }
112
113         if ( e->h > e->visible_height ) {
114                 page = e->visible_height;
115         } else {
116                 page = e->h;
117         }
118
119         gtk_adjustment_configure(e->vadj, e->scroll_pos, 0, e->h, 100,
120                                  e->visible_height, page);
121 }
122
123
124 static void rewrap_range(GtkNarrativeView *e, int min, int max)
125 {
126         PangoContext *pc;
127         PangoLanguage *lang;
128         const char *langname;
129
130         pc = gtk_widget_get_pango_context(GTK_WIDGET(e));
131
132         langname = narrative_get_language(e->n);
133         lang = pango_language_from_string(langname);
134
135         /* Wrap everything with the current width, to get the total height */
136         narrative_wrap_range(e->n,
137                              narrative_get_stylesheet(e->n),
138                              lang, pc, e->w,
139                              narrative_get_imagestore(e->n),
140                              min, max, e->sel_start, e->sel_end);
141 }
142
143
144 static void update_size(GtkNarrativeView *e)
145 {
146         e->w = e->visible_width;
147         e->h = narrative_get_height(e->n);
148
149         set_vertical_params(e);
150         set_horizontal_params(e);
151 }
152
153
154 static gboolean gtknv_resize_sig(GtkWidget *widget, GdkEventConfigure *event,
155                                  GtkNarrativeView *e)
156 {
157         e->visible_height = event->height;
158         e->visible_width = event->width;
159         e->w = e->visible_width;
160
161         rewrap_range(e, 0, e->n->n_items-1);
162
163         update_size(e);
164
165         return FALSE;
166 }
167
168
169 static void gtknv_emit_change_sig(GtkNarrativeView *e)
170 {
171         g_signal_emit_by_name(e, "changed");
172 }
173
174
175 static void sc_editor_set_property(GObject *obj, guint id, const GValue *val,
176                                    GParamSpec *spec)
177 {
178         GtkNarrativeView *e = GTK_NARRATIVE_VIEW(obj);
179
180         switch ( id ) {
181
182                 case GTKNARRATIVEVIEW_VPOL :
183                 e->vpol = g_value_get_enum(val);
184                 break;
185
186                 case GTKNARRATIVEVIEW_HPOL :
187                 e->hpol = g_value_get_enum(val);
188                 break;
189
190                 case GTKNARRATIVEVIEW_VADJ :
191                 e->vadj = g_value_get_object(val);
192                 set_vertical_params(e);
193                 if ( e->vadj != NULL ) {
194                         g_signal_connect(G_OBJECT(e->vadj), "value-changed",
195                                          G_CALLBACK(vertical_adjust), e);
196                 }
197                 break;
198
199                 case GTKNARRATIVEVIEW_HADJ :
200                 e->hadj = g_value_get_object(val);
201                 set_horizontal_params(e);
202                 if ( e->hadj != NULL ) {
203                         g_signal_connect(G_OBJECT(e->hadj), "value-changed",
204                                          G_CALLBACK(horizontal_adjust), e);
205                 }
206                 break;
207
208                 default :
209                 printf("setting %i\n", id);
210                 break;
211
212         }
213 }
214
215
216 static void sc_editor_get_property(GObject *obj, guint id, GValue *val,
217                                    GParamSpec *spec)
218 {
219         GtkNarrativeView *e = GTK_NARRATIVE_VIEW(obj);
220
221         switch ( id ) {
222
223                 case GTKNARRATIVEVIEW_VADJ :
224                 g_value_set_object(val, e->vadj);
225                 break;
226
227                 case GTKNARRATIVEVIEW_HADJ :
228                 g_value_set_object(val, e->hadj);
229                 break;
230
231                 case GTKNARRATIVEVIEW_VPOL :
232                 g_value_set_enum(val, e->vpol);
233                 break;
234
235                 case GTKNARRATIVEVIEW_HPOL :
236                 g_value_set_enum(val, e->hpol);
237                 break;
238
239                 default :
240                 G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, id, spec);
241                 break;
242
243         }
244 }
245
246
247 static GtkSizeRequestMode get_request_mode(GtkWidget *widget)
248 {
249         return GTK_SIZE_REQUEST_CONSTANT_SIZE;
250 }
251
252
253 static void get_preferred_width(GtkWidget *widget, gint *min, gint *natural)
254 {
255         *min = 100;
256         *natural = 640;
257 }
258
259
260 static void get_preferred_height(GtkWidget *widget, gint *min, gint *natural)
261 {
262         *min = 1000;
263         *natural = 1000;
264 }
265
266
267 static void gtk_narrative_view_class_init(GtkNarrativeViewClass *klass)
268 {
269         GObjectClass *goc = G_OBJECT_CLASS(klass);
270         goc->set_property = sc_editor_set_property;
271         goc->get_property = sc_editor_get_property;
272         g_object_class_override_property(goc, GTKNARRATIVEVIEW_VADJ, "vadjustment");
273         g_object_class_override_property(goc, GTKNARRATIVEVIEW_HADJ, "hadjustment");
274         g_object_class_override_property(goc, GTKNARRATIVEVIEW_VPOL, "vscroll-policy");
275         g_object_class_override_property(goc, GTKNARRATIVEVIEW_HPOL, "hscroll-policy");
276
277         GTK_WIDGET_CLASS(klass)->get_request_mode = get_request_mode;
278         GTK_WIDGET_CLASS(klass)->get_preferred_width = get_preferred_width;
279         GTK_WIDGET_CLASS(klass)->get_preferred_height = get_preferred_height;
280         GTK_WIDGET_CLASS(klass)->get_preferred_height_for_width = NULL;
281
282         g_signal_new("changed", GTK_TYPE_NARRATIVE_VIEW,
283                      G_SIGNAL_RUN_LAST, 0,
284                      NULL, NULL, NULL, G_TYPE_NONE, 0);
285         g_signal_new("slide-double-clicked", GTK_TYPE_NARRATIVE_VIEW,
286                      G_SIGNAL_RUN_LAST, 0,
287                      NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_POINTER);
288 }
289
290
291 static void gtk_narrative_view_init(GtkNarrativeView *e)
292 {
293         e->vpol = GTK_SCROLL_NATURAL;
294         e->hpol = GTK_SCROLL_NATURAL;
295         e->vadj = gtk_adjustment_new(0, 0, 100, 1, 10, 10);
296         e->hadj = gtk_adjustment_new(0, 0, 100, 1, 10, 10);
297 }
298
299
300 static void paste_storycode_received(GtkClipboard *cb, GtkSelectionData *seldata,
301                                      gpointer vp)
302 {
303 //      GtkNarrativeView *e = vp;
304 //      SCBlock *nf;
305 //      const guchar *t;
306 //
307 //      t = gtk_selection_data_get_data(seldata);
308 //
309 //      printf("received storycode paste\n");
310 //      printf("'%s'\n", t);
311 //      if ( t == NULL ) return;
312 //
313 //      /* FIXME: It might not be a new frame */
314 //      nf = sc_parse((char *)t);
315 //      show_sc_blocks(nf);
316 //      sc_block_append_block(sc_block_child(e->scblocks), nf);
317 //      full_rerender(e);
318 }
319
320
321 static void paste_text_received(GtkClipboard *cb, GtkSelectionData *seldata,
322                                 gpointer vp)
323 {
324 //      GtkNarrativeView *e = vp;
325 //      SCBlock *bl;
326 //      guchar *t;
327 //      SCBlock *cur_bl;
328 //      size_t cur_sc_pos;
329 //      size_t offs;
330 //      Paragraph *para;
331 //
332 //      t = gtk_selection_data_get_text(seldata);
333 //
334 //      printf("received text paste\n");
335 //      printf("'%s'\n", t);
336 //      if ( t == NULL ) return;
337 //
338 //      bl = sc_parse((char *)t);
339 //
340 //      if ( e->cursor_frame == NULL ) {
341 //              fprintf(stderr, _("No frame selected for paste\n"));
342 //              return;
343 //      }
344 //
345 //      para = e->cursor_frame->paras[e->cpos.para];
346 //      offs = narrative_pos_trail_to_offset(para, e->cpos.pos, e->cpos.trail);
347 //
348 //      get_sc_pos(e->cursor_frame, e->cpos.para, offs, &cur_bl, &cur_sc_pos);
349 //      sc_insert_block(cur_bl, cur_sc_pos, bl);
350 //      full_rerender(e);
351 }
352
353
354 static void paste_targets_received(GtkClipboard *cb, GdkAtom *targets,
355                                    gint n_targets, gpointer vp)
356 {
357         GtkNarrativeView *e = vp;
358         int i;
359         int have_sc = 0;
360         int index_sc, index_text;
361         int have_text = 0;
362
363         if ( targets == NULL ) {
364                 fprintf(stderr, "No paste targets offered.\n");
365                 return;
366         }
367
368         for ( i=0; i<n_targets; i++ ) {
369                 gchar *name = gdk_atom_name(targets[i]);
370                 if ( g_strcmp0(name, "text/x-storycode") == 0 ) {
371                         have_sc = 1;
372                         index_sc = i;
373                 }
374                 if ( g_strcmp0(name, "text/plain") == 0 ) {
375                         have_text = 1;
376                         index_text = i;
377                 }
378                 g_free(name);
379         }
380
381         if ( have_sc ) {
382                 printf("storycode is offered\n");
383                 gtk_clipboard_request_contents(cb, targets[index_sc],
384                                                paste_storycode_received, e);
385         } else if ( have_text ) {
386                 printf("text is offered\n");
387                 gtk_clipboard_request_contents(cb, targets[index_text],
388                                                paste_text_received, e);
389         } else {
390                 printf("nothing useful is offered\n");
391         }
392 }
393
394
395 void sc_editor_paste(GtkNarrativeView *e)
396 {
397         GtkClipboard *cb;
398         GdkAtom atom;
399
400         printf("pasting\n");
401
402         atom = gdk_atom_intern("CLIPBOARD", FALSE);
403         if ( atom == GDK_NONE ) return;
404         cb = gtk_clipboard_get(atom);
405         gtk_clipboard_request_targets(cb, paste_targets_received, e);
406 }
407
408
409 static void clipboard_get(GtkClipboard *cb, GtkSelectionData *seldata,
410                           guint info, gpointer data)
411 {
412         char *t = data;
413
414         printf("clipboard get\n");
415
416         if ( info == 0 ) {
417                 printf("sending SC frame\n");
418                 gtk_selection_data_set(seldata,
419                                        gtk_selection_data_get_target(seldata),
420                                        8, (const guchar *)t, strlen(t)+1);
421         } else {
422                 GdkAtom target;
423                 gchar *name;
424                 target = gtk_selection_data_get_target(seldata);
425                 name = gdk_atom_name(target);
426                 fprintf(stderr, "Don't know what to send for %s\n", name);
427                 g_free(name);
428         }
429 }
430
431
432 static void clipboard_clear(GtkClipboard *cb, gpointer data)
433 {
434         free(data);
435 }
436
437
438 static void copy_selection(GtkNarrativeView *e)
439 {
440 //      char *t;
441 //      GtkClipboard *cb;
442 //      GdkAtom atom;
443 //      GtkTargetEntry targets[1];
444 //
445 //      atom = gdk_atom_intern("CLIPBOARD", FALSE);
446 //      if ( atom == GDK_NONE ) return;
447 //
448 //      cb = gtk_clipboard_get(atom);
449 //
450 //      targets[0].target = "text/x-storycode";
451 //      targets[0].flags = 0;
452 //      targets[0].info = 0;
453 //
454 //      printf("copying selection\n");
455 //
456 //      bl = block_at_cursor(e->cursor_frame, e->cpos.para, 0);
457 //      if ( bl == NULL ) return;
458 //
459 //      t = serialise_sc_block(bl);
460 //
461 //      gtk_clipboard_set_with_data(cb, targets, 1,
462 //                                  clipboard_get, clipboard_clear, t);
463 }
464
465
466 static gint gtknv_destroy_sig(GtkWidget *window, GtkNarrativeView *e)
467 {
468         return 0;
469 }
470
471
472 static double gtknv_para_top(Narrative *n, int pnum)
473 {
474         int i;
475         double py = 0.0;
476         for ( i=0; i<pnum; i++ ) py += narrative_item_get_height(n, i);
477         return py;
478 }
479
480
481 static void draw_para_highlight(cairo_t *cr, Narrative *n, int cursor_para,
482                                 double w)
483 {
484         double cx, cy;
485         struct narrative_item *item;
486
487         item = &n->items[cursor_para];
488         cx = item->space_l;
489         cy = gtknv_para_top(n, cursor_para) + item->space_t;
490
491         cairo_new_path(cr);
492         cairo_rectangle(cr, cx-5.0, cy-5.0, item->obj_w+10.0, item->obj_h+10.0);
493         cairo_set_source_rgba(cr, 0.7, 0.7, 1.0, 0.5);
494         cairo_set_line_width(cr, 5.0);
495         cairo_stroke(cr);
496 }
497
498
499 static void gtknv_get_cursor_pos(Narrative *n, struct edit_pos cpos,
500                                  double *x, double *y, double *h)
501 {
502         size_t offs;
503         PangoRectangle rect;
504         struct narrative_item *item;
505
506         item = &n->items[cpos.para];
507         if ( !narrative_item_is_text(n, cpos.para) ) {
508                 *x = item->space_l;
509                 *y = gtknv_para_top(n, cpos.para) + item->space_t;
510                 *h = item->obj_h + item->space_t + item->space_b;
511                 return;
512         }
513
514         if ( item->layout == NULL ) {
515                 fprintf(stderr, "get_cursor_pos: No layout\n");
516                 return;
517         }
518
519         offs = narrative_pos_trail_to_offset(n, cpos.para, cpos.pos, cpos.trail);
520         pango_layout_get_cursor_pos(item->layout, offs, &rect, NULL);
521         *x = pango_units_to_double(rect.x) + item->space_l;
522         *y = pango_units_to_double(rect.y) + gtknv_para_top(n, cpos.para) + item->space_t;
523         *h = pango_units_to_double(rect.height);
524 }
525
526
527 static void gtknv_draw_caret(cairo_t *cr, Narrative *n, struct edit_pos cpos, double ww)
528 {
529         assert(n != NULL);
530
531         if ( narrative_item_is_text(n, cpos.para) ) {
532
533                 /* Normal text-style cursor */
534
535                 double cx, clow, chigh, h;
536                 const double t = 1.8;
537
538                 gtknv_get_cursor_pos(n, cpos, &cx, &clow, &h);
539
540                 chigh = clow+h;
541
542                 cairo_move_to(cr, cx, clow);
543                 cairo_line_to(cr, cx, chigh);
544
545                 cairo_move_to(cr, cx-t, clow-t);
546                 cairo_line_to(cr, cx, clow);
547                 cairo_move_to(cr, cx+t, clow-t);
548                 cairo_line_to(cr, cx, clow);
549
550                 cairo_move_to(cr, cx-t, chigh+t);
551                 cairo_line_to(cr, cx, chigh);
552                 cairo_move_to(cr, cx+t, chigh+t);
553                 cairo_line_to(cr, cx, chigh);
554
555                 cairo_set_source_rgb(cr, 0.86, 0.0, 0.0);
556                 cairo_set_line_width(cr, 1.0);
557                 cairo_stroke(cr);
558
559         } else {
560
561                 /* Block highlight cursor */
562
563                 double cx, cy, cw, ch;
564                 struct narrative_item *item;
565
566                 item = &n->items[cpos.para];
567
568                 cx = item->space_l - 5.5;
569                 cy = gtknv_para_top(n, cpos.para) + item->space_t - 5.5;
570                 cw = item->obj_w + 11.0;
571                 ch = item->obj_h + 11.0;
572
573                 cairo_new_path(cr);
574                 cairo_rectangle(cr, cx, cy, cw, ch);
575                 cairo_set_source_rgb(cr, 0.86, 0.0, 0.0);
576                 cairo_set_line_width(cr, 1.0);
577                 cairo_stroke(cr);
578
579         }
580 }
581
582
583 static void gtknv_draw_overlay(cairo_t *cr, GtkNarrativeView *e)
584 {
585         if ( e->para_highlight ) {
586                 draw_para_highlight(cr, e->n, e->cpos.para, e->w);
587         } else {
588                 gtknv_draw_caret(cr, e->n, e->cpos, e->w);
589         }
590 }
591
592
593 static gboolean gtknv_draw_sig(GtkWidget *da, cairo_t *cr, GtkNarrativeView *e)
594 {
595         if ( e->rewrap_needed ) {
596                 rewrap_range(e, 0, e->n->n_items);
597                 e->rewrap_needed = 0;
598         }
599
600         /* Ultimate background */
601         cairo_set_source_rgba(cr, 0.8, 0.8, 1.0, 1.0);
602         cairo_paint(cr);
603
604         cairo_translate(cr, -e->h_scroll_pos, -e->scroll_pos);
605
606         /* Rendering background */
607         cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0);
608         cairo_rectangle(cr, 0.0, 0.0, e->w, e->h);
609         cairo_fill(cr);
610
611         /* Contents */
612         narrative_render_cairo(e->n, cr, narrative_get_stylesheet(e->n));
613
614         /* Editing overlay */
615         cairo_translate(cr, e->n->space_l, e->n->space_t);
616         gtknv_draw_overlay(cr, e);
617
618         return FALSE;
619 }
620
621
622 static void check_cursor_visible(GtkNarrativeView *e)
623 {
624         double x, y, h;
625
626         gtknv_get_cursor_pos(e->n, e->cpos, &x, &y, &h);
627
628         /* Off the bottom? */
629         if ( y - e->scroll_pos + h > e->visible_height ) {
630                 e->scroll_pos = y + h - e->visible_height;
631                 e->scroll_pos += e->n->space_b;
632         }
633
634         /* Off the top? */
635         if ( y < e->scroll_pos ) {
636                 e->scroll_pos = y - e->n->space_t;
637         }
638 }
639
640
641 static size_t gtknv_end_offset_of_para(Narrative *n, int pnum)
642 {
643         int i;
644         size_t len;
645         assert(pnum >= 0);
646         if ( !narrative_item_is_text(n, pnum) ) return 0;
647         for ( i=0; i<n->items[pnum].n_runs; i++ ) {
648                 len += strlen(n->items[pnum].runs[i].text);
649         }
650         return len;
651 }
652
653
654 static void sort_positions(struct edit_pos *a, struct edit_pos *b)
655 {
656         if ( a->para > b->para ) {
657                 size_t tpos;
658                 int tpara, ttrail;
659                 tpara = b->para;   tpos = b->pos;  ttrail = b->trail;
660                 b->para = a->para;  b->pos = a->pos;  b->trail = a->trail;
661                 a->para = tpara;    a->pos = tpos;    a->trail = ttrail;
662         }
663
664         if ( (a->para == b->para) && (a->pos > b->pos) )
665         {
666                 size_t tpos = b->pos;
667                 int ttrail = b->trail;
668                 b->pos = a->pos;  b->trail = a->trail;
669                 a->pos = tpos;    a->trail = ttrail;
670         }
671 }
672
673
674 static void gtknv_cursor_moveh(Narrative *n, struct edit_pos *cp, signed int dir)
675 {
676         struct narrative_item *item = &n->items[cp->para];
677         int np = cp->pos;
678         int otrail = cp->trail;
679
680         if ( !narrative_item_is_text(n, cp->para) ) {
681                 if ( dir > 0 ) {
682                         np = G_MAXINT;
683                         cp->trail = 0;
684                 } else {
685                         np = -1;
686                         cp->trail = 0;
687                 }
688         } else {
689                 if ( item->layout == NULL ) return;
690                 pango_layout_move_cursor_visually(item->layout, 1, cp->pos,
691                                                   cp->trail, dir,
692                                                   &np, &cp->trail);
693         }
694
695         if ( np == -1 ) {
696                 if ( cp->para > 0 ) {
697                         size_t end_offs;
698                         cp->para--;
699                         end_offs = gtknv_end_offset_of_para(n, cp->para);
700                         if ( end_offs > 0 ) {
701                                 cp->pos = end_offs - 1;
702                                 cp->trail = 1;
703                         } else {
704                                 /* Jumping into an empty paragraph */
705                                 cp->pos = 0;
706                                 cp->trail = 0;
707                         }
708                         return;
709                 } else {
710                         /* Can't move any further */
711                         return;
712                 }
713         }
714
715         if ( np == G_MAXINT ) {
716                 if ( cp->para < n->n_items-1 ) {
717                         cp->para++;
718                         cp->pos = 0;
719                         cp->trail = 0;
720                         return;
721                 } else {
722                         /* Can't move any further, undo change to cp->trail */
723                         cp->trail = otrail;
724                         return;
725                 }
726         }
727
728         cp->pos = np;
729 }
730
731
732 static void gtknv_unset_selection(GtkNarrativeView *e)
733 {
734         int a, b;
735
736         a = e->sel_start.para;
737         b = e->sel_end.para;
738         if ( a > b ) {
739                 a = e->sel_end.para;
740                 b = e->sel_start.para;
741         }
742         e->sel_start.para = 0;
743         e->sel_start.pos = 0;
744         e->sel_start.trail = 0;
745         e->sel_end.para = 0;
746         e->sel_end.pos = 0;
747         e->sel_end.trail = 0;
748         rewrap_range(e, a, b);
749 }
750
751
752 static int positions_equal(struct edit_pos a, struct edit_pos b)
753 {
754         if ( a.para != b.para ) return 0;
755         if ( a.pos != b.pos ) return 0;
756         if ( a.trail != b.trail ) return 0;
757         return 1;
758 }
759
760
761 static void gtknv_do_backspace(GtkNarrativeView *e, signed int dir)
762 {
763         struct edit_pos p1, p2;
764         size_t o1, o2;
765
766         if ( !positions_equal(e->sel_start, e->sel_end) ) {
767
768                 /* Block delete */
769                 p1 = e->sel_start;
770                 p2 = e->sel_end;
771
772         } else {
773
774                 /* Delete one character, as represented visually */
775                 p2 = e->cpos;
776                 p1 = p2;
777                 gtknv_cursor_moveh(e->n, &p1, dir);
778         }
779
780         sort_positions(&p1, &p2);
781         o1 = narrative_pos_trail_to_offset(e->n, p1.para, p1.pos, p1.trail);
782         o2 = narrative_pos_trail_to_offset(e->n, p2.para, p2.pos, p2.trail);
783         narrative_delete_block(e->n, p1.para, o1, p2.para, o2);
784         e->cpos = p1;
785         gtknv_unset_selection(e);
786
787         /* The only paragraphs which still exist and might have been
788          * affected by the deletion are sel_start.para and the one
789          * immediately afterwards. */
790         rewrap_range(e, p1.para, p1.para+1);
791         update_size(e);
792         check_cursor_visible(e);
793         gtknv_emit_change_sig(e);
794         gtknv_redraw(e);
795 }
796
797
798 static void gtknv_insert_text_in_paragraph(struct narrative_item *item, size_t offs,
799                                            char *t)
800 {
801         char *n;
802         int run;
803         size_t run_offs;
804
805         run = narrative_which_run(item, offs, &run_offs);
806
807         n = malloc(strlen(t) + strlen(item->runs[run].text) + 1);
808         if ( n == NULL ) return;
809         strncpy(n, item->runs[run].text, run_offs);
810         n[run_offs] = '\0';
811         strcat(n, t);
812         strcat(n, item->runs[run].text+run_offs);
813         free(item->runs[run].text);
814         item->runs[run].text = n;
815 }
816
817
818 static void gtknv_insert_text(char *t, GtkNarrativeView *e)
819 {
820         if ( !positions_equal(e->sel_start, e->sel_end) ) {
821                 gtknv_do_backspace(e, 0);
822         }
823
824         if ( narrative_item_is_text(e->n, e->cpos.para) ) {
825
826                 size_t off = narrative_pos_trail_to_offset(e->n, e->cpos.para,
827                                                            e->cpos.pos, e->cpos.trail);
828
829                 if ( strcmp(t, "\n") == 0 ) {
830                         narrative_split_item(e->n, e->cpos.para, off);
831                         rewrap_range(e, e->cpos.para, e->cpos.para+1);
832                         e->cpos.para += 1;
833                         e->cpos.pos = 0;
834                         e->cpos.trail = 0;
835                 } else {
836                         gtknv_insert_text_in_paragraph(&e->n->items[e->cpos.para], off, t);
837                         rewrap_range(e, e->cpos.para, e->cpos.para);
838                         gtknv_cursor_moveh(e->n, &e->cpos, +1);
839                 }
840
841                 update_size(e);
842
843         } /* else do nothing */
844
845         gtknv_emit_change_sig(e);
846         check_cursor_visible(e);
847         gtknv_redraw(e);
848 }
849
850
851 static gboolean gtknv_im_commit_sig(GtkIMContext *im, gchar *str,
852                                     GtkNarrativeView *e)
853 {
854         gtknv_insert_text(str, e);
855         return FALSE;
856 }
857
858
859 static int gtknv_find_cursor(Narrative *n, double x, double y, struct edit_pos *pos)
860 {
861         double cur_y;
862         struct narrative_item *item;
863         int i = 0;
864
865         cur_y = n->space_t;
866
867         do {
868                 cur_y += narrative_item_get_height(n, i++);
869         } while ( (cur_y < y) && (i<n->n_items) );
870
871         pos->para = i-1;
872         item = &n->items[pos->para];
873         if ( !narrative_item_is_text(n, pos->para) ) {
874                 pos->pos = 0;
875                 return 0;
876         }
877
878         pango_layout_xy_to_index(item->layout,
879                                  pango_units_from_double(x - n->space_l - item->space_l),
880                                  pango_units_from_double(y - n->space_t - gtknv_para_top(n, pos->para)),
881                                  &pos->pos, &pos->trail);
882
883         return 0;
884 }
885
886
887 static gboolean gtknv_button_press_sig(GtkWidget *da, GdkEventButton *event,
888                                        GtkNarrativeView *e)
889 {
890         gdouble x, y;
891
892         x = event->x;
893         y = event->y + e->scroll_pos;
894
895         /* Clicked an existing frame, no immediate dragging */
896         e->drag_status = NARRATIVE_DRAG_STATUS_COULD_DRAG;
897         gtknv_unset_selection(e);
898         gtknv_find_cursor(e->n, x, y, &e->sel_start);
899         e->sel_end = e->sel_start;
900         e->cpos = e->sel_start;
901
902         if ( event->type == GDK_2BUTTON_PRESS ) {
903                 struct narrative_item *item = &e->n->items[e->cpos.para];
904                 if ( item->type == NARRATIVE_ITEM_SLIDE ) {
905                         g_signal_emit_by_name(e, "slide-double-clicked",
906                                               item->slide);
907                 }
908         }
909
910         gtk_widget_grab_focus(GTK_WIDGET(da));
911         gtknv_redraw(e);
912         return FALSE;
913 }
914
915
916 static void sorti(int *a, int *b)
917 {
918         if ( *a > *b ) {
919                 int tmp = *a;
920                 *a = *b;
921                 *b = tmp;
922         }
923 }
924
925
926 static gboolean gtknv_motion_sig(GtkWidget *da, GdkEventMotion *event,
927                                  GtkNarrativeView *e)
928 {
929         gdouble x, y;
930         struct edit_pos old_sel_end;
931         int minp, maxp;
932
933         x = event->x;
934         y = event->y + e->scroll_pos;
935
936         if ( e->drag_status == NARRATIVE_DRAG_STATUS_COULD_DRAG ) {
937                 /* We just got a motion signal, and the status was "could drag",
938                  * therefore the drag has started. */
939                 e->drag_status = NARRATIVE_DRAG_STATUS_DRAGGING;
940         }
941
942         old_sel_end = e->sel_end;
943         gtknv_find_cursor(e->n, x, y, &e->sel_end);
944
945         minp = e->sel_start.para;
946         maxp = e->sel_end.para;
947         sorti(&minp, &maxp);
948         if ( !positions_equal(e->sel_start, old_sel_end) ) {
949                 if ( old_sel_end.para > maxp ) maxp = old_sel_end.para;
950                 if ( old_sel_end.para < minp ) minp = old_sel_end.para;
951         }
952
953         rewrap_range(e, minp, maxp);
954         gtknv_find_cursor(e->n, x, y, &e->cpos);
955         gtknv_redraw(e);
956
957         gdk_event_request_motions(event);
958         return FALSE;
959 }
960
961
962 static gboolean gtknv_key_press_sig(GtkWidget *da, GdkEventKey *event,
963                                     GtkNarrativeView *e)
964 {
965         gboolean r;
966         int claim = 0;
967
968         /* Throw the event to the IM context and let it sort things out */
969         r = gtk_im_context_filter_keypress(GTK_IM_CONTEXT(e->im_context),
970                                            event);
971         if ( r ) return FALSE;  /* IM ate it */
972
973         switch ( event->keyval ) {
974
975                 case GDK_KEY_Left :
976                 gtknv_cursor_moveh(e->n, &e->cpos, -1);
977                 check_cursor_visible(e);
978                 gtknv_redraw(e);
979                 claim = 1;
980                 break;
981
982                 case GDK_KEY_Right :
983                 gtknv_cursor_moveh(e->n, &e->cpos, +1);
984                 check_cursor_visible(e);
985                 gtknv_redraw(e);
986                 claim = 1;
987                 break;
988
989                 case GDK_KEY_Up :
990                 gtknv_cursor_moveh(e->n, &e->cpos, -1);
991                 check_cursor_visible(e);
992                 gtknv_redraw(e);
993                 claim = 1;
994                 break;
995
996                 case GDK_KEY_Down :
997                 gtknv_cursor_moveh(e->n, &e->cpos, +1);
998                 check_cursor_visible(e);
999                 gtknv_redraw(e);
1000                 claim = 1;
1001                 break;
1002
1003                 case GDK_KEY_Return :
1004                 gtknv_im_commit_sig(NULL, "\n", e);
1005                 claim = 1;
1006                 break;
1007
1008                 case GDK_KEY_BackSpace :
1009                 gtknv_do_backspace(e, -1);
1010                 claim = 1;
1011                 break;
1012
1013                 case GDK_KEY_Delete :
1014                 gtknv_do_backspace(e, +1);
1015                 claim = 1;
1016                 break;
1017
1018                 case GDK_KEY_C :
1019                 case GDK_KEY_c :
1020                 if ( event->state == GDK_CONTROL_MASK ) {
1021                         copy_selection(e);
1022                 }
1023                 break;
1024
1025                 case GDK_KEY_V :
1026                 case GDK_KEY_v :
1027                 if ( event->state == GDK_CONTROL_MASK ) {
1028                         sc_editor_paste(e);
1029                 }
1030                 break;
1031
1032
1033         }
1034
1035         if ( claim ) return TRUE;
1036         return FALSE;
1037 }
1038
1039
1040 static gboolean gtknv_dnd_motion(GtkWidget *widget, GdkDragContext *drag_context,
1041                                  gint x, gint y, guint time, GtkNarrativeView *e)
1042 {
1043         return TRUE;
1044 }
1045
1046
1047 static gboolean gtknv_dnd_drop(GtkWidget *widget, GdkDragContext *drag_context,
1048                                gint x, gint y, guint time, GtkNarrativeView *e)
1049 {
1050         GdkAtom target;
1051
1052         target = gtk_drag_dest_find_target(widget, drag_context, NULL);
1053
1054         if ( target == GDK_NONE ) {
1055                 gtk_drag_finish(drag_context, FALSE, FALSE, time);
1056         } else {
1057                 gtk_drag_get_data(widget, drag_context, target, time);
1058         }
1059
1060         return TRUE;
1061 }
1062
1063
1064 static void gtknv_dnd_receive(GtkWidget *widget, GdkDragContext *drag_context,
1065                               gint x, gint y, GtkSelectionData *seldata,
1066                               guint info, guint time, GtkNarrativeView *e)
1067 {
1068 }
1069
1070
1071 static void gtknv_dnd_leave(GtkWidget *widget, GdkDragContext *drag_context,
1072                             guint time, GtkNarrativeView *nview)
1073 {
1074         nview->drag_status = NARRATIVE_DRAG_STATUS_NONE;
1075 }
1076
1077
1078 static gint gtknv_realise_sig(GtkWidget *da, GtkNarrativeView *e)
1079 {
1080         GdkWindow *win;
1081
1082         /* Keyboard and input method stuff */
1083         e->im_context = gtk_im_multicontext_new();
1084         win = gtk_widget_get_window(GTK_WIDGET(e));
1085         gtk_im_context_set_client_window(GTK_IM_CONTEXT(e->im_context), win);
1086         gdk_window_set_accept_focus(win, TRUE);
1087         g_signal_connect(G_OBJECT(e->im_context), "commit", G_CALLBACK(gtknv_im_commit_sig), e);
1088         g_signal_connect(G_OBJECT(e), "key-press-event", G_CALLBACK(gtknv_key_press_sig), e);
1089
1090         return FALSE;
1091 }
1092
1093
1094 GtkWidget *gtk_narrative_view_new(Narrative *n)
1095 {
1096         GtkNarrativeView *nview;
1097         GtkTargetEntry targets[1];
1098
1099         nview = g_object_new(GTK_TYPE_NARRATIVE_VIEW, NULL);
1100
1101         nview->w = 100;
1102         nview->h = 100;
1103         nview->scroll_pos = 0;
1104         nview->n = n;
1105         nview->rewrap_needed = 0;
1106         nview->para_highlight = 0;
1107
1108         gtk_widget_set_size_request(GTK_WIDGET(nview),
1109                                     nview->w, nview->h);
1110
1111         g_signal_connect(G_OBJECT(nview), "destroy",
1112                          G_CALLBACK(gtknv_destroy_sig), nview);
1113         g_signal_connect(G_OBJECT(nview), "realize",
1114                          G_CALLBACK(gtknv_realise_sig), nview);
1115         g_signal_connect(G_OBJECT(nview), "button-press-event",
1116                          G_CALLBACK(gtknv_button_press_sig), nview);
1117         g_signal_connect(G_OBJECT(nview), "motion-notify-event",
1118                          G_CALLBACK(gtknv_motion_sig), nview);
1119         g_signal_connect(G_OBJECT(nview), "configure-event",
1120                          G_CALLBACK(gtknv_resize_sig), nview);
1121
1122         /* Drag and drop */
1123         targets[0].target = "text/uri-list";
1124         targets[0].flags = 0;
1125         targets[0].info = 1;
1126         gtk_drag_dest_set(GTK_WIDGET(nview), 0, targets, 1,
1127                           GDK_ACTION_PRIVATE);
1128         g_signal_connect(nview, "drag-data-received",
1129                          G_CALLBACK(gtknv_dnd_receive), nview);
1130         g_signal_connect(nview, "drag-motion",
1131                          G_CALLBACK(gtknv_dnd_motion), nview);
1132         g_signal_connect(nview, "drag-drop",
1133                          G_CALLBACK(gtknv_dnd_drop), nview);
1134         g_signal_connect(nview, "drag-leave",
1135                          G_CALLBACK(gtknv_dnd_leave), nview);
1136
1137         gtk_widget_set_can_focus(GTK_WIDGET(nview), TRUE);
1138         gtk_widget_add_events(GTK_WIDGET(nview),
1139                               GDK_POINTER_MOTION_HINT_MASK
1140                                | GDK_BUTTON1_MOTION_MASK
1141                                | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
1142                                | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK
1143                                | GDK_SCROLL_MASK);
1144
1145         g_signal_connect(G_OBJECT(nview), "draw",
1146                          G_CALLBACK(gtknv_draw_sig), nview);
1147
1148         gtk_widget_grab_focus(GTK_WIDGET(nview));
1149
1150         gtk_widget_show(GTK_WIDGET(nview));
1151
1152         return GTK_WIDGET(nview);
1153 }
1154
1155
1156 void gtk_narrative_view_set_para_highlight(GtkNarrativeView *e, int para_highlight)
1157 {
1158         e->para_highlight = para_highlight;
1159         gtknv_redraw(e);
1160 }
1161
1162
1163 int gtk_narrative_view_get_cursor_para(GtkNarrativeView *e)
1164 {
1165         return e->cpos.para;
1166 }
1167
1168
1169 void gtk_narrative_view_set_cursor_para(GtkNarrativeView *e, signed int pos)
1170 {
1171         double h;
1172         int i;
1173
1174         if ( pos < 0 ) pos = e->n->n_items-1;
1175         e->cpos.para = pos;
1176         e->cpos.pos = 0;
1177         e->cpos.trail = 0;
1178
1179         h = 0;
1180         for ( i=0; i<e->cpos.para; i++ ) {
1181                 h += narrative_item_get_height(e->n, i);
1182         }
1183         h += narrative_item_get_height(e->n, e->cpos.para)/2;
1184         e->scroll_pos = h - (e->visible_height/2);
1185         set_vertical_params(e);
1186
1187         gtknv_redraw(e);
1188 }
1189
1190
1191 void gtk_narrative_view_add_slide_at_cursor(GtkNarrativeView *e)
1192 {
1193         Slide *s;
1194
1195         s = slide_new();
1196         if ( s == NULL ) return;
1197
1198         if ( narrative_item_is_text(e->n, e->cpos.para) ) {
1199                 size_t off = narrative_pos_trail_to_offset(e->n, e->cpos.para,
1200                                                            e->cpos.pos, e->cpos.trail);
1201                 if ( (off > 0) && (off < gtknv_end_offset_of_para(e->n, e->cpos.para)) ) {
1202                         narrative_split_item(e->n, e->cpos.para, off);
1203                         narrative_insert_slide(e->n, s, e->cpos.para+1);
1204                 } else if ( off == 0 ) {
1205                         narrative_insert_slide(e->n, s, e->cpos.para);
1206                 } else {
1207                         narrative_insert_slide(e->n, s, e->cpos.para+1);
1208                 }
1209         } else {
1210                 narrative_insert_slide(e->n, s, e->cpos.para+1);
1211         }
1212
1213         rewrap_range(e, e->cpos.para, e->cpos.para+2);
1214         e->cpos.para++;
1215         e->cpos.pos = 0;
1216         e->cpos.trail = 0;
1217         update_size(e);
1218         check_cursor_visible(e);
1219         gtknv_emit_change_sig(e);
1220         gtknv_redraw(e);
1221 }
1222
1223
1224 extern void gtk_narrative_view_redraw(GtkNarrativeView *e)
1225 {
1226         e->rewrap_needed = 1;
1227         gtknv_emit_change_sig(e);
1228         gtknv_redraw(e);
1229 }