Do all rendering one level higher
[colloquium.git] / src / sc_editor.c
1 /*
2  * sc_editor.c
3  *
4  * Copyright © 2013-2018 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 "colloquium.h"
37 #include "presentation.h"
38 #include "slide_window.h"
39 #include "render.h"
40 #include "frame.h"
41 #include "sc_parse.h"
42 #include "sc_interp.h"
43 #include "sc_editor.h"
44 #include "slideshow.h"
45 #include "debugger.h"
46 #include "utils.h"
47
48
49 static void scroll_interface_init(GtkScrollable *iface)
50 {
51 }
52
53
54 enum
55 {
56         SCEDITOR_0,
57         SCEDITOR_VADJ,
58         SCEDITOR_HADJ,
59         SCEDITOR_VPOL,
60         SCEDITOR_HPOL,
61 };
62
63
64 G_DEFINE_TYPE_WITH_CODE(SCEditor, sc_editor, GTK_TYPE_DRAWING_AREA,
65                         G_IMPLEMENT_INTERFACE(GTK_TYPE_SCROLLABLE,
66                                               scroll_interface_init))
67
68 static void debug_paragraphs(SCEditor *e)
69 {
70         struct frame *fr = e->cursor_frame;
71         int i;
72
73         printf("Paragraphs in current frame:\n");
74         for ( i=0; i<fr->n_paras; i++ ) {
75                 show_para(fr->paras[i]);
76         }
77 }
78
79
80 static void horizontal_adjust(GtkAdjustment *adj, SCEditor *e)
81 {
82         e->h_scroll_pos = gtk_adjustment_get_value(adj);
83         sc_editor_redraw(e);
84 }
85
86
87 static void set_horizontal_params(SCEditor *e)
88 {
89         if ( e->hadj == NULL ) return;
90         gtk_adjustment_configure(e->hadj, e->h_scroll_pos, 0, e->w, 100,
91                                  e->visible_width, e->visible_width);
92 }
93
94
95 static void vertical_adjust(GtkAdjustment *adj, SCEditor *e)
96 {
97         e->scroll_pos = gtk_adjustment_get_value(adj);
98         sc_editor_redraw(e);
99 }
100
101
102 static void set_vertical_params(SCEditor *e)
103 {
104         double page;
105
106         if ( e->vadj == NULL ) return;
107
108         /* Ensure we do not scroll off the top of the document */
109         if ( e->scroll_pos < 0.0 ) e->scroll_pos = 0.0;
110
111         /* Ensure we do not scroll off the bottom of the document */
112         if ( e->scroll_pos > e->h - e->visible_height ) {
113                 e->scroll_pos = e->h - e->visible_height;
114         }
115
116         /* If we can show the whole document, show it at the top */
117         if ( e->h < e->visible_height ) {
118                 e->scroll_pos = 0.0;
119         }
120
121         if ( e->h > e->visible_height ) {
122                 page = e->visible_height;
123         } else {
124                 page = e->h;
125         }
126
127         gtk_adjustment_configure(e->vadj, e->scroll_pos, 0, e->h, 100,
128                                  e->visible_height, page);
129 }
130
131
132 static void update_size(SCEditor *e)
133 {
134         if ( e->flow ) {
135
136                 double total = total_height(e->top);
137
138                 e->w = e->top->w;
139                 e->h = total + e->top->pad_t + e->top->pad_b;
140
141                 e->log_w = e->w;
142                 e->log_h = e->h;
143                 e->top->h = e->h;
144         } else {
145                 e->top->w = e->log_w;
146                 e->top->h = e->log_h;
147         }
148
149         if ( e->flow && (e->top->h < e->visible_height) ) {
150                 e->top->h = e->visible_height;
151         }
152
153         set_vertical_params(e);
154         set_horizontal_params(e);
155 }
156
157
158 static gboolean resize_sig(GtkWidget *widget, GdkEventConfigure *event,
159                            SCEditor *e)
160 {
161         PangoContext *pc;
162
163         pc = gdk_pango_context_get();
164
165         if ( e->scale ) {
166
167                 double sx, sy;
168                 double aw, ah;
169
170                 e->w = event->width;
171                 e->h = event->height;
172                 sx = (double)e->w / e->log_w;
173                 sy = (double)e->h / e->log_h;
174                 e->view_scale = (sx < sy) ? sx : sy;
175
176                 /* Actual size (in device units) */
177                 aw = e->view_scale * e->log_w;
178                 ah = e->view_scale * e->log_h;
179
180                 e->border_offs_x = (event->width - aw)/2.0;
181                 e->border_offs_y = (event->height - ah)/2.0;
182
183         }
184
185         e->visible_height = event->height;
186         e->visible_width = event->width;
187
188         /* Interpret and shape, if not already done */
189         if ( e->top == NULL ) {
190                 double w, h;
191                 if ( e->flow ) {
192                         w = event->width;
193                         h = 0.0;
194                 } else {
195                         w = e->log_w;
196                         h = e->log_h;
197                 }
198                 e->top = interp_and_shape(e->scblocks,
199                                           e->stylesheets, e->cbl,
200                                           e->is, e->slidenum, pc,
201                                           w, h, e->lang);
202                 e->top->scblocks = e->scblocks;
203                 recursive_wrap(e->top, pc);
204         }
205
206         if ( e->flow ) {
207                 /* Wrap using current width */
208                 e->top->w = event->width;
209                 e->top->h = 0.0;  /* To be updated in a moment */
210                 e->top->x = 0.0;
211                 e->top->y = 0.0;
212                 /* Only the top level needs to be wrapped */
213                 wrap_frame(e->top, pc);
214         }
215
216         update_size(e);
217
218         g_object_unref(pc);
219
220         return FALSE;
221 }
222
223
224 static void emit_change_sig(SCEditor *e)
225 {
226         g_signal_emit_by_name(e, "changed");
227 }
228
229
230 void sc_editor_set_flow(SCEditor *e, int flow)
231 {
232         e->flow = flow;
233 }
234
235
236 static void sc_editor_set_property(GObject *obj, guint id, const GValue *val,
237                                    GParamSpec *spec)
238 {
239         SCEditor *e = SC_EDITOR(obj);
240
241         switch ( id ) {
242
243                 case SCEDITOR_VPOL :
244                 e->vpol = g_value_get_enum(val);
245                 break;
246
247                 case SCEDITOR_HPOL :
248                 e->hpol = g_value_get_enum(val);
249                 break;
250
251                 case SCEDITOR_VADJ :
252                 e->vadj = g_value_get_object(val);
253                 set_vertical_params(e);
254                 if ( e->vadj != NULL ) {
255                         g_signal_connect(G_OBJECT(e->vadj), "value-changed",
256                                          G_CALLBACK(vertical_adjust), e);
257                 }
258                 break;
259
260                 case SCEDITOR_HADJ :
261                 e->hadj = g_value_get_object(val);
262                 set_horizontal_params(e);
263                 if ( e->hadj != NULL ) {
264                         g_signal_connect(G_OBJECT(e->hadj), "value-changed",
265                                          G_CALLBACK(horizontal_adjust), e);
266                 }
267                 break;
268
269                 default :
270                 printf("setting %i\n", id);
271                 break;
272
273         }
274 }
275
276
277 static void sc_editor_get_property(GObject *obj, guint id, GValue *val,
278                                    GParamSpec *spec)
279 {
280         SCEditor *e = SC_EDITOR(obj);
281
282         switch ( id ) {
283
284                 case SCEDITOR_VADJ :
285                 g_value_set_object(val, e->vadj);
286                 break;
287
288                 case SCEDITOR_HADJ :
289                 g_value_set_object(val, e->hadj);
290                 break;
291
292                 case SCEDITOR_VPOL :
293                 g_value_set_enum(val, e->vpol);
294                 break;
295
296                 case SCEDITOR_HPOL :
297                 g_value_set_enum(val, e->hpol);
298                 break;
299
300                 default :
301                 G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, id, spec);
302                 break;
303
304         }
305 }
306
307
308 static GtkSizeRequestMode get_request_mode(GtkWidget *widget)
309 {
310         return GTK_SIZE_REQUEST_CONSTANT_SIZE;
311 }
312
313
314 static void get_preferred_width(GtkWidget *widget, gint *min, gint *natural)
315 {
316         SCEditor *e = SC_EDITOR(widget);
317         if ( e->flow ) {
318                 *min = 100;
319                 *natural = 640;
320         } else {
321                 *min = e->w;
322                 *natural = e->w;
323         }
324 }
325
326
327 static void get_preferred_height(GtkWidget *widget, gint *min, gint *natural)
328 {
329         SCEditor *e = SC_EDITOR(widget);
330         if ( e->flow ) {
331                 *min = 1000;
332                 *natural = 1000;
333         } else {
334                 *min = e->h;
335                 *natural = e->h;
336         }
337 }
338
339
340 static void sc_editor_class_init(SCEditorClass *klass)
341 {
342         GObjectClass *goc = G_OBJECT_CLASS(klass);
343         goc->set_property = sc_editor_set_property;
344         goc->get_property = sc_editor_get_property;
345         g_object_class_override_property(goc, SCEDITOR_VADJ, "vadjustment");
346         g_object_class_override_property(goc, SCEDITOR_HADJ, "hadjustment");
347         g_object_class_override_property(goc, SCEDITOR_VPOL, "vscroll-policy");
348         g_object_class_override_property(goc, SCEDITOR_HPOL, "hscroll-policy");
349
350         GTK_WIDGET_CLASS(klass)->get_request_mode = get_request_mode;
351         GTK_WIDGET_CLASS(klass)->get_preferred_width = get_preferred_width;
352         GTK_WIDGET_CLASS(klass)->get_preferred_height = get_preferred_height;
353         GTK_WIDGET_CLASS(klass)->get_preferred_height_for_width = NULL;
354
355         g_signal_new("changed", SC_TYPE_EDITOR, G_SIGNAL_RUN_LAST, 0,
356                      NULL, NULL, NULL, G_TYPE_NONE, 0);
357 }
358
359
360 static void sc_editor_init(SCEditor *e)
361 {
362         e->vpol = GTK_SCROLL_NATURAL;
363         e->hpol = GTK_SCROLL_NATURAL;
364         e->vadj = gtk_adjustment_new(0, 0, 100, 1, 10, 10);
365         e->hadj = gtk_adjustment_new(0, 0, 100, 1, 10, 10);
366 }
367
368
369 void sc_editor_set_background(SCEditor *e, double r, double g, double b)
370 {
371         e->bgcol[0] = r;
372         e->bgcol[1] = g;
373         e->bgcol[2] = b;
374 }
375
376
377 void sc_editor_remove_cursor(SCEditor *e)
378 {
379         e->cursor_frame = NULL;
380         e->cpos.para = 0;
381         e->cpos.pos = 0;
382         e->cpos.trail = 0;
383         e->selection = NULL;
384 }
385
386
387 /* (Re-)run the entire rendering pipeline.
388  * NB "full" means "full".  All frame, line and box handles will become
389  * invalid.  The cursor position will be unset. */
390 static void full_rerender(SCEditor *e)
391 {
392         PangoContext *pc;
393
394         frame_free(e->top);
395         sc_editor_remove_cursor(e);
396
397         pc = gdk_pango_context_get();
398
399         e->top = interp_and_shape(e->scblocks,
400                                   e->stylesheets, e->cbl,
401                                   e->is, e->slidenum,
402                                   pc, e->log_w, 0.0, e->lang);
403
404         e->top->x = 0.0;
405         e->top->y = 0.0;
406         e->top->w = e->w;
407         e->top->h = 0.0;  /* To be updated in a moment */
408
409         recursive_wrap(e->top, pc);
410         update_size(e);
411
412         sc_editor_redraw(e);
413
414         g_object_unref(pc);
415 }
416
417
418 void sc_editor_redraw(SCEditor *e)
419 {
420         gint w, h;
421
422         w = gtk_widget_get_allocated_width(GTK_WIDGET(e));
423         h = gtk_widget_get_allocated_height(GTK_WIDGET(e));
424
425         gtk_widget_queue_draw_area(GTK_WIDGET(e), 0, 0, w, h);
426 }
427
428
429 void paste_received(GtkClipboard *cb, const gchar *t, gpointer vp)
430 {
431         SCEditor *e = vp;
432         SCBlock *nf;
433         nf = sc_parse(t);
434         sc_block_append_block(e->scblocks, nf);
435         full_rerender(e);
436 }
437
438
439 void sc_editor_paste(SCEditor *e)
440 {
441         GtkClipboard *cb;
442         GdkAtom atom;
443
444         atom = gdk_atom_intern("CLIPBOARD", FALSE);
445         if ( atom == GDK_NONE ) return;
446         cb = gtk_clipboard_get(atom);
447         gtk_clipboard_request_text(cb, paste_received, e);
448 }
449
450
451 void sc_editor_add_storycode(SCEditor *e, const char *sc)
452 {
453         SCBlock *nf;
454         nf = sc_parse(sc);
455         sc_block_append_block(sc_block_child(e->scblocks), nf);
456         full_rerender(e);
457 }
458
459
460 void sc_editor_copy_selected_frame(SCEditor *e)
461 {
462         char *t;
463         GtkClipboard *cb;
464         GdkAtom atom;
465
466         if ( e->selection == NULL ) return;
467
468         atom = gdk_atom_intern("CLIPBOARD", FALSE);
469         if ( atom == GDK_NONE ) return;
470
471         cb = gtk_clipboard_get(atom);
472
473         t = serialise_sc_block(e->selection->scblocks);
474
475         gtk_clipboard_set_text(cb, t, -1);
476         free(t);
477 }
478
479
480 void sc_editor_delete_selected_frame(SCEditor *e)
481 {
482         SCBlock *scb_old = e->scblocks;
483         sc_block_delete(&e->scblocks, e->selection->scblocks);
484         assert(scb_old == e->scblocks);
485         full_rerender(e);
486         emit_change_sig(e);
487 }
488
489
490 static gint destroy_sig(GtkWidget *window, SCEditor *e)
491 {
492         return 0;
493 }
494
495
496 static void draw_editing_box(cairo_t *cr, struct frame *fr)
497 {
498         const double dash[] = {2.0, 2.0};
499         double xmin, ymin, width, height;
500         double ptot_w, ptot_h;
501
502         xmin = fr->x;
503         ymin = fr->y;
504         width = fr->w;
505         height = fr->h;
506
507         cairo_new_path(cr);
508         cairo_rectangle(cr, xmin, ymin, width, height);
509         cairo_set_source_rgb(cr, 0.0, 0.69, 1.0);
510         cairo_set_line_width(cr, 0.5);
511         cairo_stroke(cr);
512
513         cairo_new_path(cr);
514         ptot_w = fr->pad_l + fr->pad_r;
515         ptot_h = fr->pad_t + fr->pad_b;
516         cairo_rectangle(cr, xmin+fr->pad_l, ymin+fr->pad_t,
517                             width-ptot_w, height-ptot_h);
518         cairo_set_dash(cr, dash, 2, 0.0);
519         cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
520         cairo_set_line_width(cr, 0.1);
521         cairo_stroke(cr);
522
523         cairo_set_dash(cr, NULL, 0, 0.0);
524 }
525
526
527 static void draw_para_highlight(cairo_t *cr, struct frame *fr, int cursor_para)
528 {
529         double cx, cy, w, h;
530
531         if ( get_para_highlight(fr, cursor_para, &cx, &cy, &w, &h) != 0 ) {
532                 return;
533         }
534
535         cairo_new_path(cr);
536         cairo_rectangle(cr, cx+fr->x, cy+fr->y, w, h);
537         cairo_set_source_rgba(cr, 0.7, 0.7, 1.0, 0.5);
538         cairo_set_line_width(cr, 5.0);
539         cairo_stroke(cr);
540 }
541
542
543 static void draw_caret(cairo_t *cr, struct frame *fr, struct edit_pos cpos,
544                        int hgh)
545 {
546         double cx, clow, chigh, h;
547         const double t = 1.8;
548         size_t offs;
549         Paragraph *para;
550
551         if ( hgh ) {
552                 draw_para_highlight(cr, fr, cpos.para);
553                 return;
554         }
555
556         assert(fr != NULL);
557
558         para = fr->paras[cpos.para];
559         if ( para_type(para) != PARA_TYPE_TEXT ) {
560                 draw_para_highlight(cr, fr, cpos.para);
561                 return;
562         }
563
564         offs = pos_trail_to_offset(para, cpos.pos, cpos.trail);
565         get_cursor_pos(fr, cpos.para, offs, &cx, &clow, &h);
566
567         cx += fr->x;
568         clow += fr->y;
569         chigh = clow + h;
570
571         cairo_move_to(cr, cx, clow);
572         cairo_line_to(cr, cx, chigh);
573
574         cairo_move_to(cr, cx-t, clow-t);
575         cairo_line_to(cr, cx, clow);
576         cairo_move_to(cr, cx+t, clow-t);
577         cairo_line_to(cr, cx, clow);
578
579         cairo_move_to(cr, cx-t, chigh+t);
580         cairo_line_to(cr, cx, chigh);
581         cairo_move_to(cr, cx+t, chigh+t);
582         cairo_line_to(cr, cx, chigh);
583
584         cairo_set_source_rgb(cr, 0.86, 0.0, 0.0);
585         cairo_set_line_width(cr, 1.0);
586         cairo_stroke(cr);
587 }
588
589
590 static void draw_resize_handle(cairo_t *cr, double x, double y)
591 {
592         cairo_new_path(cr);
593         cairo_rectangle(cr, x, y, 20.0, 20.0);
594         cairo_set_source_rgba(cr, 0.9, 0.9, 0.9, 0.5);
595         cairo_fill(cr);
596 }
597
598
599 static void draw_overlay(cairo_t *cr, SCEditor *e)
600 {
601         double x, y, w, h;
602
603         if ( e->selection != NULL ) {
604
605                 draw_editing_box(cr, e->selection);
606
607                 x = e->selection->x;
608                 y = e->selection->y;
609                 w = e->selection->w;
610                 h = e->selection->h;
611
612                 if ( e->selection->resizable ) {
613                         /* Draw resize handles */
614                         draw_resize_handle(cr, x, y+h-20.0);
615                         draw_resize_handle(cr, x+w-20.0, y);
616                         draw_resize_handle(cr, x, y);
617                         draw_resize_handle(cr, x+w-20.0, y+h-20.0);
618                 }
619
620                 draw_caret(cr, e->cursor_frame, e->cpos, e->para_highlight);
621
622         }
623
624         if ( (e->drag_status == DRAG_STATUS_DRAGGING)
625           && ((e->drag_reason == DRAG_REASON_CREATE)
626               || (e->drag_reason == DRAG_REASON_IMPORT)) )
627         {
628                 cairo_new_path(cr);
629                 cairo_rectangle(cr, e->start_corner_x, e->start_corner_y,
630                                     e->drag_corner_x - e->start_corner_x,
631                                     e->drag_corner_y - e->start_corner_y);
632                 cairo_set_source_rgb(cr, 0.5, 0.5, 0.5);
633                 cairo_set_line_width(cr, 0.5);
634                 cairo_stroke(cr);
635         }
636
637         if ( (e->drag_status == DRAG_STATUS_DRAGGING)
638           && ((e->drag_reason == DRAG_REASON_RESIZE)
639            || (e->drag_reason == DRAG_REASON_MOVE)) )
640         {
641                 cairo_new_path(cr);
642                 cairo_rectangle(cr, e->box_x, e->box_y,
643                                     e->box_width, e->box_height);
644                 cairo_set_source_rgb(cr, 0.5, 0.5, 0.5);
645                 cairo_set_line_width(cr, 0.5);
646                 cairo_stroke(cr);
647         }
648 }
649
650
651 static gboolean draw_sig(GtkWidget *da, cairo_t *cr, SCEditor *e)
652 {
653         /* Ultimate background */
654         if ( e->bg_pixbuf != NULL ) {
655                 gdk_cairo_set_source_pixbuf(cr, e->bg_pixbuf, 0.0, 0.0);
656                 cairo_pattern_t *patt = cairo_get_source(cr);
657                 cairo_pattern_set_extend(patt, CAIRO_EXTEND_REPEAT);
658                 cairo_paint(cr);
659         } else {
660                 cairo_set_source_rgba(cr, 0.8, 0.8, 1.0, 1.0);
661                 cairo_paint(cr);
662         }
663
664         cairo_translate(cr, e->border_offs_x, e->border_offs_y);
665         cairo_translate(cr, -e->h_scroll_pos, -e->scroll_pos);
666         cairo_scale(cr, e->view_scale, e->view_scale);
667
668         /* Rendering background */
669         cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0);
670         cairo_rectangle(cr, 0.0, 0.0, e->log_w, e->log_h);
671         cairo_fill(cr);
672
673         /* Contents */
674         recursive_draw(e->top, cr, e->is,
675                        e->scroll_pos/e->view_scale,
676                        (e->scroll_pos + e->visible_height)/e->view_scale);
677
678         /* Editing overlay */
679         draw_overlay(cr, e);
680
681         return FALSE;
682 }
683
684
685 SCBlock *split_paragraph_at_cursor(SCEditor *e)
686 {
687         size_t offs;
688         Paragraph *para;
689
690         if ( e->cursor_frame == NULL ) return NULL;
691
692         para = e->cursor_frame->paras[e->cpos.para];
693         offs = pos_trail_to_offset(para, e->cpos.pos, e->cpos.trail);
694         return split_paragraph(e->cursor_frame, e->cpos.para, offs, e->pc);
695 }
696
697
698 static void check_cursor_visible(SCEditor *e)
699 {
700         double x, y, h;
701         size_t offs;
702         Paragraph *para;
703
704         if ( e->cursor_frame == NULL ) return;
705
706         para = e->cursor_frame->paras[e->cpos.para];
707         offs = pos_trail_to_offset(para, e->cpos.pos, e->cpos.trail);
708         get_cursor_pos(e->cursor_frame, e->cpos.para, offs, &x, &y, &h);
709
710         /* Off the bottom? */
711         if ( y - e->scroll_pos + h > e->visible_height ) {
712                 e->scroll_pos = y + h - e->visible_height;
713                 e->scroll_pos += e->cursor_frame->pad_b;
714         }
715
716         /* Off the top? */
717         if ( y < e->scroll_pos ) {
718                 e->scroll_pos = y - e->cursor_frame->pad_t;
719         }
720 }
721
722
723 static void do_backspace(struct frame *fr, SCEditor *e)
724 {
725         double wrapw = e->cursor_frame->w - e->cursor_frame->pad_l - e->cursor_frame->pad_r;
726
727         if ( e->sel_active ) {
728
729                 /* Delete the selected block */
730                 delete_text_from_frame(e->cursor_frame, e->sel_start, e->sel_end, wrapw);
731
732                 /* Cursor goes at start of deletion */
733                 sort_positions(&e->sel_start, &e->sel_end);
734                 e->cpos = e->sel_start;
735                 e->sel_active = 0;
736
737         } else {
738
739                 if ( para_type(e->cursor_frame->paras[e->cpos.para]) == PARA_TYPE_TEXT ) {
740
741                         /* Delete one character */
742                         struct edit_pos p1, p2;
743
744                         p1 = e->cpos;
745
746                         p2 = p1;
747
748                         cursor_moveh(e->cursor_frame, &p2, -1);
749                         show_edit_pos(p1);
750                         show_edit_pos(p2);
751
752                         delete_text_from_frame(e->cursor_frame, p1, p2, wrapw);
753                         e->cpos = p2;
754
755                 } else {
756
757                         /* FIXME: Implement this */
758                         fprintf(stderr, "Deleting non-text paragraph\n");
759
760                 }
761
762         }
763
764         emit_change_sig(e);
765         sc_editor_redraw(e);
766 }
767
768
769 static void insert_text(char *t, SCEditor *e)
770 {
771         Paragraph *para;
772
773         if ( e->cursor_frame == NULL ) return;
774
775         if ( e->sel_active ) {
776                 do_backspace(e->cursor_frame, e);
777         }
778
779         if ( strcmp(t, "\n") == 0 ) {
780                 split_paragraph_at_cursor(e);
781                 if ( e->flow ) update_size(e);
782                 cursor_moveh(e->cursor_frame, &e->cpos, +1);
783                 check_cursor_visible(e);
784                 emit_change_sig(e);
785                 sc_editor_redraw(e);
786                 return;
787         }
788
789         para = e->cursor_frame->paras[e->cpos.para];
790
791         /* Is this paragraph even a text one? */
792         if ( para_type(para) == PARA_TYPE_TEXT ) {
793
794                 size_t off;
795
796                 /* Yes. The "easy" case */
797
798                 if ( !position_editable(e->cursor_frame, e->cpos) ) {
799                         fprintf(stderr, "Position not editable\n");
800                         return;
801                 }
802
803                 off = pos_trail_to_offset(para, e->cpos.pos, e->cpos.trail);
804                 insert_text_in_paragraph(para, off, t);
805                 wrap_paragraph(para, NULL,
806                                e->cursor_frame->w - e->cursor_frame->pad_l
807                                             - e->cursor_frame->pad_r, 0, 0);
808                 if ( e->flow ) update_size(e);
809
810                 cursor_moveh(e->cursor_frame, &e->cpos, +1);
811
812         } else {
813
814                 SCBlock *bd;
815                 SCBlock *ad;
816                 Paragraph *pnew;
817
818                 bd = para_scblock(para);
819                 if ( bd == NULL ) {
820                         fprintf(stderr, "No SCBlock for para\n");
821                         return;
822                 }
823
824                 /* No. Create a new text paragraph straight afterwards */
825                 ad = sc_block_insert_after(bd, NULL, NULL, strdup(t));
826                 if ( ad == NULL ) {
827                         fprintf(stderr, "Failed to add SCBlock\n");
828                         return;
829                 }
830
831                 pnew = insert_paragraph(e->cursor_frame, e->cpos.para);
832                 if ( pnew == NULL ) {
833                         fprintf(stderr, "Failed to insert paragraph\n");
834                         return;
835                 }
836                 add_run(pnew, ad, ad, e->cursor_frame->fontdesc,
837                         e->cursor_frame->col);
838
839                 wrap_frame(e->cursor_frame, e->pc);
840
841                 e->cpos.para += 1;
842                 e->cpos.pos = 0;
843                 e->cpos.trail = 1;
844
845         }
846
847         emit_change_sig(e);
848         check_cursor_visible(e);
849         sc_editor_redraw(e);
850 }
851
852
853 static gboolean im_commit_sig(GtkIMContext *im, gchar *str,
854                               SCEditor *e)
855 {
856         insert_text(str, e);
857         return FALSE;
858 }
859
860
861 static int within_frame(struct frame *fr, double x, double y)
862 {
863         if ( fr == NULL ) return 0;
864         if ( x < fr->x ) return 0;
865         if ( y < fr->y ) return 0;
866         if ( x > fr->x + fr->w ) return 0;
867         if ( y > fr->y + fr->h ) return 0;
868         return 1;
869 }
870
871
872 static struct frame *find_frame_at_position(struct frame *fr,
873                                             double x, double y)
874 {
875         int i;
876
877         for ( i=0; i<fr->num_children; i++ ) {
878
879                 if ( within_frame(fr->children[i], x, y) ) {
880                         return find_frame_at_position(fr->children[i], x, y);
881                 }
882
883         }
884
885         if ( within_frame(fr, x, y) ) return fr;
886         return NULL;
887 }
888
889
890 static enum corner which_corner(double xp, double yp, struct frame *fr)
891 {
892         double x, y;  /* Relative to object position */
893
894         x = xp - fr->x;
895         y = yp - fr->y;
896
897         if ( x < 0.0 ) return CORNER_NONE;
898         if ( y < 0.0 ) return CORNER_NONE;
899         if ( x > fr->w ) return CORNER_NONE;
900         if ( y > fr->h ) return CORNER_NONE;
901
902         /* Top left? */
903         if ( (x<20.0) && (y<20.0) ) return CORNER_TL;
904         if ( (x>fr->w-20.0) && (y<20.0) ) return CORNER_TR;
905         if ( (x<20.0) && (y>fr->h-20.0) ) return CORNER_BL;
906         if ( (x>fr->w-20.0) && (y>fr->h-20.0) ) return CORNER_BR;
907
908         return CORNER_NONE;
909 }
910
911
912 static void calculate_box_size(struct frame *fr, SCEditor *e,
913                                double x, double y)
914 {
915         double ddx, ddy, dlen, mult;
916         double vx, vy, dbx, dby;
917
918         ddx = x - e->start_corner_x;
919         ddy = y - e->start_corner_y;
920
921         if ( !fr->is_image ) {
922
923                 switch ( e->drag_corner ) {
924
925                         case CORNER_BR :
926                         e->box_x = fr->x;
927                         e->box_y = fr->y;
928                         e->box_width = fr->w + ddx;
929                         e->box_height = fr->h + ddy;
930                         break;
931
932                         case CORNER_BL :
933                         e->box_x = fr->x + ddx;
934                         e->box_y = fr->y;
935                         e->box_width = fr->w - ddx;
936                         e->box_height = fr->h + ddy;
937                         break;
938
939                         case CORNER_TL :
940                         e->box_x = fr->x + ddx;
941                         e->box_y = fr->y + ddy;
942                         e->box_width = fr->w - ddx;
943                         e->box_height = fr->h - ddy;
944                         break;
945
946                         case CORNER_TR :
947                         e->box_x = fr->x;
948                         e->box_y = fr->y + ddy;
949                         e->box_width = fr->w + ddx;
950                         e->box_height = fr->h - ddy;
951                         break;
952
953                         case CORNER_NONE :
954                         break;
955
956                 }
957                 return;
958
959
960         }
961
962         switch ( e->drag_corner ) {
963
964                 case CORNER_BR :
965                 vx = fr->w;
966                 vy = fr->h;
967                 break;
968
969                 case CORNER_BL :
970                 vx = -fr->w;
971                 vy = fr->h;
972                 break;
973
974                 case CORNER_TL :
975                 vx = -fr->w;
976                 vy = -fr->h;
977                 break;
978
979                 case CORNER_TR :
980                 vx = fr->w;
981                 vy = -fr->h;
982                 break;
983
984                 case CORNER_NONE :
985                 default:
986                 vx = 0.0;
987                 vy = 0.0;
988                 break;
989
990         }
991
992         dlen = (ddx*vx + ddy*vy) / e->diagonal_length;
993         mult = (dlen+e->diagonal_length) / e->diagonal_length;
994
995         e->box_width = fr->w * mult;
996         e->box_height = fr->h * mult;
997         dbx = e->box_width - fr->w;
998         dby = e->box_height - fr->h;
999
1000         if ( e->box_width < 40.0 ) {
1001                 mult = 40.0 / fr->w;
1002         }
1003         if ( e->box_height < 40.0 ) {
1004                 mult = 40.0 / fr->h;
1005         }
1006         e->box_width = fr->w * mult;
1007         e->box_height = fr->h * mult;
1008         dbx = e->box_width - fr->w;
1009         dby = e->box_height - fr->h;
1010
1011         switch ( e->drag_corner ) {
1012
1013                 case CORNER_BR :
1014                 e->box_x = fr->x;
1015                 e->box_y = fr->y;
1016                 break;
1017
1018                 case CORNER_BL :
1019                 e->box_x = fr->x - dbx;
1020                 e->box_y = fr->y;
1021                 break;
1022
1023                 case CORNER_TL :
1024                 e->box_x = fr->x - dbx;
1025                 e->box_y = fr->y - dby;
1026                 break;
1027
1028                 case CORNER_TR :
1029                 e->box_x = fr->x;
1030                 e->box_y = fr->y - dby;
1031                 break;
1032
1033                 case CORNER_NONE :
1034                 break;
1035
1036         }
1037 }
1038
1039
1040 static void check_paragraph(struct frame *fr, PangoContext *pc,
1041                             SCBlock *scblocks)
1042 {
1043         if ( fr->n_paras > 0 ) return;
1044         Paragraph *para = last_para(fr);
1045
1046         if ( scblocks == NULL ) {
1047                 /* We have no SCBlocks at all!  Better create one... */
1048                 scblocks = sc_parse("");
1049                 fr->scblocks = scblocks;
1050         }
1051
1052         /* We are creating the first paragraph.  It uses the last SCBlock
1053          * in the chain */
1054         while ( sc_block_next(scblocks) != NULL ) {
1055                 scblocks = sc_block_next(scblocks);
1056         }
1057         scblocks = sc_block_append(scblocks, NULL, NULL, strdup(""), NULL);
1058
1059         add_run(para, scblocks, scblocks, fr->fontdesc, fr->col);
1060         wrap_paragraph(para, pc, fr->w - fr->pad_l - fr->pad_r, 0, 0);
1061 }
1062
1063
1064 static void rewrap_paragraph_range(struct frame *fr, int a, int b,
1065                                    struct edit_pos sel_start,
1066                                    struct edit_pos sel_end,
1067                                    int sel_active)
1068 {
1069         int i;
1070         int sel_s, sel_e;
1071         Paragraph *para;
1072
1073         if ( a > b ) {
1074                 int t = a;
1075                 a = b; b = t;
1076         }
1077
1078         if ( fr == NULL ) return;
1079         if ( fr->paras == NULL ) return;
1080
1081         sort_positions(&sel_start, &sel_end);
1082
1083         //printf("frame %p\n", fr);
1084         //printf("start: ");
1085         //show_edit_pos(sel_start);
1086         //printf("  end: ");
1087         //show_edit_pos(sel_end);
1088
1089         para = fr->paras[sel_start.para];
1090         sel_s = pos_trail_to_offset(para, sel_start.pos, sel_start.trail);
1091         para = fr->paras[sel_end.para];
1092         sel_e = pos_trail_to_offset(para, sel_end.pos, sel_end.trail);
1093
1094         for ( i=a; i<=b; i++ ) {
1095                 size_t srt, end;
1096                 if ( sel_active ) {
1097                         if ( i == sel_start.para ) {
1098                                 srt = sel_s;
1099                         } else {
1100                                 srt = 0;
1101                         }
1102                         if ( i == sel_end.para ) {
1103                                 end = sel_e;
1104                         } else {
1105                                 end = G_MAXUINT;
1106                         }
1107                         if ( i > sel_start.para && i < sel_end.para ) {
1108                                 end = G_MAXUINT;
1109                         }
1110                 } else {
1111                         srt = 0;
1112                         end = 0;
1113                 }
1114                 wrap_paragraph(fr->paras[i], NULL,
1115                                fr->w - fr->pad_l - fr->pad_r, srt, end);
1116         }
1117 }
1118
1119
1120 static void unset_selection(SCEditor *e)
1121 {
1122         int a, b;
1123
1124         if ( !e->sel_active ) return;
1125
1126         a = e->sel_start.para;
1127         b = e->sel_end.para;
1128         if ( a > b ) {
1129                 a = e->sel_end.para;
1130                 b = e->sel_start.para;
1131         }
1132         e->sel_active = 0;
1133         rewrap_paragraph_range(e->cursor_frame, a, b, e->sel_start, e->sel_end, 0);
1134 }
1135
1136
1137 static gboolean button_press_sig(GtkWidget *da, GdkEventButton *event,
1138                                  SCEditor *e)
1139 {
1140         enum corner c;
1141         gdouble x, y;
1142         struct frame *clicked;
1143         int shift;
1144
1145         x = event->x - e->border_offs_x;
1146         y = event->y - e->border_offs_y + e->scroll_pos;
1147         x /= e->view_scale;
1148         y /= e->view_scale;
1149         shift = event->state & GDK_SHIFT_MASK;
1150
1151         if ( within_frame(e->selection, x, y) ) {
1152                 clicked = e->selection;
1153         } else {
1154                 clicked = find_frame_at_position(e->top, x, y);
1155         }
1156
1157         /* Clicked within the currently selected frame
1158          *   -> resize, move or select text */
1159         if ( (e->selection != NULL) && (clicked == e->selection) ) {
1160
1161                 struct frame *fr;
1162
1163                 fr = e->selection;
1164
1165                 /* Within the resizing region? */
1166                 c = which_corner(x, y, fr);
1167                 if ( (c != CORNER_NONE) && fr->resizable && shift ) {
1168
1169                         e->drag_reason = DRAG_REASON_RESIZE;
1170                         e->drag_corner = c;
1171
1172                         e->start_corner_x = x;
1173                         e->start_corner_y = y;
1174                         e->diagonal_length = pow(fr->w, 2.0);
1175                         e->diagonal_length += pow(fr->h, 2.0);
1176                         e->diagonal_length = sqrt(e->diagonal_length);
1177
1178                         calculate_box_size(fr, e, x, y);
1179
1180                         e->drag_status = DRAG_STATUS_COULD_DRAG;
1181                         e->drag_reason = DRAG_REASON_RESIZE;
1182
1183                 } else {
1184
1185                         /* Position cursor and prepare for possible drag */
1186                         e->cursor_frame = clicked;
1187                         check_paragraph(e->cursor_frame, e->pc, sc_block_child(fr->scblocks));
1188                         find_cursor(clicked, x-fr->x, y-fr->y, &e->cpos);
1189                         ensure_run(e->cursor_frame, e->cpos);
1190
1191                         e->start_corner_x = x;
1192                         e->start_corner_y = y;
1193
1194                         if ( event->type == GDK_2BUTTON_PRESS ) {
1195                                 check_callback_click(e->cursor_frame, e->cpos.para);
1196                         }
1197
1198                         if ( fr->resizable && shift ) {
1199                                 e->drag_status = DRAG_STATUS_COULD_DRAG;
1200                                 e->drag_reason = DRAG_REASON_MOVE;
1201                         } else if ( !e->para_highlight ) {
1202                                 e->drag_status = DRAG_STATUS_COULD_DRAG;
1203                                 e->drag_reason = DRAG_REASON_TEXTSEL;
1204                                 unset_selection(e);
1205                                 find_cursor(clicked, x-fr->x, y-fr->y, &e->sel_start);
1206                         }
1207
1208                 }
1209
1210         } else if ( (clicked == NULL)
1211                  || ( !e->top_editable && (clicked == e->top) ) )
1212         {
1213                 /* Clicked no object. Deselect old object.
1214                  * If shift held, set up for creating a new one. */
1215                 e->selection = NULL;
1216                 unset_selection(e);
1217
1218                 if ( shift ) {
1219                         e->start_corner_x = x;
1220                         e->start_corner_y = y;
1221                         e->drag_status = DRAG_STATUS_COULD_DRAG;
1222                         e->drag_reason = DRAG_REASON_CREATE;
1223                 } else {
1224                         e->drag_status = DRAG_STATUS_NONE;
1225                         e->drag_reason = DRAG_REASON_NONE;
1226                 }
1227
1228         } else {
1229
1230                 /* Clicked an existing frame, no immediate dragging */
1231                 e->drag_status = DRAG_STATUS_COULD_DRAG;
1232                 e->drag_reason = DRAG_REASON_TEXTSEL;
1233                 unset_selection(e);
1234                 find_cursor(clicked, x-clicked->x, y-clicked->y,
1235                             &e->sel_start);
1236                 find_cursor(clicked, x-clicked->x, y-clicked->y,
1237                             &e->sel_end);
1238                 e->selection = clicked;
1239                 e->cursor_frame = clicked;
1240                 if ( clicked == e->top ) {
1241                         check_paragraph(e->cursor_frame, e->pc, clicked->scblocks);
1242                 } else {
1243                         check_paragraph(e->cursor_frame, e->pc,
1244                                         sc_block_child(clicked->scblocks));
1245                 }
1246                 find_cursor(clicked, x-clicked->x, y-clicked->y, &e->cpos);
1247                 ensure_run(e->cursor_frame, e->cpos);
1248
1249         }
1250
1251         gtk_widget_grab_focus(GTK_WIDGET(da));
1252         sc_editor_redraw(e);
1253         return FALSE;
1254 }
1255
1256
1257 static gboolean motion_sig(GtkWidget *da, GdkEventMotion *event,
1258                            SCEditor *e)
1259 {
1260         struct frame *fr = e->selection;
1261         gdouble x, y;
1262
1263         x = event->x - e->border_offs_x;
1264         y = event->y - e->border_offs_y + e->scroll_pos;
1265         x /= e->view_scale;
1266         y /= e->view_scale;
1267
1268         if ( e->drag_status == DRAG_STATUS_COULD_DRAG ) {
1269
1270                 /* We just got a motion signal, and the status was "could drag",
1271                  * therefore the drag has started. */
1272                 e->drag_status = DRAG_STATUS_DRAGGING;
1273
1274         }
1275
1276         switch ( e->drag_reason ) {
1277
1278                 case DRAG_REASON_NONE :
1279                 break;
1280
1281                 case DRAG_REASON_CREATE :
1282                 e->drag_corner_x = x;
1283                 e->drag_corner_y = y;
1284                 sc_editor_redraw(e);
1285                 break;
1286
1287                 case DRAG_REASON_IMPORT :
1288                 /* Do nothing, handled by dnd_motion() */
1289                 break;
1290
1291                 case DRAG_REASON_RESIZE :
1292                 calculate_box_size(fr, e,  x, y);
1293                 sc_editor_redraw(e);
1294                 break;
1295
1296                 case DRAG_REASON_MOVE :
1297                 e->box_x = (fr->x - e->start_corner_x) + x;
1298                 e->box_y = (fr->y - e->start_corner_y) + y;
1299                 e->box_width = fr->w;
1300                 e->box_height = fr->h;
1301                 sc_editor_redraw(e);
1302                 break;
1303
1304                 case DRAG_REASON_TEXTSEL :
1305                 unset_selection(e);
1306                 find_cursor(fr, x-fr->x, y-fr->y, &e->sel_end);
1307                 rewrap_paragraph_range(fr, e->sel_start.para, e->sel_end.para,
1308                                        e->sel_start, e->sel_end, 1);
1309                 find_cursor(fr, x-fr->x, y-fr->y, &e->cpos);
1310                 e->sel_active = !positions_equal(e->sel_start, e->sel_end);
1311                 sc_editor_redraw(e);
1312                 break;
1313
1314         }
1315
1316         gdk_event_request_motions(event);
1317         return FALSE;
1318 }
1319
1320
1321 static struct frame *create_frame(SCEditor *e, double x, double y,
1322                                   double w, double h)
1323 {
1324         struct frame *parent;
1325         struct frame *fr;
1326         SCBlock *scblocks;
1327
1328         parent = e->top;
1329
1330         if ( w < 0.0 ) {
1331                 x += w;
1332                 w = -w;
1333         }
1334
1335         if ( h < 0.0 ) {
1336                 y += h;
1337                 h = -h;
1338         }
1339
1340         /* Add to frame structure */
1341         fr = add_subframe(parent);
1342
1343         /* Add to SC */
1344         scblocks = sc_block_append_end(sc_block_child(e->scblocks), "f", NULL, NULL);
1345         fr->scblocks = scblocks;
1346         sc_block_append_inside(scblocks, NULL, NULL, strdup(""));
1347
1348         fr->x = x;
1349         fr->y = y;
1350         fr->w = w;
1351         fr->h = h;
1352         fr->is_image = 0;
1353         fr->empty = 1;
1354         fr->resizable = 1;
1355
1356         update_geom(fr);
1357
1358         full_rerender(e);
1359         return find_frame_with_scblocks(e->top, scblocks);
1360 }
1361
1362
1363 static void do_resize(SCEditor *e, double x, double y, double w, double h)
1364 {
1365         struct frame *fr;
1366
1367         assert(e->selection != NULL);
1368
1369         if ( w < 0.0 ) {
1370                 w = -w;
1371                 x -= w;
1372         }
1373
1374         if ( h < 0.0 ) {
1375                 h = -h;
1376                 y -= h;
1377         }
1378
1379         fr = e->selection;
1380         fr->x = x;
1381         fr->y = y;
1382         fr->w = w;
1383         fr->h = h;
1384         update_geom(fr);
1385
1386         full_rerender(e);
1387         sc_editor_redraw(e);
1388 }
1389
1390
1391 static gboolean button_release_sig(GtkWidget *da, GdkEventButton *event,
1392                                    SCEditor *e)
1393 {
1394         gdouble x, y;
1395         struct frame *fr;
1396
1397         x = event->x - e->border_offs_x;
1398         y = event->y - e->border_offs_y;
1399         x /= e->view_scale;
1400         y /= e->view_scale;
1401
1402         /* Not dragging?  Then I don't care. */
1403         if ( e->drag_status != DRAG_STATUS_DRAGGING ) return FALSE;
1404
1405         e->drag_corner_x = x;
1406         e->drag_corner_y = y;
1407         e->drag_status = DRAG_STATUS_NONE;
1408
1409         switch ( e->drag_reason )
1410         {
1411
1412                 case DRAG_REASON_NONE :
1413                 printf("Release on pointless drag.\n");
1414                 break;
1415
1416                 case DRAG_REASON_CREATE :
1417                 fr = create_frame(e, e->start_corner_x, e->start_corner_y,
1418                                      e->drag_corner_x - e->start_corner_x,
1419                                      e->drag_corner_y - e->start_corner_y);
1420                 if ( fr != NULL ) {
1421                         check_paragraph(fr, e->pc, sc_block_child(fr->scblocks));
1422                         e->selection = fr;
1423                         e->cursor_frame = fr;
1424                         e->cpos.para = 0;
1425                         e->cpos.pos = 0;
1426                         e->cpos.trail = 0;
1427                 } else {
1428                         fprintf(stderr, "Failed to create frame!\n");
1429                 }
1430                 break;
1431
1432                 case DRAG_REASON_IMPORT :
1433                 /* Do nothing, handled in dnd_drop() or dnd_leave() */
1434                 break;
1435
1436                 case DRAG_REASON_RESIZE :
1437                 do_resize(e, e->box_x, e->box_y, e->box_width, e->box_height);
1438                 break;
1439
1440                 case DRAG_REASON_MOVE :
1441                 do_resize(e, e->box_x, e->box_y, e->box_width, e->box_height);
1442                 break;
1443
1444                 case DRAG_REASON_TEXTSEL :
1445                 /* Do nothing (text is already selected) */
1446                 break;
1447
1448         }
1449
1450         e->drag_reason = DRAG_REASON_NONE;
1451
1452         gtk_widget_grab_focus(GTK_WIDGET(da));
1453         sc_editor_redraw(e);
1454         return FALSE;
1455 }
1456
1457
1458 static void copy_selection(SCEditor *e)
1459 {
1460         GtkClipboard *cb;
1461         char *storycode;
1462         SCBlock *bl;
1463
1464         bl = block_at_cursor(e->cursor_frame, e->cpos.para, 0);
1465         if ( bl == NULL ) return;
1466
1467         storycode = serialise_sc_block(bl);
1468
1469         cb = gtk_clipboard_get(GDK_NONE);
1470         gtk_clipboard_set_text(cb, storycode, -1);
1471         free(storycode);
1472 }
1473
1474
1475 static void paste_callback(GtkClipboard *cb, const gchar *text, void *vp)
1476 {
1477         SCEditor *e = vp;
1478         SCBlock *bl = sc_parse(text);
1479         SCBlock *cur_bl;
1480         size_t cur_sc_pos;
1481         size_t offs;
1482         Paragraph *para;
1483
1484         para = e->cursor_frame->paras[e->cpos.para];
1485         offs = pos_trail_to_offset(para, e->cpos.pos, e->cpos.trail);
1486
1487         get_sc_pos(e->cursor_frame, e->cpos.para, offs, &cur_bl, &cur_sc_pos);
1488         sc_insert_block(cur_bl, cur_sc_pos, bl);
1489         full_rerender(e);
1490 }
1491
1492
1493 static void paste_selection(SCEditor *e)
1494 {
1495         GtkClipboard *cb;
1496
1497         cb = gtk_clipboard_get(GDK_NONE);
1498         gtk_clipboard_request_text(cb, paste_callback, e);
1499 }
1500
1501
1502 static gboolean key_press_sig(GtkWidget *da, GdkEventKey *event,
1503                               SCEditor *e)
1504 {
1505         gboolean r;
1506         int claim = 0;
1507
1508         /* Throw the event to the IM context and let it sort things out */
1509         r = gtk_im_context_filter_keypress(GTK_IM_CONTEXT(e->im_context),
1510                                            event);
1511         if ( r ) return FALSE;  /* IM ate it */
1512
1513         switch ( event->keyval ) {
1514
1515                 case GDK_KEY_Escape :
1516                 if ( !e->para_highlight ) {
1517                         sc_editor_remove_cursor(e);
1518                         sc_editor_redraw(e);
1519                         claim = 1;
1520                 }
1521                 break;
1522
1523                 case GDK_KEY_Left :
1524                 if ( e->selection != NULL ) {
1525                         cursor_moveh(e->cursor_frame, &e->cpos, -1);
1526                         sc_editor_redraw(e);
1527                 }
1528                 claim = 1;
1529                 break;
1530
1531                 case GDK_KEY_Right :
1532                 if ( e->selection != NULL ) {
1533                         cursor_moveh(e->cursor_frame, &e->cpos, +1);
1534                         sc_editor_redraw(e);
1535                 }
1536                 claim = 1;
1537                 break;
1538
1539                 case GDK_KEY_Up :
1540                 if ( e->selection != NULL ) {
1541                         cursor_moveh(e->cursor_frame, &e->cpos, -1);
1542                         sc_editor_redraw(e);
1543                 }
1544                 claim = 1;
1545                 break;
1546
1547                 case GDK_KEY_Down :
1548                 if ( e->selection != NULL ) {
1549                         cursor_moveh(e->cursor_frame, &e->cpos, +1);
1550                         sc_editor_redraw(e);
1551                 }
1552                 claim = 1;
1553                 break;
1554
1555
1556                 case GDK_KEY_Return :
1557                 im_commit_sig(NULL, "\n", e);
1558                 claim = 1;
1559                 break;
1560
1561                 case GDK_KEY_BackSpace :
1562                 if ( e->selection != NULL ) {
1563                         do_backspace(e->selection, e);
1564                         claim = 1;
1565                 }
1566                 break;
1567
1568                 case GDK_KEY_F5 :
1569                 full_rerender(e);
1570                 break;
1571
1572                 case GDK_KEY_F6 :
1573                 show_edit_pos(e->cpos);
1574                 break;
1575
1576                 case GDK_KEY_F7 :
1577                 if ( event->state & GDK_CONTROL_MASK ) {
1578                         debug_paragraphs(e);
1579                 } else if ( event->state & GDK_SHIFT_MASK ) {
1580                         if ( e->cursor_frame != NULL ) {
1581                                 show_sc_block(e->cursor_frame->scblocks, "");
1582                         }
1583                 } else {
1584                         open_debugger(e->cursor_frame);
1585                 }
1586                 break;
1587
1588                 case GDK_KEY_C :
1589                 case GDK_KEY_c :
1590                 if ( event->state == GDK_CONTROL_MASK ) {
1591                         copy_selection(e);
1592                 }
1593                 break;
1594
1595                 case GDK_KEY_V :
1596                 case GDK_KEY_v :
1597                 if ( event->state == GDK_CONTROL_MASK ) {
1598                         paste_selection(e);
1599                 }
1600                 break;
1601
1602
1603         }
1604
1605         if ( claim ) return TRUE;
1606         return FALSE;
1607 }
1608
1609
1610 static gboolean dnd_motion(GtkWidget *widget, GdkDragContext *drag_context,
1611                            gint x, gint y, guint time, SCEditor *e)
1612 {
1613         GdkAtom target;
1614
1615         /* If we haven't already requested the data, do so now */
1616         if ( !e->drag_preview_pending && !e->have_drag_data ) {
1617
1618                 target = gtk_drag_dest_find_target(widget, drag_context, NULL);
1619
1620                 if ( target != GDK_NONE ) {
1621                         gtk_drag_get_data(widget, drag_context, target, time);
1622                         e->drag_preview_pending = 1;
1623                 } else {
1624                         e->import_acceptable = 0;
1625                         gdk_drag_status(drag_context, 0, time);
1626                 }
1627
1628         }
1629
1630         if ( e->have_drag_data && e->import_acceptable ) {
1631
1632                 gdk_drag_status(drag_context, GDK_ACTION_LINK, time);
1633                 e->start_corner_x = x - e->import_width/2.0;
1634                 e->start_corner_y = y - e->import_height/2.0;
1635                 e->drag_corner_x = x + e->import_width/2.0;
1636                 e->drag_corner_y = y + e->import_height/2.0;
1637
1638                 sc_editor_redraw(e);
1639
1640         }
1641
1642         return TRUE;
1643 }
1644
1645
1646 static gboolean dnd_drop(GtkWidget *widget, GdkDragContext *drag_context,
1647                          gint x, gint y, guint time, SCEditor *e)
1648 {
1649         GdkAtom target;
1650
1651         target = gtk_drag_dest_find_target(widget, drag_context, NULL);
1652
1653         if ( target == GDK_NONE ) {
1654                 gtk_drag_finish(drag_context, FALSE, FALSE, time);
1655         } else {
1656                 gtk_drag_get_data(widget, drag_context, target, time);
1657         }
1658
1659         return TRUE;
1660 }
1661
1662
1663 /* Scale the image down if it's a silly size */
1664 static void check_import_size(SCEditor *e)
1665 {
1666         if ( e->import_width > e->w ) {
1667
1668                 int new_import_width;
1669
1670                 new_import_width = e->w/2;
1671                 e->import_height = (new_import_width * e->import_height) /
1672                                      e->import_width;
1673                 e->import_width = new_import_width;
1674
1675         }
1676
1677         if ( e->import_height > e->h ) {
1678
1679                 int new_import_height;
1680
1681                 new_import_height = e->w/2;
1682                 e->import_width = (new_import_height*e->import_width) /
1683                                     e->import_height;
1684                 e->import_height = new_import_height;
1685
1686         }
1687 }
1688
1689
1690 static void dnd_receive(GtkWidget *widget, GdkDragContext *drag_context,
1691                         gint x, gint y, GtkSelectionData *seldata,
1692                         guint info, guint time, SCEditor *e)
1693 {
1694         if ( e->drag_preview_pending ) {
1695
1696                 gchar *filename = NULL;
1697                 GdkPixbufFormat *f;
1698                 gchar **uris;
1699                 int w, h;
1700
1701                 e->have_drag_data = 1;
1702                 e->drag_preview_pending = 0;
1703                 uris = gtk_selection_data_get_uris(seldata);
1704                 if ( uris != NULL ) {
1705                         filename = g_filename_from_uri(uris[0], NULL, NULL);
1706                 }
1707                 g_strfreev(uris);
1708
1709                 if ( filename == NULL ) {
1710
1711                         /* This doesn't even look like a sensible URI.
1712                          * Bail out. */
1713                         gdk_drag_status(drag_context, 0, time);
1714                         if ( e->drag_highlight ) {
1715                                 gtk_drag_unhighlight(widget);
1716                                 e->drag_highlight = 0;
1717                         }
1718                         e->import_acceptable = 0;
1719                         return;
1720
1721                 }
1722                 chomp(filename);
1723
1724                 f = gdk_pixbuf_get_file_info(filename, &w, &h);
1725                 g_free(filename);
1726
1727                 e->import_width = w;
1728                 e->import_height = h;
1729
1730                 if ( f == NULL ) {
1731
1732                         gdk_drag_status(drag_context, 0, time);
1733                         if ( e->drag_highlight ) {
1734                                 gtk_drag_unhighlight(widget);
1735                                 e->drag_highlight = 0;
1736                         }
1737                         e->drag_status = DRAG_STATUS_NONE;
1738                         e->drag_reason = DRAG_REASON_NONE;
1739                         e->import_acceptable = 0;
1740
1741                 } else {
1742
1743                         /* Looks like a sensible image */
1744                         gdk_drag_status(drag_context, GDK_ACTION_PRIVATE, time);
1745                         e->import_acceptable = 1;
1746
1747                         if ( !e->drag_highlight ) {
1748                                 gtk_drag_highlight(widget);
1749                                 e->drag_highlight = 1;
1750                         }
1751
1752                         check_import_size(e);
1753                         e->drag_reason = DRAG_REASON_IMPORT;
1754                         e->drag_status = DRAG_STATUS_DRAGGING;
1755
1756                 }
1757
1758         } else {
1759
1760                 gchar **uris;
1761                 char *filename = NULL;
1762
1763                 uris = gtk_selection_data_get_uris(seldata);
1764                 if ( uris != NULL ) {
1765                         filename = g_filename_from_uri(uris[0], NULL, NULL);
1766                 }
1767                 g_strfreev(uris);
1768
1769                 if ( filename != NULL ) {
1770
1771                         struct frame *fr;
1772                         char *opts;
1773                         size_t len;
1774                         int w, h;
1775
1776                         gtk_drag_finish(drag_context, TRUE, FALSE, time);
1777                         chomp(filename);
1778
1779                         w = e->drag_corner_x - e->start_corner_x;
1780                         h = e->drag_corner_y - e->start_corner_y;
1781
1782                         len = strlen(filename)+64;
1783                         opts = malloc(len);
1784                         if ( opts == NULL ) {
1785                                 free(filename);
1786                                 fprintf(stderr, "Failed to allocate SC\n");
1787                                 return;
1788                         }
1789                         snprintf(opts, len, "1fx1f+0+0,filename=\"%s\"",
1790                                  filename);
1791
1792                         fr = create_frame(e, e->start_corner_x,
1793                                              e->start_corner_y, w, h);
1794                         fr->is_image = 1;
1795                         fr->empty = 0;
1796                         sc_block_set_name(sc_block_child(fr->scblocks), strdup("image"));
1797                         sc_block_set_options(sc_block_child(fr->scblocks), opts);
1798                         full_rerender(e);
1799                         sc_editor_remove_cursor(e);
1800                         sc_editor_redraw(e);
1801                         free(filename);
1802
1803                 } else {
1804
1805                         gtk_drag_finish(drag_context, FALSE, FALSE, time);
1806
1807                 }
1808
1809         }
1810 }
1811
1812
1813 static void dnd_leave(GtkWidget *widget, GdkDragContext *drag_context,
1814                       guint time, SCEditor *sceditor)
1815 {
1816         if ( sceditor->drag_highlight ) {
1817                 gtk_drag_unhighlight(widget);
1818         }
1819         sceditor->have_drag_data = 0;
1820         sceditor->drag_highlight = 0;
1821         sceditor->drag_status = DRAG_STATUS_NONE;
1822         sceditor->drag_reason = DRAG_REASON_NONE;
1823 }
1824
1825
1826 static gint realise_sig(GtkWidget *da, SCEditor *e)
1827 {
1828         GdkWindow *win;
1829
1830         /* Keyboard and input method stuff */
1831         e->im_context = gtk_im_multicontext_new();
1832         win = gtk_widget_get_window(GTK_WIDGET(e));
1833         gtk_im_context_set_client_window(GTK_IM_CONTEXT(e->im_context), win);
1834         gdk_window_set_accept_focus(win, TRUE);
1835         g_signal_connect(G_OBJECT(e->im_context), "commit", G_CALLBACK(im_commit_sig), e);
1836         g_signal_connect(G_OBJECT(e), "key-press-event", G_CALLBACK(key_press_sig), e);
1837
1838         /* FIXME: Can do this "properly" by setting up a separate font map */
1839         e->pc = gtk_widget_get_pango_context(GTK_WIDGET(e));
1840
1841         return FALSE;
1842 }
1843
1844
1845 void sc_editor_set_scblock(SCEditor *e, SCBlock *scblocks)
1846 {
1847         e->scblocks = scblocks;
1848         full_rerender(e);
1849 }
1850
1851
1852 SCBlock *sc_editor_get_scblock(SCEditor *e)
1853 {
1854         return e->scblocks;
1855 }
1856
1857
1858 static void update_size_request(SCEditor *e)
1859 {
1860         gtk_widget_set_size_request(GTK_WIDGET(e), 0, e->h + 2.0*e->min_border);
1861 }
1862
1863
1864 void sc_editor_set_size(SCEditor *e, int w, int h)
1865 {
1866         e->w = w;
1867         e->h = h;
1868         update_size_request(e);
1869         if ( gtk_widget_get_mapped(GTK_WIDGET(e)) ) {
1870                 full_rerender(e);
1871                 sc_editor_redraw(e);
1872         }
1873 }
1874
1875
1876 void sc_editor_set_logical_size(SCEditor *e, double w, double h)
1877 {
1878         e->log_w = w;
1879         e->log_h = h;
1880         if ( gtk_widget_get_mapped(GTK_WIDGET(e)) ) {
1881                 full_rerender(e);
1882                 sc_editor_redraw(e);
1883         }
1884 }
1885
1886
1887 void sc_editor_set_slidenum(SCEditor *e, int slidenum)
1888 {
1889         e->slidenum = slidenum;
1890 }
1891
1892
1893 void sc_editor_set_min_border(SCEditor *e, double min_border)
1894 {
1895         e->min_border = min_border;
1896         update_size_request(e);
1897 }
1898
1899
1900 void sc_editor_set_top_frame_editable(SCEditor *e, int top_frame_editable)
1901 {
1902         e->top_editable = top_frame_editable;
1903 }
1904
1905
1906 static SCBlock **copy_ss_list(SCBlock **stylesheets)
1907 {
1908         int n_ss = 0;
1909         int i = 0;
1910         SCBlock **ssc;
1911
1912         if ( stylesheets == NULL ) return NULL;
1913
1914         while ( stylesheets[n_ss] != NULL ) n_ss++;
1915         n_ss++;  /* One more for sentinel */
1916
1917         ssc = malloc(n_ss*sizeof(SCBlock *));
1918         if ( ssc == NULL ) return NULL;
1919
1920         for ( i=0; i<n_ss; i++ ) ssc[i] = stylesheets[i];
1921
1922         return ssc;
1923 }
1924
1925
1926 void sc_editor_set_stylesheets(SCEditor *e, SCBlock **stylesheets)
1927 {
1928         int i = 0;;
1929         while ( e->stylesheets[i] != NULL ) {
1930                 sc_block_free(e->stylesheets[i++]);
1931         }
1932         free(e->stylesheets);
1933         e->stylesheets = copy_ss_list(stylesheets);
1934 }
1935
1936
1937 void sc_editor_set_callbacks(SCEditor *e, SCCallbackList *cbl)
1938 {
1939         if ( e->cbl != NULL ) sc_callback_list_free(e->cbl);
1940         e->cbl = cbl;
1941 }
1942
1943
1944 void sc_editor_set_para_highlight(SCEditor *e, int para_highlight)
1945 {
1946         e->para_highlight = para_highlight;
1947         sc_editor_redraw(e);
1948 }
1949
1950 int sc_editor_get_cursor_para(SCEditor *e)
1951 {
1952         if ( e->cursor_frame == NULL ) return 0;
1953         return e->cpos.para;
1954 }
1955
1956
1957 void *sc_editor_get_cursor_bvp(SCEditor *e)
1958 {
1959         Paragraph *para;
1960         if ( e->cursor_frame == NULL ) return 0;
1961         para = e->cursor_frame->paras[e->cpos.para];
1962         return get_para_bvp(para);
1963 }
1964
1965
1966 void sc_editor_set_cursor_para(SCEditor *e, signed int pos)
1967 {
1968         double h;
1969         int i;
1970
1971         if ( e->cursor_frame == NULL ) {
1972                 e->cursor_frame = e->top;
1973                 e->selection = e->top;
1974         }
1975
1976         if ( pos < 0 ) {
1977                 e->cpos.para = e->cursor_frame->n_paras - 1;
1978         } else if ( pos >= e->cursor_frame->n_paras ) {
1979                 e->cpos.para = e->cursor_frame->n_paras - 1;
1980         } else {
1981                 e->cpos.para = pos;
1982         }
1983         e->cpos.pos = 0;
1984         e->cpos.trail = 0;
1985
1986         h = 0;
1987         for ( i=0; i<e->cpos.para; i++ ) {
1988                 h += paragraph_height(e->cursor_frame->paras[i]);
1989         }
1990         h += (paragraph_height(e->cursor_frame->paras[e->cpos.para]))/2;
1991         e->scroll_pos = h - (e->visible_height/2);
1992         set_vertical_params(e);
1993
1994         sc_editor_redraw(e);
1995 }
1996
1997
1998 int sc_editor_get_num_paras(SCEditor *e)
1999 {
2000         if ( e->cursor_frame == NULL ) return 1;
2001         return e->cursor_frame->n_paras;
2002 }
2003
2004
2005 void sc_editor_set_scale(SCEditor *e, int scale)
2006 {
2007         e->scale = scale;
2008         if ( !scale ) {
2009                 e->view_scale = 1.0;
2010         }
2011 }
2012
2013
2014 void sc_editor_set_imagestore(SCEditor *e, ImageStore *is)
2015 {
2016         if ( e->is != NULL ) {
2017                 fprintf(stderr, "WARNING: Changing imagestore\n");
2018         }
2019         e->is = is;
2020 }
2021
2022
2023 SCEditor *sc_editor_new(SCBlock *scblocks, SCBlock **stylesheets,
2024                         PangoLanguage *lang, const char *storename)
2025 {
2026         SCEditor *sceditor;
2027         GtkTargetEntry targets[1];
2028         GError *err;
2029
2030         sceditor = g_object_new(SC_TYPE_EDITOR, NULL);
2031
2032         sceditor->scblocks = scblocks;
2033         sceditor->w = 100;
2034         sceditor->h = 100;
2035         sceditor->log_w = 100;
2036         sceditor->log_h = 100;
2037         sceditor->border_offs_x = 0;
2038         sceditor->border_offs_y = 0;
2039         sceditor->is = NULL;
2040         sceditor->slidenum = 0;
2041         sceditor->min_border = 0.0;
2042         sceditor->top_editable = 0;
2043         sceditor->cbl = NULL;
2044         sceditor->scroll_pos = 0;
2045         sceditor->flow = 0;
2046         sceditor->scale = 0;
2047         sceditor->view_scale = 1.0;
2048         sceditor->lang = lang;
2049
2050         sceditor->para_highlight = 0;
2051         sc_editor_remove_cursor(sceditor);
2052
2053         sceditor->stylesheets = copy_ss_list(stylesheets);
2054
2055         sceditor->bg_pixbuf = NULL;
2056
2057         err = NULL;
2058         sceditor->bg_pixbuf = gdk_pixbuf_new_from_file(DATADIR"/sky.png", &err);
2059         if ( sceditor->bg_pixbuf == NULL ) {
2060                 fprintf(stderr, "Failed to load background: %s\n",
2061                         err->message);
2062         }
2063
2064         gtk_widget_set_size_request(GTK_WIDGET(sceditor),
2065                                     sceditor->w, sceditor->h);
2066
2067         g_signal_connect(G_OBJECT(sceditor), "destroy",
2068                          G_CALLBACK(destroy_sig), sceditor);
2069         g_signal_connect(G_OBJECT(sceditor), "realize",
2070                          G_CALLBACK(realise_sig), sceditor);
2071         g_signal_connect(G_OBJECT(sceditor), "button-press-event",
2072                          G_CALLBACK(button_press_sig), sceditor);
2073         g_signal_connect(G_OBJECT(sceditor), "button-release-event",
2074                          G_CALLBACK(button_release_sig), sceditor);
2075         g_signal_connect(G_OBJECT(sceditor), "motion-notify-event",
2076                          G_CALLBACK(motion_sig), sceditor);
2077         g_signal_connect(G_OBJECT(sceditor), "configure-event",
2078                          G_CALLBACK(resize_sig), sceditor);
2079
2080         /* Drag and drop */
2081         targets[0].target = "text/uri-list";
2082         targets[0].flags = 0;
2083         targets[0].info = 1;
2084         gtk_drag_dest_set(GTK_WIDGET(sceditor), 0, targets, 1,
2085                           GDK_ACTION_PRIVATE);
2086         g_signal_connect(sceditor, "drag-data-received",
2087                          G_CALLBACK(dnd_receive), sceditor);
2088         g_signal_connect(sceditor, "drag-motion",
2089                          G_CALLBACK(dnd_motion), sceditor);
2090         g_signal_connect(sceditor, "drag-drop",
2091                          G_CALLBACK(dnd_drop), sceditor);
2092         g_signal_connect(sceditor, "drag-leave",
2093                          G_CALLBACK(dnd_leave), sceditor);
2094
2095         gtk_widget_set_can_focus(GTK_WIDGET(sceditor), TRUE);
2096         gtk_widget_add_events(GTK_WIDGET(sceditor),
2097                               GDK_POINTER_MOTION_HINT_MASK
2098                                | GDK_BUTTON1_MOTION_MASK
2099                                | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
2100                                | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK
2101                                | GDK_SCROLL_MASK);
2102
2103         g_signal_connect(G_OBJECT(sceditor), "draw",
2104                          G_CALLBACK(draw_sig), sceditor);
2105
2106         gtk_widget_grab_focus(GTK_WIDGET(sceditor));
2107
2108         gtk_widget_show(GTK_WIDGET(sceditor));
2109
2110         return sceditor;
2111 }