diff options
Diffstat (limited to 'src/sc_editor.c')
-rw-r--r-- | src/sc_editor.c | 1452 |
1 files changed, 1452 insertions, 0 deletions
diff --git a/src/sc_editor.c b/src/sc_editor.c new file mode 100644 index 0000000..3a398c5 --- /dev/null +++ b/src/sc_editor.c @@ -0,0 +1,1452 @@ +/* + * sc_editor.c + * + * Copyright © 2013-2014 Thomas White <taw@bitwiz.org.uk> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <string.h> +#include <gtk/gtk.h> +#include <assert.h> +#include <gdk/gdkkeysyms.h> +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <math.h> + +#include "presentation.h" +#include "slide_window.h" +#include "render.h" +#include "frame.h" +#include "wrap.h" +#include "sc_parse.h" +#include "sc_interp.h" +#include "sc_editor.h" +#include "slideshow.h" + + +enum drag_reason +{ + DRAG_REASON_NONE, + DRAG_REASON_CREATE, + DRAG_REASON_IMPORT, + DRAG_REASON_RESIZE, + DRAG_REASON_MOVE +}; + + +enum corner +{ + CORNER_NONE, + CORNER_TL, + CORNER_TR, + CORNER_BL, + CORNER_BR +}; + + +enum drag_status +{ + DRAG_STATUS_NONE, + DRAG_STATUS_COULD_DRAG, + DRAG_STATUS_DRAGGING, +}; + + +struct _sceditor +{ + GtkWidget *drawingarea; + GtkIMContext *im_context; + int slide_width; + struct presentation *p; + cairo_surface_t *surface; + + /* Pointers to the frame currently being edited */ + struct frame *selection; + + struct slide *cur_slide; + + PangoContext *pc; + + /* Location of the cursor */ + struct frame *cursor_frame; + int cursor_line; + int cursor_box; + int cursor_pos; /* characters into box */ + + /* Border surrounding actual slide within drawingarea */ + double border_offs_x; + double border_offs_y; + + /* Rubber band boxes and related stuff */ + double start_corner_x; + double start_corner_y; + double drag_corner_x; + double drag_corner_y; + double diagonal_length; + double box_x; + double box_y; + double box_width; + double box_height; + enum drag_reason drag_reason; + enum drag_status drag_status; + enum corner drag_corner; + + /* Stuff to do with drag and drop import of "content" */ + int drag_preview_pending; + int have_drag_data; + int drag_highlight; + double import_width; + double import_height; + int import_acceptable; + +}; + + +/* Update a slide, once it's been edited in some way. */ +static void rerender_slide(SCEditor *e) +{ + struct slide *s = e->cur_slide; + int n = slide_number(e->p, s); + + if ( e->surface != NULL ) { + cairo_surface_destroy(e->surface); + } + + e->surface = render_slide(s, e->slide_width, + e->p->slide_width, e->p->slide_height, + e->p->is, ISZ_EDITOR, n); +} + + +/* Force a redraw of the editor window */ +void redraw_editor(SCEditor *e) +{ + gint w, h; + + w = gtk_widget_get_allocated_width(GTK_WIDGET(e->drawingarea)); + h = gtk_widget_get_allocated_height(GTK_WIDGET(e->drawingarea)); + + gtk_widget_queue_draw_area(e->drawingarea, 0, 0, w, h); +} + + +static void move_cursor_back(SCEditor *e) +{ + int retreat = 0; + signed int cp, cb, cl; + struct wrap_line *line; + struct wrap_box *box; + + cp = e->cursor_pos; + cb = e->cursor_box; + cl = e->cursor_line; + + line = &e->cursor_frame->lines[e->cursor_line]; + box = &line->boxes[e->cursor_box]; + if ( box->type == WRAP_BOX_PANGO ) { + + if ( cp == 0 ) { + retreat = 1; + } else { + cp--; + } + + } else { + cp--; + if ( cp < 0 ) retreat = 1; + } + + if ( retreat ) { + + do { + + cb--; + + if ( cb < 0 ) { + cl--; + if ( cl < 0 ) return; + e->cursor_line = cl; + line = &e->cursor_frame->lines[cl]; + cb = line->n_boxes - 1; + } + + } while ( !line->boxes[cb].editable ); + + e->cursor_box = cb; + box = &line->boxes[cb]; + if ( box->type == WRAP_BOX_PANGO ) { + cp = box->len_chars; + if ( box->space == WRAP_SPACE_NONE ) { + cp--; + } + } else { + cp = 1; + } + + } + e->cursor_pos = cp; +} + + +void cur_box_diag(SCEditor *e) +{ + int sln, sbx, sps; + struct frame *fr; + + fr = e->cursor_frame; + sln = e->cursor_line; + sbx = e->cursor_box; + sps = e->cursor_pos; + + struct wrap_box *sbox = &e->cursor_frame->lines[sln].boxes[sbx]; + + printf("line/box/pos: [%i of %i]/[%i of %i]/[%i of %i]\n", + sln, fr->n_lines, + sbx, e->cursor_frame->lines[sln].n_boxes, + sps, sbox->len_chars); + printf("box type is %i, space type is %i\n", sbox->type, sbox->space); + if ( sbox->type == WRAP_BOX_NOTHING ) { + printf("Warning: in a nothing box!\n"); + } +} + + +void advance_cursor(SCEditor *e) +{ + int advance = 0; + signed int cp, cb, cl; + struct wrap_line *line = &e->cursor_frame->lines[e->cursor_line]; + struct wrap_box *box = &line->boxes[e->cursor_box]; + + cp = e->cursor_pos; + cb = e->cursor_box; + cl = e->cursor_line; + + switch ( box->type ) { + + case WRAP_BOX_PANGO: + if ( cp+1 > box->len_chars ) { + advance = 1; + } else { + cp++; + } + break; + + case WRAP_BOX_NOTHING: + case WRAP_BOX_SENTINEL: + advance = 1; + break; + + case WRAP_BOX_IMAGE: + cp++; + if ( cp > 1 ) advance = 1; + break; + + } + + if ( advance ) { + + do { + + cb++; + cp = 0; + + if ( box->space == WRAP_SPACE_NONE ) { + cp = 1; + } + + if ( cb >= line->n_boxes ) { + cl++; + if ( cl >= e->cursor_frame->n_lines ) { + /* Give up - could not move */ + return; + } + line = &e->cursor_frame->lines[cl]; + cb = 0; + cp = 0; + } + + } while ( !line->boxes[cb].editable ); + + e->cursor_line = cl; + e->cursor_box = cb; + + } + e->cursor_pos = cp; +} + + +static gint destroy_sig(GtkWidget *window, SCEditor *sceditor) +{ + /* FIXME: free stuff */ + return 0; +} + + +static void draw_editing_box(cairo_t *cr, struct frame *fr) +{ + const double dash[] = {2.0, 2.0}; + double xmin, ymin, width, height; + double ptot_w, ptot_h; + + xmin = fr->x; + ymin = fr->y; + width = fr->w; + height = fr->h; + + 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 = fr->pad_l + fr->pad_r; + ptot_h = fr->pad_t + fr->pad_b; + cairo_rectangle(cr, xmin+fr->pad_l, ymin+fr->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_caret(cairo_t *cr, struct frame *fr, + int cursor_line, int cursor_box, int cursor_pos) +{ + double xposd, yposd, line_height; + double cx, clow, chigh; + const double t = 1.8; + struct wrap_box *box; + int i; + + if ( fr == NULL ) return; + if ( fr->n_lines == 0 ) return; + + /* Locate the cursor in a "logical" and "geographical" sense */ + box = &fr->lines[cursor_line].boxes[cursor_box]; + get_cursor_pos(box, cursor_pos, &xposd, &yposd, &line_height); + xposd += fr->pad_l; + yposd += fr->pad_t; + + for ( i=0; i<cursor_line; i++ ) { + yposd += pango_units_to_double(fr->lines[i].height); + } + + for ( i=0; i<cursor_box; i++ ) { + int w = fr->lines[cursor_line].boxes[i].width; + w += fr->lines[cursor_line].boxes[i].sp; + xposd += pango_units_to_double(w); + } + + cx = fr->x + xposd; + clow = fr->y + yposd; + chigh = clow + line_height; + + 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_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 void draw_overlay(cairo_t *cr, SCEditor *e) +{ + double x, y, w, h; + + draw_editing_box(cr, e->selection); + + x = e->selection->x; + y = e->selection->y; + w = e->selection->w; + h = e->selection->h; + + /* Draw resize handles */ + /* FIXME: Not if this frame can't be resized */ + 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 only one frame is selected, draw the caret */ + if ( e->selection != NULL ) { + draw_caret(cr, e->cursor_frame, e->cursor_line, e->cursor_box, + e->cursor_pos); + } + + if ( (e->drag_status == DRAG_STATUS_DRAGGING) + && ((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_status == DRAG_STATUS_DRAGGING) + && ((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, + SCEditor *e) +{ + double xoff, yoff; + int width, height; + int edit_slide_height; + double ratio; + + width = gtk_widget_get_allocated_width(GTK_WIDGET(da)); + height = gtk_widget_get_allocated_height(GTK_WIDGET(da)); + + /* Overall background */ + cairo_rectangle(cr, 0.0, 0.0, width, height); + if ( slideshow_linked(e->p->slideshow) ) { + cairo_set_source_rgb(cr, 1.0, 0.3, 0.2); + } else { + cairo_set_source_rgb(cr, 0.9, 0.9, 0.9); + } + cairo_fill(cr); + + /* Get the overall size */ + ratio = e->p->slide_height/e->p->slide_width; + edit_slide_height = ratio*e->slide_width; + xoff = (width - e->slide_width)/2.0; + yoff = (height - edit_slide_height)/2.0; + e->border_offs_x = xoff; e->border_offs_y = yoff; + + /* Draw the slide from the cache */ + if ( e->cur_slide->rendered_edit != NULL ) { + cairo_set_source_surface(cr, e->cur_slide->rendered_edit, + xoff, yoff); + cairo_paint(cr); + } else { + fprintf(stderr, "Current slide not rendered yet!\n"); + } + + cairo_translate(cr, xoff, yoff); + draw_overlay(cr, e); + + return FALSE; +} + + +static void fixup_cursor(SCEditor *e) +{ + struct wrap_box *sbox; + + sbox = &e->cursor_frame->lines[e->cursor_line].boxes[e->cursor_box]; + + if ( e->cursor_pos > sbox->len_chars ) { + advance_cursor(e); + } +} + + +static void move_cursor(SCEditor *e, signed int x, signed int y) +{ + if ( x > 0 ) { + advance_cursor(e); + } else { + move_cursor_back(e); + } +} + + +static void insert_text(char *t, SCEditor *e) +{ + int sln, sbx, sps; + struct wrap_box *sbox; + struct frame *fr = e->cursor_frame; + + if ( fr == NULL ) return; + + /* If this is, say, the top level frame, do nothing */ + if ( fr->boxes == NULL ) return; + + sln = e->cursor_line; + sbx = e->cursor_box; + sps = e->cursor_pos; + sbox = &e->cursor_frame->lines[sln].boxes[sbx]; + + sc_insert_text(sbox->scblock, sps+sbox->offs_char, t); + + fr->empty = 0; + + rerender_slide(e); + + fixup_cursor(e); + advance_cursor(e); + + redraw_editor(e); +} + + +static void do_backspace(struct frame *fr, SCEditor *e) +{ + int sln, sbx, sps; + + if ( fr == NULL ) return; + + /* If this is, say, the top level frame, do nothing */ + if ( fr->n_lines == 0 ) return; + + sln = e->cursor_line; + sbx = e->cursor_box; + sps = e->cursor_pos; + struct wrap_box *sbox = &e->cursor_frame->lines[sln].boxes[sbx]; + + move_cursor_back(e); + + /* Delete may cross wrap boxes and maybe SCBlock boundaries */ + struct wrap_line *fline = &e->cursor_frame->lines[e->cursor_line]; + struct wrap_box *fbox = &fline->boxes[e->cursor_box]; + +// SCBlock *scbl = sbox->scblock; +// do { +// show_sc_blocks(scbl); +// scbl = sc_block_next(scbl); +// } while ( (scbl != fbox->scblock) && (scbl != NULL) ); + + if ( (fbox->scblock == NULL) || (sbox->scblock == NULL) ) return; + sc_delete_text(fbox->scblock, e->cursor_pos+fbox->offs_char, + sbox->scblock, sps+sbox->offs_char); + +// scbl = sbox->scblock; +// do { +// show_sc_blocks(scbl); +// scbl = sc_block_next(scbl); +// } while ( (scbl != fbox->scblock) && (scbl != NULL) ); + + rerender_slide(e); + redraw_editor(e); +} + + +static gboolean im_commit_sig(GtkIMContext *im, gchar *str, + SCEditor *e) +{ + if ( e->selection == NULL ) { + if ( str[0] == 'b' ) { + check_toggle_blank(e->p->slideshow); + } else { + printf("IM keypress: %s\n", str); + } + return FALSE; + } + + insert_text(str, e); + + return FALSE; +} + + +static int within_frame(struct frame *fr, double x, double y) +{ + if ( fr == NULL ) return 0; + if ( x < fr->x ) return 0; + if ( y < fr->y ) return 0; + if ( x > fr->x + fr->w ) return 0; + if ( y > fr->y + fr->h ) return 0; + return 1; +} + + +static struct frame *find_frame_at_position(struct frame *fr, + double x, double y) +{ + int i; + + for ( i=0; i<fr->num_children; i++ ) { + + if ( within_frame(fr->children[i], x, y) ) { + return find_frame_at_position(fr->children[i], x, y); + } + + } + + if ( within_frame(fr, x, y) ) return fr; + return NULL; +} + + +static enum corner which_corner(double xp, double yp, struct frame *fr) +{ + double x, y; /* Relative to object position */ + + x = xp - fr->x; + y = yp - fr->y; + + if ( x < 0.0 ) return CORNER_NONE; + if ( y < 0.0 ) return CORNER_NONE; + if ( x > fr->w ) return CORNER_NONE; + if ( y > fr->h ) return CORNER_NONE; + + /* Top left? */ + if ( (x<20.0) && (y<20.0) ) return CORNER_TL; + if ( (x>fr->w-20.0) && (y<20.0) ) return CORNER_TR; + if ( (x<20.0) && (y>fr->h-20.0) ) { + return CORNER_BL; + } + if ( (x>fr->w-20.0) && (y>fr->h-20.0) ) { + return CORNER_BR; + } + + return CORNER_NONE; +} + + +static void calculate_box_size(struct frame *fr, SCEditor *e, + 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 ( !fr->is_image ) { + + switch ( e->drag_corner ) { + + case CORNER_BR : + e->box_x = fr->x; + e->box_y = fr->y; + e->box_width = fr->w + ddx; + e->box_height = fr->h + ddy; + break; + + case CORNER_BL : + e->box_x = fr->x + ddx; + e->box_y = fr->y; + e->box_width = fr->w - ddx; + e->box_height = fr->h + ddy; + break; + + case CORNER_TL : + e->box_x = fr->x + ddx; + e->box_y = fr->y + ddy; + e->box_width = fr->w - ddx; + e->box_height = fr->h - ddy; + break; + + case CORNER_TR : + e->box_x = fr->x; + e->box_y = fr->y + ddy; + e->box_width = fr->w + ddx; + e->box_height = fr->h - ddy; + break; + + case CORNER_NONE : + break; + + } + return; + + + } + + switch ( e->drag_corner ) { + + case CORNER_BR : + vx = fr->w; + vy = fr->h; + break; + + case CORNER_BL : + vx = -fr->w; + vy = fr->h; + break; + + case CORNER_TL : + vx = -fr->w; + vy = -fr->h; + break; + + case CORNER_TR : + vx = fr->w; + vy = -fr->h; + 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 = fr->w * mult; + e->box_height = fr->h * mult; + dbx = e->box_width - fr->w; + dby = e->box_height - fr->h; + + if ( e->box_width < 40.0 ) { + mult = 40.0 / fr->w; + } + if ( e->box_height < 40.0 ) { + mult = 40.0 / fr->h; + } + e->box_width = fr->w * mult; + e->box_height = fr->h * mult; + dbx = e->box_width - fr->w; + dby = e->box_height - fr->h; + + switch ( e->drag_corner ) { + + case CORNER_BR : + e->box_x = fr->x; + e->box_y = fr->y; + break; + + case CORNER_BL : + e->box_x = fr->x - dbx; + e->box_y = fr->y; + break; + + case CORNER_TL : + e->box_x = fr->x - dbx; + e->box_y = fr->y - dby; + break; + + case CORNER_TR : + e->box_x = fr->x; + e->box_y = fr->y - dby; + break; + + case CORNER_NONE : + break; + + } + +} + + +static gboolean button_press_sig(GtkWidget *da, GdkEventButton *event, + SCEditor *e) +{ + enum corner c; + gdouble x, y; + struct frame *clicked; + + x = event->x - e->border_offs_x; + y = event->y - e->border_offs_y; + + if ( within_frame(e->selection, x, y) ) { + clicked = e->selection; + } else { + clicked = find_frame_at_position(e->cur_slide->top, x, y); + } + + /* If the user clicked the currently selected frame, position cursor + * or possibly prepare for resize */ + if ( (e->selection != NULL) && (clicked == e->selection) ) { + + struct frame *fr; + + fr = e->selection; + + /* Within the resizing region? */ + c = which_corner(x, y, fr); + if ( c != CORNER_NONE ) { + + e->drag_reason = DRAG_REASON_RESIZE; + e->drag_corner = c; + + e->start_corner_x = x; + e->start_corner_y = y; + e->diagonal_length = pow(fr->w, 2.0); + e->diagonal_length += pow(fr->h, 2.0); + e->diagonal_length = sqrt(e->diagonal_length); + + calculate_box_size(fr, e, x, y); + + e->drag_status = DRAG_STATUS_COULD_DRAG; + e->drag_reason = DRAG_REASON_RESIZE; + + } else { + + e->cursor_frame = clicked; + find_cursor(clicked, x-fr->x, y-fr->y, + &e->cursor_line, &e->cursor_box, + &e->cursor_pos); + + e->start_corner_x = event->x - e->border_offs_x; + e->start_corner_y = event->y - e->border_offs_y; + e->drag_status = DRAG_STATUS_COULD_DRAG; + e->drag_reason = DRAG_REASON_MOVE; + + } + + } else if ( (clicked == NULL) || (clicked == e->cur_slide->top) ) { + + /* Clicked no object. Deselect old object and set up for + * (maybe) creating a new one. */ + e->selection = NULL; + e->start_corner_x = event->x - e->border_offs_x; + e->start_corner_y = event->y - e->border_offs_y; + e->drag_status = DRAG_STATUS_COULD_DRAG; + e->drag_reason = DRAG_REASON_CREATE; + + } else { + + /* Select new frame, no immediate dragging */ + e->drag_status = DRAG_STATUS_NONE; + e->drag_reason = DRAG_REASON_NONE; + e->selection = clicked; + + } + + gtk_widget_grab_focus(GTK_WIDGET(da)); + redraw_editor(e); + return FALSE; +} + + +static gboolean motion_sig(GtkWidget *da, GdkEventMotion *event, + SCEditor *e) +{ + struct frame *fr = e->selection; + gdouble x, y; + + x = event->x - e->border_offs_x; + y = event->y - e->border_offs_y; + + if ( e->drag_status == DRAG_STATUS_COULD_DRAG ) { + + /* We just got a motion signal, and the status was "could drag", + * therefore the drag has started. */ + e->drag_status = DRAG_STATUS_DRAGGING; + + } + + switch ( e->drag_reason ) { + + case DRAG_REASON_NONE : + break; + + case DRAG_REASON_CREATE : + e->drag_corner_x = x; + e->drag_corner_y = y; + redraw_editor(e); + break; + + case DRAG_REASON_IMPORT : + /* Do nothing, handled by dnd_motion() */ + break; + + case DRAG_REASON_RESIZE : + calculate_box_size(fr, e, x, y); + redraw_editor(e); + break; + + case DRAG_REASON_MOVE : + e->box_x = (fr->x - e->start_corner_x) + x; + e->box_y = (fr->y - e->start_corner_y) + y; + e->box_width = fr->w; + e->box_height = fr->h; + redraw_editor(e); + break; + + } + + gdk_event_request_motions(event); + return FALSE; +} + + +static struct frame *create_frame(SCEditor *e, double x, double y, + double w, double h) +{ + struct frame *parent; + struct frame *fr; + + parent = e->cur_slide->top; + + if ( w < 0.0 ) { + x += w; + w = -w; + } + + if ( h < 0.0 ) { + y += h; + h = -h; + } + + fr = add_subframe(parent); + + /* Add to SC */ + fr->scblocks = sc_block_append_end(e->cur_slide->scblocks, + "f", NULL, NULL); + sc_block_set_frame(fr->scblocks, fr); + sc_block_append_inside(fr->scblocks, NULL, NULL, strdup("")); + + fr->x = x; + fr->y = y; + fr->w = w; + fr->h = h; + fr->is_image = 0; + fr->empty = 1; + + update_geom(fr); + + return fr; +} + + +static void do_resize(SCEditor *e, double x, double y, double w, double h) +{ + struct frame *fr; + + assert(e->selection != NULL); + + if ( w < 0.0 ) { + w = -w; + x -= w; + } + + if ( h < 0.0 ) { + h = -h; + y -= h; + } + + fr = e->selection; + fr->x = x; + fr->y = y; + fr->w = w; + fr->h = h; + update_geom(fr); + + rerender_slide(e); + redraw_editor(e); +} + + +static gboolean button_release_sig(GtkWidget *da, GdkEventButton *event, + SCEditor *e) +{ + gdouble x, y; + struct frame *fr; + + x = event->x - e->border_offs_x; + y = event->y - e->border_offs_y; + + /* Not dragging? Then I don't care. */ + if ( e->drag_status != DRAG_STATUS_DRAGGING ) return FALSE; + + e->drag_corner_x = x; + e->drag_corner_y = y; + e->drag_status = DRAG_STATUS_NONE; + + switch ( e->drag_reason ) + { + + case DRAG_REASON_NONE : + printf("Release on pointless drag.\n"); + break; + + case DRAG_REASON_CREATE : + fr = create_frame(e, e->start_corner_x, e->start_corner_y, + e->drag_corner_x - e->start_corner_x, + e->drag_corner_y - e->start_corner_y); + rerender_slide(e); + e->selection = fr; + break; + + case DRAG_REASON_IMPORT : + /* Do nothing, handled in dnd_drop() or dnd_leave() */ + break; + + case DRAG_REASON_RESIZE : + do_resize(e, e->box_x, e->box_y, e->box_width, e->box_height); + break; + + case DRAG_REASON_MOVE : + do_resize(e, e->box_x, e->box_y, e->box_width, e->box_height); + break; + + } + + e->drag_reason = DRAG_REASON_NONE; + + gtk_widget_grab_focus(GTK_WIDGET(da)); + redraw_editor(e); + return FALSE; +} + + +static gboolean key_press_sig(GtkWidget *da, GdkEventKey *event, + SCEditor *e) +{ + gboolean r; + int claim = 0; + + /* Throw the event to the IM context and let it sort things out */ + r = gtk_im_context_filter_keypress(GTK_IM_CONTEXT(e->im_context), + event); + if ( r ) return FALSE; /* IM ate it */ + + switch ( event->keyval ) { + + case GDK_KEY_Page_Up : + //prev_slide_sig(NULL, p); + claim = 1; + break; + + case GDK_KEY_Page_Down : + //next_slide_sig(NULL, p); FIXME! + claim = 1; + break; + + case GDK_KEY_Escape : + if ( e->p->slideshow != NULL ) end_slideshow(e->p->slideshow); + e->selection = NULL; + redraw_editor(e); + claim = 1; + break; + + case GDK_KEY_Left : + if ( e->selection != NULL ) { + move_cursor(e, -1, 0); + redraw_editor(e); + } + claim = 1; + break; + + case GDK_KEY_Right : + if ( e->selection != NULL ) { + move_cursor(e, +1, 0); + redraw_editor(e); + } + claim = 1; + break; + + case GDK_KEY_Up : + if ( e->selection != NULL ) { + move_cursor(e, 0, -1); + redraw_editor(e); + } + claim = 1; + break; + + case GDK_KEY_Down : + if ( e->selection != NULL ) { + move_cursor(e, 0, +1); + redraw_editor(e); + } + claim = 1; + break; + + + case GDK_KEY_Return : + im_commit_sig(NULL, "\n", e); + claim = 1; + break; + + case GDK_KEY_BackSpace : + if ( e->selection != NULL ) { + do_backspace(e->selection, e); + claim = 1; + } + break; + + case GDK_KEY_B : + case GDK_KEY_b : + if ( e->p->slideshow != NULL ) { + //if ( p->prefs->b_splits ) { + toggle_slideshow_link(e->p->slideshow); + //} else { + // p->ss_blank = 1-p->ss_blank; + // redraw_slideshow(p); + //} + } + claim = 1; + break; + + } + + if ( claim ) return TRUE; + return FALSE; +} + + +static gboolean dnd_motion(GtkWidget *widget, GdkDragContext *drag_context, + gint x, gint y, guint time, SCEditor *e) +{ + GdkAtom target; + + /* If we haven't already requested the data, do so now */ + if ( !e->drag_preview_pending && !e->have_drag_data ) { + + target = gtk_drag_dest_find_target(widget, drag_context, NULL); + + if ( target != GDK_NONE ) { + gtk_drag_get_data(widget, drag_context, target, time); + e->drag_preview_pending = 1; + } else { + e->import_acceptable = 0; + gdk_drag_status(drag_context, 0, time); + } + + } + + if ( e->have_drag_data && e->import_acceptable ) { + + gdk_drag_status(drag_context, GDK_ACTION_LINK, time); + e->start_corner_x = x - e->import_width/2.0; + e->start_corner_y = y - e->import_height/2.0; + e->drag_corner_x = x + e->import_width/2.0; + e->drag_corner_y = y + e->import_height/2.0; + + redraw_editor(e); + + } + + return TRUE; +} + + +static gboolean dnd_drop(GtkWidget *widget, GdkDragContext *drag_context, + gint x, gint y, guint time, SCEditor *e) +{ + GdkAtom target; + + target = gtk_drag_dest_find_target(widget, drag_context, NULL); + + if ( target == GDK_NONE ) { + gtk_drag_finish(drag_context, FALSE, FALSE, time); + } else { + gtk_drag_get_data(widget, drag_context, target, time); + } + + return TRUE; +} + + +static void chomp(char *s) +{ + size_t i; + + if ( !s ) return; + + for ( i=0; i<strlen(s); i++ ) { + if ( (s[i] == '\n') || (s[i] == '\r') ) { + s[i] = '\0'; + return; + } + } +} + + +/* Scale the image down if it's a silly size */ +static void check_import_size(SCEditor *e) +{ + if ( e->import_width > e->slide_width ) { + + int new_import_width; + + new_import_width = e->slide_width/2; + e->import_height = (new_import_width *e->import_height) + / e->import_width; + e->import_width = new_import_width; + } + + if ( e->import_height > e->p->slide_height ) { + + int new_import_height; + + new_import_height = e->p->slide_height/2; + e->import_width = (new_import_height*e->import_width) + / e->import_height; + e->import_height = new_import_height; + } +} + + +static void dnd_receive(GtkWidget *widget, GdkDragContext *drag_context, + gint x, gint y, GtkSelectionData *seldata, + guint info, guint time, SCEditor *e) +{ + if ( e->drag_preview_pending ) { + + gchar *filename = NULL; + GdkPixbufFormat *f; + gchar **uris; + int w, h; + + e->have_drag_data = 1; + e->drag_preview_pending = 0; + uris = gtk_selection_data_get_uris(seldata); + if ( uris != NULL ) { + filename = g_filename_from_uri(uris[0], NULL, NULL); + } + g_strfreev(uris); + + if ( filename == NULL ) { + + /* This doesn't even look like a sensible URI. + * Bail out. */ + gdk_drag_status(drag_context, 0, time); + if ( e->drag_highlight ) { + gtk_drag_unhighlight(widget); + e->drag_highlight = 0; + } + e->import_acceptable = 0; + return; + + } + chomp(filename); + + f = gdk_pixbuf_get_file_info(filename, &w, &h); + g_free(filename); + + e->import_width = w; + e->import_height = h; + + if ( f == NULL ) { + + gdk_drag_status(drag_context, 0, time); + if ( e->drag_highlight ) { + gtk_drag_unhighlight(widget); + e->drag_highlight = 0; + } + e->drag_status = DRAG_STATUS_NONE; + e->drag_reason = DRAG_REASON_NONE; + e->import_acceptable = 0; + + } else { + + /* Looks like a sensible image */ + gdk_drag_status(drag_context, GDK_ACTION_PRIVATE, time); + e->import_acceptable = 1; + + if ( !e->drag_highlight ) { + gtk_drag_highlight(widget); + e->drag_highlight = 1; + } + + check_import_size(e); + e->drag_reason = DRAG_REASON_IMPORT; + e->drag_status = DRAG_STATUS_DRAGGING; + + } + + } else { + + gchar **uris; + char *filename = NULL; + + uris = gtk_selection_data_get_uris(seldata); + if ( uris != NULL ) { + filename = g_filename_from_uri(uris[0], NULL, NULL); + } + g_strfreev(uris); + + if ( filename != NULL ) { + + struct frame *fr; + char *opts; + size_t len; + int w, h; + + gtk_drag_finish(drag_context, TRUE, FALSE, time); + chomp(filename); + + w = e->drag_corner_x - e->start_corner_x; + h = e->drag_corner_y - e->start_corner_y; + + len = strlen(filename)+64; + opts = malloc(len); + if ( opts == NULL ) { + free(filename); + fprintf(stderr, "Failed to allocate SC\n"); + return; + } + snprintf(opts, len, "1fx1f+0+0,filename=\"%s\"", + filename); + + fr = create_frame(e, e->start_corner_x, + e->start_corner_y, w, h); + fr->is_image = 1; + fr->empty = 0; + sc_block_append_inside(fr->scblocks, "image", opts, ""); + show_hierarchy(e->cur_slide->top, ""); + rerender_slide(e); + e->selection = fr; + redraw_editor(e); + free(filename); + + } else { + + gtk_drag_finish(drag_context, FALSE, FALSE, time); + + } + + } +} + + +static void dnd_leave(GtkWidget *widget, GdkDragContext *drag_context, + guint time, SCEditor *sceditor) +{ + if ( sceditor->drag_highlight ) { + gtk_drag_unhighlight(widget); + } + sceditor->have_drag_data = 0; + sceditor->drag_highlight = 0; + sceditor->drag_status = DRAG_STATUS_NONE; + sceditor->drag_reason = DRAG_REASON_NONE; +} + + +static gint realise_sig(GtkWidget *da, SCEditor *e) +{ + GdkWindow *win; + + /* Keyboard and input method stuff */ + e->im_context = gtk_im_multicontext_new(); + win = gtk_widget_get_window(e->drawingarea); + 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->drawingarea), "key-press-event", + G_CALLBACK(key_press_sig), e); + + /* FIXME: Can do this "properly" by setting up a separate font map */ + e->pc = gtk_widget_get_pango_context(e->drawingarea); + rerender_slide(e); + + return FALSE; +} + + +GtkWidget *sc_editor_get_widget(SCEditor *e) +{ + return e->drawingarea; +} + + +void sc_editor_set_slide(SCEditor *e, struct slide *s) +{ +} + + +/* FIXME: GObjectify this */ +SCEditor *sc_editor_new(struct presentation *p) +{ + SCEditor *sceditor; + GtkTargetEntry targets[1]; + + sceditor = calloc(1, sizeof(SCEditor)); + if ( sceditor == NULL ) return NULL; + + sceditor->drawingarea = gtk_drawing_area_new(); + + gtk_widget_set_size_request(GTK_WIDGET(sceditor->drawingarea), + p->slide_width + 20, + p->slide_height + 20); + + g_signal_connect(G_OBJECT(sceditor->drawingarea), "destroy", + G_CALLBACK(destroy_sig), sceditor); + g_signal_connect(G_OBJECT(sceditor->drawingarea), "realize", + G_CALLBACK(realise_sig), sceditor); + g_signal_connect(G_OBJECT(sceditor->drawingarea), "button-press-event", + G_CALLBACK(button_press_sig), sceditor); + g_signal_connect(G_OBJECT(sceditor->drawingarea), "button-release-event", + G_CALLBACK(button_release_sig), sceditor); + g_signal_connect(G_OBJECT(sceditor->drawingarea), "motion-notify-event", + G_CALLBACK(motion_sig), sceditor); + + /* Drag and drop */ + targets[0].target = "text/uri-list"; + targets[0].flags = 0; + targets[0].info = 1; + gtk_drag_dest_set(sceditor->drawingarea, 0, targets, 1, + GDK_ACTION_PRIVATE); + g_signal_connect(sceditor->drawingarea, "drag-data-received", + G_CALLBACK(dnd_receive), p); + g_signal_connect(sceditor->drawingarea, "drag-motion", + G_CALLBACK(dnd_motion), p); + g_signal_connect(sceditor->drawingarea, "drag-drop", + G_CALLBACK(dnd_drop), p); + g_signal_connect(sceditor->drawingarea, "drag-leave", + G_CALLBACK(dnd_leave), p); + + gtk_widget_set_can_focus(GTK_WIDGET(sceditor->drawingarea), TRUE); + gtk_widget_add_events(GTK_WIDGET(sceditor->drawingarea), + 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); + + g_signal_connect(G_OBJECT(sceditor->drawingarea), "draw", + G_CALLBACK(draw_sig), p); + + gtk_widget_grab_focus(GTK_WIDGET(sceditor->drawingarea)); + + gtk_widget_show(sceditor->drawingarea); + + return 0; +} |