diff options
-rw-r--r-- | content/content.c | 40 | ||||
-rw-r--r-- | content/content.h | 25 | ||||
-rw-r--r-- | content/content_protected.h | 4 | ||||
-rw-r--r-- | desktop/browser.c | 126 | ||||
-rw-r--r-- | desktop/browser.h | 48 | ||||
-rw-r--r-- | desktop/browser_private.h | 20 | ||||
-rw-r--r-- | desktop/frames.c | 2 | ||||
-rw-r--r-- | desktop/selection.c | 128 | ||||
-rw-r--r-- | desktop/textarea.c | 183 | ||||
-rw-r--r-- | desktop/textarea.h | 43 | ||||
-rw-r--r-- | desktop/textinput.c | 80 | ||||
-rw-r--r-- | gtk/scaffolding.c | 14 | ||||
-rw-r--r-- | gtk/window.c | 2 | ||||
-rw-r--r-- | render/box_textarea.c | 66 | ||||
-rw-r--r-- | render/box_textarea.h | 11 | ||||
-rw-r--r-- | render/html.c | 112 | ||||
-rw-r--r-- | render/html.h | 1 | ||||
-rw-r--r-- | render/html_interaction.c | 359 | ||||
-rw-r--r-- | render/html_internal.h | 70 | ||||
-rw-r--r-- | render/textplain.c | 49 | ||||
-rw-r--r-- | riscos/save.c | 22 | ||||
-rw-r--r-- | riscos/save.h | 2 | ||||
-rw-r--r-- | riscos/window.c | 56 | ||||
-rw-r--r-- | utils/utf8.c | 39 | ||||
-rw-r--r-- | utils/utf8.h | 2 |
25 files changed, 1006 insertions, 498 deletions
diff --git a/content/content.c b/content/content.c index 1a92e408b..3533d943c 100644 --- a/content/content.c +++ b/content/content.c @@ -475,6 +475,26 @@ void content_mouse_action(hlcache_handle *h, struct browser_window *bw, /** + * Handle keypresses. + * + * \param h Content handle + * \param key The UCS4 character codepoint + * \return true if key handled, false otherwise + */ + +bool content_keypress(struct hlcache_handle *h, uint32_t key) +{ + struct content *c = hlcache_handle_get_content(h); + assert(c != NULL); + + if (c->handler->keypress != NULL) + return c->handler->keypress(c, key); + + return false; +} + + +/** * Request a redraw of an area of a content * * \param h high-level cache handle @@ -738,10 +758,26 @@ void content_close(hlcache_handle *h) /** - * Find this content's selection context, if it has one. + * Tell a content that any selection it has, or one of its objects has, must be + * cleared. + */ + +void content_clear_selection(hlcache_handle *h) +{ + struct content *c = hlcache_handle_get_content(h); + assert(c != 0); + + if (c->handler->get_selection != NULL) + c->handler->clear_selection(c); +} + + +/** + * Get a text selection from a content. Ownership is passed to the caller, + * who must free() it. */ -struct selection *content_get_selection(hlcache_handle *h) +char * content_get_selection(hlcache_handle *h) { struct content *c = hlcache_handle_get_content(h); assert(c != 0); diff --git a/content/content.h b/content/content.h index 2ae0b38be..3f93d603b 100644 --- a/content/content.h +++ b/content/content.h @@ -79,6 +79,8 @@ typedef enum { CONTENT_MSG_DRAGSAVE, /**< Allow drag saving of content */ CONTENT_MSG_SAVELINK, /**< Allow URL to be saved */ CONTENT_MSG_POINTER, /**< Wants a specific mouse pointer set */ + CONTENT_MSG_SELECTION, /**< A selection made or cleared */ + CONTENT_MSG_CARET, /**< Caret movement / hiding */ CONTENT_MSG_DRAG /**< A drag started or ended */ } content_msg; @@ -153,6 +155,25 @@ union content_msg_data { } savelink; /** CONTENT_MSG_POINTER - Mouse pointer to set */ browser_pointer_shape pointer; + /** CONTENT_MSG_SELECTION - Selection made or cleared */ + struct { + bool selection; /**< false for selection cleared */ + bool read_only; + } selection; + /** CONTENT_MSG_CARET - set caret position or, hide caret */ + struct { + enum { + CONTENT_CARET_SET_POS, + CONTENT_CARET_HIDE, + CONTENT_CARET_REMOVE + } type; + struct { + int x; /**< Carret x-coord */ + int y; /**< Carret y-coord */ + int height; /**< Carret height */ + const struct rect *clip; /**< Carret clip rect */ + } pos; /**< With CONTENT_CARET_SET_POS */ + } caret; /** CONTENT_MSG_DRAG - Drag start or end */ struct { enum { @@ -219,12 +240,14 @@ void content_mouse_track(struct hlcache_handle *h, struct browser_window *bw, browser_mouse_state mouse, int x, int y); void content_mouse_action(struct hlcache_handle *h, struct browser_window *bw, browser_mouse_state mouse, int x, int y); +bool content_keypress(struct hlcache_handle *h, uint32_t key); bool content_redraw(struct hlcache_handle *h, struct content_redraw_data *data, const struct rect *clip, const struct redraw_context *ctx); void content_open(struct hlcache_handle *h, struct browser_window *bw, struct content *page, struct object_params *params); void content_close(struct hlcache_handle *h); -struct selection *content_get_selection(struct hlcache_handle *h); +void content_clear_selection(struct hlcache_handle *h); +char * content_get_selection(struct hlcache_handle *h); void content_get_contextual_content(struct hlcache_handle *h, int x, int y, struct contextual_content *data); bool content_scroll_at_point(struct hlcache_handle *h, diff --git a/content/content_protected.h b/content/content_protected.h index ecbe17fc7..8efe763aa 100644 --- a/content/content_protected.h +++ b/content/content_protected.h @@ -58,13 +58,15 @@ struct content_handler { browser_mouse_state mouse, int x, int y); void (*mouse_action)(struct content *c, struct browser_window *bw, browser_mouse_state mouse, int x, int y); + bool (*keypress)(struct content *c, uint32_t key); bool (*redraw)(struct content *c, struct content_redraw_data *data, const struct rect *clip, const struct redraw_context *ctx); void (*open)(struct content *c, struct browser_window *bw, struct content *page, struct object_params *params); void (*close)(struct content *c); - struct selection * (*get_selection)(struct content *c); + void (*clear_selection)(struct content *c); + char * (*get_selection)(struct content *c); void (*get_contextual_content)(struct content *c, int x, int y, struct contextual_content *data); bool (*scroll_at_point)(struct content *c, int x, int y, diff --git a/desktop/browser.c b/desktop/browser.c index ca72c41a2..fa0e6d121 100644 --- a/desktop/browser.c +++ b/desktop/browser.c @@ -428,42 +428,69 @@ struct browser_window * browser_window_get_root(struct browser_window *bw) } /* exported interface, documented in browser.h */ -bool browser_window_has_selection(struct browser_window *bw) +browser_editor_flags browser_window_get_editor_flags(struct browser_window *bw) { + browser_editor_flags ed_flags = BW_EDITOR_NONE; assert(bw->window); + assert(bw->parent == NULL); - if (bw->cur_sel != NULL && selection_defined(bw->cur_sel)) { - return true; - } else { - return false; + if (bw->selection.bw != NULL) { + ed_flags |= BW_EDITOR_CAN_COPY; + + if (!bw->selection.read_only) + ed_flags |= BW_EDITOR_CAN_CUT; } + + if (bw->can_edit) + ed_flags |= BW_EDITOR_CAN_PASTE; + + return ed_flags; } /* exported interface, documented in browser.h */ -void browser_window_set_selection(struct browser_window *bw, - struct selection *s) +char * browser_window_get_selection(struct browser_window *bw) { assert(bw->window); + assert(bw->parent == NULL); - if (bw->cur_sel != s && bw->cur_sel != NULL) { - /* Clear any existing selection */ - selection_clear(bw->cur_sel, true); - } + if (bw->selection.bw == NULL || + bw->selection.bw->current_content == NULL) + return NULL; - /* Replace current selection pointer */ - if (s == NULL && bw->current_content != NULL) { - bw->cur_sel = content_get_selection(bw->current_content); - } else { - bw->cur_sel = s; - } + return content_get_selection(bw->selection.bw->current_content); } -/* exported interface, documented in browser.h */ -struct selection *browser_window_get_selection(struct browser_window *bw) +/** + * Set or remove a selection. + * + * \param bw browser window with selection + * \param selection true if bw has a selection, false if removing selection + * \param read_only true iff selection is read only (e.g. can't cut it) + */ +static void browser_window_set_selection(struct browser_window *bw, + bool selection, bool read_only) { - assert(bw->window); + struct browser_window *top; + + assert(bw != NULL); + + top = browser_window_get_root(bw); + + assert(top != NULL); - return bw->cur_sel; + if (bw != top->selection.bw && top->selection.bw != NULL && + top->selection.bw->current_content != NULL) { + /* clear old selection */ + content_clear_selection(top->selection.bw->current_content); + } + + if (selection) { + top->selection.bw = bw; + } else { + top->selection.bw = NULL; + } + + top->selection.read_only = read_only; } /* exported interface, documented in browser.h */ @@ -739,7 +766,6 @@ void browser_window_initialise_common(struct browser_window *bw, bw->history = history_clone(clone->history); /* window characteristics */ - bw->cur_sel = NULL; bw->cur_search = NULL; bw->refresh_interval = -1; @@ -919,7 +945,7 @@ nserror browser_window_navigate(struct browser_window *bw, } browser_window_stop(bw); - browser_window_remove_caret(bw); + browser_window_remove_caret(bw, false); browser_window_destroy_children(bw); LOG(("Loading '%s'", nsurl_access(url))); @@ -1159,7 +1185,7 @@ browser_window_callback_errorcode(hlcache_handle *c, bw->loading_content = NULL; } else if (c == bw->current_content) { bw->current_content = NULL; - browser_window_remove_caret(bw); + browser_window_remove_caret(bw, false); } hlcache_handle_release(c); @@ -1230,7 +1256,7 @@ nserror browser_window_callback(hlcache_handle *c, browser_window_get_dimensions(bw, &width, &height, true); content_reformat(c, false, width, height); - browser_window_remove_caret(bw); + browser_window_remove_caret(bw, false); if (bw->window) gui_window_new_content(bw->window); @@ -1260,11 +1286,6 @@ nserror browser_window_callback(hlcache_handle *c, } } - if (bw->window != NULL) { - browser_window_set_selection(bw, - content_get_selection(c)); - } - /* frames */ if (content_get_type(c) == CONTENT_HTML && html_get_frameset(c) != NULL) @@ -1315,7 +1336,7 @@ nserror browser_window_callback(hlcache_handle *c, bw->loading_content = NULL; else if (c == bw->current_content) { bw->current_content = NULL; - browser_window_remove_caret(bw); + browser_window_remove_caret(bw, false); } hlcache_handle_release(c); @@ -1355,8 +1376,7 @@ nserror browser_window_callback(hlcache_handle *c, browser_window_recalculate_iframes(bw); } - if (bw->move_callback) - bw->move_callback(bw, bw->caret_p1, bw->caret_p2); + /* TODO: get caret pos redraw */ if (!(event->data.background)) { /* Reformatted content should be redrawn */ @@ -1515,6 +1535,29 @@ nserror browser_window_callback(hlcache_handle *c, } break; + case CONTENT_MSG_CARET: + switch (event->data.caret.type) { + case CONTENT_CARET_REMOVE: + browser_window_remove_caret(bw, false); + break; + case CONTENT_CARET_HIDE: + browser_window_remove_caret(bw, true); + break; + case CONTENT_CARET_SET_POS: + browser_window_place_caret(bw, + event->data.caret.pos.x, + event->data.caret.pos.y, + event->data.caret.pos.height); + break; + } + break; + + case CONTENT_MSG_SELECTION: + browser_window_set_selection(bw, + event->data.selection.selection, + event->data.selection.read_only); + break; + default: assert(0); } @@ -2083,10 +2126,8 @@ void browser_window_destroy_internal(struct browser_window *bw) if (top->focus == bw) top->focus = top; - if (bw->current_content != NULL && - top->cur_sel == content_get_selection( - bw->current_content)) { - browser_window_set_selection(top, NULL); + if (top->selection.bw == bw) { + browser_window_set_selection(top, false, false); } } @@ -2734,7 +2775,18 @@ void browser_window_mouse_click(struct browser_window *bw, switch (content_get_type(c)) { case CONTENT_HTML: case CONTENT_TEXTPLAIN: + { + /* Give bw focus */ + struct browser_window *root_bw = browser_window_get_root(bw); + if (bw != root_bw->focus) { + browser_window_remove_caret(bw, false); + browser_window_set_selection(bw, false, true); + root_bw->focus = bw; + } + + /* Pass mouse action to content */ content_mouse_action(c, bw, mouse, x, y); + } break; default: if (mouse & BROWSER_MOUSE_MOD_2) { diff --git a/desktop/browser.h b/desktop/browser.h index cad8bf706..39f2253bf 100644 --- a/desktop/browser.h +++ b/desktop/browser.h @@ -40,14 +40,6 @@ struct history; struct selection; struct fetch_multipart_data; -typedef bool (*browser_caret_callback)(struct browser_window *bw, uint32_t key, - void *p1, void *p2); -typedef void (*browser_move_callback)(struct browser_window *bw, - void *p1, void *p2); -typedef bool (*browser_paste_callback)(struct browser_window *bw, - const char *utf8, unsigned utf8_len, bool last, - void *p1, void *p2); - typedef enum { DRAGGING_NONE, @@ -60,6 +52,13 @@ typedef enum { DRAGGING_OTHER } browser_drag_type; +typedef enum { + BW_EDITOR_NONE = 0, /**< No selection, no editing */ + BW_EDITOR_CAN_COPY = (1 << 0), /**< Have selection */ + BW_EDITOR_CAN_CUT = (1 << 1), /**< Selection not read-only */ + BW_EDITOR_CAN_PASTE = (1 << 2) /**< Can paste, input */ +} browser_editor_flags; + extern bool browser_reformat_pending; /** flags to browser window go */ @@ -206,15 +205,9 @@ bool browser_window_stop_available(struct browser_window *bw); /* In desktop/textinput.c */ void browser_window_place_caret(struct browser_window *bw, - int x, int y, int height, - browser_caret_callback caret_cb, - browser_paste_callback paste_cb, - browser_move_callback move_cb, - void *p1, void *p2); -void browser_window_remove_caret(struct browser_window *bw); + int x, int y, int height); +void browser_window_remove_caret(struct browser_window *bw, bool only_hide); bool browser_window_key_press(struct browser_window *bw, uint32_t key); -bool browser_window_paste_text(struct browser_window *bw, const char *utf8, - unsigned utf8_len, bool last); /** @@ -327,29 +320,22 @@ browser_drag_type browser_window_get_drag_type(struct browser_window *bw); struct browser_window * browser_window_get_root(struct browser_window *bw); /** - * Check whether browser window contains a selection - * - * \param bw The browser window - * \return true if browser window contains a selection - */ -bool browser_window_has_selection(struct browser_window *bw); - -/** - * Set pointer to current selection. Clears any existing selection. + * Check whether browser window can accept a cut/copy/paste, or has a selection + * that could be saved. * * \param bw The browser window - * \param s The new selection + * \return flags indicating editor flags */ -void browser_window_set_selection(struct browser_window *bw, - struct selection *s); +browser_editor_flags browser_window_get_editor_flags(struct browser_window *bw); /** - * Get the current selection context from a root browser window + * Get the current selection from a root browser window, ownership passed to + * caller, who must free() it. * * \param bw The browser window - * \return the selection context, or NULL + * \return the selected text string, or NULL */ -struct selection *browser_window_get_selection(struct browser_window *bw); +char * browser_window_get_selection(struct browser_window *bw); /** diff --git a/desktop/browser_private.h b/desktop/browser_private.h index 91372acc3..4c14b1700 100644 --- a/desktop/browser_private.h +++ b/desktop/browser_private.h @@ -52,18 +52,6 @@ struct browser_window { /** Window history structure. */ struct history *history; - /** Handler for keyboard input, or 0. */ - browser_caret_callback caret_callback; - /** Handler for pasting text, or 0. */ - browser_paste_callback paste_callback; - /** Handler for repositioning caret, or 0. */ - browser_move_callback move_callback; - - /** User parameters for caret_callback, paste_callback, and - * move_callback */ - void *caret_p1; - void *caret_p2; - /** Platform specific window data. */ struct gui_window *window; @@ -158,8 +146,12 @@ struct browser_window { /** Last time a link was followed in this window */ unsigned int last_action; - /** Current selection, or NULL if none */ - struct selection *cur_sel; + /** Current selection */ + struct { + struct browser_window *bw; + bool read_only; + } selection; + bool can_edit; /** Current context for free text search, or NULL if none */ struct search_context *cur_search; diff --git a/desktop/frames.c b/desktop/frames.c index 27085ef3d..dcc66df9d 100644 --- a/desktop/frames.c +++ b/desktop/frames.c @@ -219,7 +219,6 @@ void browser_window_create_iframes(struct browser_window *bw, window->no_resize = true; window->margin_width = cur->margin_width; window->margin_height = cur->margin_height; - window->cur_sel = bw->cur_sel; window->scale = bw->scale; if (cur->name) { window->name = strdup(cur->name); @@ -338,7 +337,6 @@ void browser_window_create_frameset(struct browser_window *bw, warn_user("NoMemory", 0); } - window->cur_sel = bw->cur_sel; window->scale = bw->scale; /* linking */ diff --git a/desktop/selection.c b/desktop/selection.c index fa82ad027..536df5c84 100644 --- a/desktop/selection.c +++ b/desktop/selection.c @@ -77,9 +77,6 @@ static bool redraw_handler(const char *text, size_t length, struct box *box, size_t whitespace_length); static void selection_redraw(struct selection *s, unsigned start_idx, unsigned end_idx); -static bool save_handler(const char *text, size_t length, struct box *box, - void *handle, const char *whitespace_text, - size_t whitespace_length); static bool selected_part(struct box *box, unsigned start_idx, unsigned end_idx, unsigned *start_offset, unsigned *end_offset); static bool traverse_tree(struct box *box, unsigned start_idx, unsigned end_idx, @@ -285,10 +282,6 @@ bool selection_click(struct selection *s, browser_mouse_state mouse, (mouse & (BROWSER_MOUSE_MOD_1 | BROWSER_MOUSE_MOD_2)); int pos = -1; /* 0 = inside selection, 1 = after it */ struct browser_window *top = selection_get_browser_window(s); - - if (top == NULL) - return false; /* not our problem */ - top = browser_window_get_root(top); if (selection_defined(s)) { @@ -309,14 +302,12 @@ bool selection_click(struct selection *s, browser_mouse_state mouse, } else if (!modkeys) { if (pos && (mouse & BROWSER_MOUSE_PRESS_1)) { - /* Clear the selection if mouse is pressed outside the selection, - * Otherwise clear on release (to allow for drags) */ - browser_window_set_selection(top, s); + /* Clear the selection if mouse is pressed outside the + * selection, Otherwise clear on release (to allow for drags) */ selection_clear(s, true); } else if (mouse & BROWSER_MOUSE_DRAG_1) { /* start new selection drag */ - browser_window_set_selection(top, s); selection_clear(s, true); @@ -333,8 +324,6 @@ bool selection_click(struct selection *s, browser_mouse_state mouse, if (!selection_defined(s)) return false; /* ignore Adjust drags */ - browser_window_set_selection(top, s); - if (pos >= 0) { selection_set_end(s, idx); @@ -907,7 +896,6 @@ void selection_clear(struct selection *s, bool redraw) { int old_start, old_end; bool was_defined; - struct browser_window *top = selection_get_browser_window(s); assert(s); was_defined = selection_defined(s); @@ -918,13 +906,6 @@ void selection_clear(struct selection *s, bool redraw) s->start_idx = 0; s->end_idx = 0; - if (!top) - return; - - top = browser_window_get_root(top); - - gui_clear_selection(top->window); - if (redraw && was_defined) selection_redraw(s, old_start, old_end); } @@ -1110,111 +1091,6 @@ bool selection_highlighted(const struct selection *s, /** - * Selection traversal handler for saving the text to a file. - * - * \param text pointer to text being added, or NULL for newline - * \param length length of text to be appended (bytes) - * \param box pointer to text box (or NULL for textplain content) - * \param handle our save_state workspace pointer - * \param whitespace_text whitespace to place before text for formatting - * may be NULL - * \param whitespace_length length of whitespace_text - * \return true iff the file writing succeeded and traversal should continue. - */ - -bool save_handler(const char *text, size_t length, struct box *box, - void *handle, const char *whitespace_text, - size_t whitespace_length) -{ - struct save_text_state *sv = handle; - size_t new_length; - int space = 0; - - assert(sv); - - if (box && (box->space > 0)) - space = 1; - - if (whitespace_text) - length += whitespace_length; - - new_length = sv->length + whitespace_length + length + space; - if (new_length >= sv->alloc) { - size_t new_alloc = sv->alloc + (sv->alloc / 4); - char *new_block; - - if (new_alloc < new_length) new_alloc = new_length; - - new_block = realloc(sv->block, new_alloc); - if (!new_block) return false; - - sv->block = new_block; - sv->alloc = new_alloc; - } - if (whitespace_text) { - memcpy(sv->block + sv->length, whitespace_text, - whitespace_length); - } - memcpy(sv->block + sv->length + whitespace_length, text, length); - sv->length += length; - - if (space == 1) - sv->block[sv->length++] = ' '; - - return true; -} - - -/** - * Save the given selection to a file. - * - * \param s selection object - * \param path pathname to be used - * \return true iff the save succeeded - */ - -bool selection_save_text(struct selection *s, const char *path) -{ - struct save_text_state sv = { NULL, 0, 0 }; - utf8_convert_ret ret; - char *result; - FILE *out; - - if (!selection_traverse(s, save_handler, &sv)) { - free(sv.block); - return false; - } - - if (!sv.block) - return false; - - ret = utf8_to_local_encoding(sv.block, sv.length, &result); - free(sv.block); - - if (ret != UTF8_CONVERT_OK) { - LOG(("failed to convert to local encoding, return %d", ret)); - return false; - } - - out = fopen(path, "w"); - if (out) { - int res = fputs(result, out); - if (res < 0) { - LOG(("Warning: writing data failed")); - } - - res = fputs("\n", out); - fclose(out); - free(result); - return (res != EOF); - } - free(result); - - return false; -} - - -/** * Adjust the selection to reflect a change in the selected text, * eg. editing in a text area/input field. * diff --git a/desktop/textarea.c b/desktop/textarea.c index 455e9a4ca..2da2c206b 100644 --- a/desktop/textarea.c +++ b/desktop/textarea.c @@ -180,6 +180,7 @@ static bool textarea_select(struct textarea *ta, int c_start, int c_end, bool force_redraw) { int swap; + bool pre_existing_selection = (ta->sel_start != -1); struct textarea_msg msg; /* Ensure start is the beginning of the selection */ @@ -205,6 +206,24 @@ static bool textarea_select(struct textarea *ta, int c_start, int c_end, ta->callback(ta->data, &msg); + if (!pre_existing_selection && ta->sel_start != -1) { + /* Didn't have a selection before, but do now */ + msg.type = TEXTAREA_MSG_SELECTION_REPORT; + + msg.data.selection.have_selection = true; + msg.data.selection.read_only = (ta->flags & TEXTAREA_READONLY); + + ta->callback(ta->data, &msg); + + if (!(ta->flags & TEXTAREA_INTERNAL_CARET)) { + /* Caret hidden, and client is responsible */ + msg.type = TEXTAREA_MSG_CARET_UPDATE; + msg.data.caret.type = TEXTAREA_CARET_HIDE; + + ta->callback(ta->data, &msg); + } + } + return true; } @@ -438,11 +457,12 @@ static void textarea_scrollbar_callback(void *client_data, if (!(ta->flags & TEXTAREA_INTERNAL_CARET)) { /* Tell client where caret should be placed */ msg.ta = ta; - msg.type = TEXTAREA_MSG_MOVED_CARET; - msg.data.caret.hidden = false; - msg.data.caret.x = ta->caret_x - ta->scroll_x; - msg.data.caret.y = ta->caret_y - ta->scroll_y; - msg.data.caret.height = ta->line_height; + msg.type = TEXTAREA_MSG_CARET_UPDATE; + msg.data.caret.type = TEXTAREA_CARET_SET_POS; + msg.data.caret.pos.x = ta->caret_x - ta->scroll_x; + msg.data.caret.pos.y = ta->caret_y - ta->scroll_y; + msg.data.caret.pos.height = ta->line_height; + msg.data.caret.pos.clip = NULL; ta->callback(ta->data, &msg); } @@ -497,7 +517,7 @@ static bool textarea_reflow(struct textarea *ta, unsigned int start) int avail_width; int h_extent; /* horizontal extent */ int v_extent; /* vertical extent */ - bool restart; + bool restart = false; if (ta->lines == NULL) { ta->lines = @@ -781,10 +801,10 @@ static void textarea_get_xy_offset(struct textarea *ta, int x, int y, line = y / ta->line_height; - if (line < 0) - line = 0; if (ta->line_count - 1 < line) line = ta->line_count - 1; + if (line < 0) + line = 0; /* Get byte position */ nsfont.font_position_in_string(&ta->fstyle, @@ -798,9 +818,7 @@ static void textarea_get_xy_offset(struct textarea *ta, int x, int y, * after it. Otherwise, the caret will be placed at the start of the * following line, which is undesirable. */ - if (ta->flags & TEXTAREA_MULTILINE && - ta->show->data[ta->lines[line].b_start + - ta->lines[line].b_length] > 0 && + if (ta->flags & TEXTAREA_MULTILINE && ta->lines[line].b_length > 1 && bpos == (unsigned)ta->lines[line].b_length && ta->show->data[ta->lines[line].b_start + ta->lines[line].b_length - 1] == ' ') @@ -838,9 +856,9 @@ static bool textarea_set_caret_xy(struct textarea *ta, int x, int y) /** - * Insert text into the text area + * Insert text into the textarea * - * \param ta Text area + * \param ta Textarea widget * \param c_index 0-based character index to insert at * \param text UTF-8 text to insert * \param b_len Byte length of UTF-8 text @@ -908,9 +926,9 @@ static inline void textarea_char_to_byte_offset(struct textarea_utf8 *text, /** - * Replace text in a text area + * Replace text in a textarea * - * \param ta Text area + * \param ta Textarea widget * \param start Start character index of replaced section (inclusive) * \param end End character index of replaced section (exclusive) * \param rep Replacement UTF-8 text to insert @@ -1421,11 +1439,12 @@ bool textarea_set_caret(struct textarea *ta, int caret) if (!(ta->flags & TEXTAREA_INTERNAL_CARET)) { /* Tell client where caret should be placed */ msg.ta = ta; - msg.type = TEXTAREA_MSG_MOVED_CARET; - msg.data.caret.hidden = false; - msg.data.caret.x = x - ta->scroll_x; - msg.data.caret.y = y - ta->scroll_y; - msg.data.caret.height = ta->line_height; + msg.type = TEXTAREA_MSG_CARET_UPDATE; + msg.data.caret.type = TEXTAREA_CARET_SET_POS; + msg.data.caret.pos.x = x - ta->scroll_x; + msg.data.caret.pos.y = y - ta->scroll_y; + msg.data.caret.pos.height = ta->line_height; + msg.data.caret.pos.clip = NULL; ta->callback(ta->data, &msg); } @@ -1433,8 +1452,8 @@ bool textarea_set_caret(struct textarea *ta, int caret) } else if (!(ta->flags & TEXTAREA_INTERNAL_CARET)) { /* Caret hidden, and client is responsible: tell client */ msg.ta = ta; - msg.type = TEXTAREA_MSG_MOVED_CARET; - msg.data.caret.hidden = true; + msg.type = TEXTAREA_MSG_CARET_UPDATE; + msg.data.caret.type = TEXTAREA_CARET_HIDE; ta->callback(ta->data, &msg); } @@ -1778,8 +1797,7 @@ bool textarea_keypress(struct textarea *ta, uint32_t key) return false; caret = ta->sel_start + 1; - ta->sel_start = ta->sel_end = -1; - redraw = true; + textarea_clear_selection(ta); } else { if (!textarea_replace_text(ta, caret, caret, @@ -1792,11 +1810,8 @@ bool textarea_keypress(struct textarea *ta, uint32_t key) } else switch (key) { case KEY_SELECT_ALL: caret = ta->text.utf8_len; - - ta->sel_start = 0; - ta->sel_end = ta->text.utf8_len; - redraw = true; - break; + textarea_select(ta, 0, ta->text.utf8_len, true); + return true; case KEY_COPY_SELECTION: if (ta->sel_start != -1) { if (!textarea_replace_text(ta, @@ -1816,15 +1831,15 @@ bool textarea_keypress(struct textarea *ta, uint32_t key) return false; caret = ta->sel_start; - ta->sel_start = ta->sel_end = -1; + textarea_clear_selection(ta); } else if (caret > 0) { if (!textarea_replace_text(ta, caret - 1, caret, "", 0, false)) return false; caret--; + redraw = true; } - redraw = true; break; case KEY_CR: case KEY_NL: @@ -1838,15 +1853,15 @@ bool textarea_keypress(struct textarea *ta, uint32_t key) return false; caret = ta->sel_start + 1; - ta->sel_start = ta->sel_end = -1; + textarea_clear_selection(ta); } else { if (!textarea_replace_text(ta, caret, caret, "\n", 1, false)) return false; caret++; + redraw = true; } - redraw = true; break; case KEY_DELETE_LINE: if (readonly) @@ -1856,7 +1871,7 @@ bool textarea_keypress(struct textarea *ta, uint32_t key) ta->sel_start, ta->sel_end, "", 0, false)) return false; - ta->sel_start = ta->sel_end = -1; + textarea_clear_selection(ta); } else { if (ta->lines[line].b_length != 0) { /* Delete line */ @@ -1877,8 +1892,8 @@ bool textarea_keypress(struct textarea *ta, uint32_t key) false)) return false; } + redraw = true; } - redraw = true; break; case KEY_PASTE: { @@ -1903,7 +1918,7 @@ bool textarea_keypress(struct textarea *ta, uint32_t key) return false; caret = ta->sel_start + clipboard_chars; - ta->sel_start = ta->sel_end = -1; + textarea_clear_selection(ta); } else { if (!textarea_replace_text(ta, caret, caret, @@ -1911,8 +1926,8 @@ bool textarea_keypress(struct textarea *ta, uint32_t key) false)) return false; caret += clipboard_chars; + redraw = true; } - redraw = true; free(clipboard); } @@ -1927,8 +1942,7 @@ bool textarea_keypress(struct textarea *ta, uint32_t key) return false; caret = ta->sel_start; - ta->sel_start = ta->sel_end = -1; - redraw = true; + textarea_clear_selection(ta); } break; case KEY_ESCAPE: @@ -1941,8 +1955,7 @@ bool textarea_keypress(struct textarea *ta, uint32_t key) if (caret > 0) caret--; if (ta->sel_start != -1) { - ta->sel_start = ta->sel_end = -1; - redraw = true; + textarea_clear_selection(ta); } break; case KEY_RIGHT: @@ -1951,8 +1964,7 @@ bool textarea_keypress(struct textarea *ta, uint32_t key) if (caret < ta->text.utf8_len) caret++; if (ta->sel_start != -1) { - ta->sel_start = ta->sel_end = -1; - redraw = true; + textarea_clear_selection(ta); } break; case KEY_PAGE_UP: @@ -1970,8 +1982,7 @@ bool textarea_keypress(struct textarea *ta, uint32_t key) if (readonly) break; if (ta->sel_start != -1) { - ta->sel_start = ta->sel_end = -1; - redraw = true; + textarea_clear_selection(ta); } if (!(ta->flags & TEXTAREA_MULTILINE)) break; @@ -2012,16 +2023,14 @@ bool textarea_keypress(struct textarea *ta, uint32_t key) /* -1 because one line is added in KEY_DOWN */ line = ta->caret_pos.line + (ta->vis_height + ta->line_height - 1) / - ta->line_height - - 1; + ta->line_height - 1; } /* fall through */ case KEY_DOWN: if (readonly) break; if (ta->sel_start != -1) { - ta->sel_start = ta->sel_end = -1; - redraw = true; + textarea_clear_selection(ta); } if (!(ta->flags & TEXTAREA_MULTILINE)) break; @@ -2065,8 +2074,7 @@ bool textarea_keypress(struct textarea *ta, uint32_t key) return false; caret = ta->sel_start; - ta->sel_start = ta->sel_end = -1; - redraw = true; + textarea_clear_selection(ta); } else { if (caret < ta->text.utf8_len) { if (!textarea_replace_text(ta, @@ -2082,8 +2090,7 @@ bool textarea_keypress(struct textarea *ta, uint32_t key) break; caret -= ta->caret_pos.char_off; if (ta->sel_start != -1) { - ta->sel_start = ta->sel_end = -1; - redraw = true; + textarea_clear_selection(ta); } break; case KEY_LINE_END: @@ -2099,8 +2106,7 @@ bool textarea_keypress(struct textarea *ta, uint32_t key) - 1] == ' ') caret--; if (ta->sel_start != -1) { - ta->sel_start = ta->sel_end = -1; - redraw = true; + textarea_clear_selection(ta); } break; case KEY_TEXT_START: @@ -2108,8 +2114,7 @@ bool textarea_keypress(struct textarea *ta, uint32_t key) break; caret = 0; if (ta->sel_start != -1) { - ta->sel_start = ta->sel_end = -1; - redraw = true; + textarea_clear_selection(ta); } break; case KEY_TEXT_END: @@ -2117,8 +2122,7 @@ bool textarea_keypress(struct textarea *ta, uint32_t key) break; caret = ta->text.utf8_len; if (ta->sel_start != -1) { - ta->sel_start = ta->sel_end = -1; - redraw = true; + textarea_clear_selection(ta); } break; case KEY_WORD_LEFT: @@ -2132,7 +2136,7 @@ bool textarea_keypress(struct textarea *ta, uint32_t key) ta->sel_start, ta->sel_end, "", 0, false)) return false; - ta->sel_start = ta->sel_end = -1; + textarea_clear_selection(ta); } else { b_off = ta->lines[line].b_start; b_len = ta->lines[line].b_length; @@ -2142,8 +2146,8 @@ bool textarea_keypress(struct textarea *ta, uint32_t key) if (!textarea_replace_text(ta, caret, caret + l_len, "", 0, false)) return false; + redraw = true; } - redraw = true; break; case KEY_DELETE_LINE_START: if (readonly) @@ -2153,15 +2157,15 @@ bool textarea_keypress(struct textarea *ta, uint32_t key) ta->sel_start, ta->sel_end, "", 0, false)) return false; - ta->sel_start = ta->sel_end = -1; + textarea_clear_selection(ta); } else { if (!textarea_replace_text(ta, caret - ta->caret_pos.char_off, caret, "", 0, false)) return false; caret -= ta->caret_pos.char_off; + redraw = true; } - redraw = true; break; default: return false; @@ -2346,6 +2350,7 @@ bool textarea_clear_selection(struct textarea *ta) /* No selection to clear */ return false; + /* Clear selection and redraw */ ta->sel_start = ta->sel_end = -1; msg.ta = ta; @@ -2357,11 +2362,63 @@ bool textarea_clear_selection(struct textarea *ta) ta->callback(ta->data, &msg); + /* No more selection */ + msg.type = TEXTAREA_MSG_SELECTION_REPORT; + + msg.data.selection.have_selection = false; + msg.data.selection.read_only = (ta->flags & TEXTAREA_READONLY); + + ta->callback(ta->data, &msg); + + if (!(ta->flags & TEXTAREA_INTERNAL_CARET)) { + /* Tell client where caret should be placed */ + msg.ta = ta; + msg.type = TEXTAREA_MSG_CARET_UPDATE; + msg.data.caret.type = TEXTAREA_CARET_SET_POS; + msg.data.caret.pos.x = ta->caret_x - ta->scroll_x; + msg.data.caret.pos.y = ta->caret_y - ta->scroll_y; + msg.data.caret.pos.height = ta->line_height; + msg.data.caret.pos.clip = NULL; + + ta->callback(ta->data, &msg); + } + return true; } /* exported interface, documented in textarea.h */ +char *textarea_get_selection(struct textarea *ta) +{ + char *ret; + size_t b_start, b_end, b_len; + + if (ta->sel_start == -1) + /* No selection get */ + return NULL; + + textarea_char_to_byte_offset(ta->show, ta->sel_start, ta->sel_end, + &b_start, &b_end); + + b_len = b_end - b_start; + + if (b_len == 0) + /* No selection get */ + return NULL; + + ret = malloc(b_len + 1); /* Add space for '\0' */ + if (ret == NULL) + /* Can't get selection; no memory */ + return NULL; + + memcpy(ret, ta->show->data + b_start, b_len); + ret[b_len] = '\0'; + + return ret; +} + + +/* exported interface, documented in textarea.h */ void textarea_get_dimensions(struct textarea *ta, int *width, int *height) { if (width != NULL) diff --git a/desktop/textarea.h b/desktop/textarea.h index 3fedeee35..befd6aa99 100644 --- a/desktop/textarea.h +++ b/desktop/textarea.h @@ -45,28 +45,39 @@ typedef enum { TEXTAREA_DRAG_NONE, TEXTAREA_DRAG_SCROLLBAR, TEXTAREA_DRAG_SELECTION -} textarea_drag_type; +} textarea_drag_type; /**< Textarea drag status */ typedef enum { TEXTAREA_MSG_DRAG_REPORT, /**< Textarea drag start/end report */ + TEXTAREA_MSG_SELECTION_REPORT, /**< Textarea text selection presence */ TEXTAREA_MSG_REDRAW_REQUEST, /**< Textarea redraw request */ - TEXTAREA_MSG_MOVED_CARET /**< Textarea caret moved */ + TEXTAREA_MSG_CARET_UPDATE /**< Textarea caret */ } textarea_msg_type; struct textarea_msg { - struct textarea *ta; + struct textarea *ta; /**< The textarea widget */ - textarea_msg_type type; + textarea_msg_type type; /**< Indicates message data type */ union { - textarea_drag_type drag; - struct rect redraw; + textarea_drag_type drag; /**< With _DRAG_REPORT */ struct { - bool hidden; - int x; - int y; - int height; - } caret; - } data; + bool have_selection; /**< Selection exists */ + bool read_only; /**< Selection can't be cut */ + } selection; /**< With _SELECTION_REPORT */ + struct rect redraw; /**< With _REDRAW_REQUEST */ + struct { + enum { + TEXTAREA_CARET_SET_POS, /**< Set coord/height */ + TEXTAREA_CARET_HIDE /**< Hide */ + } type; + struct { + int x; /**< Carret x-coord */ + int y; /**< Carret y-coord */ + int height; /**< Carret height */ + struct rect *clip; /**< Carret clip rect */ + } pos; /**< With _CARET_SET_POS */ + } caret; /**< With _CARET_UPDATE */ + } data; /**< Depends on msg type */ }; typedef struct textarea_setup { @@ -206,6 +217,14 @@ bool textarea_mouse_action(struct textarea *ta, browser_mouse_state mouse, bool textarea_clear_selection(struct textarea *ta); /** + * Get selected text, ownership passed to caller, which needs to free() it. + * + * \param ta Textarea widget + * \return Selected text, or NULL if none. + */ +char *textarea_get_selection(struct textarea *ta); + +/** * Gets the dimensions of a textarea * * \param ta textarea widget diff --git a/desktop/textinput.c b/desktop/textinput.c index 660708932..6ffa02c86 100644 --- a/desktop/textinput.c +++ b/desktop/textinput.c @@ -63,11 +63,7 @@ * \param p2 Callback private data pointer, passed to callback function */ void browser_window_place_caret(struct browser_window *bw, - int x, int y, int height, - browser_caret_callback caret_cb, - browser_paste_callback paste_cb, - browser_move_callback move_cb, - void *p1, void *p2) + int x, int y, int height) { struct browser_window *root_bw; int pos_x = 0; @@ -81,14 +77,10 @@ void browser_window_place_caret(struct browser_window *bw, y = y * bw->scale + pos_y; gui_window_place_caret(root_bw->window, x, y, height * bw->scale); - bw->caret_callback = caret_cb; - bw->paste_callback = paste_cb; - bw->move_callback = move_cb; - bw->caret_p1 = p1; - bw->caret_p2 = p2; /* Set focus browser window */ root_bw->focus = bw; + root_bw->can_edit = true; } @@ -97,20 +89,19 @@ void browser_window_place_caret(struct browser_window *bw, * * \param bw The browser window from which to remove caret */ -void browser_window_remove_caret(struct browser_window *bw) +void browser_window_remove_caret(struct browser_window *bw, bool only_hide) { struct browser_window *root_bw; root_bw = browser_window_get_root(bw); + if (only_hide) + root_bw->can_edit = true; + else + root_bw->can_edit = false; + if (root_bw && root_bw->window) gui_window_remove_caret(root_bw->window); - - bw->caret_callback = NULL; - bw->paste_callback = NULL; - bw->move_callback = NULL; - bw->caret_p1 = NULL; - bw->caret_p2 = NULL; } @@ -127,59 +118,12 @@ bool browser_window_key_press(struct browser_window *bw, uint32_t key) assert(bw->window != NULL); - if (focus->caret_callback) { - /* Pass keypress onto anything that has claimed input focus */ - return focus->caret_callback(focus, key, - focus->caret_p1, focus->caret_p2); - } - - /* TODO: pass these to content to deal with */ - switch (key) { - case KEY_COPY_SELECTION: - selection_copy_to_clipboard(bw->cur_sel); - return true; - - case KEY_CLEAR_SELECTION: - selection_clear(bw->cur_sel, true); - return true; - - case KEY_SELECT_ALL: - selection_select_all(bw->cur_sel); - return true; - - case KEY_ESCAPE: - if (bw->cur_sel && selection_defined(bw->cur_sel)) { - selection_clear(bw->cur_sel, true); - return true; - } - /* if there's no selection, - * leave Escape for the caller */ - return false; - } - - return false; -} - - -/** - * Paste a block of text into a browser window at the caret position. - * - * \param bw browser window - * \param utf8 pointer to block of text - * \param utf8_len length (bytes) of text block - * \param last true iff this is the last chunk (update screen too) - * \return true iff successful - * - * TODO: Remove this function. - */ + if (focus == NULL) + focus = bw; -bool browser_window_paste_text(struct browser_window *bw, const char *utf8, - unsigned utf8_len, bool last) -{ - if (!bw->focus || !bw->focus->paste_callback) + if (focus->current_content == NULL) return false; - return bw->focus->paste_callback(bw->focus, utf8, utf8_len, last, - bw->focus->caret_p1, bw->focus->caret_p2); + return content_keypress(focus->current_content, key); } diff --git a/gtk/scaffolding.c b/gtk/scaffolding.c index 6ab058269..4097837a9 100644 --- a/gtk/scaffolding.c +++ b/gtk/scaffolding.c @@ -374,13 +374,15 @@ static guint nsgtk_scaffolding_update_edit_actions_sensitivity( } else { struct browser_window *bw = nsgtk_get_browser_window(g->top_level); - has_selection = browser_window_has_selection(bw); + browser_editor_flags edit_f = + browser_window_get_editor_flags(bw); - g->buttons[COPY_BUTTON]->sensitivity = has_selection; - g->buttons[CUT_BUTTON]->sensitivity = (has_selection && - bw->caret_callback != 0); + g->buttons[COPY_BUTTON]->sensitivity = + edit_f & BW_EDITOR_CAN_COPY; + g->buttons[CUT_BUTTON]->sensitivity = + edit_f & BW_EDITOR_CAN_CUT; g->buttons[PASTE_BUTTON]->sensitivity = - (bw->paste_callback != 0); + edit_f & BW_EDITOR_CAN_PASTE; } nsgtk_scaffolding_set_sensitivity(g); @@ -1120,7 +1122,7 @@ MULTIHANDLER(selectall) gtk_editable_select_region(GTK_EDITABLE(g->url_bar), 0, -1); } else { LOG(("Selecting all document text")); - selection_select_all(browser_window_get_selection(bw)); + browser_window_key_press(bw, KEY_SELECT_ALL); } return TRUE; diff --git a/gtk/window.c b/gtk/window.c index d8a9dd43d..7a66c5111 100644 --- a/gtk/window.c +++ b/gtk/window.c @@ -312,7 +312,7 @@ static gboolean nsgtk_window_button_press_event(GtkWidget *widget, break; case 3: /* Right button, usually. Action button, context menu. */ - browser_window_remove_caret(g->bw); + browser_window_remove_caret(g->bw, true); nsgtk_scaffolding_popup_menu(g->scaffold, g->mouse.pressed_x, g->mouse.pressed_y); return TRUE; diff --git a/render/box_textarea.c b/render/box_textarea.c index 3109904d0..3d9838fa1 100644 --- a/render/box_textarea.c +++ b/render/box_textarea.c @@ -31,14 +31,11 @@ #include "utils/log.h" -static bool box_textarea_browser_caret_callback(struct browser_window *bw, - uint32_t key, void *p1, void *p2) +bool box_textarea_keypress(html_content *html, struct box *box, uint32_t key) { - struct box *box = p1; struct form_control *gadget = box->gadget; struct textarea *ta = gadget->data.text.ta; struct form* form = box->gadget->form; - html_content *html = p2; struct content *c = (struct content *) html; assert(ta != NULL); @@ -104,21 +101,6 @@ static bool box_textarea_browser_caret_callback(struct browser_window *bw, } -static void box_textarea_browser_move_callback(struct browser_window *bw, - void *p1, void *p2) -{ -} - - -static bool box_textarea_browser_paste_callback(struct browser_window *bw, - const char *utf8, unsigned utf8_len, bool last, - void *p1, void *p2) -{ - printf("AWWOOOOOGA!\n"); - return true; -} - - /** * Callback for html form textareas. */ @@ -175,23 +157,43 @@ static void box_textarea_callback(void *data, struct textarea_msg *msg) html__redraw_a_box(html, box); break; - case TEXTAREA_MSG_MOVED_CARET: + case TEXTAREA_MSG_SELECTION_REPORT: + if (msg->data.selection.have_selection) { + /* Textarea now has a selection */ + union html_selection_owner sel_owner; + sel_owner.textarea = box; + + html_set_selection(d->html, HTML_SELECTION_TEXTAREA, + sel_owner, + msg->data.selection.read_only); + } else { + /* The textarea now has no selection */ + union html_selection_owner sel_owner; + sel_owner.none = true; + + html_set_selection(d->html, HTML_SELECTION_NONE, + sel_owner, true); + } + break; + + case TEXTAREA_MSG_CARET_UPDATE: if (html->bw == NULL) break; - if (msg->data.caret.hidden) { - browser_window_remove_caret(html->bw); + if (msg->data.caret.type == TEXTAREA_CARET_HIDE) { + union html_focus_owner focus_owner; + focus_owner.textarea = box; + html_set_focus(d->html, HTML_FOCUS_TEXTAREA, + focus_owner, true, 0, 0, 0, NULL); } else { - int x, y; - box_coords(box, &x, &y); - browser_window_place_caret(html->bw, - x + msg->data.caret.x, - y + msg->data.caret.y, - msg->data.caret.height, - box_textarea_browser_caret_callback, - box_textarea_browser_paste_callback, - box_textarea_browser_move_callback, - box, html); + union html_focus_owner focus_owner; + focus_owner.textarea = box; + html_set_focus(d->html, HTML_FOCUS_TEXTAREA, + focus_owner, false, + msg->data.caret.pos.x, + msg->data.caret.pos.y, + msg->data.caret.pos.height, + msg->data.caret.pos.clip); } break; } diff --git a/render/box_textarea.h b/render/box_textarea.h index 30414e816..a7a377076 100644 --- a/render/box_textarea.h +++ b/render/box_textarea.h @@ -41,4 +41,15 @@ struct dom_node; bool box_textarea_create_textarea(html_content *html, struct box *box, struct dom_node *node); + +/** + * Handle form textarea keypress input + * + * \param html html content object + * \param box box with textarea widget + * \param key keypress + * \return true iff keypress handled + */ +bool box_textarea_keypress(html_content *html, struct box *box, uint32_t key); + #endif diff --git a/render/html.c b/render/html.c index 4fc152a84..e697c9f12 100644 --- a/render/html.c +++ b/render/html.c @@ -346,6 +346,10 @@ html_create_html_data(html_content *c, const http_parameter *params) c->font_func = &nsfont; c->drag_type = HTML_DRAG_NONE; c->drag_owner.no_owner = true; + c->selection_type = HTML_SELECTION_NONE; + c->selection_owner.none = true; + c->focus_type = HTML_FOCUS_SELF; + c->focus_owner.self = true; c->scripts_count = 0; c->scripts = NULL; c->jscontext = NULL; @@ -1309,6 +1313,28 @@ html_object_callback(hlcache_handle *object, content_broadcast(&c->base, event->type, event->data); break; + case CONTENT_MSG_CARET: + { + union html_focus_owner focus_owner; + focus_owner.content = box; + + switch (event->data.caret.type) { + case CONTENT_CARET_REMOVE: + case CONTENT_CARET_HIDE: + html_set_focus(c, HTML_FOCUS_CONTENT, focus_owner, + true, 0, 0, 0, NULL); + break; + case CONTENT_CARET_SET_POS: + html_set_focus(c, HTML_FOCUS_CONTENT, focus_owner, + false, event->data.caret.pos.x, + event->data.caret.pos.y, + event->data.caret.pos.height, + event->data.caret.pos.clip); + break; + } + } + break; + case CONTENT_MSG_DRAG: { html_drag_type drag_type = HTML_DRAG_NONE; @@ -1332,6 +1358,23 @@ html_object_callback(hlcache_handle *object, } break; + case CONTENT_MSG_SELECTION: + { + html_selection_type sel_type; + union html_selection_owner sel_owner; + + if (event->data.selection.selection) { + sel_type = HTML_SELECTION_CONTENT; + sel_owner.content = box; + } else { + sel_type = HTML_SELECTION_NONE; + sel_owner.none = true; + } + html_set_selection(c, sel_type, sel_owner, + event->data.selection.read_only); + } + break; + default: assert(0); } @@ -1514,6 +1557,10 @@ html_convert_css_callback(hlcache_handle *css, } break; + case CONTENT_MSG_POINTER: + /* Really don't want this to continue after the switch */ + return NSERROR_OK; + default: assert(0); } @@ -2513,8 +2560,13 @@ html_open(struct content *c, html->bw = bw; html->page = (html_content *) page; + html->drag_type = HTML_DRAG_NONE; + html->drag_owner.no_owner = true; + /* text selection */ selection_init(&html->sel, html->layout); + html->selection_type = HTML_SELECTION_NONE; + html->selection_owner.none = true; for (object = html->object_list; object != NULL; object = next) { next = object->next; @@ -2541,6 +2593,8 @@ static void html_close(struct content *c) html_content *html = (html_content *) c; struct content_html_object *object, *next; + selection_clear(&html->sel, false); + if (html->search != NULL) search_destroy_context(html->search); @@ -2567,11 +2621,63 @@ static void html_close(struct content *c) * Return an HTML content's selection context */ -static struct selection *html_get_selection(struct content *c) +static void html_clear_selection(struct content *c) +{ + html_content *html = (html_content *) c; + + switch (html->selection_type) { + case HTML_SELECTION_NONE: + /* Nothing to do */ + assert(html->selection_owner.none == true); + break; + case HTML_SELECTION_TEXTAREA: + textarea_clear_selection(html->selection_owner.textarea-> + gadget->data.text.ta); + break; + case HTML_SELECTION_SELF: + assert(html->selection_owner.none == false); + selection_clear(&html->sel, true); + break; + case HTML_SELECTION_CONTENT: + content_clear_selection(html->selection_owner.content->object); + break; + default: + break; + } + + /* There is no selection now. */ + html->selection_type = HTML_SELECTION_NONE; + html->selection_owner.none = true; +} + + +/** + * Return an HTML content's selection context + */ + +static char *html_get_selection(struct content *c) { html_content *html = (html_content *) c; - return &html->sel; + switch (html->selection_type) { + case HTML_SELECTION_TEXTAREA: + return textarea_get_selection(html->selection_owner.textarea-> + gadget->data.text.ta); + case HTML_SELECTION_SELF: + assert(html->selection_owner.none == false); + return selection_get_copy(&html->sel); + case HTML_SELECTION_CONTENT: + return content_get_selection( + html->selection_owner.content->object); + case HTML_SELECTION_NONE: + /* Nothing to do */ + assert(html->selection_owner.none == true); + break; + default: + break; + } + + return NULL; } @@ -3214,10 +3320,12 @@ static const content_handler html_content_handler = { .stop = html_stop, .mouse_track = html_mouse_track, .mouse_action = html_mouse_action, + .keypress = html_keypress, .redraw = html_redraw, .open = html_open, .close = html_close, .get_selection = html_get_selection, + .clear_selection = html_clear_selection, .get_contextual_content = html_get_contextual_content, .scroll_at_point = html_scroll_at_point, .drop_file_at_point = html_drop_file_at_point, diff --git a/render/html.h b/render/html.h index a9f7967f6..c208f47d6 100644 --- a/render/html.h +++ b/render/html.h @@ -50,6 +50,7 @@ struct textarea; struct scrollbar; struct scrollbar_msg_data; struct search_context; +struct selection; /** * Container for stylesheets used by an HTML document diff --git a/render/html_interaction.c b/render/html_interaction.c index 3f1bee475..d734c6b9f 100644 --- a/render/html_interaction.c +++ b/render/html_interaction.c @@ -223,54 +223,7 @@ static size_t html_selection_drag_end(struct html_content *html, void html_mouse_track(struct content *c, struct browser_window *bw, browser_mouse_state mouse, int x, int y) { - html_content *html = (html_content*) c; - union html_drag_owner drag_owner; - - if (html->drag_type == HTML_DRAG_SELECTION && !mouse) { - /* End of selection drag */ - int dir = -1; - size_t idx; - - if (selection_dragging_start(&html->sel)) - dir = 1; - - idx = html_selection_drag_end(html, mouse, x, y, dir); - - if (idx != 0) - selection_track(&html->sel, mouse, idx); - - drag_owner.no_owner = true; - html_set_drag_type(html, HTML_DRAG_NONE, drag_owner, NULL); - } - - if (html->drag_type == HTML_DRAG_SELECTION) { - /* Selection drag */ - struct box *box; - int dir = -1; - int dx, dy; - - if (selection_dragging_start(&html->sel)) - dir = 1; - - box = box_pick_text_box(html, x, y, dir, &dx, &dy); - - if (box != NULL) { - int pixel_offset; - size_t idx; - plot_font_style_t fstyle; - - font_plot_style_from_css(box->style, &fstyle); - - nsfont.font_position_in_string(&fstyle, - box->text, box->length, - dx, &idx, &pixel_offset); - - selection_track(&html->sel, mouse, - box->byte_offset + idx); - } - } else { - html_mouse_action(c, bw, mouse, x, y); - } + html_mouse_action(c, bw, mouse, x, y); } @@ -323,6 +276,11 @@ void html_mouse_action(struct content *c, struct browser_window *bw, browser_drag_type drag_type = browser_window_get_drag_type(bw); union content_msg_data msg_data; struct dom_node *node = NULL; + union html_drag_owner drag_owner; + union html_selection_owner sel_owner; + bool click = mouse & (BROWSER_MOUSE_PRESS_1 | BROWSER_MOUSE_PRESS_2 | + BROWSER_MOUSE_CLICK_1 | BROWSER_MOUSE_CLICK_2 | + BROWSER_MOUSE_DRAG_1 | BROWSER_MOUSE_DRAG_2); if (drag_type != DRAGGING_NONE && !mouse && html->visible_select_menu != NULL) { @@ -349,7 +307,55 @@ void html_mouse_action(struct content *c, struct browser_window *bw, &width, &height); html->visible_select_menu = NULL; browser_window_redraw_rect(bw, box_x, box_y, - width, height); + width, height); + } + return; + } + + if (html->drag_type == HTML_DRAG_SELECTION) { + /* Selection drag */ + + if (!mouse) { + /* End of selection drag */ + int dir = -1; + size_t idx; + + if (selection_dragging_start(&html->sel)) + dir = 1; + + idx = html_selection_drag_end(html, mouse, x, y, dir); + + if (idx != 0) + selection_track(&html->sel, mouse, idx); + + drag_owner.no_owner = true; + html_set_drag_type(html, HTML_DRAG_NONE, + drag_owner, NULL); + return; + } + + struct box *box; + int dir = -1; + int dx, dy; + + if (selection_dragging_start(&html->sel)) + dir = 1; + + box = box_pick_text_box(html, x, y, dir, &dx, &dy); + + if (box != NULL) { + int pixel_offset; + size_t idx; + plot_font_style_t fstyle; + + font_plot_style_from_css(box->style, &fstyle); + + nsfont.font_position_in_string(&fstyle, + box->text, box->length, + dx, &idx, &pixel_offset); + + selection_track(&html->sel, mouse, + box->byte_offset + idx); } return; } @@ -413,6 +419,16 @@ void html_mouse_action(struct content *c, struct browser_window *bw, return; } + if (html->drag_type == HTML_DRAG_CONTENT_SELECTION) { + box = html->drag_owner.content; + assert(box->object != NULL); + + box_coords(box, &box_x, &box_y); + content_mouse_track(box->object, bw, mouse, + x - box_x, y - box_y); + return; + } + /* Content related drags handled by now */ assert(html->drag_type == HTML_DRAG_NONE); @@ -536,7 +552,8 @@ void html_mouse_action(struct content *c, struct browser_window *bw, /* mouse inside padding box */ if ((box->scroll_y != NULL) && - (x > (padding_right - SCROLLBAR_WIDTH))) { + (x > (padding_right - + SCROLLBAR_WIDTH))) { /* mouse above vertical box scroll */ scrollbar = box->scroll_y; @@ -546,7 +563,8 @@ void html_mouse_action(struct content *c, struct browser_window *bw, break; } else if ((box->scroll_x != NULL) && - (y > (padding_bottom - SCROLLBAR_WIDTH))) { + (y > (padding_bottom - + SCROLLBAR_WIDTH))) { /* mouse above horizontal box scroll */ scrollbar = box->scroll_x; @@ -628,6 +646,15 @@ void html_mouse_action(struct content *c, struct browser_window *bw, pointer = get_pointer_shape(gadget_box, false); + if (click && (html->selection_type != + HTML_SELECTION_TEXTAREA || + html->selection_owner.textarea != + gadget_box)) { + sel_owner.none = true; + html_set_selection(html, HTML_SELECTION_NONE, + sel_owner, true); + } + textarea_mouse_action(gadget->data.text.ta, mouse, x - gadget_box_x, y - gadget_box_y); break; @@ -679,6 +706,14 @@ void html_mouse_action(struct content *c, struct browser_window *bw, x - pos_x, y - pos_y); } } else if (html_object_box) { + + if (click && (html->selection_type != HTML_SELECTION_CONTENT || + html->selection_owner.content != + html_object_box)) { + sel_owner.none = true; + html_set_selection(html, HTML_SELECTION_NONE, + sel_owner, true); + } if (mouse & BROWSER_MOUSE_CLICK_1 || mouse & BROWSER_MOUSE_CLICK_2) { content_mouse_action(html_object_box->object, @@ -738,11 +773,18 @@ void html_mouse_action(struct content *c, struct browser_window *bw, /* if clicking in the main page, remove the selection from any * text areas */ if (!done) { - struct box *layout = html->layout; - - if (mouse && (mouse < BROWSER_MOUSE_MOD_1) && - selection_root(&html->sel) != layout) { - selection_init(&html->sel, layout); + + if (click && html->focus_type != HTML_FOCUS_SELF) { + union html_focus_owner fo; + fo.self = true; + html_set_focus(html, HTML_FOCUS_SELF, fo, + true, 0, 0, 0, NULL); + } + if (click && html->selection_type != + HTML_SELECTION_SELF) { + sel_owner.none = true; + html_set_selection(html, HTML_SELECTION_NONE, + sel_owner, true); } if (text_box) { @@ -781,8 +823,24 @@ void html_mouse_action(struct content *c, struct browser_window *bw, done = true; } - } else if (mouse & BROWSER_MOUSE_PRESS_1) + } else if (mouse & BROWSER_MOUSE_PRESS_1) { + union html_selection_owner sel_owner; + sel_owner.none = true; selection_clear(&html->sel, true); + } + + if (selection_defined(&html->sel)) { + sel_owner.none = false; + html_set_selection(html, HTML_SELECTION_SELF, + sel_owner, + selection_read_only( + &html->sel)); + } else if (click && html->selection_type != + HTML_SELECTION_NONE) { + sel_owner.none = true; + html_set_selection(html, HTML_SELECTION_NONE, + sel_owner, true); + } } if (!done) { @@ -832,7 +890,10 @@ void html_mouse_action(struct content *c, struct browser_window *bw, } if (mouse && mouse < BROWSER_MOUSE_MOD_1) { /* ensure key presses still act on the browser window */ - browser_window_remove_caret(bw); + union html_focus_owner fo; + fo.self = true; + html_set_focus(html, HTML_FOCUS_SELF, fo, + true, 0, 0, 0, NULL); } } @@ -876,6 +937,61 @@ void html_mouse_action(struct content *c, struct browser_window *bw, /** + * Handle keypresses. + * + * \param c content of type CONTENT_TEXTPLAIN + * \param key The UCS4 character codepoint + * \return true if key handled, false otherwise + */ + +bool html_keypress(struct content *c, uint32_t key) +{ + html_content *html = (html_content *) c; + struct selection *sel = &html->sel; + struct box *box; + + switch (html->focus_type) { + case HTML_FOCUS_CONTENT: + box = html->focus_owner.content; + return content_keypress(box->object, key); + + case HTML_FOCUS_TEXTAREA: + box = html->focus_owner.textarea; + return textarea_keypress(box->gadget->data.text.ta, key); + + default: + /* Deal with it below */ + break; + } + + switch (key) { + case KEY_COPY_SELECTION: + selection_copy_to_clipboard(sel); + return true; + + case KEY_CLEAR_SELECTION: + selection_clear(sel, true); + return true; + + case KEY_SELECT_ALL: + selection_select_all(sel); + return true; + + case KEY_ESCAPE: + if (selection_defined(sel)) { + selection_clear(sel, true); + return true; + } + + /* if there's no selection, leave Escape for the caller */ + return false; + } + + return false; +} + + +/** * Callback for in-page scrollbars. */ void html_overflow_scroll_callback(void *client_data, @@ -985,6 +1101,131 @@ void html_set_drag_type(html_content *html, html_drag_type drag_type, } msg_data.drag.rect = rect; - /* Inform the content's drag status change */ + /* Inform of the content's drag status change */ content_broadcast((struct content *)html, CONTENT_MSG_DRAG, msg_data); } + +/* Documented in html_internal.h */ +void html_set_focus(html_content *html, html_focus_type focus_type, + union html_focus_owner focus_owner, bool hide_caret, + int x, int y, int height, const struct rect *clip) +{ + union content_msg_data msg_data; + int x_off = 0; + int y_off = 0; + bool textarea_lost_focus = html->focus_type == HTML_FOCUS_TEXTAREA && + focus_type != HTML_FOCUS_TEXTAREA; + + assert(html != NULL); + + switch (focus_type) { + case HTML_FOCUS_SELF: + assert(focus_owner.self == true); + if (html->focus_type == HTML_FOCUS_SELF) + /* Don't need to tell anyone anything */ + return; + break; + + case HTML_FOCUS_CONTENT: + box_coords(focus_owner.content, &x_off, &y_off); + break; + + case HTML_FOCUS_TEXTAREA: + box_coords(focus_owner.textarea, &x_off, &y_off); + break; + } + + html->focus_type = focus_type; + html->focus_owner = focus_owner; + + if (textarea_lost_focus) { + msg_data.caret.type = CONTENT_CARET_REMOVE; + } else if (focus_type != HTML_FOCUS_SELF && hide_caret) { + msg_data.caret.type = CONTENT_CARET_HIDE; + } else { + msg_data.caret.type = CONTENT_CARET_SET_POS; + msg_data.caret.pos.x = x + x_off; + msg_data.caret.pos.y = y + y_off; + msg_data.caret.pos.height = height; + msg_data.caret.pos.clip = clip; + } + + /* Inform of the content's drag status change */ + content_broadcast((struct content *)html, CONTENT_MSG_CARET, msg_data); +} + +/* Documented in html_internal.h */ +void html_set_selection(html_content *html, html_selection_type selection_type, + union html_selection_owner selection_owner, bool read_only) +{ + union content_msg_data msg_data; + struct box *box; + bool changed = false; + bool same_type = html->selection_type == selection_type; + + assert(html != NULL); + + if ((selection_type == HTML_SELECTION_NONE && + html->selection_type != HTML_SELECTION_NONE) || + (selection_type != HTML_SELECTION_NONE && + html->selection_type == HTML_SELECTION_NONE)) + /* Existance of selection has changed, and we'll need to + * inform our owner */ + changed = true; + + /* Clear any existing selection */ + if (html->selection_type != HTML_SELECTION_NONE) { + switch (html->selection_type) { + case HTML_SELECTION_SELF: + if (same_type) + break; + selection_clear(&html->sel, true); + break; + case HTML_SELECTION_TEXTAREA: + if (same_type && html->selection_owner.textarea == + selection_owner.textarea) + break; + box = html->selection_owner.textarea; + textarea_clear_selection(box->gadget->data.text.ta); + break; + case HTML_SELECTION_CONTENT: + if (same_type && html->selection_owner.content == + selection_owner.content) + break; + box = html->selection_owner.content; + content_clear_selection(box->object); + break; + default: + break; + } + } + + html->selection_type = selection_type; + html->selection_owner = selection_owner; + + if (!changed) + /* Don't need to report lack of change to owner */ + return; + + /* Prepare msg */ + switch (selection_type) { + case HTML_SELECTION_NONE: + assert(selection_owner.none == true); + msg_data.selection.selection = false; + break; + case HTML_SELECTION_SELF: + assert(selection_owner.none == false); + /* fall through */ + case HTML_SELECTION_TEXTAREA: + case HTML_SELECTION_CONTENT: + msg_data.selection.selection = true; + break; + default: + break; + } + msg_data.selection.read_only = read_only; + + /* Inform of the content's selection status change */ + content_broadcast((struct content *)html, CONTENT_MSG_SELECTION, + msg_data); +} diff --git a/render/html_internal.h b/render/html_internal.h index 3e562bc39..80b126b25 100644 --- a/render/html_internal.h +++ b/render/html_internal.h @@ -43,6 +43,29 @@ union html_drag_owner { struct box *textarea; }; /**< For drags we don't own */ +typedef enum { + HTML_SELECTION_NONE, /** No selection */ + HTML_SELECTION_TEXTAREA, /** Selection in one of our textareas */ + HTML_SELECTION_SELF, /** Selection in this html content */ + HTML_SELECTION_CONTENT /** Selection in child content */ +} html_selection_type; +union html_selection_owner { + bool none; + struct box *textarea; + struct box *content; +}; /**< For getting at selections in this content or things in this content */ + +typedef enum { + HTML_FOCUS_SELF, /** Focus is our own */ + HTML_FOCUS_CONTENT, /** Focus belongs to child content */ + HTML_FOCUS_TEXTAREA /** Focus belongs to textarea */ +} html_focus_type; +union html_focus_owner { + bool self; + struct box *textarea; + struct box *content; +}; /**< For directing input */ + /** Data specific to CONTENT_HTML. */ typedef struct html_content { struct content base; @@ -114,18 +137,28 @@ typedef struct html_content { * object within a page. */ struct html_content *page; - /* Current drag type */ + /** Current drag type */ html_drag_type drag_type; /** Widget capturing all mouse events */ union html_drag_owner drag_owner; + /** Current selection state */ + html_selection_type selection_type; + /** Current selection owner */ + union html_selection_owner selection_owner; + + /** Current input focus target type */ + html_focus_type focus_type; + /** Current input focus target */ + union html_focus_owner focus_owner; + + /** HTML content's own text selection object */ + struct selection sel; + /** Open core-handled form SELECT menu, * or NULL if none currently open. */ struct form_control *visible_select_menu; - /** Selection state */ - struct selection sel; - /** Context for free text search, or NULL if none */ struct search_context *search; @@ -152,6 +185,34 @@ void html__redraw_a_box(html_content *html, struct box *box); void html_set_drag_type(html_content *html, html_drag_type drag_type, union html_drag_owner drag_owner, const struct rect *rect); +/** + * Set our selection status, and inform whatever owns the content + * + * \param html HTML content + * \param selection_type Type of selection + * \param selection_owner What owns the selection + * \param read_only True iff selection is read only + */ +void html_set_selection(html_content *html, html_selection_type selection_type, + union html_selection_owner selection_owner, bool read_only); + +/** + * Set our input focus, and inform whatever owns the content + * + * \param html HTML content + * \param focus_type Type of input focus + * \param focus_owner What owns the focus + * \param hide_caret True iff caret to be hidden + * \param x Carret x-coord rel to owner + * \param y Carret y-coord rel to owner + * \param height Carret height + * \param clip Carret clip rect + */ +void html_set_focus(html_content *html, html_focus_type focus_type, + union html_focus_owner focus_owner, bool hide_caret, + int x, int y, int height, const struct rect *clip); + + struct browser_window *html_get_browser_window(struct content *c); struct search_context *html_get_search(struct content *c); void html_set_search(struct content *c, struct search_context *s); @@ -179,6 +240,7 @@ void html_mouse_track(struct content *c, struct browser_window *bw, browser_mouse_state mouse, int x, int y); void html_mouse_action(struct content *c, struct browser_window *bw, browser_mouse_state mouse, int x, int y); +bool html_keypress(struct content *c, uint32_t key); void html_overflow_scroll_callback(void *client_data, struct scrollbar_msg_data *scrollbar_data); diff --git a/render/textplain.c b/render/textplain.c index 6cf334fe2..b9d0b81e5 100644 --- a/render/textplain.c +++ b/render/textplain.c @@ -40,6 +40,7 @@ #include "desktop/plotters.h" #include "desktop/search.h" #include "desktop/selection.h" +#include "desktop/textinput.h" #include "render/font.h" #include "render/search.h" #include "render/textplain.h" @@ -107,6 +108,7 @@ static void textplain_mouse_track(struct content *c, struct browser_window *bw, browser_mouse_state mouse, int x, int y); static void textplain_mouse_action(struct content *c, struct browser_window *bw, browser_mouse_state mouse, int x, int y); +static bool textplain_keypress(struct content *c, uint32_t key); static void textplain_reformat(struct content *c, int width, int height); static void textplain_destroy(struct content *c); static bool textplain_redraw(struct content *c, struct content_redraw_data *data, @@ -114,7 +116,7 @@ static bool textplain_redraw(struct content *c, struct content_redraw_data *data static void textplain_open(struct content *c, struct browser_window *bw, struct content *page, struct object_params *params); void textplain_close(struct content *c); -struct selection *textplain_get_selection(struct content *c); +char *textplain_get_selection(struct content *c); struct search_context *textplain_get_search(struct content *c); static nserror textplain_clone(const struct content *old, struct content **newc); @@ -139,6 +141,7 @@ static const content_handler textplain_content_handler = { .destroy = textplain_destroy, .mouse_track = textplain_mouse_track, .mouse_action = textplain_mouse_action, + .keypress = textplain_keypress, .redraw = textplain_redraw, .open = textplain_open, .close = textplain_close, @@ -716,6 +719,46 @@ void textplain_mouse_action(struct content *c, struct browser_window *bw, /** + * Handle keypresses. + * + * \param c content of type CONTENT_TEXTPLAIN + * \param key The UCS4 character codepoint + * \return true if key handled, false otherwise + */ + +bool textplain_keypress(struct content *c, uint32_t key) +{ + textplain_content *text = (textplain_content *) c; + struct selection *sel = &text->sel; + + switch (key) { + case KEY_COPY_SELECTION: + selection_copy_to_clipboard(sel); + return true; + + case KEY_CLEAR_SELECTION: + selection_clear(sel, true); + return true; + + case KEY_SELECT_ALL: + selection_select_all(sel); + return true; + + case KEY_ESCAPE: + if (selection_defined(sel)) { + selection_clear(sel, true); + return true; + } + + /* if there's no selection, leave Escape for the caller */ + return false; + } + + return false; +} + + +/** * Draw a CONTENT_TEXTPLAIN using the current set of plotters (plot). * * \param c content of type CONTENT_TEXTPLAIN @@ -893,11 +936,11 @@ void textplain_close(struct content *c) * Return an textplain content's selection context */ -struct selection *textplain_get_selection(struct content *c) +char *textplain_get_selection(struct content *c) { textplain_content *text = (textplain_content *) c; - return &text->sel; + return selection_get_copy(&text->sel); } diff --git a/riscos/save.c b/riscos/save.c index 12af6ab22..549a2ab3e 100644 --- a/riscos/save.c +++ b/riscos/save.c @@ -79,7 +79,7 @@ static gui_save_type gui_save_current_type; static hlcache_handle *gui_save_content = NULL; -static struct selection *gui_save_selection = NULL; +static char *gui_save_selection = NULL; static const char *gui_save_url = NULL; static const char *gui_save_title = NULL; static int gui_save_filetype; @@ -246,7 +246,7 @@ void ro_gui_saveas_quit(void) */ void ro_gui_save_prepare(gui_save_type save_type, hlcache_handle *h, - struct selection *s, const char *url, const char *title) + char *s, const char *url, const char *title) { char name_buf[FILENAME_MAX]; size_t leaf_offset = 0; @@ -259,6 +259,9 @@ void ro_gui_save_prepare(gui_save_type save_type, hlcache_handle *h, (save_type == GUI_SAVE_HISTORY_EXPORT_HTML) || (save_type == GUI_SAVE_TEXT_SELECTION) || h); + if (gui_save_selection == NULL) + free(gui_save_selection); + gui_save_selection = s; gui_save_url = url; gui_save_title = title; @@ -414,7 +417,11 @@ void gui_drag_save_selection(struct selection *s, struct gui_window *g) return; } - gui_save_selection = s; + + if (gui_save_selection == NULL) + free(gui_save_selection); + + gui_save_selection = selection_get_copy(s); ro_gui_save_set_state(NULL, GUI_SAVE_TEXT_SELECTION, NULL, save_leafname, LEAFNAME_MAX, @@ -890,8 +897,15 @@ bool ro_gui_save_content(hlcache_handle *h, char *path, bool force_overwrite) break; case GUI_SAVE_TEXT_SELECTION: - if (!selection_save_text(gui_save_selection, path)) + if (gui_save_selection == NULL) + return false; + if (!utf8_save_text(gui_save_selection, path)) { + free(gui_save_selection); + gui_save_selection = NULL; return false; + } + free(gui_save_selection); + gui_save_selection = NULL; xosfile_set_type(path, 0xfff); break; diff --git a/riscos/save.h b/riscos/save.h index c9a0f13e8..b219de4c8 100644 --- a/riscos/save.h +++ b/riscos/save.h @@ -30,7 +30,7 @@ wimp_w ro_gui_saveas_create(const char *template_name); void ro_gui_saveas_quit(void); void ro_gui_save_prepare(gui_save_type save_type, struct hlcache_handle *h, - struct selection *s, const char *url, + char *s, const char *url, const char *title); void ro_gui_save_start_drag(wimp_pointer *pointer); void ro_gui_drag_save_link(gui_save_type save_type, const char *url, diff --git a/riscos/window.c b/riscos/window.c index d82e855ac..0183dbb54 100644 --- a/riscos/window.c +++ b/riscos/window.c @@ -145,7 +145,7 @@ static void ro_gui_window_save_toolbar_buttons(void *data, char *config); static void ro_gui_window_update_theme(void *data, bool ok); static bool ro_gui_window_import_text(struct gui_window *g, - const char *filename, bool toolbar); + const char *filename); static void ro_gui_window_clone_options(struct browser_window *new_bw, struct browser_window *old_bw); @@ -2167,11 +2167,13 @@ bool ro_gui_window_menu_prepare(wimp_w w, wimp_i i, wimp_menu *menu, hlcache_handle *h = NULL; bool export_sprite, export_draw; os_coord pos; + browser_editor_flags editor_flags; g = (struct gui_window *) ro_gui_wimp_event_get_user_data(w); toolbar = g->toolbar; bw = g->bw; h = g->bw->current_content; + editor_flags = browser_window_get_editor_flags(bw); /* If this is the form select menu, handle it now and then exit. * Otherwise, carry on to the main browser window menu. @@ -2327,20 +2329,19 @@ bool ro_gui_window_menu_prepare(wimp_w w, wimp_i i, wimp_menu *menu, * be selected */ ro_gui_menu_set_entry_shaded(menu, BROWSER_SELECTION_SAVE, - !browser_window_has_selection(bw)); + editor_flags & ~BW_EDITOR_CAN_COPY); ro_gui_menu_set_entry_shaded(menu, BROWSER_SELECTION_COPY, - !browser_window_has_selection(bw)); + editor_flags & ~BW_EDITOR_CAN_COPY); ro_gui_menu_set_entry_shaded(menu, BROWSER_SELECTION_CUT, - !browser_window_has_selection(bw) || - selection_read_only(browser_window_get_selection(bw))); + editor_flags & ~BW_EDITOR_CAN_CUT); ro_gui_menu_set_entry_shaded(menu, BROWSER_SELECTION_PASTE, - h == NULL || bw->paste_callback == NULL); + editor_flags & ~BW_EDITOR_CAN_PASTE); ro_gui_menu_set_entry_shaded(menu, BROWSER_SELECTION_CLEAR, - !browser_window_has_selection(bw)); + editor_flags & ~BW_EDITOR_CAN_COPY); /* Navigate Submenu */ @@ -2477,7 +2478,7 @@ void ro_gui_window_menu_warning(wimp_w w, wimp_i i, wimp_menu *menu, break; case BROWSER_SELECTION_SAVE: - if (browser_window_has_selection(bw)) + if (browser_window_get_editor_flags(bw) & BW_EDITOR_CAN_COPY) ro_gui_save_prepare(GUI_SAVE_TEXT_SELECTION, NULL, browser_window_get_selection(bw), NULL, NULL); @@ -3722,14 +3723,15 @@ void ro_gui_window_toolbar_click(void *data, bool ro_gui_toolbar_dataload(struct gui_window *g, wimp_message *message) { if (message->data.data_xfer.file_type == osfile_TYPE_TEXT && - ro_gui_window_import_text(g, message->data.data_xfer.file_name, true)) { - + ro_gui_window_import_text(g, + message->data.data_xfer.file_name)) { os_error *error; /* send DataLoadAck */ message->action = message_DATA_LOAD_ACK; message->your_ref = message->my_ref; - error = xwimp_send_message(wimp_USER_MESSAGE, message, message->sender); + error = xwimp_send_message(wimp_USER_MESSAGE, message, + message->sender); if (error) { LOG(("xwimp_send_message: 0x%x: %s\n", error->errnum, error->errmess)); @@ -4546,14 +4548,15 @@ void ro_gui_window_update_theme(void *data, bool ok) * \return true iff successful */ -bool ro_gui_window_import_text(struct gui_window *g, const char *filename, - bool toolbar) +bool ro_gui_window_import_text(struct gui_window *g, const char *filename) { fileswitch_object_type obj_type; os_error *error; - char *buf, *utf8_buf; + char *buf, *utf8_buf, *sp; int size; utf8_convert_ret ret; + const char *ep; + char *p; error = xosfile_read_stamped(filename, &obj_type, NULL, NULL, &size, NULL, NULL); @@ -4596,24 +4599,19 @@ bool ro_gui_window_import_text(struct gui_window *g, const char *filename, } size = strlen(utf8_buf); - if (toolbar) { - const char *ep = utf8_buf + size; - const char *sp; - char *p = utf8_buf; + ep = utf8_buf + size; + p = utf8_buf; - /* skip leading whitespace */ - while (isspace(*p)) p++; + /* skip leading whitespace */ + while (isspace(*p)) p++; - sp = p; - while (*p && *p != '\r' && *p != '\n') - p += utf8_next(p, ep - p, 0); - *p = '\0'; + sp = p; + while (*p && *p != '\r' && *p != '\n') + p += utf8_next(p, ep - p, 0); + *p = '\0'; - if (p > sp) - ro_gui_window_launch_url(g, sp); - } - else - browser_window_paste_text(g->bw, utf8_buf, size, true); + if (p > sp) + ro_gui_window_launch_url(g, sp); free(buf); free(utf8_buf); diff --git a/utils/utf8.c b/utils/utf8.c index c0f6b106a..b4e308044 100644 --- a/utils/utf8.c +++ b/utils/utf8.c @@ -474,3 +474,42 @@ utf8_convert_ret utf8_to_html(const char *string, const char *encname, } +/** + * Save the given utf8 text to a file, converting to local encoding. + * + * \param utf8_text text to save to file + * \param path pathname to save to + * \return true iff the save succeeded + */ + +bool utf8_save_text(const char *utf8_text, const char *path) +{ + utf8_convert_ret ret; + char *conv; + FILE *out; + + ret = utf8_to_local_encoding(utf8_text, strlen(utf8_text), &conv); + + if (ret != UTF8_CONVERT_OK) { + LOG(("failed to convert to local encoding, return %d", ret)); + return false; + } + + out = fopen(path, "w"); + if (out) { + int res = fputs(conv, out); + if (res < 0) { + LOG(("Warning: writing data failed")); + } + + res = fputs("\n", out); + fclose(out); + free(conv); + return (res != EOF); + } + free(conv); + + return false; +} + + diff --git a/utils/utf8.h b/utils/utf8.h index 22aee1afa..a48d47d55 100644 --- a/utils/utf8.h +++ b/utils/utf8.h @@ -50,6 +50,8 @@ utf8_convert_ret utf8_from_enc(const char *string, const char *encname, utf8_convert_ret utf8_to_html(const char *string, const char *encname, size_t len, char **result); +bool utf8_save_text(const char *utf8_text, const char *path); + /* These two are platform specific */ utf8_convert_ret utf8_to_local_encoding(const char *string, size_t len, char **result); |