3018 lines
76 KiB
C
3018 lines
76 KiB
C
/*
|
|
* Copyright 2003 Phil Mellor <monkeyson@users.sourceforge.net>
|
|
* Copyright 2006 James Bursa <bursa@users.sourceforge.net>
|
|
* Copyright 2004 Andrew Timmins <atimmins@blueyonder.co.uk>
|
|
* Copyright 2004 John Tytgat <joty@netsurf-browser.org>
|
|
* Copyright 2006 Richard Wilson <info@tinct.net>
|
|
* Copyright 2008 Michael Drake <tlsa@netsurf-browser.org>
|
|
* Copyright 2009 Paul Blokus <paul_pl@users.sourceforge.net>
|
|
*
|
|
* This file is part of NetSurf, http://www.netsurf-browser.org/
|
|
*
|
|
* NetSurf is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; version 2 of the License.
|
|
*
|
|
* NetSurf is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/** \file
|
|
* Browser window creation and manipulation (implementation).
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <limits.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <strings.h>
|
|
#include <math.h>
|
|
|
|
#include "curl/curl.h"
|
|
#include "utils/config.h"
|
|
#include "content/fetch.h"
|
|
#include "content/fetchcache.h"
|
|
#include "content/urldb.h"
|
|
#include "css/css.h"
|
|
#include "desktop/401login.h"
|
|
#include "desktop/browser.h"
|
|
#include "desktop/frames.h"
|
|
#include "desktop/history_core.h"
|
|
#include "desktop/gui.h"
|
|
#include "desktop/options.h"
|
|
#include "desktop/scroll.h"
|
|
#include "desktop/selection.h"
|
|
#include "desktop/textinput.h"
|
|
#include "render/box.h"
|
|
#include "render/form.h"
|
|
#include "render/font.h"
|
|
#include "render/imagemap.h"
|
|
#include "render/layout.h"
|
|
#include "render/textplain.h"
|
|
#include "utils/log.h"
|
|
#include "utils/messages.h"
|
|
#include "utils/talloc.h"
|
|
#include "utils/url.h"
|
|
#include "utils/utils.h"
|
|
#include "utils/utf8.h"
|
|
|
|
/** browser window which is being redrawn. Valid only during redraw. */
|
|
struct browser_window *current_redraw_browser;
|
|
|
|
/** fake content for <a> being saved as a link */
|
|
struct content browser_window_href_content;
|
|
|
|
/** one or more windows require a reformat */
|
|
bool browser_reformat_pending;
|
|
|
|
/** maximum frame depth */
|
|
#define FRAME_DEPTH 8
|
|
|
|
static void browser_window_go_post(struct browser_window *bw,
|
|
const char *url, char *post_urlenc,
|
|
struct form_successful_control *post_multipart,
|
|
bool add_to_history, const char *referer, bool download,
|
|
bool verifiable, struct content *parent);
|
|
static void browser_window_callback(content_msg msg, struct content *c,
|
|
intptr_t p1, intptr_t p2, union content_msg_data data);
|
|
static void browser_window_refresh(void *p);
|
|
static bool browser_window_check_throbber(struct browser_window *bw);
|
|
static void browser_window_convert_to_download(struct browser_window *bw);
|
|
static void browser_window_start_throbber(struct browser_window *bw);
|
|
static void browser_window_stop_throbber(struct browser_window *bw);
|
|
static void browser_window_set_icon(struct browser_window *bw);
|
|
static void browser_window_set_status(struct browser_window *bw,
|
|
const char *text);
|
|
static void browser_window_set_pointer(struct gui_window *g,
|
|
gui_pointer_shape shape);
|
|
static void download_window_callback(fetch_msg msg, void *p, const void *data,
|
|
unsigned long size, fetch_error_code errorcode);
|
|
static void browser_window_destroy_children(struct browser_window *bw);
|
|
static void browser_window_destroy_internal(struct browser_window *bw);
|
|
static void browser_window_set_scale_internal(struct browser_window *bw,
|
|
float scale);
|
|
static struct browser_window *browser_window_find_target(
|
|
struct browser_window *bw, const char *target,
|
|
browser_mouse_state mouse);
|
|
static void browser_window_find_target_internal(struct browser_window *bw,
|
|
const char *target, int depth, struct browser_window *page,
|
|
int *rdepth, struct browser_window **bw_target);
|
|
static void browser_window_mouse_action_html(struct browser_window *bw,
|
|
browser_mouse_state mouse, int x, int y);
|
|
static void browser_window_mouse_action_text(struct browser_window *bw,
|
|
browser_mouse_state mouse, int x, int y);
|
|
static void browser_window_mouse_track_html(struct browser_window *bw,
|
|
browser_mouse_state mouse, int x, int y);
|
|
static void browser_window_mouse_track_text(struct browser_window *bw,
|
|
browser_mouse_state mouse, int x, int y);
|
|
static void browser_radio_set(struct content *content,
|
|
struct form_control *radio);
|
|
static gui_pointer_shape get_pointer_shape(struct browser_window *bw,
|
|
struct box *box, bool imagemap);
|
|
static bool browser_window_nearer_text_box(struct box *box, int bx, int by,
|
|
int x, int y, int dir, struct box **nearest, int *tx, int *ty,
|
|
int *nr_xd, int *nr_yd);
|
|
static bool browser_window_nearest_text_box(struct box *box, int bx, int by,
|
|
int fx, int fy, int x, int y, int dir, struct box **nearest,
|
|
int *tx, int *ty, int *nr_xd, int *nr_yd);
|
|
static struct box *browser_window_pick_text_box(struct browser_window *bw,
|
|
int x, int y, int dir, int *dx, int *dy);
|
|
static void browser_window_page_drag_start(struct browser_window *bw,
|
|
int x, int y);
|
|
static void browser_window_box_drag_start(struct browser_window *bw,
|
|
struct box *box, int x, int y);
|
|
|
|
/**
|
|
* Create and open a new browser window with the given page.
|
|
*
|
|
* \param url URL to start fetching in the new window (copied)
|
|
* \param clone The browser window to clone
|
|
* \param referer The referring uri (copied), or 0 if none
|
|
*/
|
|
|
|
struct browser_window *browser_window_create(const char *url,
|
|
struct browser_window *clone,
|
|
const char *referer, bool history_add, bool new_tab)
|
|
{
|
|
struct browser_window *bw;
|
|
|
|
assert(clone || history_add);
|
|
|
|
if ((bw = calloc(1, sizeof *bw)) == NULL) {
|
|
warn_user("NoMemory", 0);
|
|
return NULL;
|
|
}
|
|
|
|
/* Initialise common parts */
|
|
browser_window_initialise_common(bw, clone);
|
|
|
|
/* window characteristics */
|
|
bw->browser_window_type = BROWSER_WINDOW_NORMAL;
|
|
bw->scrolling = SCROLLING_YES;
|
|
bw->border = true;
|
|
bw->no_resize = true;
|
|
bw->last_action = wallclock();
|
|
|
|
/* gui window */
|
|
if ((bw->window = gui_create_browser_window(bw, clone, new_tab)) == NULL) {
|
|
browser_window_destroy(bw);
|
|
return NULL;
|
|
}
|
|
if (url)
|
|
browser_window_go(bw, url, referer, history_add);
|
|
|
|
|
|
return bw;
|
|
}
|
|
|
|
|
|
/**
|
|
* Initialise common parts of a browser window
|
|
*
|
|
* \param bw The window to initialise
|
|
* \param clone The window to clone, or NULL if none
|
|
*/
|
|
void browser_window_initialise_common(struct browser_window *bw,
|
|
struct browser_window *clone)
|
|
{
|
|
assert(bw);
|
|
|
|
if (!clone)
|
|
bw->history = history_create();
|
|
else
|
|
bw->history = history_clone(clone->history);
|
|
|
|
/* window characteristics */
|
|
bw->sel = selection_create(bw);
|
|
bw->refresh_interval = -1;
|
|
|
|
bw->reformat_pending = false;
|
|
bw->drag_type = DRAGGING_NONE;
|
|
bw->scale = (float) option_scale / 100.0;
|
|
|
|
/* initialise status text cache */
|
|
bw->status_text = NULL;
|
|
bw->status_text_len = 0;
|
|
bw->status_match = 0;
|
|
bw->status_miss = 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Start fetching a page in a browser window.
|
|
*
|
|
* \param bw browser window
|
|
* \param url URL to start fetching (copied)
|
|
* \param referer the referring uri (copied), or 0 if none
|
|
*
|
|
* Any existing fetches in the window are aborted.
|
|
*/
|
|
|
|
void browser_window_go(struct browser_window *bw, const char *url,
|
|
const char *referer, bool history_add)
|
|
{
|
|
/* All fetches passing through here are verifiable
|
|
* (i.e are the result of user action) */
|
|
browser_window_go_post(bw, url, 0, 0, history_add, referer,
|
|
false, true, NULL);
|
|
}
|
|
|
|
|
|
/**
|
|
* Start a download of the given URL from a browser window.
|
|
*
|
|
* \param bw browser window
|
|
* \param url URL to start downloading (copied)
|
|
* \param referer the referring uri (copied), or 0 if none
|
|
*/
|
|
|
|
void browser_window_download(struct browser_window *bw, const char *url,
|
|
const char *referer)
|
|
{
|
|
browser_window_go_post(bw, url, 0, 0, false, referer,
|
|
true, true, NULL);
|
|
}
|
|
|
|
|
|
/**
|
|
* Start fetching a page in a browser window.
|
|
*
|
|
* \param bw browser window
|
|
* \param url URL to start fetching (copied)
|
|
* \param referer the referring uri (copied), or 0 if none
|
|
*
|
|
* Any existing fetches in the window are aborted.
|
|
*/
|
|
|
|
void browser_window_go_unverifiable(struct browser_window *bw,
|
|
const char *url, const char *referer, bool history_add,
|
|
struct content *parent)
|
|
{
|
|
/* All fetches passing through here are unverifiable
|
|
* (i.e are not the result of user action) */
|
|
browser_window_go_post(bw, url, 0, 0, history_add, referer,
|
|
false, false, parent);
|
|
}
|
|
|
|
/**
|
|
* Start fetching a page in a browser window, POSTing form data.
|
|
*
|
|
* \param bw browser window
|
|
* \param url URL to start fetching (copied)
|
|
* \param post_urlenc url encoded post data, or 0 if none
|
|
* \param post_multipart multipart post data, or 0 if none
|
|
* \param add_to_history add to window history
|
|
* \param referer the referring uri (copied), or 0 if none
|
|
* \param download download, rather than render the uri
|
|
* \param verifiable this transaction is verifiable
|
|
* \param parent Parent content, or NULL
|
|
*
|
|
* Any existing fetches in the window are aborted.
|
|
*
|
|
* If post_urlenc and post_multipart are 0 the url is fetched using GET.
|
|
*
|
|
* The page is not added to the window history if add_to_history is false.
|
|
* This should be used when returning to a page in the window history.
|
|
*/
|
|
|
|
void browser_window_go_post(struct browser_window *bw, const char *url,
|
|
char *post_urlenc,
|
|
struct form_successful_control *post_multipart,
|
|
bool add_to_history, const char *referer, bool download,
|
|
bool verifiable, struct content *parent)
|
|
{
|
|
struct content *c;
|
|
char *url2;
|
|
char *fragment;
|
|
url_func_result res;
|
|
int depth = 0;
|
|
struct browser_window *cur;
|
|
int width, height;
|
|
|
|
LOG(("bw %p, url %s", bw, url));
|
|
assert(bw);
|
|
assert(url);
|
|
|
|
/* don't allow massively nested framesets */
|
|
for (cur = bw; cur->parent; cur = cur->parent)
|
|
depth++;
|
|
if (depth > FRAME_DEPTH) {
|
|
LOG(("frame depth too high."));
|
|
return;
|
|
}
|
|
|
|
res = url_normalize(url, &url2);
|
|
if (res != URL_FUNC_OK) {
|
|
LOG(("failed to normalize url %s", url));
|
|
return;
|
|
}
|
|
|
|
/* check we can actually handle this URL */
|
|
if (!fetch_can_fetch(url2)) {
|
|
gui_launch_url(url2);
|
|
free(url2);
|
|
return;
|
|
}
|
|
|
|
free(bw->frag_id);
|
|
bw->frag_id = NULL;
|
|
|
|
/* find any fragment identifier on end of URL */
|
|
res = url_fragment(url2, &fragment);
|
|
if (res == URL_FUNC_NOMEM) {
|
|
free(url2);
|
|
warn_user("NoMemory", 0);
|
|
return;
|
|
} else if (res == URL_FUNC_OK) {
|
|
bool same_url = false;
|
|
|
|
bw->frag_id = fragment;
|
|
|
|
/* Compare new URL with existing one (ignoring fragments) */
|
|
if (bw->current_content && bw->current_content->url) {
|
|
res = url_compare(bw->current_content->url, url2,
|
|
true, &same_url);
|
|
if (res == URL_FUNC_NOMEM) {
|
|
free(url2);
|
|
warn_user("NoMemory", 0);
|
|
return;
|
|
} else if (res == URL_FUNC_FAILED) {
|
|
same_url = false;
|
|
}
|
|
}
|
|
|
|
/* if we're simply moving to another ID on the same page,
|
|
* don't bother to fetch, just update the window.
|
|
*/
|
|
if (same_url && !post_urlenc && !post_multipart &&
|
|
!strchr(url2, '?')) {
|
|
free(url2);
|
|
if (add_to_history)
|
|
history_add(bw->history, bw->current_content,
|
|
bw->frag_id);
|
|
browser_window_update(bw, false);
|
|
if (bw->current_content) {
|
|
browser_window_refresh_url_bar(bw,
|
|
bw->current_content->url,
|
|
bw->frag_id);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
browser_window_stop(bw);
|
|
browser_window_remove_caret(bw);
|
|
browser_window_destroy_children(bw);
|
|
|
|
gui_window_get_dimensions(bw->window, &width, &height, true);
|
|
LOG(("Loading '%s' width %i, height %i", url2, width, height));
|
|
|
|
browser_window_set_status(bw, messages_get("Loading"));
|
|
bw->history_add = add_to_history;
|
|
c = fetchcache(url2, browser_window_callback, (intptr_t) bw, 0,
|
|
width, height, false,
|
|
post_urlenc, post_multipart, verifiable, download);
|
|
free(url2);
|
|
if (!c) {
|
|
browser_window_set_status(bw, messages_get("NoMemory"));
|
|
warn_user("NoMemory", 0);
|
|
return;
|
|
}
|
|
|
|
bw->loading_content = c;
|
|
browser_window_start_throbber(bw);
|
|
|
|
if (referer && referer != bw->referer) {
|
|
free(bw->referer);
|
|
bw->referer = strdup(referer);
|
|
}
|
|
|
|
bw->download = download;
|
|
fetchcache_go(c, referer, browser_window_callback,
|
|
(intptr_t) bw, 0, width, height,
|
|
post_urlenc, post_multipart, verifiable, parent);
|
|
}
|
|
|
|
|
|
/**
|
|
* Callback for fetchcache() for browser window fetches.
|
|
*/
|
|
|
|
void browser_window_callback(content_msg msg, struct content *c,
|
|
intptr_t p1, intptr_t p2, union content_msg_data data)
|
|
{
|
|
struct browser_window *bw = (struct browser_window *) p1;
|
|
|
|
switch (msg) {
|
|
case CONTENT_MSG_LOADING:
|
|
assert(bw->loading_content == c);
|
|
|
|
if (c->type == CONTENT_OTHER)
|
|
browser_window_convert_to_download(bw);
|
|
#ifdef WITH_THEME_INSTALL
|
|
else if (c->type == CONTENT_THEME) {
|
|
theme_install_start(c);
|
|
bw->loading_content = 0;
|
|
content_remove_user(c, browser_window_callback,
|
|
(intptr_t) bw, 0);
|
|
browser_window_stop_throbber(bw);
|
|
}
|
|
#endif
|
|
else {
|
|
bw->refresh_interval = -1;
|
|
browser_window_set_status(bw, c->status_message);
|
|
}
|
|
break;
|
|
|
|
case CONTENT_MSG_READY:
|
|
assert(bw->loading_content == c);
|
|
|
|
if (bw->current_content) {
|
|
if (bw->current_content->status ==
|
|
CONTENT_STATUS_READY ||
|
|
bw->current_content->status ==
|
|
CONTENT_STATUS_DONE)
|
|
content_close(bw->current_content);
|
|
content_remove_user(bw->current_content,
|
|
browser_window_callback,
|
|
(intptr_t) bw, 0);
|
|
}
|
|
bw->current_content = c;
|
|
bw->loading_content = NULL;
|
|
browser_window_remove_caret(bw);
|
|
bw->scroll = NULL;
|
|
gui_window_new_content(bw->window);
|
|
if (bw->current_content) {
|
|
browser_window_refresh_url_bar(bw,
|
|
bw->current_content->url,
|
|
bw->frag_id);
|
|
}
|
|
/* new content; set scroll_to_top */
|
|
browser_window_update(bw, true);
|
|
content_open(c, bw, 0, 0, 0, 0);
|
|
browser_window_set_status(bw, c->status_message);
|
|
|
|
/* history */
|
|
if (bw->history_add && bw->history) {
|
|
history_add(bw->history, c, bw->frag_id);
|
|
if (urldb_add_url(c->url)) {
|
|
urldb_set_url_title(c->url,
|
|
c->title ? c->title : c->url);
|
|
urldb_update_url_visit_data(c->url);
|
|
urldb_set_url_content_type(c->url,
|
|
c->type);
|
|
/* This is safe as we've just
|
|
* added the URL */
|
|
global_history_add(
|
|
urldb_get_url(c->url));
|
|
}
|
|
}
|
|
|
|
/* text selection */
|
|
if (c->type == CONTENT_HTML)
|
|
selection_init(bw->sel,
|
|
bw->current_content->data.html.layout);
|
|
if (c->type == CONTENT_TEXTPLAIN)
|
|
selection_init(bw->sel, NULL);
|
|
|
|
/* frames */
|
|
if (c->type == CONTENT_HTML && c->data.html.frameset)
|
|
browser_window_create_frameset(bw,
|
|
c->data.html.frameset);
|
|
if (c->type == CONTENT_HTML && c->data.html.iframe)
|
|
browser_window_create_iframes(bw, c->data.html.iframe);
|
|
|
|
break;
|
|
|
|
case CONTENT_MSG_DONE:
|
|
assert(bw->current_content == c);
|
|
|
|
browser_window_update(bw, false);
|
|
browser_window_set_status(bw, c->status_message);
|
|
browser_window_stop_throbber(bw);
|
|
browser_window_set_icon(bw);
|
|
history_update(bw->history, c);
|
|
hotlist_visited(c);
|
|
free(bw->referer);
|
|
bw->referer = 0;
|
|
if (bw->refresh_interval != -1)
|
|
schedule(bw->refresh_interval,
|
|
browser_window_refresh, bw);
|
|
break;
|
|
|
|
case CONTENT_MSG_ERROR:
|
|
browser_window_set_status(bw, data.error);
|
|
|
|
/* Only warn the user about errors in top-level windows */
|
|
if (bw->browser_window_type == BROWSER_WINDOW_NORMAL)
|
|
warn_user(data.error, 0);
|
|
|
|
if (c == bw->loading_content)
|
|
bw->loading_content = 0;
|
|
else if (c == bw->current_content) {
|
|
bw->current_content = 0;
|
|
browser_window_remove_caret(bw);
|
|
bw->scroll = NULL;
|
|
selection_init(bw->sel, NULL);
|
|
}
|
|
browser_window_stop_throbber(bw);
|
|
free(bw->referer);
|
|
bw->referer = 0;
|
|
break;
|
|
|
|
case CONTENT_MSG_STATUS:
|
|
browser_window_set_status(bw, c->status_message);
|
|
break;
|
|
|
|
case CONTENT_MSG_REFORMAT:
|
|
if (c == bw->current_content &&
|
|
c->type == CONTENT_HTML) {
|
|
/* reposition frames */
|
|
if (c->data.html.frameset)
|
|
browser_window_recalculate_frameset(bw);
|
|
/* reflow iframe positions */
|
|
if (c->data.html.iframe)
|
|
browser_window_recalculate_iframes(bw);
|
|
/* box tree may have changed, need to relabel */
|
|
selection_reinit(bw->sel, c->data.html.layout);
|
|
}
|
|
if (bw->move_callback)
|
|
bw->move_callback(bw, bw->caret_p);
|
|
browser_window_update(bw, false);
|
|
break;
|
|
|
|
case CONTENT_MSG_REDRAW:
|
|
gui_window_update_box(bw->window, &data);
|
|
break;
|
|
|
|
case CONTENT_MSG_NEWPTR:
|
|
bw->loading_content = c;
|
|
if (data.new_url) {
|
|
/* Replacement URL too, so check for new fragment */
|
|
char *fragment;
|
|
url_func_result res;
|
|
|
|
/* Remove any existing fragment */
|
|
free(bw->frag_id);
|
|
bw->frag_id = NULL;
|
|
|
|
/* Extract new one, if any */
|
|
res = url_fragment(data.new_url, &fragment);
|
|
if (res == URL_FUNC_OK) {
|
|
/* Save for later use */
|
|
bw->frag_id = fragment;
|
|
}
|
|
/* Ignore memory exhaustion here -- it'll simply result
|
|
* in the window being scrolled to the top rather than
|
|
* to the fragment. That's acceptable, given that it's
|
|
* likely that more important things will complain
|
|
* about memory shortage. */
|
|
}
|
|
break;
|
|
|
|
case CONTENT_MSG_LAUNCH:
|
|
assert(data.launch_url != NULL);
|
|
|
|
bw->loading_content = NULL;
|
|
|
|
gui_launch_url(data.launch_url);
|
|
|
|
browser_window_stop_throbber(bw);
|
|
free(bw->referer);
|
|
bw->referer = 0;
|
|
break;
|
|
|
|
case CONTENT_MSG_AUTH:
|
|
gui_401login_open(bw, c, data.auth_realm);
|
|
if (c == bw->loading_content)
|
|
bw->loading_content = 0;
|
|
else if (c == bw->current_content) {
|
|
bw->current_content = 0;
|
|
browser_window_remove_caret(bw);
|
|
bw->scroll = NULL;
|
|
selection_init(bw->sel, NULL);
|
|
}
|
|
browser_window_stop_throbber(bw);
|
|
free(bw->referer);
|
|
bw->referer = 0;
|
|
break;
|
|
|
|
case CONTENT_MSG_SSL:
|
|
gui_cert_verify(bw, c, data.ssl.certs, data.ssl.num);
|
|
if (c == bw->loading_content)
|
|
bw->loading_content = 0;
|
|
else if (c == bw->current_content) {
|
|
bw->current_content = 0;
|
|
browser_window_remove_caret(bw);
|
|
bw->scroll = NULL;
|
|
selection_init(bw->sel, NULL);
|
|
}
|
|
browser_window_stop_throbber(bw);
|
|
free(bw->referer);
|
|
bw->referer = 0;
|
|
break;
|
|
|
|
case CONTENT_MSG_REFRESH:
|
|
bw->refresh_interval = data.delay * 100;
|
|
break;
|
|
|
|
default:
|
|
assert(0);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Transfer the loading_content to a new download window.
|
|
*/
|
|
|
|
void browser_window_convert_to_download(struct browser_window *bw)
|
|
{
|
|
struct gui_download_window *download_window;
|
|
struct content *c = bw->loading_content;
|
|
struct fetch *fetch;
|
|
|
|
assert(c);
|
|
|
|
fetch = c->fetch;
|
|
|
|
if (fetch) {
|
|
/* create download window */
|
|
download_window = gui_download_window_create(c->url,
|
|
c->mime_type, fetch, c->total_size,
|
|
bw->window);
|
|
|
|
if (download_window) {
|
|
/* extract fetch from content */
|
|
c->fetch = 0;
|
|
c->fresh = false;
|
|
fetch_change_callback(fetch, download_window_callback,
|
|
download_window);
|
|
}
|
|
} else {
|
|
/* must already be a download window for this fetch */
|
|
/** \todo open it at top of stack */
|
|
}
|
|
|
|
/* remove content from browser window */
|
|
bw->loading_content = 0;
|
|
content_remove_user(c, browser_window_callback, (intptr_t) bw, 0);
|
|
browser_window_stop_throbber(bw);
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle meta http-equiv refresh time elapsing by loading a new page.
|
|
*
|
|
* \param p browser window to refresh with new page
|
|
*/
|
|
|
|
void browser_window_refresh(void *p)
|
|
{
|
|
struct browser_window *bw = p;
|
|
bool history_add = true;
|
|
|
|
assert(bw->current_content &&
|
|
(bw->current_content->status == CONTENT_STATUS_READY ||
|
|
bw->current_content->status == CONTENT_STATUS_DONE));
|
|
|
|
/* Ignore if the refresh URL has gone
|
|
* (may happen if a fetch error occurred) */
|
|
if (!bw->current_content->refresh)
|
|
return;
|
|
|
|
/* mark this content as invalid so it gets flushed from the cache */
|
|
bw->current_content->fresh = false;
|
|
|
|
if ((bw->current_content->url) &&
|
|
(bw->current_content->refresh) &&
|
|
(!strcmp(bw->current_content->url,
|
|
bw->current_content->refresh)))
|
|
history_add = false;
|
|
|
|
/* Treat an (almost) immediate refresh in a top-level browser window as
|
|
* if it were an HTTP redirect, and thus make the resulting fetch
|
|
* verifiable.
|
|
*
|
|
* See fetchcache.c for why redirected fetches should be verifiable at
|
|
* all.
|
|
*/
|
|
if (bw->refresh_interval <= 100 && bw->parent == NULL) {
|
|
browser_window_go(bw, bw->current_content->refresh,
|
|
bw->current_content->url, history_add);
|
|
} else {
|
|
browser_window_go_unverifiable(bw, bw->current_content->refresh,
|
|
bw->current_content->url, history_add,
|
|
bw->current_content);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Start the busy indicator.
|
|
*
|
|
* \param bw browser window
|
|
*/
|
|
|
|
void browser_window_start_throbber(struct browser_window *bw)
|
|
{
|
|
bw->throbbing = true;
|
|
|
|
while (bw->parent)
|
|
bw = bw->parent;
|
|
gui_window_start_throbber(bw->window);
|
|
}
|
|
|
|
|
|
/**
|
|
* Stop the busy indicator.
|
|
*
|
|
* \param bw browser window
|
|
*/
|
|
|
|
void browser_window_stop_throbber(struct browser_window *bw)
|
|
{
|
|
bw->throbbing = false;
|
|
|
|
while (bw->parent)
|
|
bw = bw->parent;
|
|
|
|
if (!browser_window_check_throbber(bw))
|
|
gui_window_stop_throbber(bw->window);
|
|
}
|
|
|
|
bool browser_window_check_throbber(struct browser_window *bw)
|
|
{
|
|
int children, index;
|
|
|
|
if (bw->throbbing)
|
|
return true;
|
|
|
|
if (bw->children) {
|
|
children = bw->rows * bw->cols;
|
|
for (index = 0; index < children; index++) {
|
|
if (browser_window_check_throbber(&bw->children[index]))
|
|
return true;
|
|
}
|
|
}
|
|
if (bw->iframes) {
|
|
for (index = 0; index < bw->iframe_count; index++) {
|
|
if (browser_window_check_throbber(&bw->iframes[index]))
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* when ready, set icon at top level
|
|
* \param bw browser_window
|
|
* current implementation ignores lower-levels' link rels completely
|
|
*/
|
|
void browser_window_set_icon(struct browser_window *bw)
|
|
{
|
|
while (bw->parent)
|
|
bw = bw->parent;
|
|
if ((bw->current_content != NULL) && (bw->current_content->type == CONTENT_HTML))
|
|
gui_window_set_icon(bw->window,
|
|
bw->current_content->data.html.favicon);
|
|
else
|
|
gui_window_set_icon(bw->window, NULL);
|
|
}
|
|
|
|
/**
|
|
* Redraw browser window, set extent to content, and update title.
|
|
*
|
|
* \param bw browser_window
|
|
* \param scroll_to_top move view to top of page
|
|
*/
|
|
|
|
void browser_window_update(struct browser_window *bw,
|
|
bool scroll_to_top)
|
|
{
|
|
struct box *pos;
|
|
int x, y;
|
|
|
|
if (!bw->current_content)
|
|
return;
|
|
|
|
if (bw->current_content->title != NULL) {
|
|
gui_window_set_title(bw->window, bw->current_content->title);
|
|
} else
|
|
gui_window_set_title(bw->window, bw->current_content->url);
|
|
|
|
gui_window_update_extent(bw->window);
|
|
|
|
if (scroll_to_top)
|
|
gui_window_set_scroll(bw->window, 0, 0);
|
|
|
|
/** \todo don't do this if the user has scrolled */
|
|
/* if frag_id exists, then try to scroll to it */
|
|
if (bw->frag_id && bw->current_content->type == CONTENT_HTML) {
|
|
if ((pos = box_find_by_id(bw->current_content->data.html.layout,
|
|
bw->frag_id)) != 0) {
|
|
box_coords(pos, &x, &y);
|
|
gui_window_set_scroll(bw->window, x, y);
|
|
}
|
|
}
|
|
|
|
gui_window_redraw_window(bw->window);
|
|
}
|
|
|
|
|
|
/**
|
|
* Stop all fetching activity in a browser window.
|
|
*
|
|
* \param bw browser window
|
|
*/
|
|
|
|
void browser_window_stop(struct browser_window *bw)
|
|
{
|
|
int children, index;
|
|
|
|
if (bw->loading_content) {
|
|
content_remove_user(bw->loading_content,
|
|
browser_window_callback, (intptr_t) bw, 0);
|
|
bw->loading_content = 0;
|
|
}
|
|
|
|
if (bw->current_content &&
|
|
bw->current_content->status != CONTENT_STATUS_DONE) {
|
|
assert(bw->current_content->status == CONTENT_STATUS_READY);
|
|
content_stop(bw->current_content,
|
|
browser_window_callback, (intptr_t) bw, 0);
|
|
}
|
|
|
|
schedule_remove(browser_window_refresh, bw);
|
|
|
|
if (bw->children) {
|
|
children = bw->rows * bw->cols;
|
|
for (index = 0; index < children; index++)
|
|
browser_window_stop(&bw->children[index]);
|
|
}
|
|
if (bw->iframes) {
|
|
children = bw->iframe_count;
|
|
for (index = 0; index < children; index++)
|
|
browser_window_stop(&bw->iframes[index]);
|
|
}
|
|
|
|
browser_window_stop_throbber(bw);
|
|
}
|
|
|
|
|
|
/**
|
|
* Reload the page in a browser window.
|
|
*
|
|
* \param bw browser window
|
|
* \param all whether to reload all objects associated with the page
|
|
*/
|
|
|
|
void browser_window_reload(struct browser_window *bw, bool all)
|
|
{
|
|
struct content *c;
|
|
unsigned int i;
|
|
|
|
if (!bw->current_content || bw->loading_content)
|
|
return;
|
|
|
|
if (all && bw->current_content->type == CONTENT_HTML) {
|
|
c = bw->current_content;
|
|
/* invalidate objects */
|
|
for (i = 0; i != c->data.html.object_count; i++) {
|
|
if (c->data.html.object[i].content)
|
|
c->data.html.object[i].content->fresh = false;
|
|
}
|
|
/* invalidate stylesheets */
|
|
for (i = STYLESHEET_START; i != c->data.html.stylesheet_count;
|
|
i++) {
|
|
if (c->data.html.stylesheets[i].c)
|
|
c->data.html.stylesheets[i].c->fresh =
|
|
false;
|
|
}
|
|
}
|
|
bw->current_content->fresh = false;
|
|
browser_window_go_post(bw, bw->current_content->url, 0, 0,
|
|
false, 0, false, true, 0);
|
|
}
|
|
|
|
|
|
/**
|
|
* Change the status bar of a browser window.
|
|
*
|
|
* \param bw browser window
|
|
* \param text new status text (copied)
|
|
*/
|
|
|
|
void browser_window_set_status(struct browser_window *bw, const char *text)
|
|
{
|
|
int text_len;
|
|
/* find topmost window */
|
|
while (bw->parent)
|
|
bw = bw->parent;
|
|
|
|
if ((bw->status_text != NULL) &&
|
|
(strcmp(text, bw->status_text) == 0)) {
|
|
/* status text is unchanged */
|
|
bw->status_match++;
|
|
return;
|
|
}
|
|
|
|
/* status text is changed */
|
|
|
|
text_len = strlen(text);
|
|
|
|
if ((bw->status_text == NULL) || (bw->status_text_len < text_len)) {
|
|
/* no current string allocation or it is not long enough */
|
|
free(bw->status_text);
|
|
bw->status_text = strdup(text);
|
|
bw->status_text_len = text_len;
|
|
} else {
|
|
/* current allocation has enough space */
|
|
memcpy(bw->status_text, text, text_len + 1);
|
|
}
|
|
|
|
bw->status_miss++;
|
|
gui_window_set_status(bw->window, bw->status_text);
|
|
}
|
|
|
|
|
|
/**
|
|
* Change the shape of the mouse pointer
|
|
*
|
|
* \param shape shape to use
|
|
*/
|
|
|
|
void browser_window_set_pointer(struct gui_window *g, gui_pointer_shape shape)
|
|
{
|
|
gui_window_set_pointer(g, shape);
|
|
}
|
|
|
|
|
|
/**
|
|
* Close and destroy a browser window.
|
|
*
|
|
* \param bw browser window
|
|
*/
|
|
|
|
void browser_window_destroy(struct browser_window *bw)
|
|
{
|
|
/* can't destoy child windows on their own */
|
|
assert(!bw->parent);
|
|
|
|
/* destroy */
|
|
browser_window_destroy_internal(bw);
|
|
free(bw);
|
|
}
|
|
|
|
|
|
/**
|
|
* Close and destroy all child browser window.
|
|
*
|
|
* \param bw browser window
|
|
*/
|
|
|
|
void browser_window_destroy_children(struct browser_window *bw)
|
|
{
|
|
int i;
|
|
|
|
if (bw->children) {
|
|
for (i = 0; i < (bw->rows * bw->cols); i++)
|
|
browser_window_destroy_internal(&bw->children[i]);
|
|
free(bw->children);
|
|
bw->children = NULL;
|
|
bw->rows = 0;
|
|
bw->cols = 0;
|
|
}
|
|
if (bw->iframes) {
|
|
for (i = 0; i < bw->iframe_count; i++)
|
|
browser_window_destroy_internal(&bw->iframes[i]);
|
|
free(bw->iframes);
|
|
bw->iframes = NULL;
|
|
bw->iframe_count = 0;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Release all memory associated with a browser window.
|
|
*
|
|
* \param bw browser window
|
|
*/
|
|
|
|
void browser_window_destroy_internal(struct browser_window *bw)
|
|
{
|
|
assert(bw);
|
|
|
|
LOG(("Destroying window"));
|
|
|
|
if ((bw->children) || (bw->iframes))
|
|
browser_window_destroy_children(bw);
|
|
if (bw->loading_content) {
|
|
content_remove_user(bw->loading_content,
|
|
browser_window_callback, (intptr_t) bw, 0);
|
|
bw->loading_content = 0;
|
|
}
|
|
if (bw->current_content) {
|
|
if (bw->current_content->status == CONTENT_STATUS_READY ||
|
|
bw->current_content->status ==
|
|
CONTENT_STATUS_DONE)
|
|
content_close(bw->current_content);
|
|
content_remove_user(bw->current_content,
|
|
browser_window_callback, (intptr_t) bw, 0);
|
|
bw->current_content = 0;
|
|
}
|
|
|
|
schedule_remove(browser_window_refresh, bw);
|
|
|
|
selection_destroy(bw->sel);
|
|
history_destroy(bw->history);
|
|
gui_window_destroy(bw->window);
|
|
|
|
free(bw->name);
|
|
free(bw->frag_id);
|
|
free(bw->status_text);
|
|
bw->status_text = NULL;
|
|
LOG(("Status text cache match:miss %d:%d",
|
|
bw->status_match, bw->status_miss));
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the browser window that is responsible for the child.
|
|
*
|
|
* \param bw The browser window to find the owner of
|
|
* \return the browser window's owner
|
|
*/
|
|
|
|
struct browser_window *browser_window_owner(struct browser_window *bw)
|
|
{
|
|
/* an iframe's parent is just the parent window */
|
|
if (bw->browser_window_type == BROWSER_WINDOW_IFRAME)
|
|
return bw->parent;
|
|
|
|
/* the parent of a frameset is either a NORMAL window or an IFRAME */
|
|
while (bw->parent) {
|
|
switch (bw->browser_window_type) {
|
|
case BROWSER_WINDOW_NORMAL:
|
|
case BROWSER_WINDOW_IFRAME:
|
|
return bw;
|
|
case BROWSER_WINDOW_FRAME:
|
|
case BROWSER_WINDOW_FRAMESET:
|
|
bw = bw->parent;
|
|
break;
|
|
}
|
|
}
|
|
return bw;
|
|
}
|
|
|
|
|
|
/**
|
|
* Reformat a browser window contents to a new width or height.
|
|
*
|
|
* \param bw the browser window to reformat
|
|
* \param width new width
|
|
* \param height new height
|
|
*/
|
|
|
|
void browser_window_reformat(struct browser_window *bw, int width, int height)
|
|
{
|
|
struct content *c = bw->current_content;
|
|
|
|
if (!c)
|
|
return;
|
|
|
|
content_reformat(c, width / bw->scale, height / bw->scale);
|
|
}
|
|
|
|
|
|
/**
|
|
* Sets the scale of a browser window
|
|
*
|
|
* \param bw The browser window to scale
|
|
* \param scale The new scale
|
|
* \param all Scale all windows in the tree (ie work up aswell as down)
|
|
*/
|
|
|
|
void browser_window_set_scale(struct browser_window *bw, float scale, bool all)
|
|
{
|
|
while (bw->parent && all)
|
|
bw = bw->parent;
|
|
browser_window_set_scale_internal(bw, scale);
|
|
if (bw->parent)
|
|
bw = bw->parent;
|
|
browser_window_recalculate_frameset(bw);
|
|
}
|
|
|
|
void browser_window_set_scale_internal(struct browser_window *bw, float scale)
|
|
{
|
|
int i;
|
|
struct content *c;
|
|
|
|
if (fabs(bw->scale-scale) < 0.0001)
|
|
return;
|
|
bw->scale = scale;
|
|
c = bw->current_content;
|
|
if (c) {
|
|
if (!content_can_reformat(c)) {
|
|
browser_window_update(bw, false);
|
|
} else {
|
|
bw->reformat_pending = true;
|
|
browser_reformat_pending = true;
|
|
}
|
|
}
|
|
|
|
gui_window_set_scale(bw->window, scale);
|
|
|
|
for (i = 0; i < (bw->cols * bw->rows); i++)
|
|
browser_window_set_scale_internal(&bw->children[i], scale);
|
|
for (i = 0; i < bw->iframe_count; i++)
|
|
browser_window_set_scale_internal(&bw->iframes[i], scale);
|
|
}
|
|
|
|
|
|
/**
|
|
* Update URL bar for a given browser window to given URL
|
|
*
|
|
* \param bw Browser window to update URL bar for.
|
|
* \param url URL for content displayed by bw, excluding any fragment.
|
|
* \param frag Additional fragment. May be NULL if none.
|
|
*/
|
|
|
|
void browser_window_refresh_url_bar(struct browser_window *bw, const char *url,
|
|
const char *frag)
|
|
{
|
|
char *url_buf;
|
|
|
|
assert(bw);
|
|
assert(url);
|
|
|
|
bw->visible_select_menu = NULL;
|
|
|
|
if (frag == NULL) {
|
|
/* With no fragment, we may as well pass url straight through
|
|
* saving a malloc, copy, free cycle.
|
|
*/
|
|
gui_window_set_url(bw->window, url);
|
|
} else {
|
|
url_buf = malloc(strlen(url) + 1 /* # */ +
|
|
strlen(frag) + 1 /* \0 */);
|
|
if (url_buf != NULL) {
|
|
/* This sprintf is safe because of the above size
|
|
* calculation, thus we don't need snprintf
|
|
*/
|
|
sprintf(url_buf, "%s#%s", url, frag);
|
|
gui_window_set_url(bw->window, url_buf);
|
|
free(url_buf);
|
|
} else {
|
|
warn_user("NoMemory", 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Locate a browser window in the specified stack according.
|
|
*
|
|
* \param bw the browser_window to search all relatives of
|
|
* \param target the target to locate
|
|
* \param new_window always return a new window (ie 'Open Link in New Window')
|
|
*/
|
|
|
|
struct browser_window *browser_window_find_target(struct browser_window *bw,
|
|
const char *target, browser_mouse_state mouse)
|
|
{
|
|
struct browser_window *bw_target;
|
|
struct browser_window *top;
|
|
struct content *c;
|
|
int rdepth;
|
|
|
|
/* use the base target if we don't have one */
|
|
c = bw->current_content;
|
|
if (!target && c && c->data.html.base_target)
|
|
target = c->data.html.base_target;
|
|
if (!target)
|
|
target = TARGET_SELF;
|
|
|
|
/* allow the simple case of target="_blank" to be ignored if requested
|
|
*/
|
|
if ((!(mouse & BROWSER_MOUSE_CLICK_2)) &&
|
|
(!((mouse & BROWSER_MOUSE_CLICK_2) &&
|
|
(mouse & BROWSER_MOUSE_MOD_2))) &&
|
|
(!option_target_blank)) {
|
|
/* not a mouse button 2 click
|
|
* not a mouse button 1 click with ctrl pressed
|
|
* configured to ignore target="_blank" */
|
|
if ((target == TARGET_BLANK) || (!strcasecmp(target, "_blank")))
|
|
return bw;
|
|
}
|
|
|
|
/* handle reserved keywords */
|
|
if (((option_button_2_tab) && (mouse & BROWSER_MOUSE_CLICK_2)) ||
|
|
((!option_button_2_tab) &&
|
|
((mouse & BROWSER_MOUSE_CLICK_1) &&
|
|
(mouse & BROWSER_MOUSE_MOD_2))) ||
|
|
((option_button_2_tab) && ((target == TARGET_BLANK) ||
|
|
(!strcasecmp(target, "_blank"))))) {
|
|
/* open in new tab if:
|
|
* - button_2 opens in new tab and button_2 was pressed
|
|
* OR
|
|
* - button_2 doesn't open in new tabs and button_1 was
|
|
* pressed with ctrl held
|
|
* OR
|
|
* - button_2 opens in new tab and the link target is "_blank"
|
|
*/
|
|
bw_target = browser_window_create(NULL, bw, NULL, false, true);
|
|
if (!bw_target)
|
|
return bw;
|
|
return bw_target;
|
|
} else if (((!option_button_2_tab) &&
|
|
(mouse & BROWSER_MOUSE_CLICK_2)) ||
|
|
((option_button_2_tab) &&
|
|
((mouse & BROWSER_MOUSE_CLICK_1) &&
|
|
(mouse & BROWSER_MOUSE_MOD_2))) ||
|
|
((!option_button_2_tab) && ((target == TARGET_BLANK) ||
|
|
(!strcasecmp(target, "_blank"))))) {
|
|
/* open in new window if:
|
|
* - button_2 doesn't open in new tabs and button_2 was pressed
|
|
* OR
|
|
* - button_2 opens in new tab and button_1 was pressed with
|
|
* ctrl held
|
|
* OR
|
|
* - button_2 doesn't open in new tabs and the link target is
|
|
* "_blank"
|
|
*/
|
|
bw_target = browser_window_create(NULL, bw, NULL, false, false);
|
|
if (!bw_target)
|
|
return bw;
|
|
return bw_target;
|
|
} else if ((target == TARGET_SELF) || (!strcasecmp(target, "_self"))) {
|
|
return bw;
|
|
} else if ((target == TARGET_PARENT) ||
|
|
(!strcasecmp(target, "_parent"))) {
|
|
if (bw->parent)
|
|
return bw->parent;
|
|
return bw;
|
|
} else if ((target == TARGET_TOP) || (!strcasecmp(target, "_top"))) {
|
|
while (bw->parent)
|
|
bw = bw->parent;
|
|
return bw;
|
|
}
|
|
|
|
/* find frame according to B.8, ie using the following priorities:
|
|
*
|
|
* 1) current frame
|
|
* 2) closest to front
|
|
*/
|
|
rdepth = -1;
|
|
bw_target = NULL;
|
|
for (top = bw; top->parent; top = top->parent);
|
|
browser_window_find_target_internal(top, target, 0, bw, &rdepth,
|
|
&bw_target);
|
|
if (bw_target)
|
|
return bw_target;
|
|
|
|
/* we require a new window using the target name */
|
|
if (!option_target_blank)
|
|
return bw;
|
|
bw_target = browser_window_create(NULL, bw, NULL, false, false);
|
|
if (!bw_target)
|
|
return bw;
|
|
|
|
/* frame names should begin with an alphabetic character (a-z,A-Z),
|
|
* however in practice you get things such as '_new' and '2left'. The
|
|
* only real effect this has is when giving out names as it can be
|
|
* assumed that an author intended '_new' to create a new nameless
|
|
* window (ie '_blank') whereas in the case of '2left' the intention
|
|
* was for a new named window. As such we merely special case windows
|
|
* that begin with an underscore. */
|
|
if (target[0] != '_') {
|
|
bw_target->name = strdup(target);
|
|
if (!bw_target->name)
|
|
warn_user("NoMemory", 0);
|
|
}
|
|
return bw_target;
|
|
}
|
|
|
|
void browser_window_find_target_internal(struct browser_window *bw,
|
|
const char *target, int depth, struct browser_window *page,
|
|
int *rdepth, struct browser_window **bw_target)
|
|
{
|
|
int i;
|
|
|
|
if ((bw->name) && (!strcasecmp(bw->name, target))) {
|
|
if ((bw == page) || (depth > *rdepth)) {
|
|
*rdepth = depth;
|
|
*bw_target = bw;
|
|
}
|
|
}
|
|
|
|
if ((!bw->children) && (!bw->iframes))
|
|
return;
|
|
|
|
depth++;
|
|
|
|
if (bw->children != NULL) {
|
|
for (i = 0; i < (bw->cols * bw->rows); i++) {
|
|
if ((bw->children[i].name) &&
|
|
(!strcasecmp(bw->children[i].name,
|
|
target))) {
|
|
if ((page == &bw->children[i]) ||
|
|
(depth > *rdepth)) {
|
|
*rdepth = depth;
|
|
*bw_target = &bw->children[i];
|
|
}
|
|
}
|
|
if (bw->children[i].children)
|
|
browser_window_find_target_internal(
|
|
&bw->children[i],
|
|
target, depth, page,
|
|
rdepth, bw_target);
|
|
}
|
|
}
|
|
|
|
if (bw->iframes != NULL) {
|
|
for (i = 0; i < bw->iframe_count; i++)
|
|
browser_window_find_target_internal(&bw->iframes[i],
|
|
target, depth, page, rdepth, bw_target);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Callback for fetch for download window fetches.
|
|
*/
|
|
|
|
void download_window_callback(fetch_msg msg, void *p, const void *data,
|
|
unsigned long size, fetch_error_code errorcode)
|
|
{
|
|
struct gui_download_window *download_window = p;
|
|
|
|
switch (msg) {
|
|
case FETCH_PROGRESS:
|
|
break;
|
|
case FETCH_DATA:
|
|
gui_download_window_data(download_window, data, size);
|
|
break;
|
|
|
|
case FETCH_FINISHED:
|
|
gui_download_window_done(download_window);
|
|
break;
|
|
|
|
case FETCH_ERROR:
|
|
gui_download_window_error(download_window, data);
|
|
break;
|
|
|
|
case FETCH_TYPE:
|
|
case FETCH_NOTMODIFIED:
|
|
case FETCH_AUTH:
|
|
case FETCH_CERT_ERR:
|
|
default:
|
|
/* not possible */
|
|
assert(0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle mouse clicks in a browser window.
|
|
*
|
|
* \param bw browser window
|
|
* \param mouse state of mouse buttons and modifier keys
|
|
* \param x coordinate of mouse
|
|
* \param y coordinate of mouse
|
|
*/
|
|
|
|
void browser_window_mouse_click(struct browser_window *bw,
|
|
browser_mouse_state mouse, int x, int y)
|
|
{
|
|
struct content *c = bw->current_content;
|
|
|
|
if (!c)
|
|
return;
|
|
|
|
switch (c->type) {
|
|
case CONTENT_HTML:
|
|
browser_window_mouse_action_html(bw, mouse, x, y);
|
|
break;
|
|
|
|
case CONTENT_TEXTPLAIN:
|
|
browser_window_mouse_action_text(bw, mouse, x, y);
|
|
break;
|
|
|
|
default:
|
|
if (mouse & BROWSER_MOUSE_MOD_2) {
|
|
if (mouse & BROWSER_MOUSE_DRAG_2)
|
|
gui_drag_save_object(GUI_SAVE_OBJECT_NATIVE, c,
|
|
bw->window);
|
|
else if (mouse & BROWSER_MOUSE_DRAG_1)
|
|
gui_drag_save_object(GUI_SAVE_OBJECT_ORIG, c,
|
|
bw->window);
|
|
}
|
|
else if (mouse & (BROWSER_MOUSE_DRAG_1 |
|
|
BROWSER_MOUSE_DRAG_2)) {
|
|
browser_window_page_drag_start(bw, x, y);
|
|
browser_window_set_pointer(bw->window, GUI_POINTER_MOVE);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle mouse clicks and movements in an HTML content window.
|
|
*
|
|
* \param bw browser window
|
|
* \param mouse state of mouse buttons and modifier keys
|
|
* \param x coordinate of mouse
|
|
* \param y coordinate of mouse
|
|
*
|
|
* This function handles both hovering and clicking. It is important that the
|
|
* code path is identical (except that hovering doesn't carry out the action),
|
|
* so that the status bar reflects exactly what will happen. Having separate
|
|
* code paths opens the possibility that an attacker will make the status bar
|
|
* show some harmless action where clicking will be harmful.
|
|
*/
|
|
|
|
void browser_window_mouse_action_html(struct browser_window *bw,
|
|
browser_mouse_state mouse, int x, int y)
|
|
{
|
|
enum { ACTION_NONE, ACTION_SUBMIT, ACTION_GO } action = ACTION_NONE;
|
|
char *title = 0;
|
|
const char *url = 0;
|
|
const char *target = 0;
|
|
char status_buffer[200];
|
|
const char *status = 0;
|
|
gui_pointer_shape pointer = GUI_POINTER_DEFAULT;
|
|
bool imagemap = false;
|
|
int box_x = 0, box_y = 0;
|
|
int gadget_box_x = 0, gadget_box_y = 0;
|
|
int text_box_x = 0;
|
|
struct box *url_box = 0;
|
|
struct box *gadget_box = 0;
|
|
struct box *text_box = 0;
|
|
struct content *c = bw->current_content;
|
|
struct box *box;
|
|
struct content *content = c;
|
|
struct content *gadget_content = c;
|
|
struct form_control *gadget = 0;
|
|
struct content *object = NULL;
|
|
struct box *next_box;
|
|
struct box *drag_candidate = NULL;
|
|
struct scroll *scroll = NULL;
|
|
plot_font_style_t fstyle;
|
|
int scroll_mouse_x = 0, scroll_mouse_y = 0;
|
|
int padding_left, padding_right, padding_top, padding_bottom;
|
|
|
|
|
|
if (bw->visible_select_menu != NULL) {
|
|
box = bw->visible_select_menu->box;
|
|
box_coords(box, &box_x, &box_y);
|
|
|
|
box_x -= box->border[LEFT].width;
|
|
box_y += box->height + box->border[BOTTOM].width +
|
|
box->padding[BOTTOM] + box->padding[TOP];
|
|
status = form_select_mouse_action(bw->visible_select_menu,
|
|
mouse, x - box_x, y - box_y);
|
|
if (status != NULL)
|
|
browser_window_set_status(bw, status);
|
|
else {
|
|
int width, height;
|
|
form_select_get_dimensions(bw->visible_select_menu,
|
|
&width, &height);
|
|
bw->visible_select_menu = NULL;
|
|
browser_window_redraw_rect(bw, box_x, box_y,
|
|
width, height);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (bw->scroll != NULL) {
|
|
struct browser_scroll_data *data = scroll_get_data(bw->scroll);
|
|
box = data->box;
|
|
box_coords(box, &box_x, &box_y);
|
|
if (scroll_is_horizontal(bw->scroll)) {
|
|
scroll_mouse_x = x - box_x ;
|
|
scroll_mouse_y = y - (box_y + box->padding[TOP] +
|
|
box->height + box->padding[BOTTOM] -
|
|
SCROLLBAR_WIDTH);
|
|
status = scroll_mouse_action(bw->scroll, mouse,
|
|
scroll_mouse_x, scroll_mouse_y);
|
|
} else {
|
|
scroll_mouse_x = x - (box_x + box->padding[LEFT] +
|
|
box->width + box->padding[RIGHT] -
|
|
SCROLLBAR_WIDTH);
|
|
scroll_mouse_y = y - box_y;
|
|
status = scroll_mouse_action(bw->scroll, mouse,
|
|
scroll_mouse_x, scroll_mouse_y);
|
|
}
|
|
|
|
browser_window_set_status(bw, status);
|
|
return;
|
|
}
|
|
|
|
bw->drag_type = DRAGGING_NONE;
|
|
|
|
/* search the box tree for a link, imagemap, form control, or
|
|
* box with scrollbars */
|
|
|
|
box = c->data.html.layout;
|
|
|
|
/* Consider the margins of the html page now */
|
|
box_x = box->margin[LEFT];
|
|
box_y = box->margin[TOP];
|
|
|
|
while ((next_box = box_at_point(box, x, y, &box_x, &box_y, &content)) !=
|
|
NULL) {
|
|
enum css_overflow_e overflow = CSS_OVERFLOW_VISIBLE;
|
|
|
|
box = next_box;
|
|
|
|
if (box->style && css_computed_visibility(box->style) ==
|
|
CSS_VISIBILITY_HIDDEN)
|
|
continue;
|
|
|
|
if (box->object)
|
|
object = box->object;
|
|
|
|
if (box->href) {
|
|
url = box->href;
|
|
target = box->target;
|
|
url_box = box;
|
|
}
|
|
|
|
if (box->usemap) {
|
|
url = imagemap_get(content, box->usemap,
|
|
box_x, box_y, x, y, &target);
|
|
if (url) {
|
|
imagemap = true;
|
|
url_box = box;
|
|
}
|
|
}
|
|
|
|
if (box->gadget) {
|
|
gadget_content = content;
|
|
gadget = box->gadget;
|
|
gadget_box = box;
|
|
gadget_box_x = box_x;
|
|
gadget_box_y = box_y;
|
|
if (gadget->form)
|
|
target = gadget->form->target;
|
|
}
|
|
|
|
if (box->title)
|
|
title = box->title;
|
|
|
|
pointer = get_pointer_shape(bw, box, false);
|
|
|
|
if (box->style)
|
|
overflow = css_computed_overflow(box->style);
|
|
|
|
if ((box->scroll_x != NULL || box->scroll_y != NULL) &&
|
|
drag_candidate == NULL)
|
|
drag_candidate = box;
|
|
|
|
if (box->scroll_y != NULL || box->scroll_x != NULL) {
|
|
padding_left = box_x + scroll_get_offset(box->scroll_x);
|
|
padding_right = padding_left + box->padding[LEFT] +
|
|
box->width + box->padding[RIGHT];
|
|
padding_top = box_y + scroll_get_offset(box->scroll_y);
|
|
padding_bottom = padding_top + box->padding[TOP] +
|
|
box->height + box->padding[BOTTOM];
|
|
|
|
if (x > padding_left && x < padding_right &&
|
|
y > padding_top && y < padding_bottom) {
|
|
/* mouse inside padding box */
|
|
|
|
if (box->scroll_y != NULL && x > padding_right -
|
|
SCROLLBAR_WIDTH) {
|
|
/* mouse above vertical box scroll */
|
|
|
|
scroll = box->scroll_y;
|
|
scroll_mouse_x = x - (padding_right -
|
|
SCROLLBAR_WIDTH);
|
|
scroll_mouse_y = y - padding_top;
|
|
break;
|
|
|
|
} else if (box->scroll_x != NULL &&
|
|
y > padding_bottom -
|
|
SCROLLBAR_WIDTH) {
|
|
/* mouse above horizontal box scroll */
|
|
|
|
scroll = box->scroll_x;
|
|
scroll_mouse_x = x - padding_left;
|
|
scroll_mouse_y = y - (padding_bottom -
|
|
SCROLLBAR_WIDTH);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (box->text && !box->object) {
|
|
text_box = box;
|
|
text_box_x = box_x;
|
|
}
|
|
}
|
|
|
|
/* use of box_x, box_y, or content below this point is probably a
|
|
* mistake; they will refer to the last box returned by box_at_point */
|
|
|
|
if (scroll) {
|
|
status = scroll_mouse_action(scroll, mouse,
|
|
scroll_mouse_x, scroll_mouse_y);
|
|
pointer = GUI_POINTER_DEFAULT;
|
|
} else if (gadget) {
|
|
switch (gadget->type) {
|
|
case GADGET_SELECT:
|
|
status = messages_get("FormSelect");
|
|
pointer = GUI_POINTER_MENU;
|
|
if (mouse & BROWSER_MOUSE_CLICK_1 &&
|
|
option_core_select_menu) {
|
|
bw->visible_select_menu = gadget;
|
|
form_open_select_menu(bw, gadget,
|
|
browser_select_menu_callback,
|
|
bw);
|
|
pointer = GUI_POINTER_DEFAULT;
|
|
} else if (mouse & BROWSER_MOUSE_CLICK_1)
|
|
gui_create_form_select_menu(bw, gadget);
|
|
break;
|
|
case GADGET_CHECKBOX:
|
|
status = messages_get("FormCheckbox");
|
|
if (mouse & BROWSER_MOUSE_CLICK_1) {
|
|
gadget->selected = !gadget->selected;
|
|
browser_redraw_box(gadget_content, gadget_box);
|
|
}
|
|
break;
|
|
case GADGET_RADIO:
|
|
status = messages_get("FormRadio");
|
|
if (mouse & BROWSER_MOUSE_CLICK_1)
|
|
browser_radio_set(gadget_content, gadget);
|
|
break;
|
|
case GADGET_IMAGE:
|
|
if (mouse & BROWSER_MOUSE_CLICK_1) {
|
|
gadget->data.image.mx = x - gadget_box_x;
|
|
gadget->data.image.my = y - gadget_box_y;
|
|
}
|
|
/* drop through */
|
|
case GADGET_SUBMIT:
|
|
if (gadget->form) {
|
|
snprintf(status_buffer, sizeof status_buffer,
|
|
messages_get("FormSubmit"),
|
|
gadget->form->action);
|
|
status = status_buffer;
|
|
pointer = get_pointer_shape(bw, gadget_box,
|
|
false);
|
|
if (mouse & (BROWSER_MOUSE_CLICK_1 |
|
|
BROWSER_MOUSE_CLICK_2))
|
|
action = ACTION_SUBMIT;
|
|
} else {
|
|
status = messages_get("FormBadSubmit");
|
|
}
|
|
break;
|
|
case GADGET_TEXTAREA:
|
|
status = messages_get("FormTextarea");
|
|
pointer = get_pointer_shape(bw, gadget_box, false);
|
|
|
|
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);
|
|
|
|
browser_window_textarea_click(bw,
|
|
mouse,
|
|
gadget_box,
|
|
gadget_box_x,
|
|
gadget_box_y,
|
|
x - gadget_box_x,
|
|
y - gadget_box_y);
|
|
}
|
|
|
|
if (text_box) {
|
|
int pixel_offset;
|
|
size_t idx;
|
|
|
|
font_plot_style_from_css(text_box->style,
|
|
&fstyle);
|
|
|
|
nsfont.font_position_in_string(&fstyle,
|
|
text_box->text,
|
|
text_box->length,
|
|
x - gadget_box_x - text_box->x,
|
|
&idx,
|
|
&pixel_offset);
|
|
|
|
selection_click(bw->sel, mouse,
|
|
text_box->byte_offset + idx);
|
|
|
|
if (selection_dragging(bw->sel)) {
|
|
bw->drag_type = DRAGGING_SELECTION;
|
|
status = messages_get("Selecting");
|
|
} else
|
|
status = c->status_message;
|
|
}
|
|
else if (mouse & BROWSER_MOUSE_PRESS_1)
|
|
selection_clear(bw->sel, true);
|
|
break;
|
|
case GADGET_TEXTBOX:
|
|
case GADGET_PASSWORD:
|
|
status = messages_get("FormTextbox");
|
|
pointer = get_pointer_shape(bw, gadget_box, false);
|
|
|
|
if ((mouse & BROWSER_MOUSE_PRESS_1) &&
|
|
!(mouse & (BROWSER_MOUSE_MOD_1 |
|
|
BROWSER_MOUSE_MOD_2))) {
|
|
browser_window_input_click(bw,
|
|
gadget_box,
|
|
gadget_box_x,
|
|
gadget_box_y,
|
|
x - gadget_box_x,
|
|
y - gadget_box_y);
|
|
}
|
|
if (text_box) {
|
|
int pixel_offset;
|
|
size_t idx;
|
|
|
|
if (mouse & (BROWSER_MOUSE_DRAG_1 |
|
|
BROWSER_MOUSE_DRAG_2))
|
|
selection_init(bw->sel, gadget_box);
|
|
|
|
font_plot_style_from_css(text_box->style,
|
|
&fstyle);
|
|
|
|
nsfont.font_position_in_string(&fstyle,
|
|
text_box->text,
|
|
text_box->length,
|
|
x - gadget_box_x - text_box->x,
|
|
&idx,
|
|
&pixel_offset);
|
|
|
|
selection_click(bw->sel, mouse,
|
|
text_box->byte_offset + idx);
|
|
|
|
if (selection_dragging(bw->sel))
|
|
bw->drag_type = DRAGGING_SELECTION;
|
|
}
|
|
else if (mouse & BROWSER_MOUSE_PRESS_1)
|
|
selection_clear(bw->sel, true);
|
|
break;
|
|
case GADGET_HIDDEN:
|
|
/* not possible: no box generated */
|
|
break;
|
|
case GADGET_RESET:
|
|
status = messages_get("FormReset");
|
|
break;
|
|
case GADGET_FILE:
|
|
status = messages_get("FormFile");
|
|
break;
|
|
case GADGET_BUTTON:
|
|
/* This gadget cannot be activated */
|
|
status = messages_get("FormButton");
|
|
break;
|
|
}
|
|
|
|
} else if (object && (mouse & BROWSER_MOUSE_MOD_2)) {
|
|
|
|
if (mouse & BROWSER_MOUSE_DRAG_2)
|
|
gui_drag_save_object(GUI_SAVE_OBJECT_NATIVE, object,
|
|
bw->window);
|
|
else if (mouse & BROWSER_MOUSE_DRAG_1)
|
|
gui_drag_save_object(GUI_SAVE_OBJECT_ORIG, object,
|
|
bw->window);
|
|
|
|
/* \todo should have a drag-saving object msg */
|
|
status = c->status_message;
|
|
|
|
} else if (url) {
|
|
if (title) {
|
|
snprintf(status_buffer, sizeof status_buffer, "%s: %s",
|
|
url, title);
|
|
status = status_buffer;
|
|
} else
|
|
status = url;
|
|
|
|
pointer = get_pointer_shape(bw, url_box, imagemap);
|
|
|
|
if (mouse & BROWSER_MOUSE_CLICK_1 &&
|
|
mouse & BROWSER_MOUSE_MOD_1) {
|
|
/* force download of link */
|
|
browser_window_go_post(bw, url, 0, 0, false,
|
|
c->url, true, true, 0);
|
|
} else if (mouse & BROWSER_MOUSE_CLICK_2 &&
|
|
mouse & BROWSER_MOUSE_MOD_1) {
|
|
free(browser_window_href_content.url);
|
|
browser_window_href_content.url = strdup(url);
|
|
if (!browser_window_href_content.url)
|
|
warn_user("NoMemory", 0);
|
|
else
|
|
gui_window_save_as_link(bw->window,
|
|
&browser_window_href_content);
|
|
|
|
} else if (mouse & (BROWSER_MOUSE_CLICK_1 |
|
|
BROWSER_MOUSE_CLICK_2))
|
|
action = ACTION_GO;
|
|
|
|
} else {
|
|
bool done = false;
|
|
|
|
/* frame resizing */
|
|
if (bw->parent) {
|
|
struct browser_window *parent;
|
|
for (parent = bw->parent; parent->parent;
|
|
parent = parent->parent);
|
|
browser_window_resize_frames(parent, mouse,
|
|
x + bw->x0, y + bw->y0,
|
|
&pointer, &status, &done);
|
|
}
|
|
|
|
/* if clicking in the main page, remove the selection from any
|
|
* text areas */
|
|
if (!done) {
|
|
if (text_box &&
|
|
(mouse & (BROWSER_MOUSE_CLICK_1 |
|
|
BROWSER_MOUSE_CLICK_2)) &&
|
|
selection_root(bw->sel) != c->data.html.layout)
|
|
selection_init(bw->sel, c->data.html.layout);
|
|
|
|
if (text_box) {
|
|
int pixel_offset;
|
|
size_t idx;
|
|
|
|
font_plot_style_from_css(text_box->style,
|
|
&fstyle);
|
|
|
|
nsfont.font_position_in_string(&fstyle,
|
|
text_box->text,
|
|
text_box->length,
|
|
x - text_box_x,
|
|
&idx,
|
|
&pixel_offset);
|
|
|
|
if (selection_click(bw->sel, mouse,
|
|
text_box->byte_offset + idx)) {
|
|
/* key presses must be directed at the
|
|
* main browser window, paste text
|
|
* operations ignored */
|
|
|
|
if (selection_dragging(bw->sel)) {
|
|
bw->drag_type =
|
|
DRAGGING_SELECTION;
|
|
status =
|
|
messages_get("Selecting");
|
|
} else
|
|
status = c->status_message;
|
|
|
|
done = true;
|
|
}
|
|
}
|
|
else if (mouse & BROWSER_MOUSE_PRESS_1)
|
|
selection_clear(bw->sel, true);
|
|
}
|
|
|
|
if (!done) {
|
|
if (title)
|
|
status = title;
|
|
else if (bw->loading_content)
|
|
status = bw->loading_content->status_message;
|
|
else
|
|
status = c->status_message;
|
|
|
|
if (mouse & BROWSER_MOUSE_DRAG_1) {
|
|
if (mouse & BROWSER_MOUSE_MOD_2) {
|
|
gui_drag_save_object(GUI_SAVE_COMPLETE,
|
|
c, bw->window);
|
|
} else {
|
|
if (drag_candidate == NULL)
|
|
browser_window_page_drag_start(
|
|
bw, x, y);
|
|
else {
|
|
browser_window_box_drag_start(
|
|
bw,
|
|
drag_candidate,
|
|
x, y);
|
|
}
|
|
pointer = GUI_POINTER_MOVE;
|
|
}
|
|
}
|
|
else if (mouse & BROWSER_MOUSE_DRAG_2) {
|
|
if (mouse & BROWSER_MOUSE_MOD_2) {
|
|
gui_drag_save_object(GUI_SAVE_SOURCE,
|
|
c, bw->window);
|
|
} else {
|
|
if (drag_candidate == NULL)
|
|
browser_window_page_drag_start(
|
|
bw, x, y);
|
|
else {
|
|
browser_window_box_drag_start(
|
|
bw,
|
|
drag_candidate,
|
|
x, y);
|
|
}
|
|
pointer = GUI_POINTER_MOVE;
|
|
}
|
|
}
|
|
}
|
|
if ((mouse & BROWSER_MOUSE_CLICK_1) &&
|
|
!selection_defined(bw->sel)) {
|
|
/* ensure key presses still act on the browser window */
|
|
browser_window_remove_caret(bw);
|
|
}
|
|
}
|
|
|
|
assert(status);
|
|
|
|
if (action == ACTION_SUBMIT || action == ACTION_GO)
|
|
bw->last_action = wallclock();
|
|
|
|
browser_window_set_status(bw, status);
|
|
browser_window_set_pointer(bw->window, pointer);
|
|
|
|
/* deferred actions that can cause this browser_window to be destroyed
|
|
and must therefore be done after set_status/pointer
|
|
*/
|
|
switch (action) {
|
|
case ACTION_SUBMIT:
|
|
browser_form_submit(bw,
|
|
browser_window_find_target(bw, target, mouse),
|
|
gadget->form, gadget);
|
|
break;
|
|
case ACTION_GO:
|
|
browser_window_go(browser_window_find_target(bw, target, mouse),
|
|
url, c->url, true);
|
|
break;
|
|
case ACTION_NONE:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle mouse clicks and movements in a TEXTPLAIN content window.
|
|
*
|
|
* \param bw browser window
|
|
* \param click type of mouse click
|
|
* \param x coordinate of mouse
|
|
* \param y coordinate of mouse
|
|
*
|
|
* This function handles both hovering and clicking. It is important that the
|
|
* code path is identical (except that hovering doesn't carry out the action),
|
|
* so that the status bar reflects exactly what will happen. Having separate
|
|
* code paths opens the possibility that an attacker will make the status bar
|
|
* show some harmless action where clicking will be harmful.
|
|
*/
|
|
|
|
void browser_window_mouse_action_text(struct browser_window *bw,
|
|
browser_mouse_state mouse, int x, int y)
|
|
{
|
|
struct content *c = bw->current_content;
|
|
gui_pointer_shape pointer = GUI_POINTER_DEFAULT;
|
|
const char *status = 0;
|
|
size_t idx;
|
|
int dir = 0;
|
|
|
|
bw->drag_type = DRAGGING_NONE;
|
|
|
|
if (!bw->sel) return;
|
|
|
|
idx = textplain_offset_from_coords(c, x, y, dir);
|
|
if (selection_click(bw->sel, mouse, idx)) {
|
|
|
|
if (selection_dragging(bw->sel)) {
|
|
bw->drag_type = DRAGGING_SELECTION;
|
|
status = messages_get("Selecting");
|
|
}
|
|
else
|
|
status = c->status_message;
|
|
}
|
|
else {
|
|
if (bw->loading_content)
|
|
status = bw->loading_content->status_message;
|
|
else
|
|
status = c->status_message;
|
|
|
|
if (mouse & (BROWSER_MOUSE_DRAG_1 | BROWSER_MOUSE_DRAG_2)) {
|
|
browser_window_page_drag_start(bw, x, y);
|
|
pointer = GUI_POINTER_MOVE;
|
|
}
|
|
}
|
|
|
|
assert(status);
|
|
|
|
browser_window_set_status(bw, status);
|
|
browser_window_set_pointer(bw->window, pointer);
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle mouse movements in a browser window.
|
|
*
|
|
* \param bw browser window
|
|
* \param mouse state of mouse buttons and modifier keys
|
|
* \param x coordinate of mouse
|
|
* \param y coordinate of mouse
|
|
*/
|
|
|
|
void browser_window_mouse_track(struct browser_window *bw,
|
|
browser_mouse_state mouse, int x, int y)
|
|
{
|
|
struct content *c = bw->current_content;
|
|
|
|
if (c == NULL && bw->drag_type != DRAGGING_FRAME)
|
|
return;
|
|
|
|
/* detect end of drag operation in case the platform-specific code
|
|
doesn't call browser_mouse_drag_end() (RISC OS code does) */
|
|
|
|
if (bw->drag_type != DRAGGING_NONE && !mouse) {
|
|
browser_window_mouse_drag_end(bw, mouse, x, y);
|
|
}
|
|
|
|
if (bw->drag_type == DRAGGING_FRAME) {
|
|
browser_window_resize_frame(bw, bw->x0 + x, bw->y0 + y);
|
|
} else if (bw->drag_type == DRAGGING_PAGE_SCROLL) {
|
|
/* mouse movement since drag started */
|
|
int scrollx = bw->drag_start_x - x;
|
|
int scrolly = bw->drag_start_y - y;
|
|
|
|
/* new scroll offsets */
|
|
scrollx += bw->drag_start_scroll_x;
|
|
scrolly += bw->drag_start_scroll_y;
|
|
|
|
bw->drag_start_scroll_x = scrollx;
|
|
bw->drag_start_scroll_y = scrolly;
|
|
|
|
gui_window_set_scroll(bw->window, scrollx, scrolly);
|
|
} else {
|
|
assert(c != NULL);
|
|
|
|
switch (c->type) {
|
|
case CONTENT_HTML:
|
|
browser_window_mouse_track_html(bw, mouse, x, y);
|
|
break;
|
|
|
|
case CONTENT_TEXTPLAIN:
|
|
browser_window_mouse_track_text(bw, mouse, x, y);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle mouse tracking (including drags) in an HTML content window.
|
|
*
|
|
* \param bw browser window
|
|
* \param mouse state of mouse buttons and modifier keys
|
|
* \param x coordinate of mouse
|
|
* \param y coordinate of mouse
|
|
*/
|
|
|
|
void browser_window_mouse_track_html(struct browser_window *bw,
|
|
browser_mouse_state mouse, int x, int y)
|
|
{
|
|
|
|
switch (bw->drag_type) {
|
|
case DRAGGING_SELECTION: {
|
|
struct box *box;
|
|
int dir = -1;
|
|
int dx, dy;
|
|
|
|
if (selection_dragging_start(bw->sel)) dir = 1;
|
|
|
|
box = browser_window_pick_text_box(bw, x, y, dir,
|
|
&dx, &dy);
|
|
if (box) {
|
|
int pixel_offset;
|
|
size_t idx;
|
|
plot_font_style_t fstyle;
|
|
|
|
font_plot_style_from_css(box->style, &fstyle);
|
|
|
|
nsfont.font_position_in_string(&fstyle,
|
|
box->text, box->length,
|
|
dx, &idx, &pixel_offset);
|
|
|
|
selection_track(bw->sel, mouse,
|
|
box->byte_offset + idx);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
browser_window_mouse_action_html(bw, mouse, x, y);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle mouse tracking (including drags) in a TEXTPLAIN content window.
|
|
*
|
|
* \param bw browser window
|
|
* \param mouse state of mouse buttons and modifier keys
|
|
* \param x coordinate of mouse
|
|
* \param y coordinate of mouse
|
|
*/
|
|
|
|
void browser_window_mouse_track_text(struct browser_window *bw,
|
|
browser_mouse_state mouse, int x, int y)
|
|
{
|
|
switch (bw->drag_type) {
|
|
|
|
case DRAGGING_SELECTION: {
|
|
struct content *c = bw->current_content;
|
|
int dir = -1;
|
|
size_t idx;
|
|
|
|
if (selection_dragging_start(bw->sel)) dir = 1;
|
|
|
|
idx = textplain_offset_from_coords(c, x, y, dir);
|
|
selection_track(bw->sel, mouse, idx);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
browser_window_mouse_action_text(bw, mouse, x, y);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Handles the end of a drag operation in a browser window.
|
|
*
|
|
* \param bw browser window
|
|
* \param mouse state of mouse buttons and modifier keys
|
|
* \param x coordinate of mouse
|
|
* \param y coordinate of mouse
|
|
*/
|
|
|
|
void browser_window_mouse_drag_end(struct browser_window *bw,
|
|
browser_mouse_state mouse, int x, int y)
|
|
{
|
|
struct box *box;
|
|
int scroll_mouse_x, scroll_mouse_y, box_x, box_y;
|
|
|
|
if (bw->visible_select_menu != NULL) {
|
|
box = bw->visible_select_menu->box;
|
|
box_coords(box, &box_x, &box_y);
|
|
|
|
box_x -= box->border[LEFT].width;
|
|
box_y += box->height + box->border[BOTTOM].width +
|
|
box->padding[BOTTOM] + box->padding[TOP];
|
|
form_select_mouse_drag_end(bw->visible_select_menu,
|
|
mouse, x - box_x, y - box_y);
|
|
return;
|
|
}
|
|
|
|
if (bw->scroll != NULL) {
|
|
struct browser_scroll_data *data = scroll_get_data(bw->scroll);
|
|
box = data->box;
|
|
box_coords(box, &box_x, &box_y);
|
|
if (scroll_is_horizontal(bw->scroll)) {
|
|
scroll_mouse_x = x - box_x;
|
|
scroll_mouse_y = y - (box_y + box->padding[TOP] +
|
|
box->height + box->padding[BOTTOM] -
|
|
SCROLLBAR_WIDTH);
|
|
scroll_mouse_drag_end(bw->scroll, mouse,
|
|
scroll_mouse_x, scroll_mouse_y);
|
|
} else {
|
|
scroll_mouse_x = x - (box_x + box->padding[LEFT] +
|
|
box->width + box->padding[RIGHT] -
|
|
SCROLLBAR_WIDTH);
|
|
scroll_mouse_y = y - box_y;
|
|
scroll_mouse_drag_end(bw->scroll, mouse,
|
|
scroll_mouse_x, scroll_mouse_y);
|
|
}
|
|
return;
|
|
}
|
|
|
|
switch (bw->drag_type) {
|
|
case DRAGGING_SELECTION: {
|
|
struct content *c = bw->current_content;
|
|
if (c) {
|
|
bool found = true;
|
|
int dir = -1;
|
|
size_t idx;
|
|
|
|
if (selection_dragging_start(bw->sel)) dir = 1;
|
|
|
|
if (c->type == CONTENT_HTML) {
|
|
int pixel_offset;
|
|
struct box *box;
|
|
int dx, dy;
|
|
|
|
box = browser_window_pick_text_box(bw,
|
|
x, y, dir, &dx, &dy);
|
|
if (box) {
|
|
plot_font_style_t fstyle;
|
|
|
|
font_plot_style_from_css(
|
|
box->style,
|
|
&fstyle);
|
|
|
|
nsfont.font_position_in_string(
|
|
&fstyle,
|
|
box->text,
|
|
box->length,
|
|
dx,
|
|
&idx,
|
|
&pixel_offset);
|
|
|
|
idx += box->byte_offset;
|
|
selection_track(bw->sel, mouse,
|
|
idx);
|
|
}
|
|
else
|
|
found = false;
|
|
}
|
|
else {
|
|
assert(c->type == CONTENT_TEXTPLAIN);
|
|
idx = textplain_offset_from_coords(c, x,
|
|
y, dir);
|
|
}
|
|
|
|
if (found)
|
|
selection_track(bw->sel, mouse, idx);
|
|
}
|
|
selection_drag_end(bw->sel);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
bw->drag_type = DRAGGING_NONE;
|
|
}
|
|
|
|
|
|
/**
|
|
* Set a radio form control and clear the others in the group.
|
|
*
|
|
* \param content content containing the form, of type CONTENT_TYPE
|
|
* \param radio form control of type GADGET_RADIO
|
|
*/
|
|
|
|
void browser_radio_set(struct content *content,
|
|
struct form_control *radio)
|
|
{
|
|
struct form_control *control;
|
|
|
|
assert(content);
|
|
assert(radio);
|
|
if (!radio->form)
|
|
return;
|
|
|
|
if (radio->selected)
|
|
return;
|
|
|
|
for (control = radio->form->controls; control;
|
|
control = control->next) {
|
|
if (control->type != GADGET_RADIO)
|
|
continue;
|
|
if (control == radio)
|
|
continue;
|
|
if (strcmp(control->name, radio->name) != 0)
|
|
continue;
|
|
|
|
if (control->selected) {
|
|
control->selected = false;
|
|
browser_redraw_box(content, control->box);
|
|
}
|
|
}
|
|
|
|
radio->selected = true;
|
|
browser_redraw_box(content, radio->box);
|
|
}
|
|
|
|
|
|
/**
|
|
* Redraw a rectangular region of a browser window
|
|
*
|
|
* \param bw browser window to be redrawn
|
|
* \param x x co-ord of top-left
|
|
* \param y y co-ord of top-left
|
|
* \param width width of rectangle
|
|
* \param height height of rectangle
|
|
*/
|
|
|
|
void browser_window_redraw_rect(struct browser_window *bw, int x, int y,
|
|
int width, int height)
|
|
{
|
|
struct content *c = bw->current_content;
|
|
|
|
if (c) {
|
|
union content_msg_data data;
|
|
|
|
data.redraw.x = x;
|
|
data.redraw.y = y;
|
|
data.redraw.width = width;
|
|
data.redraw.height = height;
|
|
|
|
data.redraw.full_redraw = true;
|
|
|
|
data.redraw.object = c;
|
|
data.redraw.object_x = 0;
|
|
data.redraw.object_y = 0;
|
|
data.redraw.object_width = c->width;
|
|
data.redraw.object_height = c->height;
|
|
|
|
content_broadcast(c, CONTENT_MSG_REDRAW, data);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Redraw a box.
|
|
*
|
|
* \param c content containing the box, of type CONTENT_HTML
|
|
* \param box box to redraw
|
|
*/
|
|
|
|
void browser_redraw_box(struct content *c, struct box *box)
|
|
{
|
|
int x, y;
|
|
union content_msg_data data;
|
|
|
|
box_coords(box, &x, &y);
|
|
|
|
data.redraw.x = x;
|
|
data.redraw.y = y;
|
|
data.redraw.width = box->padding[LEFT] + box->width +
|
|
box->padding[RIGHT];
|
|
data.redraw.height = box->padding[TOP] + box->height +
|
|
box->padding[BOTTOM];
|
|
|
|
data.redraw.full_redraw = true;
|
|
|
|
data.redraw.object = c;
|
|
data.redraw.object_x = 0;
|
|
data.redraw.object_y = 0;
|
|
data.redraw.object_width = c->width;
|
|
data.redraw.object_height = c->height;
|
|
|
|
content_broadcast(c, CONTENT_MSG_REDRAW, data);
|
|
}
|
|
|
|
|
|
/**
|
|
* Process a selection from a form select menu.
|
|
*
|
|
* \param bw browser window with menu
|
|
* \param control form control with menu
|
|
* \param item index of item selected from the menu
|
|
*/
|
|
|
|
void browser_window_form_select(struct browser_window *bw,
|
|
struct form_control *control, int item)
|
|
{
|
|
struct box *inline_box;
|
|
struct form_option *o;
|
|
int count;
|
|
|
|
assert(bw);
|
|
assert(control);
|
|
|
|
inline_box = control->box->children->children;
|
|
|
|
for (count = 0, o = control->data.select.items;
|
|
o != NULL;
|
|
count++, o = o->next) {
|
|
if (!control->data.select.multiple)
|
|
o->selected = false;
|
|
if (count == item) {
|
|
if (control->data.select.multiple) {
|
|
if (o->selected) {
|
|
o->selected = false;
|
|
control->data.select.num_selected--;
|
|
} else {
|
|
o->selected = true;
|
|
control->data.select.num_selected++;
|
|
}
|
|
} else {
|
|
o->selected = true;
|
|
}
|
|
}
|
|
if (o->selected)
|
|
control->data.select.current = o;
|
|
}
|
|
|
|
talloc_free(inline_box->text);
|
|
inline_box->text = 0;
|
|
if (control->data.select.num_selected == 0)
|
|
inline_box->text = talloc_strdup(bw->current_content,
|
|
messages_get("Form_None"));
|
|
else if (control->data.select.num_selected == 1)
|
|
inline_box->text = talloc_strdup(bw->current_content,
|
|
control->data.select.current->text);
|
|
else
|
|
inline_box->text = talloc_strdup(bw->current_content,
|
|
messages_get("Form_Many"));
|
|
if (!inline_box->text) {
|
|
warn_user("NoMemory", 0);
|
|
inline_box->length = 0;
|
|
} else
|
|
inline_box->length = strlen(inline_box->text);
|
|
inline_box->width = control->box->width;
|
|
|
|
browser_redraw_box(bw->current_content, control->box);
|
|
}
|
|
|
|
|
|
gui_pointer_shape get_pointer_shape(struct browser_window *bw, struct box *box,
|
|
bool imagemap)
|
|
{
|
|
gui_pointer_shape pointer;
|
|
css_computed_style *style;
|
|
enum css_cursor_e cursor;
|
|
lwc_string **cursor_uris;
|
|
bool loading;
|
|
|
|
assert(bw);
|
|
|
|
loading = (bw->loading_content != NULL || (bw->current_content &&
|
|
bw->current_content->status == CONTENT_STATUS_READY));
|
|
|
|
if (wallclock() - bw->last_action < 100 && loading)
|
|
/* If less than 1 second since last link followed, show
|
|
* progress indicating pointer and we're loading something */
|
|
return GUI_POINTER_PROGRESS;
|
|
|
|
if (box->type == BOX_FLOAT_LEFT || box->type == BOX_FLOAT_RIGHT)
|
|
style = box->children->style;
|
|
else
|
|
style = box->style;
|
|
|
|
if (style == NULL)
|
|
return GUI_POINTER_DEFAULT;
|
|
|
|
cursor = css_computed_cursor(style, &cursor_uris);
|
|
|
|
switch (cursor) {
|
|
case CSS_CURSOR_AUTO:
|
|
if (box->href || (box->gadget &&
|
|
(box->gadget->type == GADGET_IMAGE ||
|
|
box->gadget->type == GADGET_SUBMIT)) ||
|
|
imagemap) {
|
|
/* link */
|
|
pointer = GUI_POINTER_POINT;
|
|
} else if (box->gadget &&
|
|
(box->gadget->type == GADGET_TEXTBOX ||
|
|
box->gadget->type == GADGET_PASSWORD ||
|
|
box->gadget->type == GADGET_TEXTAREA)) {
|
|
/* text input */
|
|
pointer = GUI_POINTER_CARET;
|
|
} else {
|
|
/* anything else */
|
|
if (loading) {
|
|
/* loading new content */
|
|
pointer = GUI_POINTER_PROGRESS;
|
|
} else {
|
|
pointer = GUI_POINTER_DEFAULT;
|
|
}
|
|
}
|
|
break;
|
|
case CSS_CURSOR_CROSSHAIR:
|
|
pointer = GUI_POINTER_CROSS;
|
|
break;
|
|
case CSS_CURSOR_POINTER:
|
|
pointer = GUI_POINTER_POINT;
|
|
break;
|
|
case CSS_CURSOR_MOVE:
|
|
pointer = GUI_POINTER_MOVE;
|
|
break;
|
|
case CSS_CURSOR_E_RESIZE:
|
|
pointer = GUI_POINTER_RIGHT;
|
|
break;
|
|
case CSS_CURSOR_W_RESIZE:
|
|
pointer = GUI_POINTER_LEFT;
|
|
break;
|
|
case CSS_CURSOR_N_RESIZE:
|
|
pointer = GUI_POINTER_UP;
|
|
break;
|
|
case CSS_CURSOR_S_RESIZE:
|
|
pointer = GUI_POINTER_DOWN;
|
|
break;
|
|
case CSS_CURSOR_NE_RESIZE:
|
|
pointer = GUI_POINTER_RU;
|
|
break;
|
|
case CSS_CURSOR_SW_RESIZE:
|
|
pointer = GUI_POINTER_LD;
|
|
break;
|
|
case CSS_CURSOR_SE_RESIZE:
|
|
pointer = GUI_POINTER_RD;
|
|
break;
|
|
case CSS_CURSOR_NW_RESIZE:
|
|
pointer = GUI_POINTER_LU;
|
|
break;
|
|
case CSS_CURSOR_TEXT:
|
|
pointer = GUI_POINTER_CARET;
|
|
break;
|
|
case CSS_CURSOR_WAIT:
|
|
pointer = GUI_POINTER_WAIT;
|
|
break;
|
|
case CSS_CURSOR_PROGRESS:
|
|
pointer = GUI_POINTER_PROGRESS;
|
|
break;
|
|
case CSS_CURSOR_HELP:
|
|
pointer = GUI_POINTER_HELP;
|
|
break;
|
|
default:
|
|
pointer = GUI_POINTER_DEFAULT;
|
|
break;
|
|
}
|
|
|
|
return pointer;
|
|
}
|
|
|
|
|
|
/**
|
|
* Collect controls and submit a form.
|
|
*/
|
|
|
|
void browser_form_submit(struct browser_window *bw, struct browser_window *target,
|
|
struct form *form, struct form_control *submit_button)
|
|
{
|
|
char *data = 0, *url = 0;
|
|
struct form_successful_control *success;
|
|
|
|
assert(form);
|
|
assert(bw->current_content->type == CONTENT_HTML);
|
|
|
|
if (!form_successful_controls(form, submit_button, &success)) {
|
|
warn_user("NoMemory", 0);
|
|
return;
|
|
}
|
|
|
|
switch (form->method) {
|
|
case method_GET:
|
|
data = form_url_encode(form, success);
|
|
if (!data) {
|
|
form_free_successful(success);
|
|
warn_user("NoMemory", 0);
|
|
return;
|
|
}
|
|
url = calloc(1, strlen(form->action) +
|
|
strlen(data) + 2);
|
|
if (!url) {
|
|
form_free_successful(success);
|
|
free(data);
|
|
warn_user("NoMemory", 0);
|
|
return;
|
|
}
|
|
if (form->action[strlen(form->action)-1] == '?') {
|
|
sprintf(url, "%s%s", form->action, data);
|
|
}
|
|
else {
|
|
sprintf(url, "%s?%s", form->action, data);
|
|
}
|
|
browser_window_go(target, url,
|
|
bw->current_content->url, true);
|
|
break;
|
|
|
|
case method_POST_URLENC:
|
|
data = form_url_encode(form, success);
|
|
if (!data) {
|
|
form_free_successful(success);
|
|
warn_user("NoMemory", 0);
|
|
return;
|
|
}
|
|
browser_window_go_post(target, form->action, data, 0,
|
|
true, bw->current_content->url,
|
|
false, true, 0);
|
|
break;
|
|
|
|
case method_POST_MULTIPART:
|
|
browser_window_go_post(target, form->action, 0,
|
|
success, true,
|
|
bw->current_content->url,
|
|
false, true, 0);
|
|
break;
|
|
|
|
default:
|
|
assert(0);
|
|
}
|
|
|
|
form_free_successful(success);
|
|
free(data);
|
|
free(url);
|
|
}
|
|
|
|
/**
|
|
* Callback for in-page scrolls.
|
|
*/
|
|
void browser_scroll_callback(void *client_data,
|
|
struct scroll_msg_data *scroll_data)
|
|
{
|
|
struct browser_scroll_data *data = client_data;
|
|
struct browser_window *bw = data->bw;
|
|
struct box *box = data->box;
|
|
int x, y, box_x, box_y, diff_x, diff_y;
|
|
|
|
|
|
switch(scroll_data->msg) {
|
|
case SCROLL_MSG_REDRAW:
|
|
diff_x = box->padding[LEFT] + box->width +
|
|
box->padding[RIGHT] - SCROLLBAR_WIDTH;
|
|
diff_y = box->padding[TOP] + box->height +
|
|
box->padding[BOTTOM] - SCROLLBAR_WIDTH;
|
|
|
|
box_coords(box, &box_x, &box_y);
|
|
if (scroll_is_horizontal(scroll_data->scroll)) {
|
|
x = box_x + scroll_get_offset(box->scroll_x);
|
|
y = box_y + scroll_get_offset(box->scroll_y) +
|
|
diff_y;
|
|
} else {
|
|
x = box_x + scroll_get_offset(box->scroll_x) +
|
|
diff_x;
|
|
y = box_y + scroll_get_offset(box->scroll_y);
|
|
}
|
|
browser_window_redraw_rect(bw,
|
|
x + scroll_data->x0,
|
|
y + scroll_data->y0,
|
|
scroll_data->x1 - scroll_data->x0,
|
|
scroll_data->y1 - scroll_data->y0);
|
|
break;
|
|
case SCROLL_MSG_MOVED:
|
|
browser_redraw_box(bw->current_content, box);
|
|
break;
|
|
case SCROLL_MSG_SCROLL_START:
|
|
bw->scroll = scroll_data->scroll;
|
|
gui_window_box_scroll_start(bw->window,
|
|
scroll_data->x0, scroll_data->y0,
|
|
scroll_data->x1, scroll_data->y1);
|
|
break;
|
|
case SCROLL_MSG_SCROLL_FINISHED:
|
|
bw->scroll = NULL;
|
|
|
|
browser_window_set_pointer(bw->window,
|
|
GUI_POINTER_DEFAULT);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Callback for the core select menu.
|
|
*/
|
|
void browser_select_menu_callback(void *client_data,
|
|
int x, int y, int width, int height)
|
|
{
|
|
struct browser_window *bw = client_data;
|
|
int menu_x, menu_y;
|
|
struct box *box;
|
|
|
|
box = bw->visible_select_menu->box;
|
|
box_coords(box, &menu_x, &menu_y);
|
|
|
|
menu_x -= box->border[LEFT].width;
|
|
menu_y += box->height + box->border[BOTTOM].width +
|
|
box->padding[BOTTOM] +
|
|
box->padding[TOP];
|
|
browser_window_redraw_rect(bw, menu_x + x, menu_y + y,
|
|
width, height);
|
|
}
|
|
|
|
|
|
/**
|
|
* Check whether box is nearer mouse coordinates than current nearest box
|
|
*
|
|
* \param box box to test
|
|
* \param bx position of box, in global document coordinates
|
|
* \param by position of box, in global document coordinates
|
|
* \param x mouse point, in global document coordinates
|
|
* \param y mouse point, in global document coordinates
|
|
* \param dir direction in which to search (-1 = above-left,
|
|
* +1 = below-right)
|
|
* \param nearest nearest text box found, or NULL if none
|
|
* updated if box is nearer than existing nearest
|
|
* \param tx position of text_box, in global document coordinates
|
|
* updated if box is nearer than existing nearest
|
|
* \param ty position of text_box, in global document coordinates
|
|
* updated if box is nearer than existing nearest
|
|
* \param nr_xd distance to nearest text box found
|
|
* updated if box is nearer than existing nearest
|
|
* \param ny_yd distance to nearest text box found
|
|
* updated if box is nearer than existing nearest
|
|
* \return true if mouse point is inside box
|
|
*/
|
|
|
|
bool browser_window_nearer_text_box(struct box *box, int bx, int by,
|
|
int x, int y, int dir, struct box **nearest, int *tx, int *ty,
|
|
int *nr_xd, int *nr_yd)
|
|
{
|
|
int w = box->padding[LEFT] + box->width + box->padding[RIGHT];
|
|
int h = box->padding[TOP] + box->height + box->padding[BOTTOM];
|
|
int y1 = by + h;
|
|
int x1 = bx + w;
|
|
int yd = INT_MAX;
|
|
int xd = INT_MAX;
|
|
|
|
if (x >= bx && x1 > x && y >= by && y1 > y) {
|
|
*nearest = box;
|
|
*tx = bx;
|
|
*ty = by;
|
|
return true;
|
|
}
|
|
|
|
if (box->parent->list_marker != box) {
|
|
if (dir < 0) {
|
|
/* consider only those children (partly) above-left */
|
|
if (by <= y && bx < x) {
|
|
yd = y <= y1 ? 0 : y - y1;
|
|
xd = x <= x1 ? 0 : x - x1;
|
|
}
|
|
} else {
|
|
/* consider only those children (partly) below-right */
|
|
if (y1 > y && x1 > x) {
|
|
yd = y > by ? 0 : by - y;
|
|
xd = x > bx ? 0 : bx - x;
|
|
}
|
|
}
|
|
|
|
/* give y displacement precedence over x */
|
|
if (yd < *nr_yd || (yd == *nr_yd && xd <= *nr_xd)) {
|
|
*nr_yd = yd;
|
|
*nr_xd = xd;
|
|
*nearest = box;
|
|
*tx = bx;
|
|
*ty = by;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Pick the text box child of 'box' that is closest to and above-left
|
|
* (dir -ve) or below-right (dir +ve) of the point 'x,y'
|
|
*
|
|
* \param box parent box
|
|
* \param bx position of box, in global document coordinates
|
|
* \param by position of box, in global document coordinates
|
|
* \param fx position of float parent, in global document coordinates
|
|
* \param fy position of float parent, in global document coordinates
|
|
* \param x mouse point, in global document coordinates
|
|
* \param y mouse point, in global document coordinates
|
|
* \param dir direction in which to search (-1 = above-left,
|
|
* +1 = below-right)
|
|
* \param nearest nearest text box found, or NULL if none
|
|
* updated if a descendant of box is nearer than old nearest
|
|
* \param tx position of nearest, in global document coordinates
|
|
* updated if a descendant of box is nearer than old nearest
|
|
* \param ty position of nearest, in global document coordinates
|
|
* updated if a descendant of box is nearer than old nearest
|
|
* \param nr_xd distance to nearest text box found
|
|
* updated if a descendant of box is nearer than old nearest
|
|
* \param ny_yd distance to nearest text box found
|
|
* updated if a descendant of box is nearer than old nearest
|
|
* \return true if mouse point is inside text_box
|
|
*/
|
|
|
|
bool browser_window_nearest_text_box(struct box *box, int bx, int by,
|
|
int fx, int fy, int x, int y, int dir, struct box **nearest,
|
|
int *tx, int *ty, int *nr_xd, int *nr_yd)
|
|
{
|
|
struct box *child = box->children;
|
|
int c_bx, c_by;
|
|
int c_fx, c_fy;
|
|
bool in_box = false;
|
|
|
|
if (*nearest == NULL) {
|
|
*nr_xd = INT_MAX / 2; /* displacement of 'nearest so far' */
|
|
*nr_yd = INT_MAX / 2;
|
|
}
|
|
if (box->type == BOX_INLINE_CONTAINER) {
|
|
int bw = box->padding[LEFT] + box->width + box->padding[RIGHT];
|
|
int bh = box->padding[TOP] + box->height + box->padding[BOTTOM];
|
|
int b_y1 = by + bh;
|
|
int b_x1 = bx + bw;
|
|
if (x >= bx && b_x1 > x && y >= by && b_y1 > y) {
|
|
in_box = true;
|
|
}
|
|
}
|
|
|
|
while (child) {
|
|
if (child->type == BOX_FLOAT_LEFT ||
|
|
child->type == BOX_FLOAT_RIGHT) {
|
|
c_bx = fx + child->x -
|
|
scroll_get_offset(child->scroll_x);
|
|
c_by = fy + child->y -
|
|
scroll_get_offset(child->scroll_y);
|
|
} else {
|
|
c_bx = bx + child->x -
|
|
scroll_get_offset(child->scroll_x);
|
|
c_by = by + child->y -
|
|
scroll_get_offset(child->scroll_y);
|
|
}
|
|
if (child->float_children) {
|
|
c_fx = c_bx;
|
|
c_fy = c_by;
|
|
} else {
|
|
c_fx = fx;
|
|
c_fy = fy;
|
|
}
|
|
if (in_box && child->text && !child->object) {
|
|
if (browser_window_nearer_text_box(child,
|
|
c_bx, c_by, x, y, dir, nearest,
|
|
tx, ty, nr_xd, nr_yd))
|
|
return true;
|
|
} else {
|
|
if (child->list_marker) {
|
|
if (browser_window_nearer_text_box(
|
|
child->list_marker,
|
|
c_bx + child->list_marker->x,
|
|
c_by + child->list_marker->y,
|
|
x, y, dir, nearest,
|
|
tx, ty, nr_xd, nr_yd))
|
|
return true;
|
|
}
|
|
if (browser_window_nearest_text_box(child, c_bx, c_by,
|
|
c_fx, c_fy, x, y, dir, nearest, tx, ty,
|
|
nr_xd, nr_yd))
|
|
return true;
|
|
}
|
|
child = child->next;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Peform pick text on browser window contents to locate the box under
|
|
* the mouse pointer, or nearest in the given direction if the pointer is
|
|
* not over a text box.
|
|
*
|
|
* \param bw browser window
|
|
* \param x coordinate of mouse
|
|
* \param y coordinate of mouse
|
|
* \param dir direction to search (-1 = above-left, +1 = below-right)
|
|
* \param dx receives x ordinate of mouse relative to text box
|
|
* \param dy receives y ordinate of mouse relative to text box
|
|
*/
|
|
|
|
struct box *browser_window_pick_text_box(struct browser_window *bw,
|
|
int x, int y, int dir, int *dx, int *dy)
|
|
{
|
|
struct content *c = bw->current_content;
|
|
struct box *text_box = NULL;
|
|
|
|
if (c && c->type == CONTENT_HTML) {
|
|
struct box *box = c->data.html.layout;
|
|
int nr_xd, nr_yd;
|
|
int bx = box->margin[LEFT];
|
|
int by = box->margin[TOP];
|
|
int fx = bx;
|
|
int fy = by;
|
|
int tx, ty;
|
|
|
|
if (!browser_window_nearest_text_box(box, bx, by, fx, fy, x, y,
|
|
dir, &text_box, &tx, &ty, &nr_xd, &nr_yd)) {
|
|
if (text_box && text_box->text && !text_box->object) {
|
|
int w = (text_box->padding[LEFT] +
|
|
text_box->width +
|
|
text_box->padding[RIGHT]);
|
|
int h = (text_box->padding[TOP] +
|
|
text_box->height +
|
|
text_box->padding[BOTTOM]);
|
|
int x1, y1;
|
|
|
|
y1 = ty + h;
|
|
x1 = tx + w;
|
|
|
|
/* ensure point lies within the text box */
|
|
if (x < tx) x = tx;
|
|
if (y < ty) y = ty;
|
|
if (y > y1) y = y1;
|
|
if (x > x1) x = x1;
|
|
}
|
|
}
|
|
|
|
/* return coordinates relative to box */
|
|
*dx = x - tx;
|
|
*dy = y - ty;
|
|
}
|
|
|
|
return text_box;
|
|
}
|
|
|
|
|
|
/**
|
|
* Start drag scrolling the contents of the browser window
|
|
*
|
|
* \param bw browser window
|
|
* \param x x ordinate of initial mouse position
|
|
* \param y y ordinate
|
|
*/
|
|
|
|
void browser_window_page_drag_start(struct browser_window *bw, int x, int y)
|
|
{
|
|
bw->drag_type = DRAGGING_PAGE_SCROLL;
|
|
|
|
bw->drag_start_x = x;
|
|
bw->drag_start_y = y;
|
|
|
|
gui_window_get_scroll(bw->window, &bw->drag_start_scroll_x,
|
|
&bw->drag_start_scroll_y);
|
|
|
|
gui_window_scroll_start(bw->window);
|
|
}
|
|
|
|
/**
|
|
* Start drag scrolling the contents of a box
|
|
*
|
|
* \param bw browser window
|
|
* \param box the box to be scrolled
|
|
* \param x x ordinate of initial mouse position
|
|
* \param y y ordinate
|
|
*/
|
|
|
|
void browser_window_box_drag_start(struct browser_window *bw,
|
|
struct box *box, int x, int y)
|
|
{
|
|
int box_x, box_y, scroll_mouse_x, scroll_mouse_y;
|
|
|
|
box_coords(box, &box_x, &box_y);
|
|
|
|
if (box->scroll_x != NULL) {
|
|
scroll_mouse_x = x - box_x ;
|
|
scroll_mouse_y = y - (box_y + box->padding[TOP] +
|
|
box->height + box->padding[BOTTOM] -
|
|
SCROLLBAR_WIDTH);
|
|
scroll_start_content_drag(box->scroll_x,
|
|
scroll_mouse_x, scroll_mouse_y);
|
|
} else if (box->scroll_y != NULL) {
|
|
scroll_mouse_x = x - (box_x + box->padding[LEFT] +
|
|
box->width + box->padding[RIGHT] -
|
|
SCROLLBAR_WIDTH);
|
|
scroll_mouse_y = y - box_y;
|
|
|
|
scroll_start_content_drag(box->scroll_y,
|
|
scroll_mouse_x, scroll_mouse_y);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Check availability of Back action for a given browser window
|
|
*
|
|
* \param bw browser window
|
|
* \return true if Back action is available
|
|
*/
|
|
|
|
bool browser_window_back_available(struct browser_window *bw)
|
|
{
|
|
return (bw && bw->history && history_back_available(bw->history));
|
|
}
|
|
|
|
|
|
/**
|
|
* Check availability of Forward action for a given browser window
|
|
*
|
|
* \param bw browser window
|
|
* \return true if Forward action is available
|
|
*/
|
|
|
|
bool browser_window_forward_available(struct browser_window *bw)
|
|
{
|
|
return (bw && bw->history && history_forward_available(bw->history));
|
|
}
|
|
|
|
|
|
/**
|
|
* Check availability of Reload action for a given browser window
|
|
*
|
|
* \param bw browser window
|
|
* \return true if Reload action is available
|
|
*/
|
|
|
|
bool browser_window_reload_available(struct browser_window *bw)
|
|
{
|
|
return (bw && bw->current_content && !bw->loading_content);
|
|
}
|
|
|
|
|
|
/**
|
|
* Check availability of Stop action for a given browser window
|
|
*
|
|
* \param bw browser window
|
|
* \return true if Stop action is available
|
|
*/
|
|
|
|
bool browser_window_stop_available(struct browser_window *bw)
|
|
{
|
|
return (bw && (bw->loading_content ||
|
|
(bw->current_content &&
|
|
(bw->current_content->status != CONTENT_STATUS_DONE))));
|
|
}
|