aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas White <taw@bitwiz.me.uk>2019-12-21 19:43:13 +0100
committerThomas White <taw@bitwiz.me.uk>2019-12-22 09:33:21 +0100
commit718314e8b9301f826f35a05fcd9567572bd62b54 (patch)
treef22215610f0b1f3e7e6c0a7021eaaebefa81c451
parent8afb702e583c2dfc8a72e9647870a1f1da0a8ff2 (diff)
Vertical cursor motion in narrative and slide text boxes
-rw-r--r--libstorycode/gtk/gtknarrativeview.c100
-rw-r--r--libstorycode/gtk/gtknarrativeview.h1
-rw-r--r--libstorycode/gtk/gtkslideview.c78
-rw-r--r--libstorycode/gtk/gtkslideview.h2
4 files changed, 177 insertions, 4 deletions
diff --git a/libstorycode/gtk/gtknarrativeview.c b/libstorycode/gtk/gtknarrativeview.c
index 97da45b..b26c86b 100644
--- a/libstorycode/gtk/gtknarrativeview.c
+++ b/libstorycode/gtk/gtknarrativeview.c
@@ -673,6 +673,14 @@ static void sort_positions(struct edit_pos *a, struct edit_pos *b)
}
+static void set_cursor_h_pos(GtkNarrativeView *e)
+{
+ double x, y, h;
+ gtknv_get_cursor_pos(e->n, e->cpos, &x, &y, &h);
+ e->cursor_h_pos = x; /* Real position, not relative to PangoLayout */
+}
+
+
static void gtknv_cursor_moveh(Narrative *n, struct edit_pos *cp, signed int dir)
{
struct narrative_item *item = &n->items[cp->para];
@@ -777,6 +785,7 @@ static void gtknv_do_backspace(GtkNarrativeView *e, signed int dir)
p2 = e->cpos;
p1 = p2;
gtknv_cursor_moveh(e->n, &p1, dir);
+ set_cursor_h_pos(e);
}
sort_positions(&p1, &p2);
@@ -838,6 +847,7 @@ static void gtknv_insert_text(char *t, GtkNarrativeView *e)
gtknv_insert_text_in_paragraph(&e->n->items[e->cpos.para], off, t);
rewrap_range(e, e->cpos.para, e->cpos.para);
gtknv_cursor_moveh(e->n, &e->cpos, +1);
+ set_cursor_h_pos(e);
}
update_size(e);
@@ -886,6 +896,89 @@ static int gtknv_find_cursor(Narrative *n, double x, double y, struct edit_pos *
}
+static void gtknv_cursor_movev(GtkNarrativeView *e, signed int dir)
+{
+ int lineno;
+
+ assert(dir != 0);
+
+ gtknv_unset_selection(e);
+
+ if ( narrative_item_is_text(e->n, e->cpos.para) ) {
+
+ /* Starting in text */
+ PangoLayout *layout = e->n->items[e->cpos.para].layout;
+
+ if ( layout == NULL ) return;
+ pango_layout_index_to_line_x(layout, e->cpos.pos, e->cpos.trail,
+ &lineno, NULL);
+ if ( dir > 0 ) {
+ if ( lineno == pango_layout_get_line_count(layout)-1 ) {
+ /* Move to next item */
+ if ( e->cpos.para == e->n->n_items-1 ) {
+ /* No next paragraph to move to */
+ } else {
+ e->cpos.para++;
+ lineno = 0;
+ }
+ } else {
+ lineno++;
+ }
+ } else {
+ if ( lineno == 0 ) {
+ /* Move to previous paragraph */
+ if ( e->cpos.para == 0 ) {
+ /* No previous paragraph to move to */
+ } else {
+ e->cpos.para--;
+ if ( narrative_item_is_text(e->n, e->cpos.para) ) {
+ layout = e->n->items[e->cpos.para].layout;
+ lineno = pango_layout_get_line_count(layout)-1;
+ }
+ }
+ } else {
+ lineno--;
+ }
+ }
+
+ } else {
+
+ /* Starting in non-text */
+ if ( dir > 0 ) {
+ /* Move to next item */
+ if ( e->cpos.para == e->n->n_items-1 ) {
+ /* No next paragraph to move to */
+ } else {
+ e->cpos.para++;
+ lineno = 0;
+ }
+ } else {
+ /* Move to previous paragraph */
+ if ( e->cpos.para == 0 ) {
+ /* No previous paragraph to move to */
+ } else {
+ e->cpos.para--;
+ if ( narrative_item_is_text(e->n, e->cpos.para) ) {
+ PangoLayout *layout = e->n->items[e->cpos.para].layout;
+ lineno = pango_layout_get_line_count(layout)-1;
+ }
+ }
+ }
+ }
+
+ if ( narrative_item_is_text(e->n, e->cpos.para) ) {
+ /* Use the "virtual" x-position to place the cursor in this line */
+ PangoLayoutLine *line;
+ double x;
+ line = pango_layout_get_line_readonly(e->n->items[e->cpos.para].layout, lineno);
+ /* Subtract offset to PangoLayout */
+ x = e->cursor_h_pos - e->n->items[e->cpos.para].space_l;
+ pango_layout_line_x_to_index(line, pango_units_from_double(x),
+ &e->cpos.pos, &e->cpos.trail);
+ } /* else do nothing */
+}
+
+
static gboolean gtknv_button_press_sig(GtkWidget *da, GdkEventButton *event,
GtkNarrativeView *e)
{
@@ -900,6 +993,7 @@ static gboolean gtknv_button_press_sig(GtkWidget *da, GdkEventButton *event,
gtknv_find_cursor(e->n, x, y, &e->sel_start);
e->sel_end = e->sel_start;
e->cpos = e->sel_start;
+ set_cursor_h_pos(e);
if ( event->type == GDK_2BUTTON_PRESS ) {
struct narrative_item *item = &e->n->items[e->cpos.para];
@@ -977,6 +1071,7 @@ static gboolean gtknv_key_press_sig(GtkWidget *da, GdkEventKey *event,
case GDK_KEY_Left :
gtknv_cursor_moveh(e->n, &e->cpos, -1);
check_cursor_visible(e);
+ set_cursor_h_pos(e);
gtknv_redraw(e);
claim = 1;
break;
@@ -984,19 +1079,20 @@ static gboolean gtknv_key_press_sig(GtkWidget *da, GdkEventKey *event,
case GDK_KEY_Right :
gtknv_cursor_moveh(e->n, &e->cpos, +1);
check_cursor_visible(e);
+ set_cursor_h_pos(e);
gtknv_redraw(e);
claim = 1;
break;
case GDK_KEY_Up :
- gtknv_cursor_moveh(e->n, &e->cpos, -1);
+ gtknv_cursor_movev(e, -1);
check_cursor_visible(e);
gtknv_redraw(e);
claim = 1;
break;
case GDK_KEY_Down :
- gtknv_cursor_moveh(e->n, &e->cpos, +1);
+ gtknv_cursor_movev(e, +1);
check_cursor_visible(e);
gtknv_redraw(e);
claim = 1;
diff --git a/libstorycode/gtk/gtknarrativeview.h b/libstorycode/gtk/gtknarrativeview.h
index 18c97bb..02045fd 100644
--- a/libstorycode/gtk/gtknarrativeview.h
+++ b/libstorycode/gtk/gtknarrativeview.h
@@ -85,6 +85,7 @@ struct _gtknarrativeview
/* Location of the cursor */
struct edit_pos cpos;
+ double cursor_h_pos; /* Place the cursor is trying to be */
/* Rubber band boxes and related stuff */
enum narrative_drag_status drag_status;
diff --git a/libstorycode/gtk/gtkslideview.c b/libstorycode/gtk/gtkslideview.c
index 986f666..f4f4572 100644
--- a/libstorycode/gtk/gtkslideview.c
+++ b/libstorycode/gtk/gtkslideview.c
@@ -662,6 +662,24 @@ static void do_resize(GtkSlideView *e, double x, double y, double w, double h)
}
+static void set_sv_cursor_h_pos(GtkSlideView *e)
+{
+ double x, y, h;
+ double slide_w, slide_h;
+ double pad_l, pad_r, pad_t, pad_b;
+ Stylesheet *stylesheet;
+
+ stylesheet = narrative_get_stylesheet(e->n);
+ slide_get_logical_size(e->slide, stylesheet, &slide_w, &slide_h);
+ slide_item_get_padding(e->cursor_frame, stylesheet, &pad_l, &pad_r, &pad_t, &pad_b,
+ slide_w, slide_h);
+
+ gtksv_get_cursor_pos(e->cursor_frame, stylesheet, e->cpos,
+ slide_w, slide_h, &x, &y, &h);
+ e->cursor_h_pos = x - pad_l;
+}
+
+
static gboolean gtksv_button_press_sig(GtkWidget *da, GdkEventButton *event,
GtkSlideView *e)
{
@@ -720,6 +738,7 @@ static gboolean gtksv_button_press_sig(GtkWidget *da, GdkEventButton *event,
e->cursor_frame = clicked;
gtksv_find_cursor(clicked, stylesheet, x-frx, y-fry, &e->cpos,
slide_w, slide_h);
+ set_sv_cursor_h_pos(e);
e->start_corner_x = x;
e->start_corner_y = y;
@@ -732,6 +751,7 @@ static gboolean gtksv_button_press_sig(GtkWidget *da, GdkEventButton *event,
e->drag_reason = DRAG_REASON_TEXTSEL;
gtksv_find_cursor(clicked, stylesheet, x-frx, y-fry,
&e->sel_start, slide_w, slide_h);
+ set_sv_cursor_h_pos(e);
e->sel_end = e->sel_start;
}
@@ -767,6 +787,7 @@ static gboolean gtksv_button_press_sig(GtkWidget *da, GdkEventButton *event,
e->cursor_frame = clicked;
gtksv_find_cursor(clicked, stylesheet, x-frx, y-fry, &e->cpos,
slide_w, slide_h);
+ set_sv_cursor_h_pos(e);
}
@@ -838,6 +859,7 @@ static gboolean gtksv_motion_sig(GtkWidget *da, GdkEventMotion *event, GtkSlideV
gtksv_find_cursor(e->cursor_frame, stylesheet, x-frx, y-fry,
&e->sel_end, slide_w, slide_h);
e->cpos = e->sel_end;
+ set_sv_cursor_h_pos(e);
gtksv_redraw(e);
break;
@@ -984,6 +1006,56 @@ static size_t gtksv_end_offset_of_para(SlideItem *item, int pnum)
}
+static void gtksv_cursor_movev(GtkSlideView *e, signed int dir)
+{
+ int lineno;
+ PangoLayout *layout;
+ PangoLayoutLine *line;
+
+ assert(dir != 0);
+ if ( !is_text(e->cursor_frame->type) ) return;
+ if ( e->cursor_frame->paras[e->cpos.para].layout == NULL ) return;
+ gtksv_unset_selection(e);
+
+ layout = e->cursor_frame->paras[e->cpos.para].layout;
+ pango_layout_index_to_line_x(layout, e->cpos.pos, e->cpos.trail,
+ &lineno, NULL);
+
+ if ( dir > 0 ) {
+ if ( lineno == pango_layout_get_line_count(layout)-1 ) {
+ /* Move to next paragraph */
+ if ( e->cpos.para == e->cursor_frame->n_paras-1 ) {
+ /* No next paragraph to move to */
+ } else {
+ e->cpos.para++;
+ layout = e->cursor_frame->paras[e->cpos.para].layout;
+ lineno = 0;
+ }
+ } else {
+ lineno++;
+ }
+ } else {
+ if ( lineno == 0 ) {
+ /* Move to previous paragraph */
+ if ( e->cpos.para == 0 ) {
+ /* No previous paragraph to move to */
+ } else {
+ e->cpos.para--;
+ layout = e->cursor_frame->paras[e->cpos.para].layout;
+ lineno = pango_layout_get_line_count(layout)-1;
+ }
+ } else {
+ lineno--;
+ }
+ }
+
+ /* Now, use the "virtual" x-position to place the cursor in this line */
+ line = pango_layout_get_line_readonly(layout, lineno);
+ pango_layout_line_x_to_index(line, pango_units_from_double(e->cursor_h_pos),
+ &e->cpos.pos, &e->cpos.trail);
+}
+
+
static void gtksv_cursor_moveh(GtkSlideView *e, struct slide_pos *cp, signed int dir)
{
int np = cp->pos;
@@ -1175,24 +1247,26 @@ static gboolean gtksv_key_press_sig(GtkWidget *da, GdkEventKey *event,
case GDK_KEY_Left :
gtksv_cursor_moveh(e, &e->cpos, -1);
+ set_sv_cursor_h_pos(e);
gtksv_redraw(e);
claim = 1;
break;
case GDK_KEY_Right :
gtksv_cursor_moveh(e, &e->cpos, +1);
+ set_sv_cursor_h_pos(e);
gtksv_redraw(e);
claim = 1;
break;
case GDK_KEY_Up :
- gtksv_cursor_moveh(e, &e->cpos, -1);
+ gtksv_cursor_movev(e, -1);
gtksv_redraw(e);
claim = 1;
break;
case GDK_KEY_Down :
- gtksv_cursor_moveh(e, &e->cpos, +1);
+ gtksv_cursor_movev(e, +1);
gtksv_redraw(e);
claim = 1;
break;
diff --git a/libstorycode/gtk/gtkslideview.h b/libstorycode/gtk/gtkslideview.h
index a3dadff..aecf44c 100644
--- a/libstorycode/gtk/gtkslideview.h
+++ b/libstorycode/gtk/gtkslideview.h
@@ -106,6 +106,8 @@ struct _gtkslideview
/* Location of the cursor */
SlideItem *cursor_frame;
struct slide_pos cpos;
+ double cursor_h_pos; /* Horizontal position the cursor would have,
+ * if the current line were long enough. */
/* Border surrounding actual slide within drawingarea */
double border_offs_x;