2697d5f29646ef37a5f5d11537e9b5a078410ad2
[colloquium.git] / libstorycode / gtk / gtkslideview.c
1 /*
2  * gtkslideview.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 #include <libintl.h>
36 #define _(x) gettext(x)
37
38 #include <narrative.h>
39 #include <slide_render_cairo.h>
40 #include <stylesheet.h>
41
42 #include "gtkslideview.h"
43 #include "slide_priv.h"
44
45
46 G_DEFINE_TYPE_WITH_CODE(GtkSlideView, gtk_slide_view, GTK_TYPE_DRAWING_AREA,
47                         NULL)
48
49 static int resizable(SlideItem *item)
50 {
51         if ( item->type == SLIDE_ITEM_TEXT ) return 1;
52         if ( item->type == SLIDE_ITEM_IMAGE ) return 1;
53         return 0;
54 }
55
56 static gboolean gtksv_resize_sig(GtkWidget *widget, GdkEventConfigure *event,
57                                  GtkSlideView *e)
58 {
59         double sx, sy;
60         double aw, ah;
61         double log_w, log_h;
62         Stylesheet *ss;
63
64         ss = narrative_get_stylesheet(e->n);
65         if ( slide_get_logical_size(e->slide, ss, &log_w, &log_h) ) {
66                 fprintf(stderr, "Failed to get logical size\n");
67                 return FALSE;
68         }
69
70         e->w = event->width;
71         e->h = event->height;
72         sx = (double)e->w / log_w;
73         sy = (double)e->h / log_h;
74         e->view_scale = (sx < sy) ? sx : sy;
75
76         /* Actual size (in device units) */
77         aw = e->view_scale * log_w;
78         ah = e->view_scale * log_h;
79
80         e->border_offs_x = (event->width - aw)/2.0;
81         e->border_offs_y = (event->height - ah)/2.0;
82
83         e->visible_height = event->height;
84         e->visible_width = event->width;
85
86         //update_size(e);
87
88         return FALSE;
89 }
90
91
92 static void gtksv_emit_change_sig(GtkSlideView *e)
93 {
94         g_signal_emit_by_name(e, "changed");
95 }
96
97
98 static void gtk_slide_view_class_init(GtkSlideViewClass *klass)
99 {
100         g_signal_new("changed", GTK_TYPE_SLIDE_VIEW, G_SIGNAL_RUN_LAST, 0,
101                      NULL, NULL, NULL, G_TYPE_NONE, 0);
102 }
103
104
105 static void gtk_slide_view_init(GtkSlideView *e)
106 {
107 }
108
109
110 static void gtksv_redraw(GtkSlideView *e)
111 {
112         gint w, h;
113         w = gtk_widget_get_allocated_width(GTK_WIDGET(e));
114         h = gtk_widget_get_allocated_height(GTK_WIDGET(e));
115         gtk_widget_queue_draw_area(GTK_WIDGET(e), 0, 0, w, h);
116 }
117
118
119 static gint gtksv_destroy_sig(GtkWidget *window, GtkSlideView *e)
120 {
121         return 0;
122 }
123
124
125 static void draw_editing_box(cairo_t *cr, SlideItem *item,
126                              Stylesheet *stylesheet, double slide_w, double slide_h,
127                              double xmin, double ymin, double width, double height)
128 {
129         const double dash[] = {2.0, 2.0};
130         double ptot_w, ptot_h;
131         double pad_l, pad_r, pad_t, pad_b;
132
133         slide_item_get_padding(item, stylesheet, &pad_l, &pad_r, &pad_t, &pad_b,
134                                slide_w, slide_h);
135
136         cairo_new_path(cr);
137         cairo_rectangle(cr, xmin, ymin, width, height);
138         cairo_set_source_rgb(cr, 0.0, 0.69, 1.0);
139         cairo_set_line_width(cr, 0.5);
140         cairo_stroke(cr);
141
142         cairo_new_path(cr);
143         ptot_w = pad_l + pad_r;
144         ptot_h = pad_t + pad_b;
145         cairo_rectangle(cr, xmin+pad_l, ymin+pad_t, width-ptot_w, height-ptot_h);
146         cairo_set_dash(cr, dash, 2, 0.0);
147         cairo_set_source_rgba(cr, 0.8, 0.8, 1.0, 0.5);
148         cairo_set_line_width(cr, 1.0);
149         cairo_stroke(cr);
150
151         cairo_set_dash(cr, NULL, 0, 0.0);
152 }
153
154
155 static void draw_resize_handle(cairo_t *cr, double x, double y)
156 {
157         cairo_new_path(cr);
158         cairo_rectangle(cr, x, y, 20.0, 20.0);
159         cairo_set_source_rgba(cr, 0.9, 0.9, 0.9, 0.5);
160         cairo_fill(cr);
161 }
162
163
164 static double gtksv_para_top(SlideItem *item, int pnum, double paraspace_t, double paraspace_b)
165 {
166         int i;
167         double py = 0.0;
168         for ( i=0; i<pnum; i++ ) {
169                 PangoRectangle rect;
170                 pango_layout_get_extents(item->paras[i].layout, NULL, &rect);
171                 py += pango_units_to_double(rect.height);
172                 py += paraspace_t;
173                 py += paraspace_b;
174         }
175         return py + paraspace_t;
176 }
177
178
179 static void get_paraspace(SlideItem *item, Stylesheet *stylesheet,
180                           double paraspace[4], double slide_w, double slide_h)
181 {
182         struct length paraspacel[4];
183         const char *stn;
184
185         switch ( item->type ) {
186                 case SLIDE_ITEM_TEXT : stn = "SLIDE.TEXT"; break;
187                 case SLIDE_ITEM_SLIDETITLE : stn = "SLIDE.SLIDETITLE"; break;
188                 case SLIDE_ITEM_PRESTITLE : stn = "SLIDE.PRESTITLE"; break;
189                 default : stn = NULL; break;
190         }
191
192         if ( stylesheet_get_paraspace(stylesheet, stn, paraspacel) == 0 ) {
193                 paraspace[0] = lcalc(paraspacel[0], slide_w);
194                 paraspace[1] = lcalc(paraspacel[1], slide_w);
195                 paraspace[2] = lcalc(paraspacel[2], slide_h);
196                 paraspace[3] = lcalc(paraspacel[3], slide_h);
197         } else {
198                 paraspace[0] = 0.0;
199                 paraspace[1] = 0.0;
200                 paraspace[2] = 0.0;
201                 paraspace[3] = 0.0;
202         }
203 }
204
205
206 static int gtksv_get_cursor_pos(SlideItem *item, Stylesheet *stylesheet,
207                                 struct slide_pos cpos, double slide_w, double slide_h,
208                                 double *x, double *y, double *h)
209 {
210         size_t offs;
211         PangoRectangle rect;
212         double padl, padr, padt, padb;
213         double paraspace[4];
214
215         if ( item->paras == NULL ) return 1;
216
217         if ( item->paras[cpos.para].layout == NULL ) {
218                 fprintf(stderr, "get_cursor_pos: No layout\n");
219                 return 1;
220         }
221
222         slide_item_get_padding(item, stylesheet, &padl, &padr, &padt, &padb,
223                                slide_w, slide_h);
224         get_paraspace(item, stylesheet, paraspace, slide_w, slide_h);
225
226         offs = slide_pos_trail_to_offset(item, cpos.para, cpos.pos, cpos.trail);
227         pango_layout_get_cursor_pos(item->paras[cpos.para].layout, offs, &rect, NULL);
228         *x = pango_units_to_double(rect.x) + padl + paraspace[0];
229         *y = pango_units_to_double(rect.y)
230              + gtksv_para_top(item, cpos.para, paraspace[2], paraspace[3])
231              + padt;
232         *h = pango_units_to_double(rect.height);
233         return 0;
234 }
235
236
237 static void gtksv_draw_caret(cairo_t *cr, Stylesheet *stylesheet,
238                              SlideItem *item, struct slide_pos cpos,
239                              double frx, double fry, double slide_w, double slide_h)
240 {
241         double cx, clow, chigh, h;
242         const double t = 1.8;
243
244         if ( gtksv_get_cursor_pos(item, stylesheet, cpos, slide_w, slide_h,
245                                   &cx, &clow, &h) ) return;
246
247         cx += frx;
248         clow += fry;
249         chigh = clow + h;
250
251         cairo_move_to(cr, cx, clow);
252         cairo_line_to(cr, cx, chigh);
253
254         cairo_move_to(cr, cx-t, clow-t);
255         cairo_line_to(cr, cx, clow);
256         cairo_move_to(cr, cx+t, clow-t);
257         cairo_line_to(cr, cx, clow);
258
259         cairo_move_to(cr, cx-t, chigh+t);
260         cairo_line_to(cr, cx, chigh);
261         cairo_move_to(cr, cx+t, chigh+t);
262         cairo_line_to(cr, cx, chigh);
263
264         cairo_set_source_rgb(cr, 0.86, 0.0, 0.0);
265         cairo_set_line_width(cr, 1.0);
266         cairo_stroke(cr);
267 }
268
269
270 static void gtksv_draw_overlay(cairo_t *cr, GtkSlideView *e)
271 {
272         if ( e->cursor_frame != NULL ) {
273
274                 double x, y, w, h;
275                 double slide_w, slide_h;
276                 Stylesheet *stylesheet;
277
278                 stylesheet = narrative_get_stylesheet(e->n);
279                 slide_get_logical_size(e->slide, stylesheet, &slide_w, &slide_h);
280                 slide_item_get_geom(e->cursor_frame, stylesheet, &x, &y, &w, &h,
281                                     slide_w, slide_h);
282                 draw_editing_box(cr, e->cursor_frame, stylesheet, slide_w, slide_h,
283                                  x, y, w, h);
284
285                 if ( resizable(e->cursor_frame) ) {
286                         /* Draw resize handles */
287                         draw_resize_handle(cr, x, y+h-20.0);
288                         draw_resize_handle(cr, x+w-20.0, y);
289                         draw_resize_handle(cr, x, y);
290                         draw_resize_handle(cr, x+w-20.0, y+h-20.0);
291                 }
292
293                 if ( e->cursor_frame->type != SLIDE_ITEM_IMAGE ) {
294                         gtksv_draw_caret(cr, stylesheet, e->cursor_frame, e->cpos, x, y,
295                                          slide_w, slide_h);
296                 }
297
298         }
299
300         if ( e->drag_status == SLIDE_DRAG_STATUS_DRAGGING ) {
301
302                 if ( (e->drag_reason == DRAG_REASON_CREATE)
303                   || (e->drag_reason == DRAG_REASON_IMPORT) )
304                 {
305                         cairo_new_path(cr);
306                         cairo_rectangle(cr, e->start_corner_x, e->start_corner_y,
307                                             e->drag_corner_x - e->start_corner_x,
308                                             e->drag_corner_y - e->start_corner_y);
309                         cairo_set_source_rgb(cr, 0.5, 0.5, 0.5);
310                         cairo_set_line_width(cr, 0.5);
311                         cairo_stroke(cr);
312                 }
313
314                 if ( (e->drag_reason == DRAG_REASON_RESIZE)
315                   || (e->drag_reason == DRAG_REASON_MOVE) )
316                 {
317                         cairo_new_path(cr);
318                         cairo_rectangle(cr, e->box_x, e->box_y,
319                                             e->box_width, e->box_height);
320                         cairo_set_source_rgb(cr, 0.5, 0.5, 0.5);
321                         cairo_set_line_width(cr, 0.5);
322                         cairo_stroke(cr);
323                 }
324
325         }
326 }
327
328
329 static gboolean gtksv_draw_sig(GtkWidget *da, cairo_t *cr, GtkSlideView *e)
330 {
331         PangoContext *pc;
332
333         /* Ultimate background */
334         if ( e->bg_pixbuf != NULL ) {
335                 gdk_cairo_set_source_pixbuf(cr, e->bg_pixbuf, 0.0, 0.0);
336                 cairo_pattern_t *patt = cairo_get_source(cr);
337                 cairo_pattern_set_extend(patt, CAIRO_EXTEND_REPEAT);
338                 cairo_paint(cr);
339         } else {
340                 cairo_set_source_rgba(cr, 0.8, 0.8, 1.0, 1.0);
341                 cairo_paint(cr);
342         }
343
344         cairo_translate(cr, e->border_offs_x, e->border_offs_y);
345         cairo_translate(cr, -e->h_scroll_pos, -e->v_scroll_pos);
346         cairo_scale(cr, e->view_scale, e->view_scale);
347
348         /* Contents */
349         pc = pango_cairo_create_context(cr);
350         slide_render_cairo(e->slide, cr, narrative_get_imagestore(e->n),
351                            narrative_get_stylesheet(e->n),
352                            narrative_get_slide_number_for_slide(e->n, e->slide),
353                            pango_language_get_default(), pc,
354                            e->cursor_frame, e->sel_start, e->sel_end);
355         g_object_unref(pc);
356
357         /* Editing overlay */
358         gtksv_draw_overlay(cr, e);
359
360         return FALSE;
361 }
362
363
364 static int within_frame(SlideItem *item, Stylesheet *ss,
365                         double slide_w, double slide_h,
366                         double xp, double yp)
367 {
368         double x, y, w, h;
369
370         slide_item_get_geom(item, ss, &x, &y, &w, &h, slide_w, slide_h);
371
372         if ( xp < x ) return 0;
373         if ( yp < y ) return 0;
374         if ( xp > x + w ) return 0;
375         if ( yp > y + h ) return 0;
376         return 1;
377 }
378
379
380 static SlideItem *find_frame_at_position(Slide *s, Stylesheet *ss,
381                                                  double slide_w, double slide_h,
382                                                  double x, double y)
383 {
384         int i;
385         for ( i=0; i<s->n_items; i++ ) {
386                 if ( within_frame(&s->items[i], ss, slide_w, slide_h, x, y) ) {
387                         return &s->items[i];
388                 }
389         }
390         return NULL;
391 }
392
393
394 static enum drag_corner which_corner(double xp, double yp,
395                                      double frx, double fry, double frw, double frh)
396 {
397         double x, y;  /* Relative to object position */
398
399         x = xp - frx;
400         y = yp - fry;
401
402         if ( x < 0.0 ) return CORNER_NONE;
403         if ( y < 0.0 ) return CORNER_NONE;
404         if ( x > frw ) return CORNER_NONE;
405         if ( y > frh ) return CORNER_NONE;
406
407         /* Top left? */
408         if ( (x<20.0) && (y<20.0) ) return CORNER_TL;
409         if ( (x>frw-20.0) && (y<20.0) ) return CORNER_TR;
410         if ( (x<20.0) && (y>frh-20.0) ) return CORNER_BL;
411         if ( (x>frw-20.0) && (y>frh-20.0) ) return CORNER_BR;
412
413         return CORNER_NONE;
414 }
415
416
417 static void calculate_box_size(double frx, double fry, double frw, double frh,
418                                GtkSlideView *e, int preserve_aspect,
419                                double x, double y)
420 {
421         double ddx, ddy, dlen, mult;
422         double vx, vy, dbx, dby;
423
424         ddx = x - e->start_corner_x;
425         ddy = y - e->start_corner_y;
426
427         if ( !preserve_aspect ) {
428
429                 switch ( e->drag_corner ) {
430
431                         case CORNER_BR :
432                         e->box_x = frx;
433                         e->box_y = fry;
434                         e->box_width = frw + ddx;
435                         e->box_height = frh + ddy;
436                         break;
437
438                         case CORNER_BL :
439                         e->box_x = frx + ddx;
440                         e->box_y = fry;
441                         e->box_width = frw - ddx;
442                         e->box_height = frh + ddy;
443                         break;
444
445                         case CORNER_TL :
446                         e->box_x = frx + ddx;
447                         e->box_y = fry + ddy;
448                         e->box_width = frw - ddx;
449                         e->box_height = frh - ddy;
450                         break;
451
452                         case CORNER_TR :
453                         e->box_x = frx;
454                         e->box_y = fry + ddy;
455                         e->box_width = frw + ddx;
456                         e->box_height = frh - ddy;
457                         break;
458
459                         case CORNER_NONE :
460                         break;
461
462                 }
463                 return;
464
465
466         }
467
468         switch ( e->drag_corner ) {
469
470                 case CORNER_BR :
471                 vx = frw;
472                 vy = frh;
473                 break;
474
475                 case CORNER_BL :
476                 vx = -frw;
477                 vy = frh;
478                 break;
479
480                 case CORNER_TL :
481                 vx = -frw;
482                 vy = -frh;
483                 break;
484
485                 case CORNER_TR :
486                 vx = frw;
487                 vy = -frh;
488                 break;
489
490                 case CORNER_NONE :
491                 default:
492                 vx = 0.0;
493                 vy = 0.0;
494                 break;
495
496         }
497
498         dlen = (ddx*vx + ddy*vy) / e->diagonal_length;
499         mult = (dlen+e->diagonal_length) / e->diagonal_length;
500
501         e->box_width = frw * mult;
502         e->box_height = frh * mult;
503         dbx = e->box_width - frw;
504         dby = e->box_height - frh;
505
506         if ( e->box_width < 40.0 ) {
507                 mult = 40.0 / frw;
508         }
509         if ( e->box_height < 40.0 ) {
510                 mult = 40.0 / frh;
511         }
512         e->box_width = frw * mult;
513         e->box_height = frh * mult;
514         dbx = e->box_width - frw;
515         dby = e->box_height - frh;
516
517         switch ( e->drag_corner ) {
518
519                 case CORNER_BR :
520                 e->box_x = frx;
521                 e->box_y = fry;
522                 break;
523
524                 case CORNER_BL :
525                 e->box_x = frx - dbx;
526                 e->box_y = fry;
527                 break;
528
529                 case CORNER_TL :
530                 e->box_x = frx - dbx;
531                 e->box_y = fry - dby;
532                 break;
533
534                 case CORNER_TR :
535                 e->box_x = frx;
536                 e->box_y = fry - dby;
537                 break;
538
539                 case CORNER_NONE :
540                 break;
541
542         }
543 }
544
545
546 static int is_text(enum slide_item_type type)
547 {
548         if ( type == SLIDE_ITEM_IMAGE ) return 0;
549         return 1;
550 }
551
552
553 static int gtksv_find_cursor(SlideItem *item, Stylesheet *stylesheet,
554                              double x, double y, struct slide_pos *pos,
555                              double slide_w, double slide_h)
556 {
557         double cur_y = 0.0;
558         double top;
559         int i = 0;
560         double padl, padr, padt, padb;
561         double paraspace[4];
562
563         if ( !is_text(item->type) ) {
564                 pos->para = 0;
565                 pos->pos = 0;
566                 pos->trail = 0;
567                 return 0;
568         }
569
570         slide_item_get_padding(item, stylesheet, &padl, &padr, &padt, &padb,
571                                slide_w, slide_h);
572         get_paraspace(item, stylesheet, paraspace, slide_w, slide_h);
573         x -= padl;
574         x -= paraspace[0];
575         y -= padt;
576
577         if ( item->paras == NULL ) {
578                 printf("No paragraphs (item %p)!\n", item);
579                 pos->para = 0;
580                 pos->pos = 0;
581                 pos->trail = 0;
582                 return 0;
583         }
584
585         do {
586                 PangoRectangle rect;
587                 pango_layout_get_extents(item->paras[i++].layout, NULL, &rect);
588                 top = cur_y;
589                 cur_y += pango_units_to_double(rect.height);
590                 cur_y += paraspace[2];
591                 cur_y += paraspace[3];
592         } while ( (cur_y < y) && (i<item->n_paras) );
593
594         pos->para = i-1;
595
596         pango_layout_xy_to_index(item->paras[i-1].layout,
597                                  pango_units_from_double(x - padl),
598                                  pango_units_from_double(y - top - padt),
599                                  &pos->pos, &pos->trail);
600         return 0;
601 }
602
603
604 static void gtksv_unset_selection(GtkSlideView *e)
605 {
606         e->sel_start.para = 0;
607         e->sel_start.pos = 0;
608         e->sel_start.trail = 0;
609         e->sel_end.para = 0;
610         e->sel_end.pos = 0;
611         e->sel_end.trail = 0;
612 }
613
614
615 static void do_resize(GtkSlideView *e, double x, double y, double w, double h)
616 {
617         double slide_w, slide_h;
618         Stylesheet *stylesheet;
619
620         assert(e->cursor_frame != NULL);
621
622         stylesheet = narrative_get_stylesheet(e->n);
623         slide_get_logical_size(e->slide, stylesheet, &slide_w, &slide_h);
624
625         if ( w < 0.0 ) {
626                 w = -w;
627                 x -= w;
628         }
629
630         if ( h < 0.0 ) {
631                 h = -h;
632                 y -= h;
633         }
634
635         /* If any of the units are fractional, turn the absolute values back
636          * into fractional values */
637         if ( e->cursor_frame->geom.x.unit == LENGTH_FRAC ) {
638                 e->cursor_frame->geom.x.len = x / slide_w;
639         } else {
640                 e->cursor_frame->geom.x.len = x;
641         }
642
643         if ( e->cursor_frame->geom.y.unit == LENGTH_FRAC ) {
644                 e->cursor_frame->geom.y.len = y / slide_h;
645         } else {
646                 e->cursor_frame->geom.y.len = y;
647         }
648
649         if ( e->cursor_frame->geom.w.unit == LENGTH_FRAC ) {
650                 e->cursor_frame->geom.w.len = w / slide_w;
651         } else {
652                 e->cursor_frame->geom.w.len = w;
653         }
654
655         if ( e->cursor_frame->geom.h.unit == LENGTH_FRAC ) {
656                 e->cursor_frame->geom.h.len = h / slide_h;
657         } else {
658                 e->cursor_frame->geom.h.len = h;
659         }
660
661         gtksv_redraw(e);
662 }
663
664
665 static gboolean gtksv_button_press_sig(GtkWidget *da, GdkEventButton *event,
666                                        GtkSlideView *e)
667 {
668         enum drag_corner c;
669         gdouble x, y;
670         Stylesheet *stylesheet;
671         SlideItem *clicked;
672         int shift;
673         double slide_w, slide_h;
674         double frx, fry, frw, frh;
675
676         stylesheet = narrative_get_stylesheet(e->n);
677         slide_get_logical_size(e->slide, stylesheet, &slide_w, &slide_h);
678
679         x = event->x - e->border_offs_x + e->h_scroll_pos;
680         y = event->y - e->border_offs_y + e->v_scroll_pos;
681         x /= e->view_scale;
682         y /= e->view_scale;
683         shift = event->state & GDK_SHIFT_MASK;
684
685         clicked = find_frame_at_position(e->slide, stylesheet,
686                                          slide_w, slide_h, x, y);
687
688         if ( clicked != NULL ) {
689                 slide_item_get_geom(clicked, stylesheet, &frx, &fry, &frw, &frh,
690                                     slide_w, slide_h);
691         }
692
693         /* Clicked within the currently selected frame
694          *   -> resize, move or select text */
695         if ( (e->cursor_frame != NULL) && (clicked == e->cursor_frame) ) {
696
697                 /* Within the resizing region? */
698                 c = which_corner(x, y, frx, fry, frw, frh);
699                 if ( (c != CORNER_NONE) && resizable(e->cursor_frame) && shift ) {
700
701                         e->drag_reason = DRAG_REASON_RESIZE;
702                         e->drag_corner = c;
703
704                         e->start_corner_x = x;
705                         e->start_corner_y = y;
706                         e->diagonal_length = pow(frw, 2.0);
707                         e->diagonal_length += pow(frh, 2.0);
708                         e->diagonal_length = sqrt(e->diagonal_length);
709
710                         calculate_box_size(frx, fry, frw, frh, e,
711                                            (e->cursor_frame->type == SLIDE_ITEM_IMAGE),
712                                            x, y);
713
714                         e->drag_status = SLIDE_DRAG_STATUS_COULD_DRAG;
715                         e->drag_reason = DRAG_REASON_RESIZE;
716
717                 } else {
718
719                         /* Position cursor and prepare for possible drag */
720                         e->cursor_frame = clicked;
721                         gtksv_find_cursor(clicked, stylesheet, x-frx, y-fry, &e->cpos,
722                                           slide_w, slide_h);
723
724                         e->start_corner_x = x;
725                         e->start_corner_y = y;
726
727                         if ( resizable(clicked) && shift ) {
728                                 e->drag_status = SLIDE_DRAG_STATUS_COULD_DRAG;
729                                 e->drag_reason = DRAG_REASON_MOVE;
730                         } else {
731                                 e->drag_status = SLIDE_DRAG_STATUS_COULD_DRAG;
732                                 e->drag_reason = DRAG_REASON_TEXTSEL;
733                                 gtksv_find_cursor(clicked, stylesheet, x-frx, y-fry,
734                                                   &e->sel_start, slide_w, slide_h);
735                                 e->sel_end = e->sel_start;
736                         }
737
738                 }
739
740         } else if ( clicked == NULL ) {
741
742                 /* Clicked no object. Deselect old object.
743                  * If shift held, set up for creating a new one. */
744                 e->cursor_frame = NULL;
745                 gtksv_unset_selection(e);
746
747                 if ( shift ) {
748                         e->start_corner_x = x;
749                         e->start_corner_y = y;
750                         e->drag_status = SLIDE_DRAG_STATUS_COULD_DRAG;
751                         e->drag_reason = DRAG_REASON_CREATE;
752                 } else {
753                         e->drag_status = SLIDE_DRAG_STATUS_NONE;
754                         e->drag_reason = DRAG_REASON_NONE;
755                 }
756
757         } else {
758
759                 /* Clicked an existing frame, no immediate dragging */
760                 e->drag_status = SLIDE_DRAG_STATUS_COULD_DRAG;
761                 e->drag_reason = DRAG_REASON_TEXTSEL;
762                 gtksv_unset_selection(e);
763                 gtksv_find_cursor(clicked, stylesheet, x-frx, y-fry, &e->sel_start,
764                                   slide_w, slide_h);
765                 gtksv_find_cursor(clicked, stylesheet, x-frx, y-fry, &e->sel_end,
766                                   slide_w, slide_h);
767                 e->cursor_frame = clicked;
768                 gtksv_find_cursor(clicked, stylesheet, x-frx, y-fry, &e->cpos,
769                                   slide_w, slide_h);
770
771         }
772
773         gtk_widget_grab_focus(GTK_WIDGET(da));
774         gtksv_redraw(e);
775         return FALSE;
776 }
777
778
779 static gboolean gtksv_motion_sig(GtkWidget *da, GdkEventMotion *event, GtkSlideView *e)
780 {
781         gdouble x, y;
782         double frx, fry, frw, frh;
783         Stylesheet *stylesheet;
784         double slide_w, slide_h;
785
786         x = event->x - e->border_offs_x + e->h_scroll_pos;
787         y = event->y - e->border_offs_y + e->v_scroll_pos;
788         x /= e->view_scale;
789         y /= e->view_scale;
790
791         stylesheet = narrative_get_stylesheet(e->n);
792         slide_get_logical_size(e->slide, stylesheet, &slide_w, &slide_h);
793
794         if ( e->drag_status == SLIDE_DRAG_STATUS_COULD_DRAG ) {
795
796                 /* We just got a motion signal, and the status was "could drag",
797                  * therefore the drag has started. */
798                 e->drag_status = SLIDE_DRAG_STATUS_DRAGGING;
799
800         }
801
802         if ( e->cursor_frame != NULL ) {
803                 slide_item_get_geom(e->cursor_frame, stylesheet,
804                                     &frx, &fry, &frw, &frh, slide_w, slide_h);
805         }
806
807         switch ( e->drag_reason ) {
808
809                 case DRAG_REASON_NONE :
810                 break;
811
812                 case DRAG_REASON_CREATE :
813                 e->drag_corner_x = x;
814                 e->drag_corner_y = y;
815                 gtksv_redraw(e);
816                 break;
817
818                 case DRAG_REASON_IMPORT :
819                 /* Do nothing, handled by dnd_motion() */
820                 break;
821
822                 case DRAG_REASON_RESIZE :
823                 calculate_box_size(frx, fry, frw, frh, e,
824                                    (e->cursor_frame->type == SLIDE_ITEM_IMAGE),
825                                    x, y);
826                 gtksv_redraw(e);
827                 break;
828
829                 case DRAG_REASON_MOVE :
830                 e->box_x = (frx - e->start_corner_x) + x;
831                 e->box_y = (fry - e->start_corner_y) + y;
832                 e->box_width = frw;
833                 e->box_height = frh;
834                 gtksv_redraw(e);
835                 break;
836
837                 case DRAG_REASON_TEXTSEL :
838                 gtksv_find_cursor(e->cursor_frame, stylesheet, x-frx, y-fry,
839                                   &e->sel_end, slide_w, slide_h);
840                 e->cpos = e->sel_end;
841                 gtksv_redraw(e);
842                 break;
843
844         }
845
846         gdk_event_request_motions(event);
847         return FALSE;
848 }
849
850
851 static SlideItem *create_image(GtkSlideView *e, const char *filename,
852                                double cx, double cy, double w, double h)
853 {
854         struct frame_geom geom;
855         SlideItem *item;
856         char *fn = strdup(filename);
857         if ( fn == NULL ) return NULL;
858         geom.x.len = cx;  geom.x.unit = LENGTH_UNIT;
859         geom.y.len = cy;  geom.y.unit = LENGTH_UNIT;
860         geom.w.len = w;   geom.w.unit = LENGTH_UNIT;
861         geom.h.len = h;   geom.h.unit = LENGTH_UNIT;
862         item = slide_item_image(fn, geom);
863         slide_add_item(e->slide, item);
864         return item;
865 }
866
867
868 static SlideItem *create_frame(GtkSlideView *e, double cx, double cy,
869                                double w, double h)
870 {
871         SlideItem *item;
872         struct frame_geom geom;
873         struct text_run *runs;
874         int nruns = 1;
875
876         /* Ownership of this struct will be taken over by the Slide. */
877         runs = malloc(sizeof(struct text_run));
878         if ( runs == NULL ) return NULL;
879         runs[0].type = TEXT_RUN_NORMAL;
880         runs[0].text = strdup("Slide title");
881         if ( runs[0].text == NULL ) return NULL;
882
883         if ( w < 0.0 ) {
884                 cx += w;
885                 w = -w;
886         }
887
888         if ( h < 0.0 ) {
889                 cy += h;
890                 h = -h;
891         }
892
893         geom.x.len = cx;  geom.x.unit = LENGTH_UNIT;
894         geom.y.len = cy;  geom.y.unit = LENGTH_UNIT;
895         geom.w.len = w;   geom.w.unit = LENGTH_UNIT;
896         geom.h.len = h;   geom.h.unit = LENGTH_UNIT;
897         item = slide_item_text(&runs, &nruns, 1, geom, ALIGN_INHERIT);
898         slide_add_item(e->slide, item);
899         return item;
900 }
901
902
903 static gboolean gtksv_button_release_sig(GtkWidget *da, GdkEventButton *event,
904                                          GtkSlideView *e)
905 {
906         gdouble x, y;
907         SlideItem *fr;
908
909         x = event->x - e->border_offs_x + e->h_scroll_pos;
910         y = event->y - e->border_offs_y + e->v_scroll_pos;
911         x /= e->view_scale;
912         y /= e->view_scale;
913
914         /* Not dragging?  Then I don't care. */
915         if ( e->drag_status != SLIDE_DRAG_STATUS_DRAGGING ) return FALSE;
916
917         e->drag_corner_x = x;
918         e->drag_corner_y = y;
919         e->drag_status = SLIDE_DRAG_STATUS_NONE;
920
921         switch ( e->drag_reason )
922         {
923
924                 case DRAG_REASON_NONE :
925                 printf("Release on pointless drag.\n");
926                 break;
927
928                 case DRAG_REASON_CREATE :
929                 fr = create_frame(e, e->start_corner_x, e->start_corner_y,
930                                      e->drag_corner_x - e->start_corner_x,
931                                      e->drag_corner_y - e->start_corner_y);
932                 if ( fr != NULL ) {
933                         e->cursor_frame = fr;
934                         e->cpos.para = 0;
935                         e->cpos.pos = 0;
936                         e->cpos.trail = 0;
937                         gtksv_unset_selection(e);
938                 } else {
939                         fprintf(stderr, _("Failed to create frame!\n"));
940                 }
941                 break;
942
943                 case DRAG_REASON_IMPORT :
944                 /* Do nothing, handled in dnd_drop() or dnd_leave() */
945                 break;
946
947                 case DRAG_REASON_RESIZE :
948                 do_resize(e, e->box_x, e->box_y, e->box_width, e->box_height);
949                 break;
950
951                 case DRAG_REASON_MOVE :
952                 do_resize(e, e->box_x, e->box_y, e->box_width, e->box_height);
953                 break;
954
955                 case DRAG_REASON_TEXTSEL :
956                 /* Do nothing (text is already selected) */
957                 break;
958
959         }
960
961         e->drag_reason = DRAG_REASON_NONE;
962
963         gtk_widget_grab_focus(GTK_WIDGET(da));
964         gtksv_redraw(e);
965         return FALSE;
966 }
967
968
969 static size_t gtksv_end_offset_of_para(SlideItem *item, int pnum)
970 {
971         struct slide_text_paragraph *para;
972         int i;
973         size_t offs;
974
975         assert(pnum >= 0);
976         if ( !is_text(item->type) ) return 0;
977         para = &item->paras[pnum];
978
979         offs = 0;
980         for ( i=0; i<para->n_runs; i++ ) {
981                 offs += strlen(para->runs[i].text);
982         }
983         return offs;
984 }
985
986
987 static void gtksv_cursor_moveh(GtkSlideView *e, struct slide_pos *cp, signed int dir)
988 {
989         int np = cp->pos;
990
991         if ( !is_text(e->cursor_frame->type) ) return;
992         if ( e->cursor_frame->paras[e->cpos.para].layout == NULL ) return;
993         gtksv_unset_selection(e);
994
995         pango_layout_move_cursor_visually(e->cursor_frame->paras[e->cpos.para].layout,
996                                           1, cp->pos, cp->trail, dir,
997                                           &np, &cp->trail);
998
999         if ( np == -1 ) {
1000                 if ( cp->para > 0 ) {
1001                         size_t end_offs;
1002                         cp->para--;
1003                         end_offs = gtksv_end_offset_of_para(e->cursor_frame, cp->para);
1004                         if ( end_offs > 0 ) {
1005                                 cp->pos = end_offs - 1;
1006                                 cp->trail = 1;
1007                         } else {
1008                                 /* Jumping into an empty paragraph */
1009                                 cp->pos = 0;
1010                                 cp->trail = 0;
1011                         }
1012                         return;
1013                 } else {
1014                         /* Can't move any further */
1015                         return;
1016                 }
1017         }
1018
1019         if ( np == G_MAXINT ) {
1020                 if ( cp->para < e->cursor_frame->n_paras-1 ) {
1021                         cp->para++;
1022                         cp->pos = 0;
1023                         cp->trail = 0;
1024                         return;
1025                 } else {
1026                         /* Can't move any further */
1027                         cp->trail = 1;
1028                         return;
1029                 }
1030         }
1031
1032         cp->pos = np;
1033 }
1034
1035
1036 static int slide_positions_equal(struct slide_pos a, struct slide_pos b)
1037 {
1038         if ( a.para != b.para ) return 0;
1039         if ( a.pos != b.pos ) return 0;
1040         if ( a.trail != b.trail ) return 0;
1041         return 1;
1042 }
1043
1044
1045 static void sort_slide_positions(struct slide_pos *a, struct slide_pos *b)
1046 {
1047         if ( a->para > b->para ) {
1048                 size_t tpos;
1049                 int tpara, ttrail;
1050                 tpara = b->para;   tpos = b->pos;  ttrail = b->trail;
1051                 b->para = a->para;  b->pos = a->pos;  b->trail = a->trail;
1052                 a->para = tpara;    a->pos = tpos;    a->trail = ttrail;
1053         }
1054
1055         if ( (a->para == b->para) && (a->pos > b->pos) )
1056         {
1057                 size_t tpos = b->pos;
1058                 int ttrail = b->trail;
1059                 b->pos = a->pos;  b->trail = a->trail;
1060                 a->pos = tpos;    a->trail = ttrail;
1061         }
1062 }
1063
1064
1065 static void gtksv_do_backspace(GtkSlideView *e, signed int dir)
1066 {
1067         /* FIXME! */
1068 #if 0
1069         struct slide_pos p1, p2;
1070         size_t o1, o2;
1071
1072         if ( e->cursor_frame->type == SLIDE_ITEM_IMAGE ) {
1073                 gtk_slide_view_delete_selected_frame(e);
1074                 return;
1075         }
1076
1077         if ( !slide_positions_equal(e->sel_start, e->sel_end) ) {
1078
1079                 /* Block delete */
1080                 p1 = e->sel_start;
1081                 p2 = e->sel_end;
1082
1083         } else {
1084
1085                 /* Delete one character, as represented visually */
1086                 p2 = e->cpos;
1087                 p1 = p2;
1088                 gtksv_cursor_moveh(e, &p1, dir);
1089         }
1090
1091         sort_slide_positions(&p1, &p2);
1092         o1 = pos_trail_to_offset(e->cursor_frame, p1.para, p1.run, p1.pos, p1.trail);
1093         o2 = pos_trail_to_offset(e->cursor_frame, p2.para, p1.run, p2.pos, p2.trail);
1094         slide_item_delete_text(e->cursor_frame, p1.para, o1, p2.para, o2);
1095         e->cpos = p1;
1096         gtksv_unset_selection(e);
1097
1098         pango_layout_set_text(e->cursor_frame->paras[e->cpos.para].layout,
1099                               e->cursor_frame->paras[e->cpos.para].text, -1);
1100
1101         gtksv_emit_change_sig(e);
1102         gtksv_redraw(e);
1103 #endif
1104 }
1105
1106
1107 static void gtksv_insert_text_in_paragraph(SlideItem *item, int para,
1108                                            size_t offs, char *t)
1109 {
1110         /* FIXME! */
1111 #if 0
1112         char *n = malloc(strlen(t) + strlen(item->paras[para].text) + 1);
1113         if ( n == NULL ) return;
1114         strncpy(n, item->paras[para].text, offs);
1115         n[offs] = '\0';
1116         strcat(n, t);
1117         strcat(n, item->paras[para].text+offs);
1118         free(item->paras[para].text);
1119         item->paras[para].text = n;
1120 #endif
1121 }
1122
1123
1124 static void gtksv_insert_text(char *t, GtkSlideView *e)
1125 {
1126         /* FIXME! */
1127 #if 0
1128         size_t off;
1129
1130         if ( e->cursor_frame == NULL ) return;
1131         if ( !is_text(e->cursor_frame->type) ) return;
1132
1133         if ( !slide_positions_equal(e->sel_start, e->sel_end) ) {
1134                 gtksv_do_backspace(e, 0);
1135         }
1136         gtksv_unset_selection(e);
1137
1138         if ( strcmp(t, "\n") == 0 ) {
1139                 off = pos_trail_to_offset(e->cursor_frame, e->cpos.para,
1140                                           e->cpos.pos, e->cpos.trail);
1141                 slide_item_split_text_paragraph(e->cursor_frame, e->cpos.para, off);
1142                 e->cpos.para++;
1143                 e->cpos.pos = 0;
1144                 e->cpos.trail = 0;
1145                 gtksv_emit_change_sig(e);
1146                 gtksv_redraw(e);
1147                 return;
1148         }
1149
1150         off = pos_trail_to_offset(e->cursor_frame, e->cpos.para,
1151                                   e->cpos.pos, e->cpos.trail);
1152         gtksv_insert_text_in_paragraph(e->cursor_frame, e->cpos.para, off, t);
1153         pango_layout_set_text(e->cursor_frame->paras[e->cpos.para].layout,
1154                               e->cursor_frame->paras[e->cpos.para].text, -1);
1155         gtksv_cursor_moveh(e, &e->cpos, +1);
1156         gtksv_emit_change_sig(e);
1157         gtksv_redraw(e);
1158 #endif
1159 }
1160
1161
1162 static gboolean gtksv_im_commit_sig(GtkIMContext *im, gchar *str,
1163                                     GtkSlideView *e)
1164 {
1165         gtksv_insert_text(str, e);
1166         return FALSE;
1167 }
1168
1169
1170 static gboolean gtksv_key_press_sig(GtkWidget *da, GdkEventKey *event,
1171                                     GtkSlideView *e)
1172 {
1173         gboolean r;
1174         int claim = 0;
1175
1176         /* Throw the event to the IM context and let it sort things out */
1177         r = gtk_im_context_filter_keypress(GTK_IM_CONTEXT(e->im_context),
1178                                            event);
1179         if ( r ) return FALSE;  /* IM ate it */
1180
1181         if ( e->cursor_frame == NULL ) return FALSE;
1182
1183         switch ( event->keyval ) {
1184
1185                 case GDK_KEY_Left :
1186                 gtksv_cursor_moveh(e, &e->cpos, -1);
1187                 gtksv_redraw(e);
1188                 claim = 1;
1189                 break;
1190
1191                 case GDK_KEY_Right :
1192                 gtksv_cursor_moveh(e, &e->cpos, +1);
1193                 gtksv_redraw(e);
1194                 claim = 1;
1195                 break;
1196
1197                 case GDK_KEY_Up :
1198                 gtksv_cursor_moveh(e, &e->cpos, -1);
1199                 gtksv_redraw(e);
1200                 claim = 1;
1201                 break;
1202
1203                 case GDK_KEY_Down :
1204                 gtksv_cursor_moveh(e, &e->cpos, +1);
1205                 gtksv_redraw(e);
1206                 claim = 1;
1207                 break;
1208
1209                 case GDK_KEY_Return :
1210                 gtksv_im_commit_sig(NULL, "\n", e);
1211                 claim = 1;
1212                 break;
1213
1214                 case GDK_KEY_BackSpace :
1215                 gtksv_do_backspace(e, -1);
1216                 claim = 1;
1217                 break;
1218
1219                 case GDK_KEY_Delete :
1220                 gtksv_do_backspace(e, +1);
1221                 claim = 1;
1222                 break;
1223
1224         }
1225
1226         if ( claim ) return TRUE;
1227         return FALSE;
1228 }
1229
1230
1231 void gtk_slide_view_delete_selected_frame(GtkSlideView *e)
1232 {
1233         if ( e->cursor_frame == NULL ) return;
1234         slide_delete_item(e->slide, e->cursor_frame);
1235         gtksv_unset_selection(e);
1236         e->cursor_frame = NULL;
1237         gtksv_emit_change_sig(e);
1238         gtksv_redraw(e);
1239 }
1240
1241
1242 static gboolean dnd_motion(GtkWidget *widget, GdkDragContext *drag_context,
1243                            gint x, gint y, guint time, GtkSlideView *e)
1244 {
1245         GdkAtom target;
1246
1247         /* If we haven't already requested the data, do so now */
1248         if ( !e->drag_preview_pending && !e->have_drag_data ) {
1249
1250                 target = gtk_drag_dest_find_target(widget, drag_context, NULL);
1251
1252                 if ( target != GDK_NONE ) {
1253                         gtk_drag_get_data(widget, drag_context, target, time);
1254                         e->drag_preview_pending = 1;
1255                 } else {
1256                         e->import_acceptable = 0;
1257                         gdk_drag_status(drag_context, 0, time);
1258                 }
1259
1260         }
1261
1262         if ( e->have_drag_data && e->import_acceptable ) {
1263
1264                 gdk_drag_status(drag_context, GDK_ACTION_LINK, time);
1265                 e->start_corner_x = x - e->import_width/2.0;
1266                 e->start_corner_y = y - e->import_height/2.0;
1267                 e->drag_corner_x = x + e->import_width/2.0;
1268                 e->drag_corner_y = y + e->import_height/2.0;
1269
1270                 gtksv_redraw(e);
1271
1272         }
1273
1274         return TRUE;
1275 }
1276
1277
1278 static gboolean dnd_drop(GtkWidget *widget, GdkDragContext *drag_context,
1279                          gint x, gint y, guint time, GtkSlideView *e)
1280 {
1281         GdkAtom target;
1282
1283         target = gtk_drag_dest_find_target(widget, drag_context, NULL);
1284
1285         if ( target == GDK_NONE ) {
1286                 gtk_drag_finish(drag_context, FALSE, FALSE, time);
1287         } else {
1288                 gtk_drag_get_data(widget, drag_context, target, time);
1289         }
1290
1291         return TRUE;
1292 }
1293
1294
1295 /* Scale the image down if it's a silly size */
1296 static void check_import_size(GtkSlideView *e)
1297 {
1298         if ( e->import_width > e->w ) {
1299
1300                 int new_import_width;
1301
1302                 new_import_width = e->w/2;
1303                 e->import_height = (new_import_width * e->import_height) /
1304                                      e->import_width;
1305                 e->import_width = new_import_width;
1306
1307         }
1308
1309         if ( e->import_height > e->h ) {
1310
1311                 int new_import_height;
1312
1313                 new_import_height = e->w/2;
1314                 e->import_width = (new_import_height*e->import_width) /
1315                                     e->import_height;
1316                 e->import_height = new_import_height;
1317
1318         }
1319 }
1320
1321
1322 static void chomp(char *s)
1323 {
1324         size_t i;
1325
1326         if ( !s ) return;
1327
1328         for ( i=0; i<strlen(s); i++ ) {
1329                 if ( (s[i] == '\n') || (s[i] == '\r') ) {
1330                         s[i] = '\0';
1331                         return;
1332                 }
1333         }
1334 }
1335
1336
1337 static void dnd_receive(GtkWidget *widget, GdkDragContext *drag_context,
1338                         gint x, gint y, GtkSelectionData *seldata,
1339                         guint info, guint time, GtkSlideView *e)
1340 {
1341         if ( e->drag_preview_pending ) {
1342
1343                 gchar *filename = NULL;
1344                 GdkPixbufFormat *f;
1345                 gchar **uris;
1346                 int w, h;
1347
1348                 e->have_drag_data = 1;
1349                 e->drag_preview_pending = 0;
1350                 uris = gtk_selection_data_get_uris(seldata);
1351                 if ( uris != NULL ) {
1352                         filename = g_filename_from_uri(uris[0], NULL, NULL);
1353                 }
1354                 g_strfreev(uris);
1355
1356                 if ( filename == NULL ) {
1357
1358                         /* This doesn't even look like a sensible URI.
1359                          * Bail out. */
1360                         gdk_drag_status(drag_context, 0, time);
1361                         if ( e->drag_highlight ) {
1362                                 gtk_drag_unhighlight(widget);
1363                                 e->drag_highlight = 0;
1364                         }
1365                         e->import_acceptable = 0;
1366                         return;
1367
1368                 }
1369                 chomp(filename);
1370
1371                 f = gdk_pixbuf_get_file_info(filename, &w, &h);
1372                 g_free(filename);
1373
1374                 e->import_width = w;
1375                 e->import_height = h;
1376
1377                 if ( f == NULL ) {
1378
1379                         gdk_drag_status(drag_context, 0, time);
1380                         if ( e->drag_highlight ) {
1381                                 gtk_drag_unhighlight(widget);
1382                                 e->drag_highlight = 0;
1383                         }
1384                         e->drag_status = SLIDE_DRAG_STATUS_NONE;
1385                         e->drag_reason = DRAG_REASON_NONE;
1386                         e->import_acceptable = 0;
1387
1388                 } else {
1389
1390                         /* Looks like a sensible image */
1391                         gdk_drag_status(drag_context, GDK_ACTION_PRIVATE, time);
1392                         e->import_acceptable = 1;
1393
1394                         if ( !e->drag_highlight ) {
1395                                 gtk_drag_highlight(widget);
1396                                 e->drag_highlight = 1;
1397                         }
1398
1399                         check_import_size(e);
1400                         e->drag_reason = DRAG_REASON_IMPORT;
1401                         e->drag_status = SLIDE_DRAG_STATUS_DRAGGING;
1402
1403                 }
1404
1405         } else {
1406
1407                 gchar **uris;
1408                 char *filename = NULL;
1409
1410                 uris = gtk_selection_data_get_uris(seldata);
1411                 if ( uris != NULL ) {
1412                         filename = g_filename_from_uri(uris[0], NULL, NULL);
1413                 }
1414                 g_strfreev(uris);
1415
1416                 if ( filename != NULL ) {
1417
1418                         int w, h;
1419
1420                         gtk_drag_finish(drag_context, TRUE, FALSE, time);
1421                         chomp(filename);
1422
1423                         w = e->drag_corner_x - e->start_corner_x;
1424                         h = e->drag_corner_y - e->start_corner_y;
1425
1426                         e->cursor_frame = create_image(e, filename,
1427                                                        e->start_corner_x, e->start_corner_y,
1428                                                        w, h);
1429                         free(filename);
1430                         gtksv_redraw(e);
1431
1432                 } else {
1433
1434                         gtk_drag_finish(drag_context, FALSE, FALSE, time);
1435
1436                 }
1437
1438         }
1439 }
1440
1441
1442 static void dnd_leave(GtkWidget *widget, GdkDragContext *drag_context,
1443                       guint time, GtkSlideView *e)
1444 {
1445         if ( e->drag_highlight ) {
1446                 gtk_drag_unhighlight(widget);
1447         }
1448         e->have_drag_data = 0;
1449         e->drag_highlight = 0;
1450         e->drag_status = SLIDE_DRAG_STATUS_NONE;
1451         e->drag_reason = DRAG_REASON_NONE;
1452 }
1453
1454
1455 static gint gtksv_realise_sig(GtkWidget *da, GtkSlideView *e)
1456 {
1457         GdkWindow *win;
1458
1459         /* Keyboard and input method stuff */
1460         e->im_context = gtk_im_multicontext_new();
1461         win = gtk_widget_get_window(GTK_WIDGET(e));
1462         gtk_im_context_set_client_window(GTK_IM_CONTEXT(e->im_context), win);
1463         gdk_window_set_accept_focus(win, TRUE);
1464         g_signal_connect(G_OBJECT(e->im_context), "commit", G_CALLBACK(gtksv_im_commit_sig), e);
1465         g_signal_connect(G_OBJECT(e), "key-press-event", G_CALLBACK(gtksv_key_press_sig), e);
1466
1467         return FALSE;
1468 }
1469
1470
1471 void gtk_slide_view_set_scale(GtkSlideView *e, double scale)
1472 {
1473         e->view_scale = 1.0;
1474 }
1475
1476
1477 void gtk_slide_view_set_slide(GtkWidget *widget, Slide *slide)
1478 {
1479         GtkSlideView *e = GTK_SLIDE_VIEW(widget);
1480         e->slide = slide;
1481         gtksv_unset_selection(e);
1482         e->cursor_frame = NULL;
1483         gtksv_redraw(e);
1484 }
1485
1486
1487 GtkWidget *gtk_slide_view_new(Narrative *n, Slide *slide)
1488 {
1489         GtkSlideView *sv;
1490         GtkTargetEntry targets[1];
1491         GError *err;
1492
1493         sv = g_object_new(GTK_TYPE_SLIDE_VIEW, NULL);
1494
1495         sv->n = n;
1496         sv->slide = slide;
1497         sv->w = 100;
1498         sv->h = 100;
1499         sv->border_offs_x = 0;
1500         sv->border_offs_y = 0;
1501         sv->min_border = 0.0;
1502         sv->h_scroll_pos = 0;
1503         sv->v_scroll_pos = 0;
1504         sv->view_scale = 1.0;
1505
1506         err = NULL;
1507         sv->bg_pixbuf = gdk_pixbuf_new_from_resource("/uk/me/bitwiz/Colloquium/sky.png",
1508                                                            &err);
1509         if ( sv->bg_pixbuf == NULL ) {
1510                 fprintf(stderr, _("Failed to load background: %s\n"),
1511                         err->message);
1512         }
1513
1514         gtk_widget_set_size_request(GTK_WIDGET(sv),
1515                                     sv->w, sv->h);
1516
1517         g_signal_connect(G_OBJECT(sv), "destroy",
1518                          G_CALLBACK(gtksv_destroy_sig), sv);
1519         g_signal_connect(G_OBJECT(sv), "realize",
1520                          G_CALLBACK(gtksv_realise_sig), sv);
1521         g_signal_connect(G_OBJECT(sv), "button-press-event",
1522                          G_CALLBACK(gtksv_button_press_sig), sv);
1523         g_signal_connect(G_OBJECT(sv), "button-release-event",
1524                          G_CALLBACK(gtksv_button_release_sig), sv);
1525         g_signal_connect(G_OBJECT(sv), "motion-notify-event",
1526                          G_CALLBACK(gtksv_motion_sig), sv);
1527         g_signal_connect(G_OBJECT(sv), "configure-event",
1528                          G_CALLBACK(gtksv_resize_sig), sv);
1529
1530         /* Drag and drop */
1531         targets[0].target = "text/uri-list";
1532         targets[0].flags = 0;
1533         targets[0].info = 1;
1534         gtk_drag_dest_set(GTK_WIDGET(sv), 0, targets, 1,
1535                           GDK_ACTION_PRIVATE);
1536         g_signal_connect(sv, "drag-data-received",
1537                          G_CALLBACK(dnd_receive), sv);
1538         g_signal_connect(sv, "drag-motion",
1539                          G_CALLBACK(dnd_motion), sv);
1540         g_signal_connect(sv, "drag-drop",
1541                          G_CALLBACK(dnd_drop), sv);
1542         g_signal_connect(sv, "drag-leave",
1543                          G_CALLBACK(dnd_leave), sv);
1544
1545         gtk_widget_set_can_focus(GTK_WIDGET(sv), TRUE);
1546         gtk_widget_add_events(GTK_WIDGET(sv),
1547                               GDK_POINTER_MOTION_HINT_MASK
1548                                | GDK_BUTTON1_MOTION_MASK
1549                                | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
1550                                | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK
1551                                | GDK_SCROLL_MASK);
1552
1553         g_signal_connect(G_OBJECT(sv), "draw",
1554                          G_CALLBACK(gtksv_draw_sig), sv);
1555
1556         gtk_widget_grab_focus(GTK_WIDGET(sv));
1557
1558         gtk_widget_show(GTK_WIDGET(sv));
1559
1560         return GTK_WIDGET(sv);
1561 }