/* * gtkslideview.c * * Copyright © 2013-2019 Thomas White * * This file is part of Colloquium. * * Colloquium is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #define _(x) gettext(x) #include #include #include //#include "slide_window.h" #include "gtkslideview.h" #include "slide_priv.h" //#include "slideshow.h" G_DEFINE_TYPE_WITH_CODE(GtkSlideView, gtk_slide_view, GTK_TYPE_DRAWING_AREA, NULL) static gboolean resize_sig(GtkWidget *widget, GdkEventConfigure *event, GtkSlideView *e) { double sx, sy; double aw, ah; double log_w, log_h; Stylesheet *ss; ss = presentation_get_stylesheet(e->p); if ( slide_get_logical_size(e->slide, ss, &log_w, &log_h) ) { fprintf(stderr, "Failed to get logical size\n"); return FALSE; } e->w = event->width; e->h = event->height; sx = (double)e->w / log_w; sy = (double)e->h / log_h; e->view_scale = (sx < sy) ? sx : sy; /* Actual size (in device units) */ aw = e->view_scale * log_w; ah = e->view_scale * log_h; e->border_offs_x = (event->width - aw)/2.0; e->border_offs_y = (event->height - ah)/2.0; e->visible_height = event->height; e->visible_width = event->width; //update_size(e); return FALSE; } static void emit_change_sig(GtkSlideView *e) { g_signal_emit_by_name(e, "changed"); } static void gtk_slide_view_class_init(GtkSlideViewClass *klass) { g_signal_new("changed", GTK_TYPE_SLIDE_VIEW, G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); } static void gtk_slide_view_init(GtkSlideView *e) { } static void redraw(GtkSlideView *e) { gint w, h; w = gtk_widget_get_allocated_width(GTK_WIDGET(e)); h = gtk_widget_get_allocated_height(GTK_WIDGET(e)); gtk_widget_queue_draw_area(GTK_WIDGET(e), 0, 0, w, h); } static gint destroy_sig(GtkWidget *window, GtkSlideView *e) { return 0; } static void draw_editing_box(cairo_t *cr, struct slide_item *item, double xmin, double ymin, double width, double height) { const double dash[] = {2.0, 2.0}; double ptot_w, ptot_h; double pad_l, pad_r, pad_t, pad_b; pad_l = 0.0; pad_r = 0.0; pad_t = 0.0; pad_b = 0.0; /* FIXME */ cairo_new_path(cr); cairo_rectangle(cr, xmin, ymin, width, height); cairo_set_source_rgb(cr, 0.0, 0.69, 1.0); cairo_set_line_width(cr, 0.5); cairo_stroke(cr); cairo_new_path(cr); ptot_w = pad_l + pad_r; ptot_h = pad_t + pad_b; cairo_rectangle(cr, xmin+pad_l, ymin+pad_t, width-ptot_w, height-ptot_h); cairo_set_dash(cr, dash, 2, 0.0); cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); cairo_set_line_width(cr, 0.1); cairo_stroke(cr); cairo_set_dash(cr, NULL, 0, 0.0); } static void draw_resize_handle(cairo_t *cr, double x, double y) { cairo_new_path(cr); cairo_rectangle(cr, x, y, 20.0, 20.0); cairo_set_source_rgba(cr, 0.9, 0.9, 0.9, 0.5); cairo_fill(cr); } static size_t pos_trail_to_offset(struct slide_item *item, int para, size_t offs, int trail) { glong char_offs; char *ptr; char_offs = g_utf8_pointer_to_offset(item->paragraphs[para], item->paragraphs[para]+offs); char_offs += trail; ptr = g_utf8_offset_to_pointer(item->paragraphs[para], char_offs); return ptr - item->paragraphs[para]; } static double para_top(struct slide_item *item, int pnum) { int i; double py = 0.0; for ( i=0; ilayouts[i], NULL, &rect); py += pango_units_to_double(rect.height); } return py; } static int get_cursor_pos(struct slide_item *item, struct slide_pos cpos, double *x, double *y, double *h) { size_t offs; PangoRectangle rect; if ( item->layouts[cpos.para] == NULL ) { fprintf(stderr, "get_cursor_pos: No layout\n"); return 1; } offs = pos_trail_to_offset(item, cpos.para, cpos.pos, cpos.trail); pango_layout_get_cursor_pos(item->layouts[cpos.para], offs, &rect, NULL); *x = pango_units_to_double(rect.x); *y = pango_units_to_double(rect.y) + para_top(item, cpos.para); *h = pango_units_to_double(rect.height); return 0; } static void draw_caret(cairo_t *cr, struct slide_item *item, struct slide_pos cpos, double frx, double fry) { double cx, clow, chigh, h; const double t = 1.8; if ( get_cursor_pos(item, cpos, &cx, &clow, &h) ) return; cx += frx; clow += fry; chigh = clow + h; cairo_move_to(cr, cx, clow); cairo_line_to(cr, cx, chigh); cairo_move_to(cr, cx-t, clow-t); cairo_line_to(cr, cx, clow); cairo_move_to(cr, cx+t, clow-t); cairo_line_to(cr, cx, clow); cairo_move_to(cr, cx-t, chigh+t); cairo_line_to(cr, cx, chigh); cairo_move_to(cr, cx+t, chigh+t); cairo_line_to(cr, cx, chigh); cairo_set_source_rgb(cr, 0.86, 0.0, 0.0); cairo_set_line_width(cr, 1.0); cairo_stroke(cr); } static void draw_overlay(cairo_t *cr, GtkSlideView *e) { if ( e->cursor_frame != NULL ) { double x, y, w, h; double slide_w, slide_h; Stylesheet *stylesheet; stylesheet = presentation_get_stylesheet(e->p); slide_get_logical_size(e->slide, stylesheet, &slide_w, &slide_h); slide_item_get_geom(e->cursor_frame, stylesheet, &x, &y, &w, &h, slide_w, slide_h); draw_editing_box(cr, e->cursor_frame, x, y, w, h); if ( e->cursor_frame->resizable ) { /* Draw resize handles */ draw_resize_handle(cr, x, y+h-20.0); draw_resize_handle(cr, x+w-20.0, y); draw_resize_handle(cr, x, y); draw_resize_handle(cr, x+w-20.0, y+h-20.0); } if ( e->cursor_frame->type != SLIDE_ITEM_IMAGE ) { draw_caret(cr, e->cursor_frame, e->cpos, x, y); } } if ( e->drag_status == DRAG_STATUS_DRAGGING ) { if ( (e->drag_reason == DRAG_REASON_CREATE) || (e->drag_reason == DRAG_REASON_IMPORT) ) { cairo_new_path(cr); cairo_rectangle(cr, e->start_corner_x, e->start_corner_y, e->drag_corner_x - e->start_corner_x, e->drag_corner_y - e->start_corner_y); cairo_set_source_rgb(cr, 0.5, 0.5, 0.5); cairo_set_line_width(cr, 0.5); cairo_stroke(cr); } if ( (e->drag_reason == DRAG_REASON_RESIZE) || (e->drag_reason == DRAG_REASON_MOVE) ) { cairo_new_path(cr); cairo_rectangle(cr, e->box_x, e->box_y, e->box_width, e->box_height); cairo_set_source_rgb(cr, 0.5, 0.5, 0.5); cairo_set_line_width(cr, 0.5); cairo_stroke(cr); } } } static gboolean draw_sig(GtkWidget *da, cairo_t *cr, GtkSlideView *e) { PangoContext *pc; /* Ultimate background */ if ( e->bg_pixbuf != NULL ) { gdk_cairo_set_source_pixbuf(cr, e->bg_pixbuf, 0.0, 0.0); cairo_pattern_t *patt = cairo_get_source(cr); cairo_pattern_set_extend(patt, CAIRO_EXTEND_REPEAT); cairo_paint(cr); } else { cairo_set_source_rgba(cr, 0.8, 0.8, 1.0, 1.0); cairo_paint(cr); } cairo_translate(cr, e->border_offs_x, e->border_offs_y); cairo_translate(cr, -e->h_scroll_pos, -e->v_scroll_pos); cairo_scale(cr, e->view_scale, e->view_scale); /* Contents */ pc = pango_cairo_create_context(cr); slide_render_cairo(e->slide, cr, presentation_get_imagestore(e->p), presentation_get_stylesheet(e->p), presentation_get_slide_number(e->p, e->slide), pango_language_get_default(), pc); g_object_unref(pc); /* Editing overlay */ draw_overlay(cr, e); return FALSE; } static int within_frame(struct slide_item *item, Stylesheet *ss, double slide_w, double slide_h, double xp, double yp) { double x, y, w, h; slide_item_get_geom(item, ss, &x, &y, &w, &h, slide_w, slide_h); if ( xp < x ) return 0; if ( yp < y ) return 0; if ( xp > x + w ) return 0; if ( yp > y + h ) return 0; return 1; } static struct slide_item *find_frame_at_position(Slide *s, Stylesheet *ss, double slide_w, double slide_h, double x, double y) { int i; for ( i=0; in_items; i++ ) { if ( within_frame(&s->items[i], ss, slide_w, slide_h, x, y) ) { return &s->items[i]; } } return NULL; } static enum drag_corner which_corner(double xp, double yp, double frx, double fry, double frw, double frh) { double x, y; /* Relative to object position */ x = xp - frx; y = yp - fry; if ( x < 0.0 ) return CORNER_NONE; if ( y < 0.0 ) return CORNER_NONE; if ( x > frw ) return CORNER_NONE; if ( y > frh ) return CORNER_NONE; /* Top left? */ if ( (x<20.0) && (y<20.0) ) return CORNER_TL; if ( (x>frw-20.0) && (y<20.0) ) return CORNER_TR; if ( (x<20.0) && (y>frh-20.0) ) return CORNER_BL; if ( (x>frw-20.0) && (y>frh-20.0) ) return CORNER_BR; return CORNER_NONE; } static void calculate_box_size(double frx, double fry, double frw, double frh, GtkSlideView *e, int preserve_aspect, double x, double y) { double ddx, ddy, dlen, mult; double vx, vy, dbx, dby; ddx = x - e->start_corner_x; ddy = y - e->start_corner_y; if ( !preserve_aspect ) { switch ( e->drag_corner ) { case CORNER_BR : e->box_x = frx; e->box_y = fry; e->box_width = frw + ddx; e->box_height = frh + ddy; break; case CORNER_BL : e->box_x = frx + ddx; e->box_y = fry; e->box_width = frw - ddx; e->box_height = frh + ddy; break; case CORNER_TL : e->box_x = frx + ddx; e->box_y = fry + ddy; e->box_width = frw - ddx; e->box_height = frh - ddy; break; case CORNER_TR : e->box_x = frx; e->box_y = fry + ddy; e->box_width = frw + ddx; e->box_height = frh - ddy; break; case CORNER_NONE : break; } return; } switch ( e->drag_corner ) { case CORNER_BR : vx = frw; vy = frh; break; case CORNER_BL : vx = -frw; vy = frh; break; case CORNER_TL : vx = -frw; vy = -frh; break; case CORNER_TR : vx = frw; vy = -frh; break; case CORNER_NONE : default: vx = 0.0; vy = 0.0; break; } dlen = (ddx*vx + ddy*vy) / e->diagonal_length; mult = (dlen+e->diagonal_length) / e->diagonal_length; e->box_width = frw * mult; e->box_height = frh * mult; dbx = e->box_width - frw; dby = e->box_height - frh; if ( e->box_width < 40.0 ) { mult = 40.0 / frw; } if ( e->box_height < 40.0 ) { mult = 40.0 / frh; } e->box_width = frw * mult; e->box_height = frh * mult; dbx = e->box_width - frw; dby = e->box_height - frh; switch ( e->drag_corner ) { case CORNER_BR : e->box_x = frx; e->box_y = fry; break; case CORNER_BL : e->box_x = frx - dbx; e->box_y = fry; break; case CORNER_TL : e->box_x = frx - dbx; e->box_y = fry - dby; break; case CORNER_TR : e->box_x = frx; e->box_y = fry - dby; break; case CORNER_NONE : break; } } static int find_cursor(struct slide_item *item, double x, double y, struct slide_pos *pos) { #if 0 double cur_y; int i = 0; cur_y = item->space_t; do { cur_y += n->items[i++].h; } while ( (cur_y < y) && (in_items) ); pos->para = i-1; item = &n->items[pos->para]; if ( item->type == NARRATIVE_ITEM_SLIDE ) { pos->pos = 0; return 0; } pango_layout_xy_to_index(item->layout, pango_units_from_double(x - n->space_l - item->space_l), pango_units_from_double(y - n->space_t - para_top(n, pos->para)), &pos->pos, &pos->trail); #endif return 0; } static void unset_selection(GtkSlideView *e) { int a, b; a = e->sel_start.para; b = e->sel_end.para; if ( a > b ) { a = e->sel_end.para; b = e->sel_start.para; } //rewrap_paragraph_range(e->cursor_frame, a, b, e->sel_start, e->sel_end, 0); } static gboolean button_press_sig(GtkWidget *da, GdkEventButton *event, GtkSlideView *e) { enum drag_corner c; gdouble x, y; Stylesheet *stylesheet; struct slide_item *clicked; int shift; double slide_w, slide_h; double frx, fry, frw, frh; stylesheet = presentation_get_stylesheet(e->p); slide_get_logical_size(e->slide, stylesheet, &slide_w, &slide_h); x = event->x - e->border_offs_x + e->h_scroll_pos; y = event->y - e->border_offs_y + e->v_scroll_pos; x /= e->view_scale; y /= e->view_scale; shift = event->state & GDK_SHIFT_MASK; clicked = find_frame_at_position(e->slide, stylesheet, slide_w, slide_h, x, y); if ( clicked != NULL ) { slide_item_get_geom(clicked, stylesheet, &frx, &fry, &frw, &frh, slide_w, slide_h); } /* Clicked within the currently selected frame * -> resize, move or select text */ if ( (e->cursor_frame != NULL) && (clicked == e->cursor_frame) ) { /* Within the resizing region? */ c = which_corner(x, y, frx, fry, frw, frh); if ( (c != CORNER_NONE) && e->cursor_frame->resizable && shift ) { e->drag_reason = DRAG_REASON_RESIZE; e->drag_corner = c; e->start_corner_x = x; e->start_corner_y = y; e->diagonal_length = pow(frw, 2.0); e->diagonal_length += pow(frh, 2.0); e->diagonal_length = sqrt(e->diagonal_length); calculate_box_size(frx, fry, frw, frh, e, e->cursor_frame->type == SLIDE_ITEM_IMAGE ? 1 : 0, x, y); e->drag_status = DRAG_STATUS_COULD_DRAG; e->drag_reason = DRAG_REASON_RESIZE; } else { /* Position cursor and prepare for possible drag */ e->cursor_frame = clicked; find_cursor(clicked, x-frx, y-fry, &e->cpos); e->start_corner_x = x; e->start_corner_y = y; if ( clicked->resizable && shift ) { e->drag_status = DRAG_STATUS_COULD_DRAG; e->drag_reason = DRAG_REASON_MOVE; } else { e->drag_status = DRAG_STATUS_COULD_DRAG; e->drag_reason = DRAG_REASON_TEXTSEL; unset_selection(e); find_cursor(clicked, x-frx, y-fry, &e->sel_start); } } } else if ( clicked == NULL ) { /* Clicked no object. Deselect old object. * If shift held, set up for creating a new one. */ e->cursor_frame = NULL; unset_selection(e); if ( shift ) { e->start_corner_x = x; e->start_corner_y = y; e->drag_status = DRAG_STATUS_COULD_DRAG; e->drag_reason = DRAG_REASON_CREATE; } else { e->drag_status = DRAG_STATUS_NONE; e->drag_reason = DRAG_REASON_NONE; } } else { /* Clicked an existing frame, no immediate dragging */ e->drag_status = DRAG_STATUS_COULD_DRAG; e->drag_reason = DRAG_REASON_TEXTSEL; unset_selection(e); find_cursor(clicked, x-frx, y-fry, &e->sel_start); find_cursor(clicked, x-frx, y-fry, &e->sel_end); e->cursor_frame = clicked; find_cursor(clicked, x-frx, y-fry, &e->cpos); } gtk_widget_grab_focus(GTK_WIDGET(da)); redraw(e); return FALSE; } static gint realise_sig(GtkWidget *da, GtkSlideView *e) { GdkWindow *win; /* Keyboard and input method stuff */ e->im_context = gtk_im_multicontext_new(); win = gtk_widget_get_window(GTK_WIDGET(e)); gtk_im_context_set_client_window(GTK_IM_CONTEXT(e->im_context), win); gdk_window_set_accept_focus(win, TRUE); //g_signal_connect(G_OBJECT(e->im_context), "commit", G_CALLBACK(im_commit_sig), e); //g_signal_connect(G_OBJECT(e), "key-press-event", G_CALLBACK(key_press_sig), e); return FALSE; } static void update_size_request(GtkSlideView *e) { gtk_widget_set_size_request(GTK_WIDGET(e), 0, e->h + 2.0*e->min_border); } void gtk_slide_view_set_scale(GtkSlideView *e, double scale) { e->view_scale = 1.0; } void gtk_slide_view_set_slide(GtkWidget *e, Slide *slide) { GTK_SLIDE_VIEW(e)->slide = slide; redraw(GTK_SLIDE_VIEW(e)); } GtkWidget *gtk_slide_view_new(Presentation *p, Slide *slide) { GtkSlideView *sv; GtkTargetEntry targets[1]; GError *err; sv = g_object_new(GTK_TYPE_SLIDE_VIEW, NULL); sv->p = p; sv->slide = slide; sv->w = 100; sv->h = 100; sv->border_offs_x = 0; sv->border_offs_y = 0; sv->min_border = 0.0; sv->h_scroll_pos = 0; sv->v_scroll_pos = 0; sv->view_scale = 1.0; err = NULL; sv->bg_pixbuf = gdk_pixbuf_new_from_resource("/uk/me/bitwiz/Colloquium/sky.png", &err); if ( sv->bg_pixbuf == NULL ) { fprintf(stderr, _("Failed to load background: %s\n"), err->message); } gtk_widget_set_size_request(GTK_WIDGET(sv), sv->w, sv->h); g_signal_connect(G_OBJECT(sv), "destroy", G_CALLBACK(destroy_sig), sv); g_signal_connect(G_OBJECT(sv), "realize", G_CALLBACK(realise_sig), sv); g_signal_connect(G_OBJECT(sv), "button-press-event", G_CALLBACK(button_press_sig), sv); //g_signal_connect(G_OBJECT(sv), "button-release-event", // G_CALLBACK(button_release_sig), sv); //g_signal_connect(G_OBJECT(sv), "motion-notify-event", // G_CALLBACK(motion_sig), sv); g_signal_connect(G_OBJECT(sv), "configure-event", G_CALLBACK(resize_sig), sv); /* Drag and drop */ //targets[0].target = "text/uri-list"; //targets[0].flags = 0; //targets[0].info = 1; //gtk_drag_dest_set(GTK_WIDGET(sv), 0, targets, 1, // GDK_ACTION_PRIVATE); //g_signal_connect(sv, "drag-data-received", // G_CALLBACK(dnd_receive), sv); //g_signal_connect(sv, "drag-motion", // G_CALLBACK(dnd_motion), sv); //g_signal_connect(sv, "drag-drop", // G_CALLBACK(dnd_drop), sv); //g_signal_connect(sv, "drag-leave", // G_CALLBACK(dnd_leave), sv); gtk_widget_set_can_focus(GTK_WIDGET(sv), TRUE); gtk_widget_add_events(GTK_WIDGET(sv), GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON1_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_SCROLL_MASK); g_signal_connect(G_OBJECT(sv), "draw", G_CALLBACK(draw_sig), sv); gtk_widget_grab_focus(GTK_WIDGET(sv)); gtk_widget_show(GTK_WIDGET(sv)); return GTK_WIDGET(sv); }