diff options
-rw-r--r-- | !NetSurf/Resources/en/Messages | 29 | ||||
-rw-r--r-- | desktop/browser.c | 25 | ||||
-rw-r--r-- | desktop/gui.h | 2 | ||||
-rw-r--r-- | desktop/selection.c | 61 | ||||
-rw-r--r-- | desktop/selection.h | 8 | ||||
-rw-r--r-- | desktop/textinput.c | 922 | ||||
-rw-r--r-- | gtk/dialogs/gtk_options.c | 24 | ||||
-rw-r--r-- | gtk/gtk_download.c | 638 | ||||
-rw-r--r-- | gtk/gtk_download.h | 56 | ||||
-rw-r--r-- | gtk/gtk_gui.c | 34 | ||||
-rw-r--r-- | gtk/gtk_scaffolding.c | 28 | ||||
-rw-r--r-- | gtk/gtk_scaffolding.h | 2 | ||||
-rw-r--r-- | gtk/gtk_selection.c | 27 | ||||
-rw-r--r-- | gtk/gtk_window.c | 14 | ||||
-rw-r--r-- | gtk/options.h | 9 | ||||
-rw-r--r-- | gtk/res/downloads.glade | 118 | ||||
-rw-r--r-- | gtk/res/netsurf.glade | 183 | ||||
-rw-r--r-- | gtk/res/options.glade | 132 | ||||
-rw-r--r-- | riscos/download.c | 2 | ||||
-rw-r--r-- | riscos/window.c | 2 |
20 files changed, 1654 insertions, 662 deletions
diff --git a/!NetSurf/Resources/en/Messages b/!NetSurf/Resources/en/Messages index 7c73f50b1..3e218b985 100644 --- a/!NetSurf/Resources/en/Messages +++ b/!NetSurf/Resources/en/Messages @@ -298,6 +298,35 @@ DownloadU:%s of unknown • %s/s • %s total Downloaded:%s complete • average %s/s • %s total Unwritten:Writing data to file failed. +# GTK download window tokens +# +# This section contains tokens which are used in the gtk +# download window. +# + +# Column Headers +# +gtkProgress:Progress +gtkDetails:Details +gtkSpeed:Speed +gtkRemaining:Remaining + +# Status +# +gtkError:Error +gtkComplete:Complete +gtkCanceled:Canceled +gtkWorking:Working + +# Dialogs +# +gtkStartDownload:Download file? +gtkFailed:Download failed +gtkFileError:File error: %s +gtkInfo:%s from %s is %s in size +gtkSave:Save file as... +gtkUnknownHost:an unknown host +gtkUnknownFile: # Printing user interface tokens # ============================== diff --git a/desktop/browser.c b/desktop/browser.c index 55cfcb56b..155febc50 100644 --- a/desktop/browser.c +++ b/desktop/browser.c @@ -629,7 +629,8 @@ void browser_window_convert_to_download(struct browser_window *bw) if (fetch) { /* create download window */ download_window = gui_download_window_create(c->url, - c->mime_type, fetch, c->total_size); + c->mime_type, fetch, c->total_size, + bw->window); if (download_window) { /* extract fetch from content */ @@ -1230,8 +1231,6 @@ void browser_window_mouse_click(struct browser_window *bw, if (!c) return; - browser_window_remove_caret(bw); - switch (c->type) { case CONTENT_HTML: browser_window_mouse_action_html(bw, mouse, x, y); @@ -1426,7 +1425,7 @@ void browser_window_mouse_action_html(struct browser_window *bw, status = messages_get("FormTextarea"); pointer = GUI_POINTER_CARET; - if (mouse & (BROWSER_MOUSE_CLICK_1 | BROWSER_MOUSE_CLICK_2)) { + if (mouse & (BROWSER_MOUSE_PRESS_1 | BROWSER_MOUSE_PRESS_2)) { if (text_box && selection_root(bw->sel) != gadget_box) selection_init(bw->sel, gadget_box); @@ -1446,7 +1445,7 @@ void browser_window_mouse_action_html(struct browser_window *bw, nsfont_position_in_string(text_box->style, text_box->text, text_box->length, - x - text_box_x, + x - gadget_box_x - text_box->x, &idx, &pixel_offset); @@ -1459,14 +1458,14 @@ void browser_window_mouse_action_html(struct browser_window *bw, } else status = c->status_message; } - else if (mouse & BROWSER_MOUSE_CLICK_1) + else if (mouse & BROWSER_MOUSE_PRESS_1) selection_clear(bw->sel, true); break; case GADGET_TEXTBOX: case GADGET_PASSWORD: status = messages_get("FormTextbox"); pointer = GUI_POINTER_CARET; - if ((mouse & BROWSER_MOUSE_CLICK_1) && + if ((mouse & BROWSER_MOUSE_PRESS_1) && !(mouse & (BROWSER_MOUSE_MOD_1 | BROWSER_MOUSE_MOD_2))) { browser_window_input_click(bw, @@ -1487,7 +1486,7 @@ void browser_window_mouse_action_html(struct browser_window *bw, nsfont_position_in_string(text_box->style, text_box->text, text_box->length, - x - text_box_x, + x - gadget_box_x - text_box->x, &idx, &pixel_offset); @@ -1497,7 +1496,7 @@ void browser_window_mouse_action_html(struct browser_window *bw, if (selection_dragging(bw->sel)) bw->drag_type = DRAGGING_SELECTION; } - else if (mouse & BROWSER_MOUSE_CLICK_1) + else if (mouse & BROWSER_MOUSE_PRESS_1) selection_clear(bw->sel, true); break; case GADGET_HIDDEN: @@ -1586,7 +1585,6 @@ void browser_window_mouse_action_html(struct browser_window *bw, /* key presses must be directed at the * main browser window, paste text * operations ignored */ - browser_window_remove_caret(bw); if (selection_dragging(bw->sel)) { bw->drag_type = @@ -1599,7 +1597,7 @@ void browser_window_mouse_action_html(struct browser_window *bw, done = true; } } - else if (mouse & BROWSER_MOUSE_CLICK_1) + else if (mouse & BROWSER_MOUSE_PRESS_1) selection_clear(bw->sel, true); } @@ -1838,9 +1836,10 @@ void browser_window_mouse_track_html(struct browser_window *bw, if (box) { int pixel_offset; size_t idx; + nsfont_position_in_string(box->style, box->text, box->length, - dx, &idx, &pixel_offset); + dx, &idx, &pixel_offset); selection_track(bw->sel, mouse, box->byte_offset + idx); @@ -2194,7 +2193,7 @@ void browser_window_redraw_rect(struct browser_window *bw, int x, int y, void browser_redraw_box(struct content *c, struct box *box) { int x, y; - union content_msg_data data; + union content_msg_data data; box_coords(box, &x, &y); diff --git a/desktop/gui.h b/desktop/gui.h index 6ed0243d7..77fd861c3 100644 --- a/desktop/gui.h +++ b/desktop/gui.h @@ -99,7 +99,7 @@ void gui_window_set_scale(struct gui_window *g, float scale); struct gui_download_window *gui_download_window_create(const char *url, const char *mime_type, struct fetch *fetch, - unsigned int total_size); + unsigned int total_size, struct gui_window *gui); void gui_download_window_data(struct gui_download_window *dw, const char *data, unsigned int size); void gui_download_window_error(struct gui_download_window *dw, diff --git a/desktop/selection.c b/desktop/selection.c index 74ae3165f..2be6853a2 100644 --- a/desktop/selection.c +++ b/desktop/selection.c @@ -50,7 +50,9 @@ */ #define IS_INPUT(box) ((box) && (box)->gadget && \ - ((box)->gadget->type == GADGET_TEXTAREA || (box)->gadget->type == GADGET_TEXTBOX)) + ((box)->gadget->type == GADGET_TEXTAREA || \ + (box)->gadget->type == GADGET_TEXTBOX || \ + (box)->gadget->type == GADGET_PASSWORD)) /** check whether the given text box is in the same number space as the current selection; number spaces are identified by their uppermost nybble */ @@ -69,7 +71,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 unsigned selection_label_subtree(struct box *box, unsigned idx); static bool save_handler(const char *text, size_t length, struct box *box, void *handle, const char *whitespace_text, size_t whitespace_length); @@ -79,8 +80,7 @@ static bool traverse_tree(struct box *box, unsigned start_idx, unsigned end_idx, unsigned int num_space, seln_traverse_handler handler, void *handle, save_text_whitespace *before, bool *first, bool do_marker); -static struct box *get_box(struct box *b, unsigned offset, int *pidx); - +static struct box *get_box(struct box *b, unsigned offset, size_t *pidx); /** * Creates a new selection object associated with a browser window. @@ -261,11 +261,14 @@ bool selection_click(struct selection *s, browser_mouse_state mouse, gui_drag_save_selection(s, s->bw->window); } else if (!modkeys) { - if (mouse & BROWSER_MOUSE_DRAG_1) { - + 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) */ + selection_clear(s, true); + else if (mouse & BROWSER_MOUSE_DRAG_1) { /* start new selection drag */ selection_clear(s, true); - + selection_set_start(s, idx); selection_set_end(s, idx); @@ -291,12 +294,13 @@ bool selection_click(struct selection *s, browser_mouse_state mouse, } gui_start_selection(s->bw->window); } - else if (pos && (mouse & BROWSER_MOUSE_CLICK_1)) { - - /* clear selection */ - selection_clear(s, true); - s->drag_state = DRAG_NONE; - } + /* Selection should be cleared when button is released but in + * the RO interface click is the same as press */ +// else if (!pos && (mouse & BROWSER_MOUSE_CLICK_1)) { +// /* clear selection */ +// selection_clear(s, true); +// s->drag_state = DRAG_NONE; +// } else if (mouse & BROWSER_MOUSE_CLICK_2) { /* ignore Adjust clicks when there's no selection */ @@ -699,15 +703,12 @@ void selection_select_all(struct selection *s) old_end = s->end_idx; s->defined = true; - s->start_idx = 0; - s->end_idx = s->max_idx; - - if (was_defined) { - selection_redraw(s, 0, old_start); - selection_redraw(s, old_end, s->end_idx); - } + + if (IS_INPUT(s->root)) + selection_set_start(s, s->root->children->children->byte_offset); else - selection_redraw(s, 0, s->max_idx); + selection_set_start(s, 0); + selection_set_end(s, s->max_idx); } @@ -722,9 +723,17 @@ void selection_set_start(struct selection *s, unsigned offset) { bool was_defined = selection_defined(s); unsigned old_start = s->start_idx; - + s->start_idx = offset; s->defined = (s->start_idx < s->end_idx); + + if (s->root->gadget && s->defined) { + /* update the caret text_box and offset so that it stays at the + * beginning of the selection */ + s->root->gadget->caret_text_box = selection_get_start(s, + &s->root->gadget->caret_box_offset); + assert(s->root->gadget->caret_text_box != NULL); + } if (was_defined) { if (offset < old_start) @@ -773,14 +782,14 @@ void selection_set_end(struct selection *s, unsigned offset) * \return ptr to box, or NULL if no selection defined */ -struct box *get_box(struct box *b, unsigned offset, int *pidx) +struct box *get_box(struct box *b, unsigned offset, size_t *pidx) { struct box *child = b->children; if (b->text) { if (offset >= b->byte_offset && - offset < b->byte_offset + b->length + b->space) { + offset <= b->byte_offset + b->length + b->space) { /* it's in this box */ *pidx = offset - b->byte_offset; @@ -810,7 +819,7 @@ struct box *get_box(struct box *b, unsigned offset, int *pidx) * \return ptr to box, or NULL if no selection defined */ -struct box *selection_get_start(struct selection *s, int *pidx) +struct box *selection_get_start(struct selection *s, size_t *pidx) { return (s->defined ? get_box(s->root, s->start_idx, pidx) : NULL); } @@ -824,7 +833,7 @@ struct box *selection_get_start(struct selection *s, int *pidx) * \return ptr to box, or NULL if no selection defined. */ -struct box *selection_get_end(struct selection *s, int *pidx) +struct box *selection_get_end(struct selection *s, size_t *pidx) { return (s->defined ? get_box(s->root, s->end_idx, pidx) : NULL); } diff --git a/desktop/selection.h b/desktop/selection.h index c04e503f3..1fca4c383 100644 --- a/desktop/selection.h +++ b/desktop/selection.h @@ -84,8 +84,8 @@ void selection_select_all(struct selection *s); void selection_set_start(struct selection *s, unsigned idx); void selection_set_end(struct selection *s, unsigned idx); -struct box *selection_get_start(struct selection *s, int *pidx); -struct box *selection_get_end(struct selection *s, int *pidx); +struct box *selection_get_start(struct selection *s, size_t *pidx); +struct box *selection_get_end(struct selection *s, size_t *pidx); bool selection_click(struct selection *s, browser_mouse_state mouse, unsigned idx); @@ -105,6 +105,8 @@ bool selection_highlighted(struct selection *s, unsigned start, unsigned end, bool selection_save_text(struct selection *s, const char *path); void selection_update(struct selection *s, size_t byte_offset, int change, - bool redraw); + bool redraw); + +unsigned selection_label_subtree(struct box *box, unsigned idx); #endif diff --git a/desktop/textinput.c b/desktop/textinput.c index c2678d021..6d9f7d5d2 100644 --- a/desktop/textinput.c +++ b/desktop/textinput.c @@ -62,11 +62,13 @@ static bool browser_window_textarea_paste_text(struct browser_window *bw, const char *utf8, unsigned utf8_len, bool last, void *handle); static bool browser_window_input_paste_text(struct browser_window *bw, const char *utf8, unsigned utf8_len, bool last, void *handle); -static void browser_window_textarea_move_caret(struct browser_window *bw, void *p); +static void browser_window_textarea_move_caret(struct browser_window *bw, + void *p); static void browser_window_input_move_caret(struct browser_window *bw, void *p); static void input_update_display(struct browser_window *bw, struct box *input, - unsigned form_offset, unsigned box_offset, bool to_textarea, - bool redraw); + unsigned box_offset, bool to_textarea, bool redraw); +static size_t get_form_offset(struct box* input, struct box* text_box, + size_t char_offset); 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, @@ -75,6 +77,7 @@ static struct box *textarea_insert_break(struct browser_window *bw, struct box *text_box, size_t char_offset); static bool delete_handler(struct browser_window *bw, struct box *b, int offset, size_t length); +static void delete_selection(struct selection *s); 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); @@ -102,7 +105,8 @@ void caret_remove(struct caret *c) int w = (c->height + 7) / 8; int xc = c->x; c->defined = false; - browser_window_redraw_rect(c->bw, xc - w, c->y, 2 * w, c->height); + browser_window_redraw_rect(c->bw, + xc - w, c->y, 2 * w, c->height); } } @@ -114,7 +118,7 @@ void caret_remove(struct caret *c) * * \param c structure describing text caret * \param bw browser window containing caret - * \param box INLINE box containing caret + * \param box TEXT box containing caret * \param char_offset byte offset within UTF-8 representation * \param pixel_offset from left side of box */ @@ -145,26 +149,26 @@ void caret_set_position(struct caret *c, struct browser_window *bw, /** * Given the x,y co-ordinates of a point within a textarea, return the - * INLINE box pointer, and the character and pixel offsets within that + * TEXT box pointer, and the character and pixel offsets within that * box at which the caret should be positioned. (eg. for mouse clicks, * drag-and-drop insertions etc) * * \param textarea the textarea being considered * \param x x ordinate of point * \param y y ordinate of point - * \param pchar_offset receives the char offset within the INLINE box - * \param ppixel_offset receives the pixel offset within the INLINE box - * \return pointer to INLINE box + * \param pchar_offset receives the char offset within the TEXT box + * \param ppixel_offset receives the pixel offset within the TEXT box + * \return pointer to TEXT box */ struct box *textarea_get_position(struct box *textarea, int x, int y, int *pchar_offset, int *ppixel_offset) { /* A textarea is an INLINE_BLOCK containing a single - * INLINE_CONTAINER, which contains the text as runs of INLINE - * separated by BR. There is at least one INLINE. The first and - * last boxes are INLINE. Consecutive BR may not be present. These - * constraints are satisfied by using a 0-length INLINE for blank + * INLINE_CONTAINER, which contains the text as runs of TEXT + * separated by BR. There is at least one TEXT. The first and + * last boxes are TEXT. Consecutive BR may not be present. These + * constraints are satisfied by using a 0-length TEXT for blank * lines. */ struct box *inline_container, *text_box; @@ -251,10 +255,10 @@ void browser_window_textarea_click(struct browser_window *bw, int x, int y) { /* A textarea is an INLINE_BLOCK containing a single - * INLINE_CONTAINER, which contains the text as runs of INLINE - * separated by BR. There is at least one INLINE. The first and - * last boxes are INLINE. Consecutive BR may not be present. These - * constraints are satisfied by using a 0-length INLINE for blank + * INLINE_CONTAINER, which contains the text as runs of TEXT + * separated by BR. There is at least one TEXT. The first and + * last boxes are TEXT. Consecutive BR may not be present. These + * constraints are satisfied by using a 0-length TEXT for blank * lines. */ int char_offset = 0, pixel_offset = 0; @@ -275,7 +279,7 @@ void browser_window_textarea_click(struct browser_window *bw, scrolled = ensure_caret_visible(textarea); box_x -= textarea->scroll_x; box_y -= textarea->scroll_y; - + browser_window_place_caret(bw, box_x + inline_container->x + text_box->x + pixel_offset, @@ -315,6 +319,7 @@ bool browser_window_textarea_callback(struct browser_window *bw, char utf8[6]; unsigned int utf8_len; bool scrolled,reflow = false; + bool selection_exists = bw->sel->defined; /* box_dump(textarea, 0); */ LOG(("key %i at %i in '%.*s'", key, char_offset, @@ -325,7 +330,7 @@ bool browser_window_textarea_callback(struct browser_window *bw, box_y -= textarea->scroll_y; if (!(key <= 0x001F || (0x007F <= key && key <= 0x009F))) { - /* normal character insertion */ + /* normal character insertion */ utf8_len = utf8_from_ucs4(key, utf8); if (!textbox_insert(bw, text_box, char_offset, utf8, utf8_len)) @@ -336,15 +341,20 @@ bool browser_window_textarea_callback(struct browser_window *bw, } else switch (key) { case KEY_DELETE_LEFT: - if (char_offset == 0) { + if (selection_exists) { + /* Have a selection; delete it */ + textbox_delete(bw, text_box, 0, 0); + } else if (char_offset == 0) { /* at the start of a text box */ struct box *prev; - while (text_box->prev && text_box->prev->type == BOX_BR) { + if (text_box->prev && text_box->prev->type == BOX_BR) { /* previous box is BR: remove it */ box_unlink_and_free(text_box->prev); } + /* This needs to be after the BR removal, as that may + * result in no previous box existing */ if (!text_box->prev) /* at very beginning of text area: ignore */ return true; @@ -357,7 +367,7 @@ bool browser_window_textarea_callback(struct browser_window *bw, char_offset = prev->length; /* caret at join */ if (!textbox_insert(bw, prev, prev->length, - text_box->text, text_box->length)) + text_box->text, text_box->length)) return true; box_unlink_and_free(text_box); @@ -367,17 +377,40 @@ bool browser_window_textarea_callback(struct browser_window *bw, } else { /* delete a character */ - int prev_offset = char_offset; - char_offset = utf8_prev(text_box->text, char_offset); + size_t prev_offset = char_offset; + size_t new_offset = + utf8_prev(text_box->text, char_offset); - textbox_delete(bw, text_box, char_offset, - prev_offset - char_offset); + if (textbox_delete(bw, text_box, new_offset, + prev_offset - new_offset)) + char_offset = new_offset; } reflow = true; break; - case KEY_DELETE_LINE_END: { + case KEY_DELETE_LINE_START: + { + struct box *start_box = line_start(text_box); + + /* Clear the selection, if one exists */ + if (selection_exists) + selection_clear(bw->sel, false); + + textarea_cut(bw, start_box, 0, text_box, char_offset); + text_box = start_box; + char_offset = 0; + reflow = true; + } + break; + + case KEY_DELETE_LINE_END: + { struct box *end_box = line_end(text_box); + + /* Clear the selection, if one exists */ + if (selection_exists) + selection_clear(bw->sel, false); + if (end_box != text_box || char_offset < text_box->length + text_box->space) { /* there's something at the end of the line to delete */ @@ -389,15 +422,20 @@ bool browser_window_textarea_callback(struct browser_window *bw, } /* no break */ case KEY_DELETE_RIGHT: /* delete to right */ - if (char_offset >= text_box->length) { + if (selection_exists) { + /* Delete selection */ + textbox_delete(bw, text_box, 0, 0); + } else if (char_offset >= text_box->length) { /* at the end of a text box */ struct box *next; - while (text_box->next && text_box->next->type == BOX_BR) { + if (text_box->next && text_box->next->type == BOX_BR) { /* next box is a BR: remove it */ box_unlink_and_free(text_box->next); } + /* This test is after the BR removal, as that may + * result in no subsequent box being present */ if (!text_box->next) /* at very end of text area: ignore */ return true; @@ -409,7 +447,7 @@ bool browser_window_textarea_callback(struct browser_window *bw, assert(next->text); if (!textbox_insert(bw, text_box, text_box->length, - next->text, next->length)) + next->text, next->length)) return true; box_unlink_and_free(next); @@ -419,16 +457,23 @@ bool browser_window_textarea_callback(struct browser_window *bw, } else { /* delete a character */ - int next_offset = utf8_next(text_box->text, text_box->length, - char_offset); + size_t next_offset = utf8_next(text_box->text, + text_box->length, char_offset); + textbox_delete(bw, text_box, char_offset, - next_offset - char_offset); + next_offset - char_offset); } reflow = true; break; case 10: case 13: /* paragraph break */ + if (selection_exists) { + /* If we have a selection, then delete it, + * so it's replaced by the break */ + textbox_delete(bw, text_box, 0, 0); + } + new_text = textarea_insert_break(bw, text_box, char_offset); if (!new_text) return true; @@ -440,46 +485,60 @@ bool browser_window_textarea_callback(struct browser_window *bw, reflow = true; break; - case 21: { /* Ctrl + U */ + case 21: /* Ctrl + U */ + { struct box *start_box = line_start(text_box); struct box *end_box = line_end(text_box); + /* Clear the selection, if one exists */ + if (selection_exists) + selection_clear(bw->sel, false); + textarea_cut(bw, start_box, 0, end_box, end_box->length); text_box = start_box; char_offset = 0; reflow = true; } - break; + break; case 22: /* Ctrl + V */ gui_paste_from_clipboard(bw->window, - box_x + inline_container->x + text_box->x + pixel_offset, + box_x + inline_container->x + + text_box->x + pixel_offset, box_y + inline_container->y + text_box->y); /* screen updated and caret repositioned already */ return true; - case 24: { /* Ctrl + X */ - int start_idx, end_idx; - struct box *start_box = selection_get_start(bw->sel, &start_idx); + case 24: /* Ctrl + X */ + { + size_t 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); - + 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) { + if (selection_exists) { + /* In selection, move caret to end */ + text_box = selection_get_end(bw->sel, &char_offset); + } else if (char_offset < text_box->length) { + /* Within-box movement */ char_offset = utf8_next(text_box->text, text_box->length, char_offset); } else { + /* Between-box movement */ if (!text_box->next) /* at end of text area: ignore */ return true; @@ -492,9 +551,14 @@ bool browser_window_textarea_callback(struct browser_window *bw, break; case KEY_LEFT: - if (char_offset != 0) { + if (selection_exists) { + /* In selection, move caret to start */ + text_box = selection_get_start(bw->sel, &char_offset); + } else if (char_offset > 0) { + /* Within-box movement */ char_offset = utf8_prev(text_box->text, char_offset); } else { + /* Between-box movement */ if (!text_box->prev) /* at start of text area: ignore */ return true; @@ -507,6 +571,7 @@ bool browser_window_textarea_callback(struct browser_window *bw, break; case KEY_UP: + selection_clear(bw->sel, true); browser_window_textarea_click(bw, BROWSER_MOUSE_CLICK_1, textarea, box_x, box_y, @@ -515,6 +580,7 @@ bool browser_window_textarea_callback(struct browser_window *bw, return true; case KEY_DOWN: + selection_clear(bw->sel, true); browser_window_textarea_click(bw, BROWSER_MOUSE_CLICK_1, textarea, box_x, box_y, @@ -549,7 +615,12 @@ bool browser_window_textarea_callback(struct browser_window *bw, char_offset = text_box->length; break; - case KEY_WORD_LEFT: { + case KEY_WORD_LEFT: + { + /* if there is a selection, caret should stay at beginning */ + if (selection_exists) + break; + bool start_of_word = (char_offset <= 0 || isspace(text_box->text[char_offset - 1])); @@ -561,7 +632,7 @@ bool browser_window_textarea_callback(struct browser_window *bw, if (start_of_word) { /* find the preceding non-BR box */ prev = text_box->prev; - while (prev && prev->type == BOX_BR) + if (prev && prev->type == BOX_BR) prev = prev->prev; } @@ -570,13 +641,22 @@ bool browser_window_textarea_callback(struct browser_window *bw, break; } + assert(prev->type == BOX_TEXT); + text_box = prev; char_offset = prev->length; } } - break; + break; + + case KEY_WORD_RIGHT: + { + /* if there is a selection, caret should move to the end */ + if (selection_exists) { + text_box = selection_get_end(bw->sel, &char_offset); + break; + } - case KEY_WORD_RIGHT: { bool in_word = (char_offset < text_box->length && !isspace(text_box->text[char_offset])); @@ -585,7 +665,7 @@ bool browser_window_textarea_callback(struct browser_window *bw, struct box *next = text_box->next; /* find the next non-BR box */ - while (next && next->type == BOX_BR) + if (next && next->type == BOX_BR) next = next->next; if (!next) { @@ -594,20 +674,22 @@ bool browser_window_textarea_callback(struct browser_window *bw, break; } + assert(next->type == BOX_TEXT); + text_box = next; char_offset = 0; - if (in_word && - text_box->length > 0 && - !isspace(text_box->text[0])) { + if (in_word && text_box->length > 0 && + !isspace(text_box->text[0])) { /* just stay at the start of this box */ break; } } } - break; + break; - case KEY_PAGE_UP: { + case KEY_PAGE_UP: + { int nlines = (textarea->height / text_box->height) - 1; while (nlines-- > 0) @@ -616,30 +698,22 @@ bool browser_window_textarea_callback(struct browser_window *bw, if (char_offset > text_box->length) char_offset = text_box->length; } - break; + break; - case KEY_PAGE_DOWN: { + case KEY_PAGE_DOWN: + { int nlines = (textarea->height / text_box->height) - 1; + while (nlines-- > 0) text_box = line_below(text_box); - /* 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 */ - + /* 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 */ if (char_offset > text_box->length) char_offset = text_box->length; } - break; - - case KEY_DELETE_LINE_START: { - struct box *start_box = line_start(text_box); - textarea_cut(bw, start_box, 0, text_box, char_offset); - text_box = start_box; - char_offset = 0; - reflow = true; - } - break; + break; default: return false; @@ -685,9 +759,21 @@ bool browser_window_textarea_callback(struct browser_window *bw, char_offset = text_box->length + text_box->space; } } - + nsfont_width(text_box->style, text_box->text, char_offset, &pixel_offset); + + selection_clear(bw->sel, true); + + browser_window_place_caret(bw, + box_x + inline_container->x + text_box->x + + pixel_offset, + box_y + inline_container->y + text_box->y, + text_box->height, + browser_window_textarea_callback, + browser_window_textarea_paste_text, + browser_window_textarea_move_caret, + textarea); textarea->gadget->caret_inline_container = inline_container; textarea->gadget->caret_text_box = text_box; @@ -700,16 +786,6 @@ bool browser_window_textarea_callback(struct browser_window *bw, box_x -= textarea->scroll_x; box_y -= textarea->scroll_y; - browser_window_place_caret(bw, - box_x + inline_container->x + text_box->x + - pixel_offset, - box_y + inline_container->y + text_box->y, - text_box->height, - browser_window_textarea_callback, - browser_window_textarea_paste_text, - browser_window_textarea_move_caret, - textarea); - if (scrolled || reflow) browser_redraw_box(bw->current_content, textarea); @@ -735,54 +811,31 @@ void browser_window_input_click(struct browser_window* bw, size_t char_offset = 0; int pixel_offset = 0, dx = 0; struct box *text_box = input->children->children; - int uchars; - unsigned int offset; nsfont_position_in_string(text_box->style, text_box->text, text_box->length, x - text_box->x, &char_offset, &pixel_offset); assert(char_offset <= text_box->length); + /* Shift the text box horizontally to ensure that the + * caret position is visible, and ideally centred */ text_box->x = 0; if ((input->width < text_box->width) && (input->width / 2 < pixel_offset)) { dx = text_box->x; + /* Move left so caret is centred */ text_box->x = input->width / 2 - pixel_offset; + /* Clamp, so text box's right hand edge coincides + * with the input's right hand edge */ if (text_box->x < input->width - text_box->width) text_box->x = input->width - text_box->width; dx -= text_box->x; } input->gadget->caret_box_offset = char_offset; - /* Update caret_form_offset */ - for (uchars = 0, offset = 0; offset < char_offset; uchars++) { - if ((text_box->text[offset] & 0x80) == 0x00) { - offset++; - continue; - } - assert((text_box->text[offset] & 0xC0) == 0xC0); - for (++offset; offset < char_offset && - (text_box->text[offset] & 0xC0) == 0x80; - offset++) - /* do nothing */; - } - /* uchars is the number of real Unicode characters at the left - * side of the caret. - */ - for (offset = 0; uchars > 0 && offset < input->gadget->length; - uchars--) { - if ((input->gadget->value[offset] & 0x80) == 0x00) { - offset++; - continue; - } - assert((input->gadget->value[offset] & 0xC0) == 0xC0); - for (++offset; offset < input->gadget->length && - (input->gadget->value[offset] & 0xC0) == 0x80; - offset++) - /* do nothing */; - } - assert(uchars == 0); - input->gadget->caret_form_offset = offset; + input->gadget->caret_form_offset = + get_form_offset(input, text_box, char_offset); input->gadget->caret_pixel_offset = pixel_offset; + browser_window_place_caret(bw, box_x + input->children->x + text_box->x + pixel_offset, @@ -797,7 +850,6 @@ void browser_window_input_click(struct browser_window* bw, browser_redraw_box(bw->current_content, input); } - /** * Key press callback for text or password input boxes. * @@ -813,8 +865,10 @@ bool browser_window_input_callback(struct browser_window *bw, { struct box *input = (struct box *)p; struct box *text_box = input->children->children; - unsigned int box_offset = input->gadget->caret_box_offset; - size_t form_offset = input->gadget->caret_form_offset; + size_t box_offset = input->gadget->caret_box_offset; + size_t form_offset = input->gadget->caret_form_offset = + get_form_offset(input, text_box, box_offset); + size_t end_offset; int pixel_offset = input->gadget->caret_pixel_offset; int box_x, box_y; struct form* form = input->gadget->form; @@ -822,168 +876,137 @@ bool browser_window_input_callback(struct browser_window *bw, char utf8[6]; unsigned int utf8_len; bool to_textarea = false; + bool selection_exists = bw->sel->defined; + + selection_get_end(bw->sel, &end_offset); box_coords(input, &box_x, &box_y); - + + /* normal character insertion */ if (!(key <= 0x001F || (0x007F <= key && key <= 0x009F))) { - char *value; - /* have we exceeded max length of input? */ utf8_len = utf8_length(input->gadget->value); if (utf8_len >= input->gadget->maxlength) return true; - - /* normal character insertion */ - - /* Insert key in gadget */ + utf8_len = utf8_from_ucs4(key, utf8); - - value = realloc(input->gadget->value, - input->gadget->length + utf8_len + 1); - if (!value) { - warn_user("NoMemory", 0); - return true; - } - input->gadget->value = value; - - memmove(input->gadget->value + form_offset + utf8_len, - input->gadget->value + form_offset, - input->gadget->length - form_offset); - memcpy(input->gadget->value + form_offset, utf8, utf8_len); - input->gadget->length += utf8_len; - input->gadget->value[input->gadget->length] = 0; - form_offset += utf8_len; - - /* Insert key in text box */ - /* Convert space into NBSP */ - utf8_len = utf8_from_ucs4( - (input->gadget->type == GADGET_PASSWORD) ? - '*' : (key == ' ') ? 160 : key, - utf8); - + if (!textbox_insert(bw, text_box, box_offset, utf8, utf8_len)) return true; - + box_offset += utf8_len; + changed = true; } else switch (key) { - case KEY_DELETE_LEFT: { - int prev_offset; + case KEY_DELETE_LEFT: + { + int prev_offset, new_offset; - if (box_offset <= 0) + if (selection_exists) { + textbox_delete(bw, text_box, 0, 0); + } else { + /* Can't delete left from text box start */ + if (box_offset == 0) return true; - - /* Gadget */ - prev_offset = form_offset; - /* Go to the previous valid UTF-8 character */ - form_offset = utf8_prev(input->gadget->value, - form_offset); - - memmove(input->gadget->value + form_offset, - input->gadget->value + prev_offset, - input->gadget->length - prev_offset); - input->gadget->length -= prev_offset - form_offset; - input->gadget->value[input->gadget->length] = 0; - - /* Text box */ + prev_offset = box_offset; - /* Go to the previous valid UTF-8 character */ - box_offset = utf8_prev(text_box->text, box_offset); + new_offset = utf8_prev(text_box->text, box_offset); - textbox_delete(bw, text_box, box_offset, - prev_offset - box_offset); - changed = true; + if (textbox_delete(bw, text_box, new_offset, + prev_offset - new_offset)) + box_offset = new_offset; } + + changed = true; + } break; - case KEY_DELETE_RIGHT: { - unsigned next_offset; + case KEY_DELETE_RIGHT: + { + unsigned next_offset; + if (selection_exists) { + textbox_delete(bw, text_box, 0, 0); + } else { + /* Can't delete right from text box end */ if (box_offset >= text_box->length) return true; - - /* Gadget */ + /* Go to the next valid UTF-8 character */ - next_offset = utf8_next(input->gadget->value, - input->gadget->length, - form_offset); - - memmove(input->gadget->value + form_offset, - input->gadget->value + next_offset, - input->gadget->length - next_offset); - input->gadget->length -= next_offset - form_offset; - input->gadget->value[input->gadget->length] = 0; - - /* Text box */ - /* Go to the next valid UTF-8 character */ - next_offset = utf8_next(text_box->text, text_box->length, - box_offset); + next_offset = utf8_next(text_box->text, + text_box->length, box_offset); textbox_delete(bw, text_box, box_offset, next_offset - box_offset); - changed = true; } + + changed = true; + } break; - case 9: { /* Tab */ - struct form_control *next_input; - /* Find next text entry field that is actually - * displayed (i.e. has an associated box) */ - for (next_input = input->gadget->next; - next_input && - ((next_input->type != GADGET_TEXTBOX && - next_input->type != GADGET_TEXTAREA && - next_input->type != GADGET_PASSWORD) || - !next_input->box); - next_input = next_input->next) - ; - if (!next_input) - return true; + case 9: /* Tab */ + { + struct form_control *next_input; + /* Find next text entry field that is actually + * displayed (i.e. has an associated box) */ + for (next_input = input->gadget->next; + next_input && + ((next_input->type != GADGET_TEXTBOX && + next_input->type != GADGET_TEXTAREA && + next_input->type != GADGET_PASSWORD) || + !next_input->box); + next_input = next_input->next) + ; + if (!next_input) + return true; - input = next_input->box; - text_box = input->children->children; - form_offset = box_offset = 0; - to_textarea = next_input->type == GADGET_TEXTAREA; - } + input = next_input->box; + text_box = input->children->children; + box_offset = 0; + to_textarea = next_input->type == GADGET_TEXTAREA; + } break; case 10: case 13: /* Return/Enter hit */ + selection_clear(bw->sel, true); + if (form) browser_form_submit(bw, bw, form, 0); return true; - case 11: { /* Shift + Tab */ - struct form_control *prev_input; - /* Find previous text entry field that is actually - * displayed (i.e. has an associated box) */ - for (prev_input = input->gadget->prev; - prev_input && - ((prev_input->type != GADGET_TEXTBOX && - prev_input->type != GADGET_TEXTAREA && - prev_input->type != GADGET_PASSWORD) || - !prev_input->box); - prev_input = prev_input->prev) - ; - if (!prev_input) - return true; + case 11: /* Shift + Tab */ + { + struct form_control *prev_input; + /* Find previous text entry field that is actually + * displayed (i.e. has an associated box) */ + for (prev_input = input->gadget->prev; + prev_input && + ((prev_input->type != GADGET_TEXTBOX && + prev_input->type != GADGET_TEXTAREA && + prev_input->type != GADGET_PASSWORD) || + !prev_input->box); + prev_input = prev_input->prev) + ; + if (!prev_input) + return true; - input = prev_input->box; - text_box = input->children->children; - form_offset = box_offset = 0; - to_textarea = prev_input->type == GADGET_TEXTAREA; - } + input = prev_input->box; + text_box = input->children->children; + box_offset = 0; + to_textarea = prev_input->type == GADGET_TEXTAREA; + } break; case 21: /* Ctrl + U */ - text_box->text[0] = 0; - text_box->length = 0; + /* Clear the selection, if one exists */ + if (selection_exists) + selection_clear(bw->sel, false); + + textarea_cut(bw, text_box, 0, text_box, text_box->length); box_offset = 0; - input->gadget->value[0] = 0; - input->gadget->length = 0; - form_offset = 0; changed = true; break; @@ -995,93 +1018,98 @@ bool browser_window_input_callback(struct browser_window *bw, /* screen updated and caret repositioned already */ return true; + case 24: /* Ctrl + X */ + { + size_t 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; + box_offset = start_idx; + changed = true; + } + } + break; case KEY_RIGHT: - /* Text box */ + if (selection_exists) { + box_offset = end_offset; + break; + } + /* Go to the next valid UTF-8 character */ box_offset = utf8_next(text_box->text, text_box->length, box_offset); - /* Gadget */ - /* Go to the next valid UTF-8 character */ - form_offset = utf8_next(input->gadget->value, - input->gadget->length, form_offset); break; case KEY_LEFT: - /* Text box */ + /* If there is a selection, caret should remain at start */ + if (selection_exists) + break; + /* Go to the previous valid UTF-8 character */ box_offset = utf8_prev(text_box->text, box_offset); - /* Gadget */ - /* Go to the previous valid UTF-8 character */ - form_offset = utf8_prev(input->gadget->value, form_offset); break; case KEY_LINE_START: - box_offset = form_offset = 0; + box_offset = 0; break; case KEY_LINE_END: box_offset = text_box->length; - form_offset = input->gadget->length; break; - case KEY_WORD_LEFT: { - size_t 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 { + case KEY_WORD_LEFT: + /* If there is a selection, caret should remain at start */ + if (selection_exists) + break; + + if (!word_left(text_box->text, &box_offset, NULL)) box_offset = 0; - form_offset = 0; + + break; + + case KEY_WORD_RIGHT: + if (selection_exists) { + box_offset = end_offset; + break; } - } - break; - - case KEY_WORD_RIGHT: { - size_t 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 { + + if (!word_right(text_box->text, text_box->length, + &box_offset, NULL)) box_offset = text_box->length; - form_offset = input->gadget->length; - } - } - break; + + break; case KEY_DELETE_LINE_START: - if (box_offset <= 0) + if (selection_exists) + selection_clear(bw->sel, true); + + if (box_offset == 0) return true; - /* Text box */ - textbox_delete(bw, text_box, 0, box_offset); + textarea_cut(bw, text_box, 0, text_box, 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 (selection_exists) + selection_clear(bw->sel, true); + if (box_offset >= text_box->length) return true; - /* Text box */ - textbox_delete(bw, text_box, box_offset, text_box->length - box_offset); - /* Gadget */ - input->gadget->length = form_offset; - input->gadget->value[form_offset] = 0; + textarea_cut(bw, text_box, box_offset, + text_box, text_box->length); + changed = true; break; @@ -1089,10 +1117,8 @@ bool browser_window_input_callback(struct browser_window *bw, return false; } - input->gadget->caret_form_offset = form_offset; - - input_update_display(bw, input, form_offset, box_offset, - to_textarea, changed); + selection_clear(bw->sel, true); + input_update_display(bw, input, box_offset, to_textarea, changed); return true; } @@ -1137,8 +1163,54 @@ void browser_window_remove_caret(struct browser_window *bw) bw->paste_callback = NULL; bw->move_callback = NULL; bw->caret_p = NULL; + + selection_clear(bw->sel, true); } +/** + * Calculates the form_offset from the box_offset + * + * \param input The root box containing both the textbox and gadget + * \param text_box The textbox containing the caret + * \param char_offset The caret offset within text_box + * \return the translated form_offset + */ + +size_t get_form_offset(struct box* input, struct box* text_box, + size_t char_offset) +{ + int uchars; + unsigned int offset; + + for (uchars = 0, offset = 0; offset < char_offset; uchars++) { + if ((text_box->text[offset] & 0x80) == 0x00) { + offset++; + continue; + } + assert((text_box->text[offset] & 0xC0) == 0xC0); + for (++offset; offset < char_offset && + (text_box->text[offset] & 0xC0) == 0x80; + offset++) + /* do nothing */; + } + /* uchars is the number of real Unicode characters at the left + * side of the caret. + */ + for (offset = 0; uchars > 0 && offset < input->gadget->length; + uchars--) { + if ((input->gadget->value[offset] & 0x80) == 0x00) { + offset++; + continue; + } + assert((input->gadget->value[offset] & 0xC0) == 0xC0); + for (++offset; offset < input->gadget->length && + (input->gadget->value[offset] & 0xC0) == 0x80; + offset++) + /* do nothing */; + } + assert(uchars == 0); + return offset; +} /** * Handle key presses in a browser window. @@ -1168,13 +1240,15 @@ bool browser_window_key_press(struct browser_window *bw, uint32_t key) selection_clear(bw->sel, true); return true; } - /* if there's no selection, leave Escape for the caller */ + /* if there's no selection, + * leave Escape for the caller */ return false; } /* pass on to the appropriate field */ if (!bw->caret_callback) return false; + return bw->caret_callback(bw, key, bw->caret_p); } @@ -1194,6 +1268,7 @@ bool browser_window_paste_text(struct browser_window *bw, const char *utf8, { if (!bw->paste_callback) return false; + return bw->paste_callback(bw, utf8, utf8_len, last, bw->caret_p); } @@ -1254,7 +1329,8 @@ bool browser_window_textarea_paste_text(struct browser_window *bw, char_offset = 0; /* handle CR/LF and LF/CR terminations */ - if ((*p == '\n' && p[1] == '\r') || (*p == '\r' && p[1] == '\n')) + if ((*p == '\n' && p[1] == '\r') || + (*p == '\r' && p[1] == '\n')) p++; utf8 = ++p; } @@ -1269,26 +1345,33 @@ bool browser_window_textarea_paste_text(struct browser_window *bw, /* reflow textarea preserving width and height */ textarea_reflow(bw, textarea, inline_container); /* reflowing may have broken our caret offset - this bit should hopefully continue to work if textarea_reflow - is fixed to update the caret itself */ + * this bit should hopefully continue to work if + * textarea_reflow is fixed to update the caret itself */ char_offset = textarea->gadget->caret_box_offset; text_box = textarea->gadget->caret_text_box; - while((char_offset > text_box->length+text_box->space) && (text_box->next) && (text_box->next->type == BOX_TEXT)) - { - LOG(("Caret out of range: Was %d in boxlen %d space %d",char_offset,text_box->length,text_box->space)); - char_offset -= text_box->length+text_box->space; + + while ((char_offset > text_box->length + text_box->space) && + (text_box->next) && + (text_box->next->type == BOX_TEXT)) { + LOG(("Caret out of range: Was %d in boxlen %d " + "space %d", char_offset, + text_box->length, text_box->space)); + char_offset -= text_box->length + text_box->space; text_box = text_box->next; } - /* not sure if this will happen or not... but won't stick an assert here as we can recover from it */ - if(char_offset > text_box->length) - { - LOG(("Caret moved beyond end of line: Was %d in boxlen %d",char_offset,text_box->length)); + + /* not sure if this will happen or not... + * but won't stick an assert here as we can recover from it */ + if (char_offset > text_box->length) { + LOG(("Caret moved beyond end of line: " + "Was %d in boxlen %d", char_offset, + text_box->length)); char_offset = text_box->length; } + textarea->gadget->caret_text_box = text_box; textarea->gadget->caret_box_offset = char_offset; - nsfont_width(text_box->style, text_box->text, char_offset, &pixel_offset); @@ -1312,7 +1395,6 @@ bool browser_window_textarea_paste_text(struct browser_window *bw, textarea); browser_redraw_box(bw->current_content, textarea); - } return success; @@ -1335,8 +1417,7 @@ bool browser_window_input_paste_text(struct browser_window *bw, { struct box *input = handle; struct box *text_box = input->children->children; - unsigned int box_offset = input->gadget->caret_box_offset; - unsigned int form_offset = input->gadget->caret_form_offset; + size_t box_offset = input->gadget->caret_box_offset; unsigned int nchars = utf8_length(input->gadget->value); const char *ep = utf8 + utf8_len; const char *p = utf8; @@ -1351,7 +1432,6 @@ bool browser_window_input_paste_text(struct browser_window *bw, char buf[80 + 6]; int nbytes = 0; unsigned utf8_len; - char *value; /* how many more chars can we insert in one go? */ while (p < ep && nbytes < 80 && @@ -1373,26 +1453,7 @@ bool browser_window_input_paste_text(struct browser_window *bw, } utf8_len = p - utf8; - - value = realloc(input->gadget->value, - input->gadget->length + utf8_len + 1); - if (!value) { - /* we still need to update the screen */ - warn_user("NoMemory", 0); - update = true; - success = false; - break; - } - input->gadget->value = value; - - memmove(input->gadget->value + form_offset + utf8_len, - input->gadget->value + form_offset, - input->gadget->length - form_offset); - memcpy(input->gadget->value + form_offset, utf8, utf8_len); - input->gadget->length += utf8_len; - input->gadget->value[input->gadget->length] = 0; - form_offset += utf8_len; - + if (!textbox_insert(bw, text_box, box_offset, buf, nbytes)) { /* we still need to update the screen */ update = true; @@ -1400,6 +1461,9 @@ bool browser_window_input_paste_text(struct browser_window *bw, break; } box_offset += nbytes; + /* Keep caret_form_offset in sync -- textbox_insert uses this + * to determine where to insert into the gadget's value */ + input->gadget->caret_form_offset += nbytes; /* handle CR/LF and LF/CR terminations */ if (*p == '\n') { @@ -1414,7 +1478,7 @@ bool browser_window_input_paste_text(struct browser_window *bw, } if (update) - input_update_display(bw, input, form_offset, box_offset, false, true); + input_update_display(bw, input, box_offset, false, true); return success; } @@ -1501,8 +1565,7 @@ void browser_window_input_move_caret(struct browser_window *bw, void *p) */ void input_update_display(struct browser_window *bw, struct box *input, - unsigned form_offset, unsigned box_offset, bool to_textarea, - bool redraw) + unsigned box_offset, bool to_textarea, bool redraw) { struct box *text_box = input->children->children; unsigned pixel_offset; @@ -1517,11 +1580,15 @@ void input_update_display(struct browser_window *bw, struct box *input, nsfont_width(text_box->style, text_box->text, box_offset, (int *) &pixel_offset); + + /* Shift text box horizontally, so caret is visible */ dx = text_box->x; text_box->x = 0; if (input->width < text_box->width && - input->width / 2 < (int)pixel_offset) { + input->width / 2 < (int) pixel_offset) { + /* Make caret appear in centre of text input */ text_box->x = input->width / 2 - pixel_offset; + /* Clamp if we've shifted too far left */ if (text_box->x < input->width - text_box->width) text_box->x = input->width - text_box->width; } @@ -1535,7 +1602,6 @@ void input_update_display(struct browser_window *bw, struct box *input, } input->gadget->caret_box_offset = box_offset; - input->gadget->caret_form_offset = form_offset; browser_window_place_caret(bw, box_x + input->children->x + @@ -1561,7 +1627,7 @@ void input_update_display(struct browser_window *bw, struct box *input, * * \param bw browser window * \param text_box text box - * \param char_offset offsets (bytes) at which to insert text + * \param char_offset offset (bytes) at which to insert text * \param utf8 UTF-8 text to insert * \param utf8_len length (bytes) of UTF-8 text to insert * \return true iff successful @@ -1571,7 +1637,33 @@ bool textbox_insert(struct browser_window *bw, struct box *text_box, unsigned char_offset, const char *utf8, unsigned utf8_len) { char *text; + struct box *input = text_box->parent->parent; + + if (bw->sel->defined) + delete_selection(bw->sel); + + /* insert into form gadget (text and password inputs only) */ + if (input->gadget && (input->gadget->type == GADGET_TEXTBOX || + input->gadget->type == GADGET_PASSWORD) && + input->gadget->value) { + size_t form_offset = input->gadget->caret_form_offset; + char *value = realloc(input->gadget->value, + input->gadget->length + utf8_len + 1); + if (!value) { + warn_user("NoMemory", 0); + return true; + } + input->gadget->value = value; + memmove(input->gadget->value + form_offset + utf8_len, + input->gadget->value + form_offset, + input->gadget->length - form_offset); + memcpy(input->gadget->value + form_offset, utf8, utf8_len); + input->gadget->length += utf8_len; + input->gadget->value[input->gadget->length] = 0; + } + + /* insert in text box */ text = talloc_realloc(bw->current_content, text_box->text, char, text_box->length + text_box->space + utf8_len + 1); @@ -1597,15 +1689,15 @@ bool textbox_insert(struct browser_window *bw, struct box *text_box, text_box->length - char_offset); } - memcpy(text_box->text + char_offset, utf8, utf8_len); + memcpy(text_box->text + char_offset, + input->gadget->type == GADGET_PASSWORD ? "*": utf8, + utf8_len); text_box->length += utf8_len; - /* nothing should assume that the text is terminated, but just in case */ + /* nothing should assume that the text is terminated, + * but just in case */ text_box->text[text_box->length] = 0; - selection_update(bw->sel, text_box->byte_offset + char_offset, - utf8_len, false); - text_box->width = UNKNOWN_WIDTH; return true; @@ -1619,12 +1711,40 @@ bool textbox_insert(struct browser_window *bw, struct box *text_box, * \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 + * \return true on success, false otherwise + * + * ::char_offset and ::utf8_len are only considered when there is no selection. + * If there is a selection, the entire selected area is deleted. */ bool textbox_delete(struct browser_window *bw, struct box *text_box, unsigned char_offset, unsigned utf8_len) { unsigned next_offset = char_offset + utf8_len; + struct box *form = text_box->parent->parent; + + if (bw->sel->defined) { + delete_selection(bw->sel); + return true; + } + + /* delete from form gadget (text and password inputs only) */ + if (form->gadget && (form->gadget->type == GADGET_TEXTBOX || + form->gadget->type == GADGET_PASSWORD) && + form->gadget->value) { + size_t form_offset = get_form_offset(form, text_box, + char_offset); + size_t next_offset = get_form_offset(form, text_box, + char_offset + utf8_len); + + memmove(form->gadget->value + form_offset, + form->gadget->value + next_offset, + form->gadget->length - next_offset); + form->gadget->length -= utf8_len; + form->gadget->value[form->gadget->length] = 0; + } + + /* delete from visible textbox */ if (next_offset <= text_box->length + text_box->space) { /* handle removal of trailing space */ if (text_box->space && next_offset > text_box->length) { @@ -1635,27 +1755,27 @@ bool textbox_delete(struct browser_window *bw, struct box *text_box, char_offset = tmp; else text_box->space = false; - } - else + } else { text_box->space = false; + } + text_box->length = char_offset; - } - else { + } else { memmove(text_box->text + char_offset, text_box->text + next_offset, text_box->length - next_offset); text_box->length -= utf8_len; } - /* nothing should assume that the text is terminated, but just in case */ + /* nothing should assume that the text is terminated, + * but just in case */ text_box->text[text_box->length] = 0; - selection_update(bw->sel, text_box->byte_offset + char_offset, - -(int)utf8_len, false); - text_box->width = UNKNOWN_WIDTH; + return true; } + return false; } @@ -1673,22 +1793,53 @@ bool textbox_delete(struct browser_window *bw, struct box *text_box, bool delete_handler(struct browser_window *bw, struct box *b, int offset, size_t length) { - if (offset <= 0 && length >= b->length + b->space) { - selection_update(bw->sel, b->byte_offset, - -(b->length + b->space), false); + size_t text_length = b->length + b->space; + /* only remove if its not the first box */ + if (offset <= 0 && length >= text_length && b->prev != NULL) { /* remove the entire box */ box_unlink_and_free(b); return true; - } - else { - return textbox_delete(bw, b, offset, length); - } + } else + return textbox_delete(bw, b, offset, + min(length, text_length - offset)); } /** + * Remove the selected text from a text box and gadget (if applicable) + * + * \param s The selection to be removed + */ + +void delete_selection(struct selection *s) +{ + assert(s->defined); + size_t start_offset, end_offset; + struct box *text_box = selection_get_start(s, &start_offset); + struct box *end_box = selection_get_end(s, &end_offset); + struct box *next; + size_t sel_len = s->end_idx - s->start_idx; + int beginning = 0; + + /* Clear selection so that deletion from textboxes proceeds */ + selection_clear(s, true); + + /* handle first box */ + delete_handler(s->bw, text_box, start_offset, sel_len); + if (text_box == end_box) + return; + + for (text_box = text_box->next; text_box != end_box; text_box = next) { + next = text_box->next; + box_unlink_and_free(text_box); + } + + delete_handler(s->bw, end_box, beginning, end_offset); +} + +/** * Locate the first inline box at the start of this line * * \param text_box text box from which to start searching @@ -1760,8 +1911,8 @@ struct box *line_below(struct box *text_box) * \param char_offset offset (in bytes) at which text box is to be split */ -struct box *textarea_insert_break(struct browser_window *bw, struct box *text_box, - size_t char_offset) +struct box *textarea_insert_break(struct browser_window *bw, + struct box *text_box, size_t char_offset) { struct box *new_br, *new_text; char *text = talloc_array(bw->current_content, char, @@ -1804,6 +1955,7 @@ struct box *textarea_insert_break(struct browser_window *bw, struct box *text_bo * \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 + * \return true iff successful */ bool textarea_cut(struct browser_window *bw, @@ -1827,8 +1979,7 @@ bool textarea_cut(struct browser_window *bw, return false; } box_unlink_and_free(box); - } - else { + } 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)) { @@ -1842,10 +1993,10 @@ bool textarea_cut(struct browser_window *bw, gui_commit_clipboard(); return false; } - } - else + } else { textbox_delete(bw, box, start_idx, - (box->length + box->space) - start_idx); + (box->length + box->space) - start_idx); + } } del = true; @@ -1855,16 +2006,19 @@ bool textarea_cut(struct browser_window *bw, /* and the last box */ if (box) { - if (gui_add_to_clipboard(box->text + start_idx, end_idx - start_idx, box->space)) { + if (gui_add_to_clipboard(box->text + start_idx, + end_idx - start_idx, end_idx > box->length)) { if (del) { - if (!delete_handler(bw, box, start_idx, end_idx - start_idx)) + if (!delete_handler(bw, box, start_idx, + end_idx - start_idx)) success = false; + } else { + textbox_delete(bw, box, start_idx, + end_idx - start_idx); } - else - textbox_delete(bw, box, start_idx, end_idx - start_idx); - } - else + } else { success = false; + } } return gui_commit_clipboard() ? success : false; @@ -1910,12 +2064,14 @@ bool word_left(const char *text, size_t *poffset, size_t *pchars) bool success = false; size_t nchars = 0; + /* Skip any spaces immediately prior to the offset */ while (offset > 0) { offset = utf8_prev(text, offset); nchars++; if (!isspace(text[offset])) break; } + /* Now skip all non-space characters */ while (offset > 0) { size_t prev = utf8_prev(text, offset); success = true; @@ -1948,12 +2104,14 @@ bool word_right(const char *text, size_t len, size_t *poffset, size_t *pchars) bool success = false; size_t nchars = 0; + /* Skip all non-space characters after the offset */ while (offset < len) { if (isspace(text[offset])) break; offset = utf8_next(text, len, offset); nchars++; } + /* Now skip all space characters */ while (offset < len) { offset = utf8_next(text, len, offset); nchars++; @@ -1977,30 +2135,44 @@ bool word_right(const char *text, size_t len, size_t *poffset, size_t *pchars) bool ensure_caret_visible(struct box *textarea) { - int cx,cy; - int scrollx,scrolly; + int cx, cy; + int scrollx, scrolly; + + assert(textarea->gadget); + scrollx = textarea->scroll_x; scrolly = textarea->scroll_y; - assert(textarea->gadget); + /* Calculate the caret coordinates */ - cx = textarea->gadget->caret_pixel_offset+textarea->gadget->caret_text_box->x; + cx = textarea->gadget->caret_pixel_offset + + textarea->gadget->caret_text_box->x; cy = textarea->gadget->caret_text_box->y; + /* Ensure they are visible */ - if (!box_hscrollbar_present(textarea)) + if (!box_hscrollbar_present(textarea)) { scrollx = 0; - else if (cx-textarea->scroll_x < 0) + } else if (cx-textarea->scroll_x < 0) { scrollx = cx; - else if (cx > textarea->scroll_x+textarea->width) - scrollx = cx-textarea->width; - if (!box_vscrollbar_present(textarea)) + } else if (cx > textarea->scroll_x + textarea->width) { + scrollx = cx - textarea->width; + } + + if (!box_vscrollbar_present(textarea)) { scrolly = 0; - else if (cy-textarea->scroll_y < 0) + } else if (cy - textarea->scroll_y < 0) { scrolly = cy; - else if (cy+textarea->gadget->caret_text_box->height > textarea->scroll_y+textarea->height) - scrolly = (cy+textarea->gadget->caret_text_box->height)-textarea->height; + } else if (cy + textarea->gadget->caret_text_box->height > + textarea->scroll_y + textarea->height) { + scrolly = (cy + textarea->gadget->caret_text_box->height) - + textarea->height; + } + if ((scrollx == textarea->scroll_x) && (scrolly == textarea->scroll_y)) return false; + textarea->scroll_x = scrollx; textarea->scroll_y = scrolly; + return true; } + diff --git a/gtk/dialogs/gtk_options.c b/gtk/dialogs/gtk_options.c index e7ed37eec..a40e49e2d 100644 --- a/gtk/dialogs/gtk_options.c +++ b/gtk/dialogs/gtk_options.c @@ -52,7 +52,6 @@ DECLARE(checkDisablePopups); DECLARE(checkDisablePlugins); DECLARE(spinHistoryAge); DECLARE(checkHoverURLs); -DECLARE(checkRequestOverwrite); DECLARE(checkDisplayRecentURLs); DECLARE(checkSendReferer); @@ -82,6 +81,10 @@ DECLARE(fontPreview); DECLARE(spinMemoryCacheSize); DECLARE(spinDiscCacheAge); +DECLARE(checkClearDownloads); +DECLARE(checkRequestOverwrite); +DECLARE(fileChooserDownloads); + /* Used when the feature is not implemented yet */ #define FIND_WIDGET(x) (x) = glade_xml_get_widget(gladeFile, #x); \ if ((x) == NULL) LOG(("Unable to find widget '%s'!", #x)) @@ -112,7 +115,6 @@ GtkDialog* nsgtk_options_init(struct browser_window *bw, GtkWindow *parent) { //CONNECT(checkDisablePlugins, "toggled"); //CONNECT(spinHistoryAge, "focus-out-event"); //CONNECT(checkHoverURLs, "toggled"); - //CONNECT(checkRequestOverwrite, "toggled"); CONNECT(checkDisplayRecentURLs, "toggled"); CONNECT(checkSendReferer, "toggled"); @@ -142,6 +144,10 @@ GtkDialog* nsgtk_options_init(struct browser_window *bw, GtkWindow *parent) { CONNECT(spinMemoryCacheSize, "value-changed"); CONNECT(spinDiscCacheAge, "value-changed"); + CONNECT(checkClearDownloads, "toggled"); + CONNECT(checkRequestOverwrite, "toggled"); + CONNECT(fileChooserDownloads, "current-folder-changed"); + g_signal_connect(G_OBJECT(wndPreferences), "response", G_CALLBACK (dialog_response_handler), NULL); @@ -166,8 +172,11 @@ GtkDialog* nsgtk_options_init(struct browser_window *bw, GtkWindow *parent) { gtk_combo_box_set_active(GTK_COMBO_BOX((x)), (y)) #define SET_FONT(x, y) (x) = glade_xml_get_widget(gladeFile, #x); \ gtk_font_button_set_font_name(GTK_FONT_BUTTON((x)), (y)) +#define SET_FILE_CHOOSER(x, y) (x) = glade_xml_get_widget(gladeFile, #x); \ + gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER((x)), (y)) #define SET_BUTTON(x) (x) = glade_xml_get_widget(gladeFile, #x); + void nsgtk_options_load(void) { char b[20]; int proxytype = 0; @@ -219,6 +228,10 @@ void nsgtk_options_load(void) { SET_SPIN(spinMemoryCacheSize, option_memory_cache_size >> 20); SET_SPIN(spinDiscCacheAge, option_disc_cache_age); + + SET_CHECK(checkClearDownloads, option_downloads_clear); + SET_CHECK(checkRequestOverwrite, option_request_overwrite); + SET_FILE_CHOOSER(fileChooserDownloads, option_downloads_directory); } static void dialog_response_handler (GtkDialog *dlg, gint res_id){ @@ -261,6 +274,9 @@ static gboolean on_dialog_close (GtkDialog *dlg, gboolean stay_alive){ LOG(("Signal emitted on '%s'", #x)); \ if ((y)) free((y)); \ (y) = strdup(gtk_font_button_get_font_name(GTK_FONT_BUTTON((x)))); +#define FILE_CHOOSER_CHANGED(x, y) gboolean on_##x##_changed(GtkWidget *widget, gpointer data) { \ + LOG(("Signal emitted on '%s'", #x)); \ + (y) = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER((x))); #define BUTTON_CLICKED(x) gboolean on_##x##_changed(GtkWidget *widget, gpointer data) { \ LOG(("Signal emitted on '%s'", #x)); @@ -362,3 +378,7 @@ BUTTON_CLICKED(fontPreview) SPIN_CHANGED(spinMemoryCacheSize, option_memory_cache_size) option_memory_cache_size <<= 20;} SPIN_CHANGED(spinDiscCacheAge, option_disc_cache_age)} + +CHECK_CHANGED(checkClearDownloads, option_downloads_clear)} +CHECK_CHANGED(checkRequestOverwrite, option_request_overwrite)} +FILE_CHOOSER_CHANGED(fileChooserDownloads, option_downloads_directory)} diff --git a/gtk/gtk_download.c b/gtk/gtk_download.c index ab16d6786..e84b6d2fd 100644 --- a/gtk/gtk_download.c +++ b/gtk/gtk_download.c @@ -17,113 +17,587 @@ */ #include <gtk/gtk.h> +#include <glib/gstdio.h> #include "utils/log.h" - +#include "utils/utils.h" +#include "utils/url.h" +#include "utils/messages.h" +#include "content/fetch.h" +#include "desktop/gui.h" #include "gtk/gtk_gui.h" +#include "gtk/gtk_scaffolding.h" +#include "gtk/options.h" #include "gtk/gtk_download.h" -static GtkWindow *wndDownload; -static GtkTreeView *treeDownloads; -static GtkListStore *list_downloads; +#define GLADE_NAME "downloads.glade" -enum { - COLUMN_URL = 0, - COLUMN_DESTINATION, - COLUMN_PERCENTAGE, - COLUMN_DESCRIPTION, +static GtkWindow *nsgtk_download_window, *nsgtk_download_parent; +static GtkWidget *nsgtk_download_progressBar; - N_COLUMNS -}; +static GtkTreeView *nsgtk_download_tree; +static GtkListStore *nsgtk_download_store; +static GtkTreeSelection *nsgtk_download_selection; +static GtkTreeIter nsgtk_download_iter; +static GList *nsgtk_download_buttons; +static gint nsgtk_downloads; +gchar* status_messages[] = { "gtkWorking", "gtkError", "gtkComplete", + "gtkCanceled" }; -void nsgtk_downloadPause_clicked(GtkToolButton *button, gpointer data); +static gboolean nsgtk_download_hide (GtkWidget *window); + +static GtkTreeView *nsgtk_download_tree_view_new(GladeXML *gladeFile); +static void nsgtk_download_tree_view_row_activated(GtkTreeView *tree, + GtkTreePath *path, GtkTreeViewColumn *column, gpointer data); + +static void nsgtk_download_store_update_item(struct gui_download_window *dl); +static void nsgtk_download_store_create_item (struct gui_download_window *dl); +static void nsgtk_download_store_clear_item (struct gui_download_window *dl); +static void nsgtk_download_store_cancel_item (struct gui_download_window *dl); + +static void nsgtk_download_selection_do(GtkWidget *button, + selection_action action); + +static void nsgtk_download_sensitivity_set(struct gui_download_window *dl, + nsgtk_download_actions sensitivity); +static void nsgtk_download_sensitivity_selection_changed( + GtkTreeSelection *selection); +static void nsgtk_download_sensitivity_update_buttons( + nsgtk_download_actions sensitivity); + +static gchar* nsgtk_download_dialog_show (gchar *filename, gchar *domain, + gchar *size); +static gchar* nsgtk_download_info_to_string (struct gui_download_window *dl); +static gchar* nsgtk_download_time_to_string (gint seconds); +static gboolean nsgtk_download_handle_error (GError *error); -static void nsgtk_download_add(const char *url, const char *dest, int prog, const char *desc) -{ - GtkTreeIter iter; - gtk_list_store_append(list_downloads, &iter); - gtk_list_store_set(list_downloads, &iter, - COLUMN_URL, url, - COLUMN_DESTINATION, dest, - COLUMN_PERCENTAGE, prog, - COLUMN_DESCRIPTION, desc, - -1); -} -void nsgtk_download_initialise(void) +void nsgtk_download_init() { - wndDownload = GTK_WINDOW(glade_xml_get_widget(gladeWindows, - "wndDownloads")); - treeDownloads = GTK_TREE_VIEW(glade_xml_get_widget(gladeWindows, - "treeDownloads")); + gchar *glade_location = g_strconcat(res_dir_location, GLADE_NAME, NULL); + GladeXML *gladeFile = glade_xml_new(glade_location, NULL, NULL); + g_free(glade_location); + + nsgtk_download_buttons = + glade_xml_get_widget_prefix(gladeFile, "button"); + nsgtk_download_progressBar = + glade_xml_get_widget(gladeFile, "progressBar"); + nsgtk_download_window = GTK_WINDOW(glade_xml_get_widget(gladeFile, + "wndDownloads")); + nsgtk_download_parent = NULL; + + gtk_window_set_transient_for(GTK_WINDOW(nsgtk_download_window), + nsgtk_download_parent); + gtk_window_set_destroy_with_parent(GTK_WINDOW(nsgtk_download_window), + FALSE); - gtk_tool_item_set_expand(GTK_TOOL_ITEM( - glade_xml_get_widget(gladeWindows, "toolProgress")), TRUE); + nsgtk_download_tree = nsgtk_download_tree_view_new(gladeFile); - list_downloads = gtk_list_store_new(N_COLUMNS, - G_TYPE_STRING, /* URL */ - G_TYPE_STRING, /* Destination */ + nsgtk_download_store = gtk_list_store_new(NSGTK_DOWNLOAD_N_COLUMNS, G_TYPE_INT, /* % complete */ - G_TYPE_STRING /* time left */ + G_TYPE_STRING, /* Description */ + G_TYPE_STRING, /* Time remaining */ + G_TYPE_STRING, /* Speed */ + G_TYPE_STRING, /* Status */ + G_TYPE_POINTER /* Download structure */ ); - gtk_tree_view_set_model(treeDownloads, GTK_TREE_MODEL(list_downloads)); - - gtk_tree_view_insert_column_with_attributes(treeDownloads, -1, - "URL", - gtk_cell_renderer_text_new(), - "ellipsize", PANGO_ELLIPSIZE_START, - "text", - COLUMN_URL, - NULL); - -/* gtk_tree_view_insert_column_with_attributes(treeDownloads, -1, - "Destination", - gtk_cell_renderer_text_new(), - "text", - COLUMN_DESTINATION, - NULL);*/ - - gtk_tree_view_insert_column_with_attributes(treeDownloads, -1, - "Progress", - gtk_cell_renderer_progress_new(), - "value", - COLUMN_PERCENTAGE, - NULL); - - gtk_tree_view_insert_column_with_attributes(treeDownloads, -1, - "Details", - gtk_cell_renderer_text_new(), - "text", - COLUMN_DESCRIPTION, - NULL); - - /* add some fake entries to play about with */ - nsgtk_download_add("http://www.netsurf-browser.org/downloads/netsurf-1.0.zip", - "/home/rjek/Downloads/netsurf-1.0.zip", - 23, - "500kB of 2MB, 120kB/sec, 12 seconds"); + gtk_tree_view_set_model(nsgtk_download_tree, + GTK_TREE_MODEL(nsgtk_download_store)); + g_object_unref(nsgtk_download_store); + + nsgtk_download_selection = + gtk_tree_view_get_selection(nsgtk_download_tree); + gtk_tree_selection_set_mode(nsgtk_download_selection, + GTK_SELECTION_MULTIPLE); + + g_signal_connect(G_OBJECT(nsgtk_download_selection), "changed", + G_CALLBACK(nsgtk_download_sensitivity_selection_changed), + NULL); + g_signal_connect(G_OBJECT(nsgtk_download_window), "delete-event", + G_CALLBACK(nsgtk_download_hide), NULL); + g_signal_connect(glade_xml_get_widget(gladeFile, "buttonClear"), + "clicked", G_CALLBACK(nsgtk_download_selection_do), + nsgtk_download_store_clear_item); + g_signal_connect(glade_xml_get_widget(gladeFile, "buttonCancel"), + "clicked", G_CALLBACK(nsgtk_download_selection_do), + nsgtk_download_store_cancel_item); + g_signal_connect(nsgtk_download_tree, "row-activated", + G_CALLBACK(nsgtk_download_tree_view_row_activated), + NULL); +} + +void nsgtk_download_destroy () +{ + gtk_tree_selection_select_all(nsgtk_download_selection); + nsgtk_download_selection_do(NULL, nsgtk_download_store_cancel_item); +} + +bool nsgtk_check_for_downloads (GtkWindow *parent) +{ + if (nsgtk_downloads != 0) { + GtkWidget *dialog; + dialog = gtk_message_dialog_new_with_markup(parent, + GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, + GTK_BUTTONS_NONE, + "<big><b>Quit NetSurf?</b></big>\n\n" + "<small>There are still downloads running, " + "if you quit now these will be canceled and the" + " files deleted</small>"); + gtk_dialog_add_buttons(GTK_DIALOG(dialog), "gtk-cancel", + GTK_RESPONSE_CANCEL, "gtk-quit", + GTK_RESPONSE_CLOSE, NULL); + + gint response = gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + + if (response == GTK_RESPONSE_CANCEL) + return true; + } + + return false; +} + +void nsgtk_download_show(GtkWindow *parent) +{ + gtk_window_set_transient_for(nsgtk_download_window, + nsgtk_download_parent); + gtk_window_present(nsgtk_download_window); +} + +gboolean nsgtk_download_hide (GtkWidget *window) +{ + gtk_widget_hide(window); + return TRUE; +} + +struct gui_download_window *gui_download_window_create(const char *url, + const char *mime_type, struct fetch *fetch, + unsigned int total_size, struct gui_window *gui) +{ + nsgtk_download_parent = nsgtk_scaffolding_get_window(gui); + struct gui_download_window *download; + gchar *domain; + gchar *filename; + gchar *destination; + gchar *size = (total_size == 0 ? + "Unknown" : human_friendly_bytesize(total_size)); + + if (url_nice(url, &filename, false) != URL_FUNC_OK) + strcpy(filename, messages_get("gtkUnknownFile")); + if (url_host(url, &domain) != URL_FUNC_OK) + strcpy(domain, messages_get("gtkUnknownHost")); + + destination = nsgtk_download_dialog_show(filename, domain, size); + if (destination == NULL) + return NULL; + + download = malloc(sizeof *download); + download->fetch = fetch; + download->name = g_string_new(filename); + download->time_left = g_string_new(""); + download->size_total = total_size; + download->size_downloaded = 0; + download->speed = 0; + download->time_remaining = -1; + download->filename = destination; + download->status = (total_size == 0 ? NSGTK_DOWNLOAD_WORKING : + NSGTK_DOWNLOAD_NONE); + download->progress = (total_size == 0 ? 100 : 0); + download->timer = g_timer_new(); + download->error = NULL; + download->write = g_io_channel_new_file(destination, "w", + &download->error); + if (nsgtk_download_handle_error(download->error)) { + free(download); + return NULL; + } + + if (g_str_has_prefix(mime_type, "text") == FALSE) + g_io_channel_set_encoding(download->write, NULL, + &download->error); + + /* Add the new row and store the reference to it (which keeps track of + * the tree changes) */ + gtk_list_store_append(nsgtk_download_store, &nsgtk_download_iter); + download->row = gtk_tree_row_reference_new( + GTK_TREE_MODEL(nsgtk_download_store), + gtk_tree_model_get_path(GTK_TREE_MODEL + (nsgtk_download_store), &nsgtk_download_iter)); + + nsgtk_download_sensitivity_set(download, NSGTK_DOWNLOAD_CANCEL); + + nsgtk_download_store_create_item(download); + nsgtk_download_show(nsgtk_download_parent); + + nsgtk_downloads++; + return download; +} + + +void gui_download_window_data(struct gui_download_window *dw, const char *data, + unsigned int size) +{ + g_io_channel_write_chars(dw->write, data, size, NULL, &dw->error); + if (dw->error != NULL) { + dw->status = NSGTK_DOWNLOAD_ERROR; + dw->speed = 0; + dw->time_remaining = -1; + dw->sensitivity = NSGTK_DOWNLOAD_CLEAR; + nsgtk_download_store_update_item(dw); + fetch_abort(dw->fetch); + + nsgtk_downloads--; + gtk_window_present(nsgtk_download_window); + return; + } + + dw->size_downloaded += size; + gfloat elapsed = g_timer_elapsed(dw->timer, NULL); + + /* If enough time has gone by, update the window */ + if (elapsed - dw->last_update > .5 && + GTK_WIDGET_VISIBLE(nsgtk_download_window)) { + dw->speed = dw->size_downloaded / elapsed; + dw->time_remaining = (dw->size_total - dw->size_downloaded)/ + dw->speed; + + if (dw->size_total) + dw->progress = (gfloat)(dw->size_downloaded)/ + dw->size_total*100; - nsgtk_download_add("http://www.rjek.com/gniggle.zip", - "/home/rjek/Downloads/gniggle.zip", - 68, - "20kB of 90kB, 63kB/sec, 2 seconds"); + nsgtk_download_store_update_item(dw); + dw->last_update = elapsed; + } +} + + +void gui_download_window_error(struct gui_download_window *dw, + const char *error_msg) +{ +} + + +void gui_download_window_done(struct gui_download_window *dw) +{ + g_io_channel_shutdown(dw->write, TRUE, &dw->error); + g_io_channel_unref(dw->write); + + dw->speed = 0; + dw->progress = 100; + nsgtk_download_sensitivity_set(dw, NSGTK_DOWNLOAD_CLEAR); + dw->status = NSGTK_DOWNLOAD_COMPLETE; + + if (option_downloads_clear) + nsgtk_download_store_clear_item(dw); + else + nsgtk_download_store_update_item(dw); + + nsgtk_downloads--; +} + + +GtkTreeView* nsgtk_download_tree_view_new(GladeXML *gladeFile) +{ + GtkTreeView *treeview = GTK_TREE_VIEW(glade_xml_get_widget(gladeFile, + "treeDownloads")); + GtkCellRenderer *renderer; + gchar *progress, *information, *speed, *remaining; + + /* Progress column */ + renderer = gtk_cell_renderer_progress_new(); + gtk_tree_view_insert_column_with_attributes (treeview, -1, + messages_get("gtkProgress"), renderer, "value", + NSGTK_DOWNLOAD_PROGRESS, "text", + NSGTK_DOWNLOAD_STATUS, NULL); + + /* Information column */ + renderer = gtk_cell_renderer_text_new(); + g_object_set(G_OBJECT(renderer), "wrap-mode", PANGO_WRAP_WORD_CHAR, + "wrap-width", 300, NULL); + gtk_tree_view_insert_column_with_attributes (treeview, -1, + messages_get("gtkDetails"), renderer, "text", + NSGTK_DOWNLOAD_INFO, NULL); + gtk_tree_view_column_set_expand(gtk_tree_view_get_column(treeview, + NSGTK_DOWNLOAD_INFO), TRUE); + + /* Time remaining column */ + renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_insert_column_with_attributes (treeview, -1, + messages_get("gtkRemaining"), renderer, "text", + NSGTK_DOWNLOAD_REMAINING, NULL); + + /* Speed column */ + renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_insert_column_with_attributes (treeview, -1, + messages_get("gtkSpeed"), renderer, "text", + NSGTK_DOWNLOAD_SPEED, NULL); + + return treeview; +} + +void nsgtk_download_tree_view_row_activated(GtkTreeView *tree, + GtkTreePath *path, GtkTreeViewColumn *column, gpointer data) +{ + GtkTreeModel *model; + GtkTreeIter iter; + + model = gtk_tree_view_get_model(tree); + + if (gtk_tree_model_get_iter(model, &iter, path)) { + /* TODO: This will be a context action (pause, start, clear) */ + nsgtk_download_selection_do(NULL, + nsgtk_download_store_clear_item); + } +} + +void nsgtk_download_store_update_item (struct gui_download_window *dl) +{ + gchar *info = nsgtk_download_info_to_string(dl); + gchar *speed = g_strconcat(human_friendly_bytesize(dl->speed), "/s", + NULL); + gchar *time = nsgtk_download_time_to_string(dl->time_remaining); + + /* Updates iter (which is needed to set and get data) with the dl row */ + gtk_tree_model_get_iter(GTK_TREE_MODEL(nsgtk_download_store), + &nsgtk_download_iter, + gtk_tree_row_reference_get_path(dl->row)); + + gtk_list_store_set (nsgtk_download_store, &nsgtk_download_iter, + NSGTK_DOWNLOAD_PROGRESS, dl->progress, + NSGTK_DOWNLOAD_INFO, info, + NSGTK_DOWNLOAD_SPEED, dl->speed == 0 ? "-" : speed, + NSGTK_DOWNLOAD_REMAINING, time, + -1); + if (dl->status != NSGTK_DOWNLOAD_NONE) + gtk_list_store_set(nsgtk_download_store, &nsgtk_download_iter, + NSGTK_DOWNLOAD_STATUS, + messages_get(status_messages[dl->status])); + + g_free(info); + g_free(speed); + g_free(time); +} + +void nsgtk_download_store_create_item (struct gui_download_window *dl) +{ + nsgtk_download_store_update_item(dl); + /* The iter has already been updated to this row */ + gtk_list_store_set (nsgtk_download_store, &nsgtk_download_iter, + NSGTK_DOWNLOAD, dl, -1); +} + +void nsgtk_download_store_clear_item (struct gui_download_window *dl) +{ + if (dl->sensitivity & NSGTK_DOWNLOAD_CLEAR) { + gtk_tree_model_get_iter(GTK_TREE_MODEL(nsgtk_download_store), + &nsgtk_download_iter, + gtk_tree_row_reference_get_path(dl->row)); + gtk_list_store_remove(nsgtk_download_store, + &nsgtk_download_iter); + g_free(dl); + } +} + +void nsgtk_download_store_cancel_item (struct gui_download_window *dl) +{ + if (dl->sensitivity & NSGTK_DOWNLOAD_CANCEL) { + dl->status = NSGTK_DOWNLOAD_CANCELED; + dl->speed = 0; + dl->progress = 0; + dl->time_remaining = -1; + nsgtk_download_sensitivity_set(dl, NSGTK_DOWNLOAD_CLEAR); + + if (dl->fetch) + fetch_abort(dl->fetch); + + g_unlink(dl->filename); + + nsgtk_download_store_update_item(dl); + + nsgtk_downloads--; + } +} + +void nsgtk_download_selection_do(GtkWidget *button, selection_action action) +{ + GList *rows, *dls = NULL; + GtkTreeModel *model = GTK_TREE_MODEL(nsgtk_download_store); + + rows = gtk_tree_selection_get_selected_rows(nsgtk_download_selection, + &model); + while (rows != NULL) { + struct gui_download_window *dl; + gtk_tree_model_get_iter(GTK_TREE_MODEL(nsgtk_download_store), + &nsgtk_download_iter, (GtkTreePath*)rows->data); + gtk_tree_model_get(GTK_TREE_MODEL(nsgtk_download_store), + &nsgtk_download_iter, NSGTK_DOWNLOAD, + &dl, -1); + dls = g_list_prepend(dls, dl); + + rows = rows->next; + } + g_list_foreach(dls, (GFunc)action, NULL); - nsgtk_download_add("http://www.whopper.com/biggy.iso", - "/home/rjek/Downlaods/biggy.iso", - 2, - "2MB of 1923MB, 3kB/sec, 20 hours"); + g_list_foreach(rows, (GFunc)gtk_tree_path_free, NULL); + g_list_foreach(rows, (GFunc)g_free, NULL); + g_list_free(rows); + g_list_free(dls); +} +void nsgtk_download_sensitivity_set(struct gui_download_window *dl, + nsgtk_download_actions sensitivity) +{ + dl->sensitivity = sensitivity; + if (gtk_tree_selection_path_is_selected(nsgtk_download_selection, + gtk_tree_row_reference_get_path(dl->row))) + nsgtk_download_sensitivity_update_buttons(sensitivity); + } -void nsgtk_download_show(void) +void nsgtk_download_sensitivity_selection_changed (GtkTreeSelection *selection) +{ + GtkTreeModel *model = GTK_TREE_MODEL(nsgtk_download_store); + GtkTreeIter iter; + GList *rows; + nsgtk_download_actions sensitivity = 0; + + rows = gtk_tree_selection_get_selected_rows(selection, &model); + while (rows != NULL) { + struct gui_download_window *dl; + gtk_tree_model_get_iter(model, &iter, (GtkTreePath*)rows->data); + gtk_tree_model_get(model, &iter, NSGTK_DOWNLOAD, &dl, -1); + sensitivity |= dl->sensitivity; + rows = rows->next; + } + + nsgtk_download_sensitivity_update_buttons(sensitivity); +} + +void nsgtk_download_sensitivity_update_buttons( + nsgtk_download_actions sensitivity) +{ + /* Glade seems to pack the buttons in an arbitrary order */ + enum { PAUSE_BUTTON, CLEAR_BUTTON, CANCEL_BUTTON, RESUME_BUTTON }; + + gtk_widget_set_sensitive(g_list_nth_data(nsgtk_download_buttons, + PAUSE_BUTTON), sensitivity & NSGTK_DOWNLOAD_PAUSE); + gtk_widget_set_sensitive(g_list_nth_data(nsgtk_download_buttons, + CLEAR_BUTTON), sensitivity & NSGTK_DOWNLOAD_CLEAR); + gtk_widget_set_sensitive(g_list_nth_data(nsgtk_download_buttons, + CANCEL_BUTTON), sensitivity & NSGTK_DOWNLOAD_CANCEL); + gtk_widget_set_sensitive(g_list_nth_data(nsgtk_download_buttons, + RESUME_BUTTON), sensitivity & NSGTK_DOWNLOAD_RESUME); +} + +gchar* nsgtk_download_dialog_show (gchar *filename, gchar *domain, gchar *size) { - gtk_widget_show_all(GTK_WIDGET(wndDownload)); - gdk_window_raise(GTK_WIDGET(wndDownload)->window); + enum { GTK_RESPONSE_DOWNLOAD, GTK_RESPONSE_SAVE_AS }; + GtkWidget *dialog; + gchar *destination = NULL; + gchar *info = g_strdup_printf(messages_get("gtkInfo"), filename, + domain, size); + + dialog = gtk_message_dialog_new_with_markup(nsgtk_download_parent, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, + "<span size=\"x-large\" weight=\"ultrabold\">%s</span>" + "\n\n<small>%s</small>", + messages_get("gtkStartDownload"), info); + + gtk_dialog_add_buttons(GTK_DIALOG(dialog), GTK_STOCK_SAVE, + GTK_RESPONSE_DOWNLOAD, GTK_STOCK_CANCEL, + GTK_RESPONSE_CANCEL, GTK_STOCK_SAVE_AS, + GTK_RESPONSE_SAVE_AS, NULL); + + gint result = gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + g_free(info); + + switch (result) { + case GTK_RESPONSE_SAVE_AS: { + dialog = gtk_file_chooser_dialog_new + (messages_get("gtkSave"), + nsgtk_download_parent, + GTK_FILE_CHOOSER_ACTION_SAVE, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, + NULL); + gtk_file_chooser_set_current_name + (GTK_FILE_CHOOSER(dialog), filename); + gtk_file_chooser_set_do_overwrite_confirmation + (GTK_FILE_CHOOSER(dialog), + option_request_overwrite); + + gint result = gtk_dialog_run(GTK_DIALOG(dialog)); + if (result == GTK_RESPONSE_ACCEPT) + destination = gtk_file_chooser_get_filename + (GTK_FILE_CHOOSER(dialog)); + gtk_widget_destroy(dialog); + break; + } + case GTK_RESPONSE_DOWNLOAD: { + destination = g_strconcat(option_downloads_directory, + "/", filename, NULL); + break; + } + } + return destination; } -void nsgtk_downloadPause_clicked(GtkToolButton *button, gpointer data) +gchar* nsgtk_download_info_to_string (struct gui_download_window *dl) +{ + if (dl->status != NSGTK_DOWNLOAD_ERROR) + return g_strdup_printf("%s\n%s of %s completed", + dl->name->str, + human_friendly_bytesize(dl->size_downloaded), + dl->size_total == 0 ? "Unknown" : + human_friendly_bytesize(dl->size_total)); + else + return g_strdup_printf("%s\n%s", dl->name->str, + dl->error->message); +} + +gchar* nsgtk_download_time_to_string (gint seconds) { + gint hours, minutes; + if (seconds < 0) + return g_strdup("-"); + + hours = seconds / 3600; + seconds -= hours * 3600; + minutes = seconds / 60; + seconds -= minutes * 60; + + if (hours > 0) + return g_strdup_printf("%u:%02u:%02u", hours, minutes, + seconds); + else + return g_strdup_printf("%u:%02u", minutes, seconds); } + +gboolean nsgtk_download_handle_error (GError *error) +{ + if (error != NULL) { + GtkWidget*dialog; + gchar *message = g_strdup_printf(messages_get("gtkFileError"), + error->message); + + dialog = gtk_message_dialog_new_with_markup + (nsgtk_download_parent, + GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + "<big><b>%s</b></big>\n\n" + "<small>%s</small>", messages_get("gtkFailed"), + message); + + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + return TRUE; + } + return FALSE; +} + + diff --git a/gtk/gtk_download.h b/gtk/gtk_download.h index cb4761f14..b7a6bd699 100644 --- a/gtk/gtk_download.h +++ b/gtk/gtk_download.h @@ -21,7 +21,59 @@ #include <gtk/gtk.h> -void nsgtk_download_initialise(void); -void nsgtk_download_show(void); +enum { + NSGTK_DOWNLOAD_PROGRESS, + NSGTK_DOWNLOAD_INFO, + NSGTK_DOWNLOAD_REMAINING, + NSGTK_DOWNLOAD_SPEED, + NSGTK_DOWNLOAD_STATUS, + NSGTK_DOWNLOAD, + + NSGTK_DOWNLOAD_N_COLUMNS +}; + +typedef enum { + NSGTK_DOWNLOAD_WORKING, + NSGTK_DOWNLOAD_ERROR, + NSGTK_DOWNLOAD_COMPLETE, + NSGTK_DOWNLOAD_CANCELED, + NSGTK_DOWNLOAD_NONE +} nsgtk_download_status; + +typedef enum { + NSGTK_DOWNLOAD_PAUSE = 1 << 0, + NSGTK_DOWNLOAD_RESUME = 1 << 1, + NSGTK_DOWNLOAD_CANCEL = 1 << 2, + NSGTK_DOWNLOAD_CLEAR = 1 << 3 +} nsgtk_download_actions; + +struct gui_download_window { + struct fetch *fetch; + nsgtk_download_actions sensitivity; + nsgtk_download_status status; + + GString *name; + GString *time_left; + gint size_total; + gint size_downloaded; + gint progress; + gfloat last_update; + gfloat time_remaining; + gfloat speed; + gchar *filename; + + GtkTreeRowReference *row; + GTimer *timer; + GIOChannel *write; + GError *error; +}; + +typedef void (*selection_action)(struct gui_download_window *dl); + +void nsgtk_download_init(); +void nsgtk_download_destroy (void); +bool nsgtk_check_for_downloads(GtkWindow *parent); +void nsgtk_download_show(GtkWindow *parent); +void nsgtk_download_add(gchar *url, gchar *destination); #endif diff --git a/gtk/gtk_gui.c b/gtk/gtk_gui.c index 61696a673..2f59fb75b 100644 --- a/gtk/gtk_gui.c +++ b/gtk/gtk_gui.c @@ -267,6 +267,12 @@ void gui_init(int argc, char** argv) LOG(("Using '%s' as certificate path", buf)); option_ca_path = strdup(buf); } + + if (!option_downloads_directory) { + char *home = getenv("HOME"); + LOG(("Using '%s' as download directory", home)); + option_downloads_directory = home; + } find_resource(buf, "messages", "./gtk/res/messages"); LOG(("Using '%s' as Messages file", buf)); @@ -301,7 +307,7 @@ void gui_init(int argc, char** argv) wndWarning = GTK_WINDOW(glade_xml_get_widget(gladeWindows, "wndWarning")); nsgtk_history_init(); - nsgtk_download_initialise(); + nsgtk_download_init(); } @@ -391,6 +397,7 @@ void gui_multitask(void) void gui_quit(void) { + nsgtk_download_destroy(); urldb_save_cookies(option_cookie_jar); urldb_save(option_url_file); free(default_stylesheet_url); @@ -401,31 +408,6 @@ void gui_quit(void) } - -struct gui_download_window *gui_download_window_create(const char *url, - const char *mime_type, struct fetch *fetch, - unsigned int total_size) -{ - return 0; -} - - -void gui_download_window_data(struct gui_download_window *dw, const char *data, - unsigned int size) -{ -} - - -void gui_download_window_error(struct gui_download_window *dw, - const char *error_msg) -{ -} - - -void gui_download_window_done(struct gui_download_window *dw) -{ -} - static void nsgtk_select_menu_clicked(GtkCheckMenuItem *checkmenuitem, gpointer user_data) { diff --git a/gtk/gtk_scaffolding.c b/gtk/gtk_scaffolding.c index 66eefc837..c13acbad5 100644 --- a/gtk/gtk_scaffolding.c +++ b/gtk/gtk_scaffolding.c @@ -102,6 +102,7 @@ struct menu_events { static int open_windows = 0; /**< current number of open browsers */ static struct gtk_scaffolding *current_model; /**< current window for model dialogue use */ +static gboolean nsgtk_window_delete_event(GtkWidget *, gpointer); static void nsgtk_window_destroy_event(GtkWidget *, gpointer); static void nsgtk_window_update_back_forward(struct gtk_scaffolding *); @@ -233,6 +234,14 @@ void nsgtk_attach_menu_handlers(GladeXML *xml, gpointer g) /* event handlers and support functions for them */ +gboolean nsgtk_window_delete_event(GtkWidget *widget, gpointer data) +{ + if (open_windows == 1 && nsgtk_check_for_downloads(GTK_WINDOW(widget))) + return TRUE; + else + return FALSE; +} + void nsgtk_window_destroy_event(GtkWidget *widget, gpointer data) { struct gtk_scaffolding *g = data; @@ -469,20 +478,24 @@ MENUHANDLER(close_window) MENUHANDLER(quit) { - netsurf_quit = true; + struct gtk_scaffolding *gw = (struct gtk_scaffolding *)g; + + if (!nsgtk_check_for_downloads(gw->window)) + netsurf_quit = true; return TRUE; } MENUHANDLER(cut) { struct gtk_scaffolding *gw = (struct gtk_scaffolding *)g; + struct browser_window *bw = nsgtk_get_browser_for_gui(gw->top_level); GtkWidget *focused = gtk_window_get_focus(gw->window); /* If the url bar has focus, let gtk handle it */ if (GTK_IS_EDITABLE (focused)) gtk_editable_cut_clipboard (GTK_EDITABLE(gw->url_bar)); else - /* TODO: Implement Cut functionality */; + browser_window_key_press(bw, 24); } MENUHANDLER(copy) @@ -630,7 +643,8 @@ MENUHANDLER(status_bar) MENUHANDLER(downloads) { - nsgtk_download_show(); + struct gtk_scaffolding *gw = (struct gtk_scaffolding *)g; + nsgtk_download_show(gw->window); return TRUE; } @@ -876,7 +890,7 @@ nsgtk_scaffolding *nsgtk_new_scaffolding(struct gui_window *toplevel) g->status_pane = GTK_PANED(GET_WIDGET("hpaned1")); g->preferences_dialog = NULL; - + /* set this window's size and position to what's in the options, or * or some sensible default if they're not set yet. */ @@ -964,6 +978,7 @@ nsgtk_scaffolding *nsgtk_new_scaffolding(struct gui_window *toplevel) gtk_widget_hide_on_delete, NULL); /* connect signals to handlers. */ + CONNECT(g->window, "delete-event", nsgtk_window_delete_event, NULL); CONNECT(g->window, "destroy", nsgtk_window_destroy_event, g); /* toolbar, URL bar, and menu bar signal handlers */ @@ -1101,6 +1116,11 @@ gboolean nsgtk_scaffolding_is_busy(struct gtk_scaffolding *scaffold) return GTK_WIDGET_SENSITIVE((GTK_WIDGET(scaffold->stop_button))); } +GtkWindow* nsgtk_scaffolding_get_window (struct gui_window *g) +{ + return g->scaffold->window; +} + void nsgtk_scaffolding_popup_menu(struct gtk_scaffolding *g, guint button) { nsgtk_scaffolding_update_edit_actions_sensitivity(g, g->popup_xml, TRUE); diff --git a/gtk/gtk_scaffolding.h b/gtk/gtk_scaffolding.h index 8e3084316..5f886b68a 100644 --- a/gtk/gtk_scaffolding.h +++ b/gtk/gtk_scaffolding.h @@ -29,6 +29,8 @@ nsgtk_scaffolding *nsgtk_new_scaffolding(struct gui_window *toplevel); gboolean nsgtk_scaffolding_is_busy(nsgtk_scaffolding *scaffold); +GtkWindow* nsgtk_scaffolding_get_window (struct gui_window *g); + void nsgtk_attach_toplevel_viewport(nsgtk_scaffolding *g, GtkViewport *vp); void nsgtk_scaffolding_destroy(nsgtk_scaffolding *scaffold); diff --git a/gtk/gtk_selection.c b/gtk/gtk_selection.c index 0e5b397fb..c3631dfc6 100644 --- a/gtk/gtk_selection.c +++ b/gtk/gtk_selection.c @@ -25,9 +25,10 @@ #include "desktop/browser.h" #include "gtk/gtk_selection.h" #include "gtk/gtk_window.h" +#include "utils/utf8.h" -GString *current_selection; -GtkClipboard *clipboard; +static GString *current_selection = NULL; +static GtkClipboard *clipboard; static bool copy_handler(const char *text, size_t length, struct box *box, void *handle, const char *whitespace_text, @@ -63,15 +64,19 @@ bool copy_handler(const char *text, size_t length, struct box *box, bool gui_copy_to_clipboard(struct selection *s) { - current_selection = g_string_new(NULL); clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); if (s->defined && selection_traverse(s, copy_handler, NULL)) - gtk_clipboard_set_text (clipboard, current_selection->str, -1); + gui_commit_clipboard(); return TRUE; } void gui_start_selection(struct gui_window *g) { + if (current_selection == NULL) + current_selection = g_string_new(NULL); + else + g_string_set_size(current_selection, 0); + gtk_widget_grab_focus(GTK_WIDGET(g->drawing_area)); } @@ -80,18 +85,26 @@ void gui_paste_from_clipboard(struct gui_window *g, int x, int y) char *text; clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); text = gtk_clipboard_wait_for_text (clipboard); - if (text) - browser_window_paste_text(g->bw, text, - strlen(text), true); + /* clipboard_wait... converts the string to utf8 for us */ + if (text != NULL) + browser_window_paste_text(g->bw, text, strlen(text), true); + free(text); } bool gui_empty_clipboard(void) { + if (!current_selection) + current_selection = g_string_new(NULL); + else + g_string_set_size(current_selection, 0); + return true; } bool gui_commit_clipboard(void) { + clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); + gtk_clipboard_set_text (clipboard, current_selection->str, -1); return true; } diff --git a/gtk/gtk_window.c b/gtk/gtk_window.c index e7363c3f1..a63341ff5 100644 --- a/gtk/gtk_window.c +++ b/gtk/gtk_window.c @@ -331,8 +331,7 @@ gboolean nsgtk_window_motion_notify_event(GtkWidget *widget, if (g->mouse->state & BROWSER_MOUSE_PRESS_1){ /* Start button 1 drag */ browser_window_mouse_click(g->bw, BROWSER_MOUSE_DRAG_1, - event->x / g->bw->scale, - event->y / g->bw->scale); + g->mouse->pressed_x, g->mouse->pressed_y); /* Replace PRESS with HOLDING and declare drag in progress */ g->mouse->state ^= (BROWSER_MOUSE_PRESS_1 | BROWSER_MOUSE_HOLDING_1); @@ -341,8 +340,7 @@ gboolean nsgtk_window_motion_notify_event(GtkWidget *widget, else if (g->mouse->state & BROWSER_MOUSE_PRESS_2){ /* Start button 2 drag */ browser_window_mouse_click(g->bw, BROWSER_MOUSE_DRAG_2, - event->x / g->bw->scale, - event->y / g->bw->scale); + g->mouse->pressed_x, g->mouse->pressed_y); /* Replace PRESS with HOLDING and declare drag in progress */ g->mouse->state ^= (BROWSER_MOUSE_PRESS_2 | BROWSER_MOUSE_HOLDING_2); @@ -382,9 +380,12 @@ gboolean nsgtk_window_button_press_event(GtkWidget *widget, g->mouse->state |= BROWSER_MOUSE_MOD_1; if (event->state & GDK_CONTROL_MASK) g->mouse->state |= BROWSER_MOUSE_MOD_2; + + g->mouse->pressed_x = event->x / g->bw->scale; + g->mouse->pressed_y = event->y / g->bw->scale; - browser_window_mouse_click(g->bw, g->mouse->state, event->x / g->bw->scale, - event->y / g->bw->scale); + browser_window_mouse_click(g->bw, g->mouse->state, g->mouse->pressed_x, + g->mouse->pressed_y); } gboolean nsgtk_window_button_release_event(GtkWidget *widget, @@ -462,7 +463,6 @@ gboolean nsgtk_window_keypress_event(GtkWidget *widget, GdkEventKey *event, { struct gui_window *g = data; uint32_t nskey = gdkkey_to_nskey(event); - if (browser_window_key_press(g->bw, nskey)) return TRUE; diff --git a/gtk/options.h b/gtk/options.h index 80d72c0d5..1c7f4f544 100644 --- a/gtk/options.h +++ b/gtk/options.h @@ -22,13 +22,22 @@ #include "desktop/options.h" extern bool option_render_resample; +extern bool option_downloads_clear; +extern bool option_request_overwrite; +extern char *option_downloads_directory; extern char *option_url_file; #define EXTRA_OPTION_DEFINE \ bool option_render_resample = false; \ +bool option_downloads_clear = false; \ +bool option_request_overwrite = true; \ +char *option_downloads_directory = 0; \ char *option_url_file = 0; #define EXTRA_OPTION_TABLE \ { "render_resample", OPTION_BOOL, &option_render_resample }, \ +{ "downloads_clear", OPTION_BOOL, &option_downloads_clear }, \ +{ "request_overwrite", OPTION_BOOL, &option_request_overwrite }, \ +{ "downloads_directory",OPTION_STRING, &option_downloads_directory }, \ { "url_file", OPTION_STRING, &option_url_file }, #endif diff --git a/gtk/res/downloads.glade b/gtk/res/downloads.glade new file mode 100644 index 000000000..ab12ad8c4 --- /dev/null +++ b/gtk/res/downloads.glade @@ -0,0 +1,118 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd"> +<!--Generated with glade3 3.4.4 on Mon Jul 14 18:30:15 2008 --> +<glade-interface> + <widget class="GtkWindow" id="wndDownloads"> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="title" translatable="yes">NetSurf Downloads</property> + <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property> + <property name="default_width">500</property> + <property name="default_height">300</property> + <property name="destroy_with_parent">True</property> + <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property> + <child> + <widget class="GtkVBox" id="vbox1"> + <property name="visible">True</property> + <child> + <widget class="GtkTreeView" id="treeDownloads"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="headers_clickable">True</property> + </widget> + </child> + <child> + <widget class="GtkHBox" id="hbox1"> + <property name="visible">True</property> + <property name="border_width">2</property> + <child> + <widget class="GtkProgressBar" id="progressBar"> + <property name="visible">True</property> + <property name="fraction">0.20999999344348907</property> + <property name="text" translatable="yes">21% of 3 files</property> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <widget class="GtkToolbar" id="toolbar2"> + <property name="visible">True</property> + <property name="toolbar_style">GTK_TOOLBAR_BOTH_HORIZ</property> + <property name="show_arrow">False</property> + <property name="icon_size">GTK_ICON_SIZE_MENU</property> + <property name="icon_size_set">True</property> + <child> + <widget class="GtkToolButton" id="buttonPause"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="is_important">True</property> + <property name="label"> Pause</property> + <property name="use_underline">True</property> + <property name="stock_id">gtk-media-pause</property> + <signal name="clicked" handler="button_clear_clicked" object="NULL"/> + </widget> + <packing> + <property name="expand">False</property> + </packing> + </child> + <child> + <widget class="GtkToolButton" id="buttonResume"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="is_important">True</property> + <property name="label"> Resume</property> + <property name="use_underline">True</property> + <property name="stock_id">gtk-media-play</property> + </widget> + <packing> + <property name="expand">False</property> + </packing> + </child> + <child> + <widget class="GtkToolButton" id="buttonCancel"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="is_important">True</property> + <property name="label"> Cancel</property> + <property name="use_underline">True</property> + <property name="stock_id">gtk-cancel</property> + </widget> + <packing> + <property name="expand">False</property> + </packing> + </child> + <child> + <widget class="GtkToolButton" id="buttonClear"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="tooltip" translatable="yes">Remove completed or cancelled downloads from the list</property> + <property name="visible_vertical">False</property> + <property name="is_important">True</property> + <property name="label"> Clear</property> + <property name="use_underline">True</property> + <property name="stock_id">gtk-clear</property> + <signal name="clicked" handler="button_clear_clicked"/> + </widget> + <packing> + <property name="expand">False</property> + </packing> + </child> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </widget> + </child> + </widget> +</glade-interface> diff --git a/gtk/res/netsurf.glade b/gtk/res/netsurf.glade index d1b1e15f4..09af6fea9 100644 --- a/gtk/res/netsurf.glade +++ b/gtk/res/netsurf.glade @@ -232,6 +232,7 @@ <property name="label">gtk-select-all</property> <property name="use_underline">True</property> <property name="use_stock">True</property> + <accelerator key="a" modifiers="GDK_CONTROL_MASK" signal="activate"/> </widget> </child> <child> @@ -329,9 +330,9 @@ <property name="visible">True</property> <property name="label" translatable="yes">Zoom _in</property> <property name="use_underline">True</property> - <accelerator key="plus" modifiers="GDK_CONTROL_MASK" signal="activate"/> - <accelerator key="KP_Add" modifiers="GDK_CONTROL_MASK" signal="activate"/> <accelerator key="equal" modifiers="GDK_CONTROL_MASK" signal="activate"/> + <accelerator key="KP_Add" modifiers="GDK_CONTROL_MASK" signal="activate"/> + <accelerator key="plus" modifiers="GDK_CONTROL_MASK" signal="activate"/> <child internal-child="image"> <widget class="GtkImage" id="image564"> <property name="visible">True</property> @@ -346,8 +347,8 @@ <property name="visible">True</property> <property name="label" translatable="yes">_Normal size</property> <property name="use_underline">True</property> - <accelerator key="0" modifiers="GDK_CONTROL_MASK" signal="activate"/> <accelerator key="KP_0" modifiers="GDK_CONTROL_MASK" signal="activate"/> + <accelerator key="0" modifiers="GDK_CONTROL_MASK" signal="activate"/> <child internal-child="image"> <widget class="GtkImage" id="image565"> <property name="visible">True</property> @@ -362,8 +363,8 @@ <property name="visible">True</property> <property name="label" translatable="yes">Zoom _out</property> <property name="use_underline">True</property> - <accelerator key="minus" modifiers="GDK_CONTROL_MASK" signal="activate"/> <accelerator key="KP_Subtract" modifiers="GDK_CONTROL_MASK" signal="activate"/> + <accelerator key="minus" modifiers="GDK_CONTROL_MASK" signal="activate"/> <child internal-child="image"> <widget class="GtkImage" id="image566"> <property name="visible">True</property> @@ -801,25 +802,17 @@ <placeholder/> </child> <child> - <widget class="GtkHSeparator" id="hseparator3"> - <property name="visible">True</property> - </widget> - <packing> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> - <property name="y_options">GTK_FILL</property> - </packing> - </child> - <child> - <widget class="GtkVScrollbar" id="coreScrollVertical"> + <widget class="GtkStatusbar" id="statusbar1"> + <property name="height_request">1</property> <property name="visible">True</property> - <property name="adjustment">0 0 100 1 10 0</property> </widget> <packing> <property name="left_attach">1</property> <property name="right_attach">2</property> - <property name="bottom_attach">2</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> <property name="x_options">GTK_FILL</property> + <property name="y_options">GTK_SHRINK | GTK_FILL</property> </packing> </child> <child> @@ -858,17 +851,25 @@ </packing> </child> <child> - <widget class="GtkStatusbar" id="statusbar1"> - <property name="height_request">1</property> + <widget class="GtkVScrollbar" id="coreScrollVertical"> <property name="visible">True</property> + <property name="adjustment">0 0 100 1 10 0</property> </widget> <packing> <property name="left_attach">1</property> <property name="right_attach">2</property> - <property name="top_attach">2</property> - <property name="bottom_attach">3</property> + <property name="bottom_attach">2</property> <property name="x_options">GTK_FILL</property> - <property name="y_options">GTK_SHRINK | GTK_FILL</property> + </packing> + </child> + <child> + <widget class="GtkHSeparator" id="hseparator3"> + <property name="visible">True</property> + </widget> + <packing> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="y_options">GTK_FILL</property> </packing> </child> </widget> @@ -912,106 +913,106 @@ <property name="column_spacing">11</property> <property name="row_spacing">10</property> <child> - <widget class="GtkEntry" id="entryLoginUser"> + <widget class="GtkLabel" id="labelLoginHost"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="has_focus">True</property> - <property name="text" translatable="yes">sesame</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">moo.yoo.com</property> </widget> <packing> <property name="left_attach">1</property> <property name="right_attach">2</property> - <property name="top_attach">2</property> - <property name="bottom_attach">3</property> - <property name="y_options"></property> + <property name="x_options">GTK_FILL</property> </packing> </child> <child> - <widget class="GtkEntry" id="entryLoginPass"> + <widget class="GtkLabel" id="label57"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="visibility">False</property> - <property name="activates_default">True</property> - <property name="text" translatable="yes">opensesame</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Password</property> </widget> <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> <property name="top_attach">3</property> <property name="bottom_attach">4</property> - <property name="y_options"></property> + <property name="x_options">GTK_FILL</property> </packing> </child> <child> - <widget class="GtkLabel" id="labelLoginRealm"> + <widget class="GtkLabel" id="label56"> <property name="visible">True</property> <property name="xalign">0</property> - <property name="label" translatable="yes">my sekr3t area</property> + <property name="label" translatable="yes">Username</property> </widget> <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> <property name="x_options">GTK_FILL</property> </packing> </child> <child> - <widget class="GtkLabel" id="label55"> + <widget class="GtkLabel" id="label54"> <property name="visible">True</property> <property name="xalign">0</property> - <property name="label" translatable="yes">Realm</property> + <property name="label" translatable="yes">Host</property> </widget> <packing> - <property name="top_attach">1</property> - <property name="bottom_attach">2</property> <property name="x_options">GTK_FILL</property> </packing> </child> <child> - <widget class="GtkLabel" id="label54"> + <widget class="GtkLabel" id="label55"> <property name="visible">True</property> <property name="xalign">0</property> - <property name="label" translatable="yes">Host</property> + <property name="label" translatable="yes">Realm</property> </widget> <packing> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> <property name="x_options">GTK_FILL</property> </packing> </child> <child> - <widget class="GtkLabel" id="label56"> + <widget class="GtkLabel" id="labelLoginRealm"> <property name="visible">True</property> <property name="xalign">0</property> - <property name="label" translatable="yes">Username</property> + <property name="label" translatable="yes">my sekr3t area</property> </widget> <packing> - <property name="top_attach">2</property> - <property name="bottom_attach">3</property> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> <property name="x_options">GTK_FILL</property> </packing> </child> <child> - <widget class="GtkLabel" id="label57"> + <widget class="GtkEntry" id="entryLoginPass"> <property name="visible">True</property> - <property name="xalign">0</property> - <property name="label" translatable="yes">Password</property> + <property name="can_focus">True</property> + <property name="visibility">False</property> + <property name="activates_default">True</property> + <property name="text" translatable="yes">opensesame</property> </widget> <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> <property name="top_attach">3</property> <property name="bottom_attach">4</property> - <property name="x_options">GTK_FILL</property> + <property name="y_options"></property> </packing> </child> <child> - <widget class="GtkLabel" id="labelLoginHost"> + <widget class="GtkEntry" id="entryLoginUser"> <property name="visible">True</property> - <property name="xalign">0</property> - <property name="label" translatable="yes">moo.yoo.com</property> + <property name="can_focus">True</property> + <property name="has_focus">True</property> + <property name="text" translatable="yes">sesame</property> </widget> <packing> <property name="left_attach">1</property> <property name="right_attach">2</property> - <property name="x_options">GTK_FILL</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + <property name="y_options"></property> </packing> </child> </widget> @@ -1344,82 +1345,82 @@ <property name="n_columns">2</property> <property name="column_spacing">4</property> <child> - <widget class="GtkLabel" id="label117"> + <widget class="GtkLabel" id="labelHistoryAddress"> <property name="visible">True</property> - <property name="xalign">1</property> - <property name="label" translatable="yes">Address</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">http://netsurf.sf.net/</property> + <property name="ellipsize">PANGO_ELLIPSIZE_MIDDLE</property> </widget> <packing> - <property name="x_options">GTK_FILL</property> + <property name="left_attach">1</property> + <property name="right_attach">2</property> <property name="y_options"></property> </packing> </child> <child> - <widget class="GtkLabel" id="label118"> + <widget class="GtkLabel" id="labelHistoryLastVisit"> <property name="visible">True</property> - <property name="xalign">1</property> - <property name="label" translatable="yes">Last visited</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Fri Aug 09 00:00:00 2006</property> + <property name="ellipsize">PANGO_ELLIPSIZE_END</property> </widget> <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> <property name="top_attach">1</property> <property name="bottom_attach">2</property> - <property name="x_options">GTK_FILL</property> <property name="y_options"></property> </packing> </child> <child> - <widget class="GtkLabel" id="label119"> + <widget class="GtkLabel" id="labelHistoryVisits"> <property name="visible">True</property> - <property name="xalign">1</property> - <property name="label" translatable="yes">Number of visits</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">2</property> + <property name="ellipsize">PANGO_ELLIPSIZE_END</property> </widget> <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> <property name="top_attach">2</property> <property name="bottom_attach">3</property> - <property name="x_options">GTK_FILL</property> <property name="y_options"></property> </packing> </child> <child> - <widget class="GtkLabel" id="labelHistoryVisits"> + <widget class="GtkLabel" id="label119"> <property name="visible">True</property> - <property name="xalign">0</property> - <property name="label" translatable="yes">2</property> - <property name="ellipsize">PANGO_ELLIPSIZE_END</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">Number of visits</property> </widget> <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> <property name="top_attach">2</property> <property name="bottom_attach">3</property> + <property name="x_options">GTK_FILL</property> <property name="y_options"></property> </packing> </child> <child> - <widget class="GtkLabel" id="labelHistoryLastVisit"> + <widget class="GtkLabel" id="label118"> <property name="visible">True</property> - <property name="xalign">0</property> - <property name="label" translatable="yes">Fri Aug 09 00:00:00 2006</property> - <property name="ellipsize">PANGO_ELLIPSIZE_END</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">Last visited</property> </widget> <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> <property name="top_attach">1</property> <property name="bottom_attach">2</property> + <property name="x_options">GTK_FILL</property> <property name="y_options"></property> </packing> </child> <child> - <widget class="GtkLabel" id="labelHistoryAddress"> + <widget class="GtkLabel" id="label117"> <property name="visible">True</property> - <property name="xalign">0</property> - <property name="label" translatable="yes">http://netsurf.sf.net/</property> - <property name="ellipsize">PANGO_ELLIPSIZE_MIDDLE</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">Address</property> </widget> <packing> - <property name="left_attach">1</property> - <property name="right_attach">2</property> + <property name="x_options">GTK_FILL</property> <property name="y_options"></property> </packing> </child> diff --git a/gtk/res/options.glade b/gtk/res/options.glade index 9e0c08650..d3f5eaca8 100644 --- a/gtk/res/options.glade +++ b/gtk/res/options.glade @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd"> -<!--Generated with glade3 3.4.4 on Mon Jun 9 14:16:52 2008 --> +<!--Generated with glade3 3.4.4 on Tue Jul 15 19:19:26 2008 --> <glade-interface> <widget class="GtkDialog" id="dlgPreferences"> <property name="border_width">5</property> @@ -311,22 +311,6 @@ <property name="visible">True</property> <property name="homogeneous">True</property> <child> - <widget class="GtkCheckButton" id="checkRequestOverwrite"> - <property name="visible">True</property> - <property name="sensitive">False</property> - <property name="can_focus">True</property> - <property name="tooltip" translatable="yes">Ask before overwriting files when downloading.</property> - <property name="label" translatable="yes">Request confirmation before overwriting files</property> - <property name="use_underline">True</property> - <property name="response_id">0</property> - <property name="draw_indicator">True</property> - </widget> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - <child> <widget class="GtkCheckButton" id="checkDisplayRecentURLs"> <property name="visible">True</property> <property name="can_focus">True</property> @@ -339,7 +323,6 @@ <packing> <property name="expand">False</property> <property name="fill">False</property> - <property name="position">1</property> </packing> </child> <child> @@ -356,7 +339,7 @@ <packing> <property name="expand">False</property> <property name="fill">False</property> - <property name="position">2</property> + <property name="position">1</property> </packing> </child> </widget> @@ -716,7 +699,7 @@ NTLM authentication</property> <widget class="GtkVBox" id="vbox2"> <property name="visible">True</property> <child> - <widget class="GtkCheckButton" id="checkResampleImages1"> + <widget class="GtkCheckButton" id="checkResampleImages"> <property name="visible">True</property> <property name="can_focus">True</property> <property name="tooltip" translatable="yes">Resample images when not at natural size</property> @@ -1474,6 +1457,114 @@ Fantasy</property> <property name="tab_fill">False</property> </packing> </child> + <child> + <widget class="GtkVBox" id="vbox3"> + <property name="visible">True</property> + <child> + <widget class="GtkFrame" id="frame1"> + <property name="visible">True</property> + <property name="border_width">5</property> + <property name="label_xalign">0</property> + <property name="shadow_type">GTK_SHADOW_NONE</property> + <child> + <widget class="GtkAlignment" id="alignment1"> + <property name="visible">True</property> + <property name="left_padding">12</property> + <child> + <widget class="GtkVBox" id="vbox4"> + <property name="visible">True</property> + <child> + <widget class="GtkCheckButton" id="checkClearDownloads"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="tooltip" translatable="yes">Erase the download from the list as soon as it completes.</property> + <property name="label" translatable="yes">Automatically clear downloads when completed</property> + <property name="use_underline">True</property> + <property name="response_id">0</property> + <property name="active">True</property> + <property name="draw_indicator">True</property> + </widget> + </child> + <child> + <widget class="GtkCheckButton" id="checkRequestOverwrite"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="tooltip" translatable="yes">Ask before overwriting files when downloading.</property> + <property name="label" translatable="yes">Request confirmation before overwriting files</property> + <property name="use_underline">True</property> + <property name="response_id">0</property> + <property name="draw_indicator">True</property> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <widget class="GtkHBox" id="hbox2"> + <property name="visible">True</property> + <child> + <widget class="GtkLabel" id="label4"> + <property name="visible">True</property> + <property name="label" translatable="yes">Download directory: </property> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <widget class="GtkFileChooserButton" id="fileChooserDownloads"> + <property name="visible">True</property> + <property name="tooltip" translatable="yes">The default directory where files are saved</property> + <property name="action">GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER</property> + <property name="width_chars">25</property> + </widget> + <packing> + <property name="position">1</property> + </packing> + </child> + </widget> + <packing> + <property name="position">2</property> + </packing> + </child> + </widget> + </child> + </widget> + </child> + <child> + <widget class="GtkLabel" id="label3"> + <property name="visible">True</property> + <property name="label" translatable="yes"><b>Downloads</b></property> + <property name="use_markup">True</property> + </widget> + <packing> + <property name="type">label_item</property> + </packing> + </child> + </widget> + <packing> + <property name="expand">False</property> + </packing> + </child> + </widget> + <packing> + <property name="position">5</property> + </packing> + </child> + <child> + <widget class="GtkLabel" id="label109"> + <property name="visible">True</property> + <property name="label" translatable="yes">Downloads</property> + </widget> + <packing> + <property name="type">tab</property> + <property name="position">5</property> + <property name="tab_fill">False</property> + </packing> + </child> </widget> <packing> <property name="position">1</property> @@ -1511,7 +1602,6 @@ Fantasy</property> <packing> <property name="expand">False</property> <property name="pack_type">GTK_PACK_END</property> - <property name="position">1</property> </packing> </child> </widget> diff --git a/riscos/download.c b/riscos/download.c index 95da4f814..d8d86b75f 100644 --- a/riscos/download.c +++ b/riscos/download.c @@ -211,7 +211,7 @@ const char *ro_gui_download_temp_name(struct gui_download_window *dw) struct gui_download_window *gui_download_window_create(const char *url, const char *mime_type, struct fetch *fetch, - unsigned int total_size) + unsigned int total_size, struct gui_window *gui) { const char *temp_name; char *nice, *scheme = NULL; diff --git a/riscos/window.c b/riscos/window.c index 29cc64e08..045cabe56 100644 --- a/riscos/window.c +++ b/riscos/window.c @@ -2856,7 +2856,7 @@ bool ro_gui_window_dataload(struct gui_window *g, wimp_message *message) const char *filename = message->data.data_xfer.file_name; - browser_window_mouse_click(g->bw, BROWSER_MOUSE_CLICK_1, pos.x, pos.y); + browser_window_mouse_click(g->bw, BROWSER_MOUSE_PRESS_1, pos.x, pos.y); if (!ro_gui_window_import_text(g, filename, false)) return true; /* it was for us, it just didn't work! */ |