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