summaryrefslogtreecommitdiff
path: root/desktop/textinput.c
diff options
context:
space:
mode:
authorAdrian Lees <adrian@aemulor.com>2005-07-20 23:27:28 +0000
committerAdrian Lees <adrian@aemulor.com>2005-07-20 23:27:28 +0000
commit1a1901d19b07ea265840962877b34b1205f6b092 (patch)
treef839d3b9687660d1b26556ba1944496aff10d8f5 /desktop/textinput.c
parent5e148741154019d69338c0f8781ed8a084cdd53d (diff)
downloadnetsurf-1a1901d19b07ea265840962877b34b1205f6b092.tar.gz
netsurf-1a1901d19b07ea265840962877b34b1205f6b092.tar.bz2
[project @ 2005-07-20 23:27:27 by adrianl]
2D scrolling of text areas/frames; First cut at selection in textareas; Further text editing actions (Word left/right; Page up/down; Cut block; Delete line start/end) svn path=/import/netsurf/; revision=1812
Diffstat (limited to 'desktop/textinput.c')
-rw-r--r--desktop/textinput.c531
1 files changed, 435 insertions, 96 deletions
diff --git a/desktop/textinput.c b/desktop/textinput.c
index e4017b435..9b8b94929 100644
--- a/desktop/textinput.c
+++ b/desktop/textinput.c
@@ -14,6 +14,7 @@
*/
#include <assert.h>
+#include <ctype.h>
#include <string.h>
#include "netsurf/desktop/browser.h"
@@ -48,14 +49,22 @@ static void input_update_display(struct browser_window *bw, struct box *input,
bool redraw);
static bool textbox_insert(struct browser_window *bw, struct box *text_box,
unsigned char_offset, const char *utf8, unsigned utf8_len);
-static bool textbox_delete(struct browser_window *bw, struct box *text_box,
- unsigned char_offset, unsigned utf8_len);
+static bool textbox_delete(struct box *text_box, unsigned char_offset,
+ unsigned utf8_len);
static struct box *textarea_insert_break(struct browser_window *bw,
struct box *text_box, size_t char_offset);
-static bool delete_handler(struct box *b, int offset, size_t length,
- void *handle);
+static bool delete_handler(struct box *b, int offset, size_t length);
+static struct box *line_start(struct box *text_box);
+static struct box *line_end(struct box *text_box);
+static struct box *line_above(struct box *text_box);
+static struct box *line_below(struct box *text_box);
+static bool textarea_cut(struct browser_window *bw,
+ struct box *start_box, unsigned start_idx,
+ struct box *end_box, unsigned end_idx);
static void textarea_reflow(struct browser_window *bw, struct box *textarea,
struct box *inline_container);
+static bool word_left(const char *text, int *poffset, int *pchars);
+static bool word_right(const char *text, int len, int *poffset, int *pchars);
/**
@@ -249,7 +258,7 @@ void browser_window_textarea_callback(struct browser_window *bw,
int prev_offset = char_offset;
char_offset = utf8_prev(text_box->text, char_offset);
- textbox_delete(bw, text_box, char_offset, prev_offset - char_offset);
+ textbox_delete(text_box, char_offset, prev_offset - char_offset);
}
reflow = true;
break;
@@ -287,8 +296,7 @@ void browser_window_textarea_callback(struct browser_window *bw,
/* delete a character */
int next_offset = utf8_next(text_box->text, text_box->length,
char_offset);
- textbox_delete(bw, text_box, char_offset,
- next_offset - char_offset);
+ textbox_delete(text_box, char_offset, next_offset - char_offset);
}
reflow = true;
break;
@@ -306,48 +314,39 @@ void browser_window_textarea_callback(struct browser_window *bw,
break;
case 21: { /* Ctrl + U */
- struct box *next;
-
- /* run back to start of line */
- while (text_box->prev && text_box->prev->type == BOX_TEXT)
- text_box = text_box->prev;
-
- /* run forwards to end */
- next = text_box->next;
- while (next && next->type == BOX_TEXT) {
- box_unlink_and_free(text_box);
- text_box = next;
- next = text_box->next;
- }
-
- /* can we take out this last inline and and the following BR? */
- if (next && next->type == BOX_BR && next->next) {
- box_unlink_and_free(text_box);
+ struct box *start_box = line_start(text_box);
+ struct box *end_box = line_end(text_box);
- /* place caret at start of next box */
- text_box = next->next;
- box_unlink_and_free(next);
- }
- else
- textbox_delete(bw, text_box, 0, text_box->length);
+ textarea_cut(bw, start_box, 0, end_box, end_box->length);
- char_offset = 0;
- reflow = true;
- }
- break;
+ text_box = start_box;
+ char_offset = 0;
+ reflow = true;
+ }
+ break;
case 22: /* Ctrl + V */
gui_paste_from_clipboard(bw->window,
box_x + inline_container->x + text_box->x + pixel_offset,
box_y + inline_container->y + text_box->y);
- break;
- case 24: /* Ctrl + X */
- if (gui_copy_to_clipboard(bw->sel)) {
- selection_traverse(bw->sel, delete_handler, bw);
+ /* screen updated and caret repositioned already */
+ return;
+
+ case 24: { /* Ctrl + X */
+ int start_idx, end_idx;
+ struct box *start_box = selection_get_start(bw->sel, &start_idx);
+ struct box *end_box = selection_get_end(bw->sel, &end_idx);
+ if (start_box && end_box) {
+ selection_clear(bw->sel, false);
+ textarea_cut(bw, start_box, start_idx, end_box, end_idx);
+
+ text_box = start_box;
+ char_offset = start_idx;
reflow = true;
}
- break;
+ }
+ break;
case KEY_RIGHT:
if ((unsigned int) char_offset < text_box->length) {
@@ -398,14 +397,12 @@ void browser_window_textarea_callback(struct browser_window *bw,
return;
case KEY_LINE_START:
- while (text_box->prev && text_box->prev->type == BOX_TEXT)
- text_box = text_box->prev;
+ text_box = line_start(text_box);
char_offset = 0;
break;
case KEY_LINE_END:
- while (text_box->next && text_box->next->type == BOX_TEXT)
- text_box = text_box->next;
+ text_box = line_end(text_box);
char_offset = text_box->length;
break;
@@ -425,41 +422,101 @@ void browser_window_textarea_callback(struct browser_window *bw,
char_offset = text_box->length;
break;
- case KEY_WORD_LEFT:
-// while () {
-// }
- break;
+ case KEY_WORD_LEFT: {
+ bool start_of_word = (char_offset <= 0 ||
+ isspace(text_box->text[char_offset - 1]));
- case KEY_WORD_RIGHT:
-// while () {
-// }
- break;
+ while (!word_left(text_box->text, &char_offset, NULL)) {
+ struct box *prev = NULL;
+
+ assert(char_offset == 0);
+
+ if (start_of_word) {
+ /* find the preceding non-BR box */
+ prev = text_box->prev;
+ while (prev && prev->type == BOX_BR)
+ prev = prev->prev;
+ }
+
+ if (!prev) {
+ /* just stay at the start of this box */
+ break;
+ }
+
+ text_box = prev;
+ char_offset = prev->length;
+ }
+ }
+ break;
+
+ case KEY_WORD_RIGHT: {
+ bool in_word = (char_offset < text_box->length &&
+ !isspace(text_box->text[char_offset]));
+
+ while (!word_right(text_box->text, text_box->length,
+ &char_offset, NULL)) {
+ struct box *next = text_box->next;
+
+ /* find the next non-BR box */
+ while (next && next->type == BOX_BR)
+ next = next->next;
+
+ if (!next) {
+ /* just stay at the end of this box */
+ char_offset = text_box->length;
+ break;
+ }
+
+ text_box = next;
+ char_offset = 0;
+
+ if (in_word &&
+ text_box->length > 0 &&
+ !isspace(text_box->text[0])) {
+ /* just stay at the start of this box */
+ break;
+ }
+ }
+ }
+ break;
+
+ case KEY_PAGE_UP: {
+ int nlines = (textarea->height / text_box->height) - 1;
+
+ while (nlines-- > 0)
+ text_box = line_above(text_box);
- case KEY_PAGE_UP:
-// while () {
-// }
if (char_offset > text_box->length)
char_offset = text_box->length;
- break;
+ }
+ break;
+
+ case KEY_PAGE_DOWN: {
+ int nlines = (textarea->height / text_box->height) - 1;
+ while (nlines-- > 0)
+ text_box = line_below(text_box);
- case KEY_PAGE_DOWN:
+ /* vague attempt to keep the caret at the same horizontal position,
+ given that the code currently cannot support it being beyond the
+ end of a line */
-// while () {
-// }
if (char_offset > text_box->length)
char_offset = text_box->length;
- break;
+ }
+ break;
case KEY_DELETE_LINE_START:
- textbox_delete(bw, text_box, 0, char_offset);
+ textarea_cut(bw, line_start(text_box), 0, text_box, char_offset);
+ char_offset = 0;
reflow = true;
break;
- case KEY_DELETE_LINE_END:
- textbox_delete(bw, text_box, char_offset,
- text_box->length - char_offset);
+ case KEY_DELETE_LINE_END: {
+ struct box *end_box = line_end(text_box);
+ textarea_cut(bw, text_box, char_offset, end_box, end_box->length);
reflow = true;
- break;
+ }
+ break;
default:
return;
@@ -673,17 +730,13 @@ void browser_window_input_callback(struct browser_window *bw,
return;
box_offset += utf8_len;
-
- nsfont_width(text_box->style, text_box->text,
- text_box->length, &text_box->width);
changed = true;
} else switch (key) {
case KEY_DELETE_LEFT: {
int prev_offset;
- if (box_offset == 0)
- return;
+ if (box_offset <= 0) return;
/* Gadget */
prev_offset = form_offset;
@@ -702,12 +755,8 @@ void browser_window_input_callback(struct browser_window *bw,
/* Go to the previous valid UTF-8 character */
box_offset = utf8_prev(text_box->text, box_offset);
- textbox_delete(bw, text_box, box_offset,
- prev_offset - box_offset);
-
- nsfont_width(text_box->style, text_box->text,
- text_box->length, &text_box->width);
-
+ textbox_delete(text_box, box_offset,
+ prev_offset - box_offset);
changed = true;
}
break;
@@ -735,12 +784,8 @@ void browser_window_input_callback(struct browser_window *bw,
next_offset = utf8_next(text_box->text, text_box->length,
box_offset);
- textbox_delete(bw, text_box, box_offset,
+ textbox_delete(text_box, box_offset,
next_offset - box_offset);
-
- nsfont_width(text_box->style, text_box->text,
- text_box->length, &text_box->width);
-
changed = true;
}
break;
@@ -797,8 +842,6 @@ void browser_window_input_callback(struct browser_window *bw,
input->gadget->value[0] = 0;
input->gadget->length = 0;
form_offset = 0;
-
- text_box->width = 0;
changed = true;
break;
@@ -806,7 +849,10 @@ void browser_window_input_callback(struct browser_window *bw,
gui_paste_from_clipboard(bw->window,
box_x + input->children->x + text_box->x + pixel_offset,
box_y + input->children->y + text_box->y);
- break;
+
+ /* screen updated and caret repositioned already */
+ return;
+
case KEY_RIGHT:
/* Text box */
@@ -837,6 +883,67 @@ void browser_window_input_callback(struct browser_window *bw,
form_offset = input->gadget->length;
break;
+ case KEY_WORD_LEFT: {
+ int nchars;
+ /* Text box */
+ if (word_left(input->gadget->value, &form_offset, &nchars)) {
+ /* Gadget */
+ while (box_offset > 0 && nchars-- > 0)
+ box_offset = utf8_prev(text_box->text, box_offset);
+ } else {
+ box_offset = 0;
+ form_offset = 0;
+ }
+ }
+ break;
+
+ case KEY_WORD_RIGHT: {
+ int nchars;
+ /* Text box */
+ if (word_right(input->gadget->value, input->gadget->length,
+ &form_offset, &nchars)) {
+ /* Gadget */
+ const char *text = text_box->text;
+ unsigned len = text_box->length;
+ while (box_offset < len && nchars-- > 0)
+ box_offset = utf8_next(text, len, box_offset);
+ } else {
+ box_offset = text_box->length;
+ form_offset = input->gadget->length;
+ }
+ }
+ break;
+
+ case KEY_DELETE_LINE_START:
+
+ if (box_offset <= 0) return;
+
+ /* Text box */
+ textbox_delete(text_box, 0, box_offset);
+ box_offset = 0;
+
+ /* Gadget */
+ memmove(input->gadget->value,
+ input->gadget->value + form_offset,
+ (input->gadget->length - form_offset) + 1); /* inc NUL */
+ input->gadget->length -= form_offset;
+ form_offset = 0;
+ changed = true;
+ break;
+
+ case KEY_DELETE_LINE_END:
+
+ if (box_offset >= text_box->length)
+ return;
+
+ /* Text box */
+ textbox_delete(text_box, box_offset, text_box->length - box_offset);
+ /* Gadget */
+ input->gadget->length = form_offset;
+ input->gadget->value[form_offset] = 0;
+ changed = true;
+ break;
+
default:
return;
}
@@ -1133,13 +1240,8 @@ bool browser_window_input_paste_text(struct browser_window *bw,
utf8 = p;
}
- if (update) {
-
- nsfont_width(text_box->style, text_box->text, text_box->length,
- &text_box->width);
-
+ if (update)
input_update_display(bw, input, form_offset, box_offset, false, true);
- }
return success;
}
@@ -1165,6 +1267,10 @@ void input_update_display(struct browser_window *bw, struct box *input,
int box_x, box_y;
int dx;
+ if (redraw)
+ nsfont_width(text_box->style, text_box->text, text_box->length,
+ &text_box->width);
+
box_coords(input, &box_x, &box_y);
nsfont_width(text_box->style, text_box->text, box_offset,
@@ -1172,7 +1278,7 @@ void input_update_display(struct browser_window *bw, struct box *input,
dx = text_box->x;
text_box->x = 0;
if (input->width < text_box->width &&
- input->width / 2 < pixel_offset) {
+ input->width / 2 < (int)pixel_offset) {
text_box->x = input->width / 2 - pixel_offset;
if (text_box->x < input->width - text_box->width)
text_box->x = input->width - text_box->width;
@@ -1245,14 +1351,12 @@ bool textbox_insert(struct browser_window *bw, struct box *text_box,
/**
* Delete a number of chars from a text box
*
- * \param bw browser window
* \param text_box text box
* \param char_offset offset within text box (bytes) of first char to delete
* \param utf8_len length (bytes) of chars to be deleted
*/
-bool textbox_delete(struct browser_window *bw, struct box *text_box,
- unsigned char_offset, unsigned utf8_len)
+bool textbox_delete(struct box *text_box, unsigned char_offset, unsigned utf8_len)
{
unsigned prev_offset = char_offset + utf8_len;
if (prev_offset <= text_box->length) {
@@ -1271,10 +1375,89 @@ bool textbox_delete(struct browser_window *bw, struct box *text_box,
}
-bool delete_handler(struct box *b, int offset, size_t length, void *handle)
+/**
+ * Delete some text from a box, or delete the box in its entirety
+ *
+ * \param b box
+ * \param offset start offset of text to be deleted (in bytes)
+ * \param length length of text to be deleted
+ * \return true iff successful
+ */
+
+bool delete_handler(struct box *b, int offset, size_t length)
+{
+ if (offset <= 0 && length >= b->length) {
+ /* remove the entire box */
+ box_unlink_and_free(b);
+ return true;
+ }
+ else {
+ return textbox_delete(b, offset, length);
+ }
+}
+
+
+/**
+ * Locate the first inline box at the start of this line
+ *
+ * \param text_box text box from which to start searching
+ */
+
+struct box *line_start(struct box *text_box)
+{
+ while (text_box->prev && text_box->prev->type == BOX_TEXT)
+ text_box = text_box->prev;
+ return text_box;
+}
+
+
+/**
+ * Locate the last inline box in this line
+ *
+ * \param text_box text box from which to start searching
+ */
+
+struct box *line_end(struct box *text_box)
{
- struct browser_window *bw = handle;
- return textbox_delete(bw, b, offset, length);
+ while (text_box->next && text_box->next->type == BOX_TEXT)
+ text_box = text_box->next;
+ return text_box;
+}
+
+
+/**
+ * Backtrack to the start of the previous line, if there is one.
+ */
+
+struct box *line_above(struct box *text_box)
+{
+ struct box *prev;
+
+ text_box = line_start(text_box);
+
+ prev = text_box->prev;
+ while (prev && prev->type == BOX_BR)
+ prev = prev->prev;
+
+ return prev ? line_start(prev) : text_box;
+}
+
+
+/**
+ * Advance to the start of the next line, if there is one.
+ */
+
+struct box *line_below(struct box *text_box)
+{
+ struct box *next;
+
+ text_box = line_end(text_box);
+
+ next = text_box->next;
+ while (next && next->type == BOX_BR)
+ next = next->next;
+
+ return next ? next : text_box;
}
@@ -1323,6 +1506,86 @@ struct box *textarea_insert_break(struct browser_window *bw, struct box *text_bo
/**
+ * Cut a range of text to the global clipboard.
+ *
+ * \param bw browser window
+ * \param start_box text box at start of range
+ * \param start_idx index (bytes) within start box
+ * \param end_box text box at end of range
+ * \param end_idx index (bytes) within end box
+ */
+
+bool textarea_cut(struct browser_window *bw,
+ struct box *start_box, unsigned start_idx,
+ struct box *end_box, unsigned end_idx)
+{
+ struct box *box = start_box;
+ bool success = true;
+ bool del = true;
+
+ if (!gui_empty_clipboard())
+ return false;
+
+ if (!start_idx && (!start_box->prev || start_box->prev->type == BOX_BR)) {
+ /* deletion would leave two adjacent BRs, so just collapse
+ the start box to an empty TEXT rather than deleting it */
+ del = false;
+ }
+
+ while (box && box != end_box) {
+ /* read before deletion, in case the whole box goes */
+ struct box *next = box->next;
+
+ if (box->type == BOX_BR) {
+ if (!gui_add_to_clipboard("\n", 1, false)) {
+ gui_commit_clipboard();
+ return false;
+ }
+ box_unlink_and_free(box);
+ }
+ else {
+ /* append box text to clipboard and then delete it */
+ if (!gui_add_to_clipboard(box->text + start_idx,
+ box->length - start_idx, box->space)) {
+ gui_commit_clipboard();
+ return false;
+ }
+
+ if (del) {
+ if (!delete_handler(box, start_idx,
+ box->length - start_idx)) {
+ gui_commit_clipboard();
+ return false;
+ }
+ }
+ else
+ textbox_delete(box, start_idx, box->length - start_idx);
+ }
+
+ del = true;
+ start_idx = 0;
+ box = next;
+ }
+
+ /* and the last box */
+ if (box) {
+ if (gui_add_to_clipboard(box->text + start_idx, end_idx, box->space)) {
+ if (del) {
+ if (!delete_handler(box, start_idx, end_idx - start_idx))
+ success = false;
+ }
+ else
+ textbox_delete(box, start_idx, end_idx - start_idx);
+ }
+ else
+ success = false;
+ }
+
+ return gui_commit_clipboard() ? success : false;
+}
+
+
+/**
* Reflow textarea preserving width and height
*
* \param bw browser window
@@ -1344,3 +1607,79 @@ void textarea_reflow(struct browser_window *bw, struct box *textarea,
layout_calculate_descendant_bboxes(textarea);
}
+
+/**
+ * Move to the start of the word containing the given character position,
+ * or the start of the preceding word if already at the start of this one.
+ *
+ * \param text UTF-8 text string
+ * \param poffset offset of caret within string (updated on exit)
+ * \param pchars receives the number of characters skipped
+ * \return true iff the start of a word was found before/at the string start
+ */
+
+bool word_left(const char *text, int *poffset, int *pchars)
+{
+ int offset = *poffset;
+ bool success = false;
+ int nchars = 0;
+
+ while (offset > 0) {
+ offset = utf8_prev(text, offset);
+ nchars++;
+ if (!isspace(text[offset])) break;
+ }
+
+ while (offset > 0) {
+ int prev = utf8_prev(text, offset);
+ success = true;
+ if (isspace(text[prev]))
+ break;
+ offset = prev;
+ nchars++;
+ }
+
+ *poffset = offset;
+ if (pchars) *pchars = nchars;
+
+ return success;
+}
+
+
+/**
+ * Move to the start of the first word following the given character position.
+ *
+ * \param text UTF-8 text string
+ * \param len length of string in bytes
+ * \param poffset offset of caret within string (updated on exit)
+ * \param pchars receives the number of characters skipped
+ * \return true iff the start of a word was found before the string end
+ */
+
+bool word_right(const char *text, int len, int *poffset, int *pchars)
+{
+ int offset = *poffset;
+ bool success = false;
+ int nchars = 0;
+
+ while (offset < len) {
+ if (isspace(text[offset])) break;
+ offset = utf8_next(text, len, offset);
+ nchars++;
+ }
+
+ while (offset < len) {
+ offset = utf8_next(text, len, offset);
+ nchars++;
+ if (offset < len && !isspace(text[offset])) {
+ success = true;
+ break;
+ }
+ }
+
+ *poffset = offset;
+ if (pchars) *pchars = nchars;
+
+ return success;
+}
+