From b147d7a36768dc5789ff47a77fd149034ee87d69 Mon Sep 17 00:00:00 2001 From: Thomas White Date: Tue, 5 Mar 2019 23:34:59 +0100 Subject: Cursor movement and basic text editing --- libstorycode/gtk/gtknarrativeview.c | 346 ++++++++++++++++++++-------------- libstorycode/narrative_render_cairo.c | 15 +- libstorycode/narrative_render_cairo.h | 4 + 3 files changed, 220 insertions(+), 145 deletions(-) diff --git a/libstorycode/gtk/gtknarrativeview.c b/libstorycode/gtk/gtknarrativeview.c index 59dda21..d33dc3b 100644 --- a/libstorycode/gtk/gtknarrativeview.c +++ b/libstorycode/gtk/gtknarrativeview.c @@ -123,32 +123,41 @@ static void set_vertical_params(GtkNarrativeView *e) } -static gboolean resize_sig(GtkWidget *widget, GdkEventConfigure *event, - GtkNarrativeView *e) +static void rewrap_range(GtkNarrativeView *e, int min, int max) { PangoContext *pc; PangoLanguage *lang; const char *langname; - pc = gdk_pango_context_get(); - - e->visible_height = event->height; - e->visible_width = event->width; - e->w = e->visible_width; + pc = gtk_widget_get_pango_context(GTK_WIDGET(e)); langname = presentation_get_language(e->p); lang = pango_language_from_string(langname); /* Wrap everything with the current width, to get the total height */ - narrative_wrap(presentation_get_narrative(e->p), - presentation_get_stylesheet(e->p), - lang, gtk_widget_get_pango_context(widget), e->w, - presentation_get_imagestore(e->p)); + narrative_wrap_range(presentation_get_narrative(e->p), + presentation_get_stylesheet(e->p), + lang, pc, e->w, + presentation_get_imagestore(e->p), + min, max); +} + +static gboolean resize_sig(GtkWidget *widget, GdkEventConfigure *event, + GtkNarrativeView *e) +{ + Narrative *n; + + n = presentation_get_narrative(e->p); + + e->visible_height = event->height; + e->visible_width = event->width; e->w = e->visible_width; - e->h = narrative_get_height(presentation_get_narrative(e->p)); - g_object_unref(pc); + rewrap_range(e, 0, n->n_items-1); + + e->w = e->visible_width; + e->h = narrative_get_height(presentation_get_narrative(e->p)); set_vertical_params(e); set_horizontal_params(e); @@ -494,13 +503,34 @@ static size_t pos_trail_to_offset(struct narrative_item *item, int offs, int tra } +static void get_cursor_pos(Narrative *n, struct edit_pos cpos, + double *x, double *y, double *h) +{ + size_t offs; + PangoRectangle rect; + struct narrative_item *item; + + item = &n->items[cpos.para]; + if ( item->type == NARRATIVE_ITEM_SLIDE ) { + *x = n->space_l + item->space_l; + *y = n->space_t + para_top(n, cpos.para); + *h = item->slide_h; + return; + } + + offs = pos_trail_to_offset(item, cpos.pos, cpos.trail); + pango_layout_get_cursor_pos(item->layout, offs, &rect, NULL); + *x = pango_units_to_double(rect.x) + n->space_l + item->space_l; + *y = pango_units_to_double(rect.y) + n->space_t + para_top(n, cpos.para); + *h = pango_units_to_double(rect.height); +} + + static void draw_caret(cairo_t *cr, Narrative *n, struct edit_pos cpos, int hgh) { - double cx, clow, chigh; + double cx, clow, chigh, h; const double t = 1.8; - size_t offs; - PangoRectangle rect; if ( hgh ) { draw_para_highlight(cr, n, cpos.para); @@ -514,14 +544,9 @@ static void draw_caret(cairo_t *cr, Narrative *n, struct edit_pos cpos, return; } - offs = pos_trail_to_offset(&n->items[cpos.para], cpos.pos, cpos.trail); - - pango_layout_get_cursor_pos(n->items[cpos.para].layout, offs, &rect, NULL); + get_cursor_pos(n, cpos, &cx, &clow, &h); - cx = pango_units_to_double(rect.x) + n->space_l + n->items[cpos.para].space_l; - clow = pango_units_to_double(rect.y) + n->space_t + para_top(n, cpos.para); - - chigh = clow + pango_units_to_double(rect.height); + chigh = clow+h; cairo_move_to(cr, cx, clow); cairo_line_to(cr, cx, chigh); @@ -575,26 +600,22 @@ static gboolean draw_sig(GtkWidget *da, cairo_t *cr, GtkNarrativeView *e) static void check_cursor_visible(GtkNarrativeView *e) { -// double x, y, h; -// size_t offs; -// Paragraph *para; -// -// if ( e->cursor_frame == NULL ) return; -// -// para = e->cursor_frame->paras[e->cpos.para]; -// offs = pos_trail_to_offset(para, e->cpos.pos, e->cpos.trail); -// get_cursor_pos(e->cursor_frame, e->cpos.para, offs, &x, &y, &h); -// -// /* Off the bottom? */ -// if ( y - e->scroll_pos + h > e->visible_height ) { -// e->scroll_pos = y + h - e->visible_height; -// e->scroll_pos += e->cursor_frame->pad_b; -// } -// -// /* Off the top? */ -// if ( y < e->scroll_pos ) { -// e->scroll_pos = y - e->cursor_frame->pad_t; -// } + Narrative *n; + double x, y, h; + + n = presentation_get_narrative(e->p); + get_cursor_pos(n, e->cpos, &x, &y, &h); + + /* Off the bottom? */ + if ( y - e->scroll_pos + h > e->visible_height ) { + e->scroll_pos = y + h - e->visible_height; + e->scroll_pos += n->space_b; + } + + /* Off the top? */ + if ( y < e->scroll_pos ) { + e->scroll_pos = y - n->space_t; + } } @@ -644,84 +665,123 @@ static void do_backspace(GtkNarrativeView *e) } +static size_t end_offset_of_para(Narrative *n, int pnum) +{ + assert(pnum >= 0); + if ( n->items[pnum].type == NARRATIVE_ITEM_SLIDE ) return 0; + return strlen(n->items[pnum].text); + +} + + +static void cursor_moveh(Narrative *n, struct edit_pos *cp, signed int dir) +{ + struct narrative_item *item = &n->items[cp->para]; + int np = cp->pos; + + if ( item->type == NARRATIVE_ITEM_SLIDE ) { + if ( dir > 0 ) { + np = G_MAXINT; + cp->trail = 0; + } else { + np = -1; + cp->trail = 0; + } + } else { + pango_layout_move_cursor_visually(item->layout, 1, cp->pos, + cp->trail, dir, + &np, &cp->trail); + } + + if ( np == -1 ) { + if ( cp->para > 0 ) { + size_t end_offs; + cp->para--; + end_offs = end_offset_of_para(n, cp->para); + if ( end_offs > 0 ) { + cp->pos = end_offs - 1; + cp->trail = 1; + } else { + /* Jumping into an empty paragraph */ + cp->pos = 0; + cp->trail = 0; + } + return; + } else { + /* Can't move any further */ + return; + } + } + + if ( np == G_MAXINT ) { + if ( cp->para < n->n_items-1 ) { + cp->para++; + cp->pos = 0; + cp->trail = 0; + return; + } else { + /* Can't move any further */ + cp->trail = 1; + return; + } + } + + cp->pos = np; +} + + +static void insert_text_in_paragraph(struct narrative_item *item, size_t offs, + char *t) +{ + char *n = malloc(strlen(t) + strlen(item->text) + 1); + if ( n == NULL ) return; + strncpy(n, item->text, offs); + n[offs] = '\0'; + strcat(n, t); + strcat(n, item->text+offs); + free(item->text); + item->text = n; +} + + static void insert_text(char *t, GtkNarrativeView *e) { -// Paragraph *para; -// -// if ( e->cursor_frame == NULL ) return; -// -// if ( e->sel_active ) { -// do_backspace(e->cursor_frame, e); -// } -// -// if ( strcmp(t, "\n") == 0 ) { -// split_paragraph_at_cursor(e); -// update_size(e); -// cursor_moveh(e->cursor_frame, &e->cpos, +1); -// check_cursor_visible(e); -// emit_change_sig(e); -// redraw(e); -// return; -// } -// -// para = e->cursor_frame->paras[e->cpos.para]; -// -// /* Is this paragraph even a text one? */ -// if ( para_type(para) == PARA_TYPE_TEXT ) { -// -// size_t off; -// -// /* Yes. The "easy" case */ -// -// if ( !position_editable(e->cursor_frame, e->cpos) ) { -// fprintf(stderr, "Position not editable\n"); -// return; -// } -// -// off = pos_trail_to_offset(para, e->cpos.pos, e->cpos.trail); -// insert_text_in_paragraph(para, off, t); -// wrap_paragraph(para, NULL, -// e->cursor_frame->w - e->cursor_frame->pad_l -// - e->cursor_frame->pad_r, 0, 0); -// update_size(e); -// -// cursor_moveh(e->cursor_frame, &e->cpos, +1); -// -// } else { -// -// SCBlock *bd; -// SCBlock *ad; -// Paragraph *pnew; -// -// bd = para_scblock(para); -// if ( bd == NULL ) { -// fprintf(stderr, "No SCBlock for para\n"); -// return; -// } -// -// /* No. Create a new text paragraph straight afterwards */ -// ad = sc_block_insert_after(bd, NULL, NULL, strdup(t)); -// if ( ad == NULL ) { -// fprintf(stderr, "Failed to add SCBlock\n"); -// return; -// } -// -// pnew = insert_paragraph(e->cursor_frame, e->cpos.para); -// if ( pnew == NULL ) { -// fprintf(stderr, "Failed to insert paragraph\n"); -// return; -// } -// add_run(pnew, ad, e->cursor_frame->fontdesc, -// e->cursor_frame->col, NULL); -// -// wrap_frame(e->cursor_frame, e->pc); -// -// e->cpos.para += 1; -// e->cpos.pos = 0; -// e->cpos.trail = 1; -// -// } -// + Narrative *n; + struct narrative_item *item; + + if ( e->sel_active ) { + do_backspace(e); + } + + n = presentation_get_narrative(e->p); + item = &n->items[e->cpos.para]; + + if ( strcmp(t, "\n") == 0 ) { + //split_paragraph_at_cursor(e); FIXME + //update_size(e); + cursor_moveh(n, &e->cpos, +1); + check_cursor_visible(e); + emit_change_sig(e); + redraw(e); + return; + } + + if ( item->type != NARRATIVE_ITEM_SLIDE ) { + + size_t off; + + off = pos_trail_to_offset(item, e->cpos.pos, e->cpos.trail); + insert_text_in_paragraph(item, off, t); + rewrap_range(e, e->cpos.para, e->cpos.para); + //update_size(e); + cursor_moveh(n, &e->cpos, +1); + + } else { + + /* FIXME: Add text after slide */ + + } + emit_change_sig(e); check_cursor_visible(e); redraw(e); @@ -754,29 +814,28 @@ static void unset_selection(GtkNarrativeView *e) static int find_cursor(Narrative *n, double x, double y, struct edit_pos *pos) { - int i; - double pad; + double cur_y; + struct narrative_item *item; + int i = 0; - pad = n->space_t; + cur_y = n->space_t; - for ( i=0; in_items; i++ ) { - struct narrative_item *item = &n->items[i]; - double npos = pad + item->h; - if ( npos > y ) { - pos->para = i; - if ( item->type == NARRATIVE_ITEM_SLIDE ) { - pos->pos = 0; - } else { - pango_layout_xy_to_index(item->layout, - pango_units_from_double(x - item->space_l), - pango_units_from_double(y - pad - item->space_t), - &pos->pos, &pos->trail); - } - return 0; - } - pad = npos; + 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); + return 0; } @@ -828,6 +887,7 @@ static gboolean key_press_sig(GtkWidget *da, GdkEventKey *event, GtkNarrativeView *e) { gboolean r; + Narrative *n; int claim = 0; /* Throw the event to the IM context and let it sort things out */ @@ -835,6 +895,8 @@ static gboolean key_press_sig(GtkWidget *da, GdkEventKey *event, event); if ( r ) return FALSE; /* IM ate it */ + n = presentation_get_narrative(e->p); + switch ( event->keyval ) { case GDK_KEY_Escape : @@ -846,25 +908,25 @@ static gboolean key_press_sig(GtkWidget *da, GdkEventKey *event, break; case GDK_KEY_Left : - //cursor_moveh(e->cursor_frame, &e->cpos, -1); + cursor_moveh(n, &e->cpos, -1); redraw(e); claim = 1; break; case GDK_KEY_Right : - //cursor_moveh(e->cursor_frame, &e->cpos, +1); + cursor_moveh(n, &e->cpos, +1); redraw(e); claim = 1; break; case GDK_KEY_Up : - //cursor_moveh(e->cursor_frame, &e->cpos, -1); + cursor_moveh(n, &e->cpos, -1); redraw(e); claim = 1; break; case GDK_KEY_Down : - //cursor_moveh(e->cursor_frame, &e->cpos, +1); + cursor_moveh(n, &e->cpos, +1); redraw(e); claim = 1; break; diff --git a/libstorycode/narrative_render_cairo.c b/libstorycode/narrative_render_cairo.c index 744822d..6293c44 100644 --- a/libstorycode/narrative_render_cairo.c +++ b/libstorycode/narrative_render_cairo.c @@ -189,8 +189,9 @@ static void wrap_slide(struct narrative_item *item, Stylesheet *ss, ImageStore * } -int narrative_wrap(Narrative *n, Stylesheet *stylesheet, PangoLanguage *lang, - PangoContext *pc, double w, ImageStore *is) +int narrative_wrap_range(Narrative *n, Stylesheet *stylesheet, PangoLanguage *lang, + PangoContext *pc, double w, ImageStore *is, + int min, int max) { int i; struct length pad[4]; @@ -204,7 +205,7 @@ int narrative_wrap(Narrative *n, Stylesheet *stylesheet, PangoLanguage *lang, n->w = w; w -= n->space_l + n->space_r; - for ( i=0; in_items; i++ ) { + for ( i=min; i<=max; i++ ) { switch ( n->items[i].type ) { @@ -237,6 +238,14 @@ int narrative_wrap(Narrative *n, Stylesheet *stylesheet, PangoLanguage *lang, } +int narrative_wrap(Narrative *n, Stylesheet *stylesheet, PangoLanguage *lang, + PangoContext *pc, double w, ImageStore *is) +{ + return narrative_wrap_range(n, stylesheet, lang, pc, w, is, + 0, n->n_items-1); +} + + double narrative_get_height(Narrative *n) { int i; diff --git a/libstorycode/narrative_render_cairo.h b/libstorycode/narrative_render_cairo.h index 835c5a9..93941df 100644 --- a/libstorycode/narrative_render_cairo.h +++ b/libstorycode/narrative_render_cairo.h @@ -31,6 +31,10 @@ #include "imagestore.h" +extern int narrative_wrap_range(Narrative *n, Stylesheet *stylesheet, + PangoLanguage *lang, PangoContext *pc, double w, + ImageStore *is, int min, int max); + extern int narrative_wrap(Narrative *n, Stylesheet *stylesheet, PangoLanguage *lang, PangoContext *pc, double w, ImageStore *is); -- cgit v1.2.3