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