netsurf/frontends/framebuffer/fbtk/fbtk.c
2016-05-15 13:44:34 +01:00

831 lines
17 KiB
C

/*
* Copyright 2008,2010 Vincent Sanders <vince@simtec.co.uk>
*
* Framebuffer windowing toolkit core.
*
* 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/>.
*/
#include <stdlib.h>
#include <sys/types.h>
#include <assert.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <stdarg.h>
#include <libnsfb.h>
#include <libnsfb_plot.h>
#include <libnsfb_plot_util.h>
#include <libnsfb_event.h>
#include <libnsfb_cursor.h>
#include "utils/utils.h"
#include "utils/log.h"
#include "desktop/browser.h"
#include "desktop/plotters.h"
#include "framebuffer/gui.h"
#include "framebuffer/fbtk.h"
#include "framebuffer/image_data.h"
#include "widget.h"
#ifdef FBTK_LOGGING
/* tree dump debug, also example of depth first tree walk */
static void
dump_tk_tree(fbtk_widget_t *widget)
{
widget = fbtk_get_root_widget(widget);
int indent = 0;
while (widget != NULL) {
LOG("%*s%p", indent, "", widget);
if (widget->first_child != NULL) {
widget = widget->first_child;
indent += 6;
} else if (widget->next != NULL) {
widget = widget->next;
} else {
while ((widget->parent != NULL) &&
(widget->parent->next == NULL)) {
widget = widget->parent;
indent -= 6;
}
if (widget->parent != NULL) {
indent -= 6;
widget = widget->parent->next;
} else {
widget = NULL;
}
}
}
}
#endif
/* exported function documented in fbtk.h */
void
fbtk_request_redraw(fbtk_widget_t *widget)
{
fbtk_widget_t *cwidget;
fbtk_widget_t *pwidget;
assert(widget != NULL);
/* if widget not mapped do not try to redraw it */
pwidget = widget;
while (pwidget != NULL) {
if (pwidget->mapped == false)
return;
pwidget = pwidget->parent;
}
widget->redraw.needed = true;
widget->redraw.x = 0;
widget->redraw.y = 0;
widget->redraw.width = widget->width;
widget->redraw.height = widget->height;
#ifdef FBTK_LOGGING
LOG("redrawing %p %d,%d %d,%d", widget, widget->redraw.x, widget->redraw.y, widget->redraw.width, widget->redraw.height);
#endif
cwidget = widget->last_child;
while (cwidget != NULL) {
fbtk_request_redraw(cwidget);
cwidget = cwidget->prev;
}
while (widget->parent != NULL) {
widget = widget->parent;
widget->redraw.child = true;
}
}
/* exported function documented in fbtk.h */
int
fbtk_set_mapping(fbtk_widget_t *widget, bool map)
{
LOG("setting mapping on %p to %d", widget, map);
widget->mapped = map;
if (map) {
fbtk_request_redraw(widget);
} else {
fbtk_request_redraw(widget->parent);
}
return 0;
}
/** swap the widget given with the next sibling.
*
* Swap a sibling widget with the next deepest in the hierachy
*/
static void
swap_siblings(fbtk_widget_t *lw)
{
fbtk_widget_t *rw = lw->next; /* the widget to swap lw with */
fbtk_widget_t *before;
fbtk_widget_t *after;
assert(rw != NULL);
LOG("Swapping %p with %p", lw, rw);
before = lw->prev;
after = rw->next;
if (before == NULL) {
/* left widget is currently the first child */
lw->parent->first_child = rw;
} else {
before->next = rw;
}
rw->prev = before;
rw->next = lw;
if (after == NULL) {
/* right widget is currently the last child */
rw->parent->last_child = lw;
} else {
after->prev = lw;
}
lw->next = after;
lw->prev = rw;
}
/* exported function documented in fbtk.h */
int
fbtk_set_zorder(fbtk_widget_t *widget, int z)
{
while (z != 0) {
if (z < 0) {
if (widget->prev == NULL)
break; /* cannot go any shallower */
/* swap with previous entry */
swap_siblings(widget->prev);
z++;
} else {
if (widget->next == NULL)
break; /* cannot go any deeper */
/* swap with subsequent entry */
swap_siblings(widget);
z--;
}
}
return z;
}
/* exported function documented in fbtk.h */
bool
fbtk_set_pos_and_size(fbtk_widget_t *widget,
int x, int y,
int width, int height)
{
if (widget->parent != NULL) {
fbtk_widget_t *parent = widget->parent;
/* make new window fit inside parent */
if (width == 0) {
width = parent->width - x;
} else if (width < 0) {
width = parent->width + width - x;
}
if ((width + x) > parent->width) {
width = parent->width - x;
}
if (height == 0) {
height = parent->height - y;
} else if (height < 0) {
height = parent->height + height - y;
}
if ((height + y) > parent->height) {
height = parent->height - y;
}
}
if ((widget->x != x) ||
(widget->y != y) ||
(widget->width != width) ||
(widget->height != height)) {
widget->x = x;
widget->y = y;
widget->width = width;
widget->height = height;
return true;
}
return false;
}
/* exported function docuemnted in fbtk.h */
void
fbtk_set_caret(fbtk_widget_t *widget, bool set,
int x, int y, int height,
void (*remove_caret)(fbtk_widget_t *widget))
{
fbtk_widget_t *root;
assert(widget != NULL);
root = fbtk_get_root_widget(widget);
if (root->u.root.caret.owner != NULL &&
root->u.root.caret.remove_cb != NULL)
root->u.root.caret.remove_cb(widget);
if (set) {
assert(remove_caret != NULL);
root->u.root.caret.owner = widget;
root->u.root.caret.x = x;
root->u.root.caret.y = y;
root->u.root.caret.height = height;
root->u.root.caret.remove_cb = remove_caret;
} else {
root->u.root.caret.owner = NULL;
root->u.root.caret.remove_cb = NULL;
}
}
/* exported function documented in fbtk.h */
int
fbtk_destroy_widget(fbtk_widget_t *widget)
{
fbtk_widget_t *parent;
int ret = 0;
ret = fbtk_post_callback(widget, FBTK_CBT_DESTROY);
while (widget->first_child != NULL) {
fbtk_destroy_widget(widget->first_child);
}
parent = widget->parent;
if (parent != NULL) {
/* unlink from siblings */
if (widget->prev != NULL) {
widget->prev->next = widget->next;
} else {
/* must be the first widget, unlink from parent */
parent->first_child = widget->next;
}
if (widget->next != NULL) {
widget->next->prev = widget->prev;
} else {
/* must be the last widget, unlink from parent */
parent->last_child = widget->prev;
}
free(widget);
}
return ret;
}
/* region coverage flags. */
enum {
POINT_LEFTOF_REGION = 1,
POINT_RIGHTOF_REGION = 2,
POINT_ABOVE_REGION = 4,
POINT_BELOW_REGION = 8,
};
/* Computes where a point lies in respect to an area. */
#define REGION(x,y,cx1,cx2,cy1,cy2) \
(( (y) > (cy2) ? POINT_BELOW_REGION : 0) | \
( (y) < (cy1) ? POINT_ABOVE_REGION : 0) | \
( (x) > (cx2) ? POINT_RIGHTOF_REGION : 0) | \
( (x) < (cx1) ? POINT_LEFTOF_REGION : 0) )
/* swap two integers */
#define SWAP(a, b) do { int t; t=(a); (a)=(b); (b)=t; } while(0)
/* exported function documented in fbtk.h */
bool
fbtk_clip_rect(const bbox_t * restrict clip, bbox_t * restrict box)
{
uint8_t region1;
uint8_t region2;
/* ensure co-ordinates are in ascending order */
if (box->x1 < box->x0)
SWAP(box->x0, box->x1);
if (box->y1 < box->y0)
SWAP(box->y0, box->y1);
region1 = REGION(box->x0, box->y0, clip->x0, clip->x1 - 1, clip->y0, clip->y1 - 1);
region2 = REGION(box->x1, box->y1, clip->x0, clip->x1 - 1, clip->y0, clip->y1 - 1);
/* area lies entirely outside the clipping rectangle */
if ((region1 | region2) && (region1 & region2))
return false;
if (box->x0 < clip->x0)
box->x0 = clip->x0;
if (box->x0 > clip->x1)
box->x0 = clip->x1;
if (box->x1 < clip->x0)
box->x1 = clip->x0;
if (box->x1 > clip->x1)
box->x1 = clip->x1;
if (box->y0 < clip->y0)
box->y0 = clip->y0;
if (box->y0 > clip->y1)
box->y0 = clip->y1;
if (box->y1 < clip->y0)
box->y1 = clip->y0;
if (box->y1 > clip->y1)
box->y1 = clip->y1;
return true;
}
/* exported function documented in fbtk.h */
bool
fbtk_clip_to_widget(fbtk_widget_t *widget, bbox_t * restrict box)
{
bbox_t wbox;
wbox.x0 = 0;
wbox.y0 = 0;
wbox.x1 = widget->width;
wbox.y1 = widget->height;
return fbtk_clip_rect(&wbox, box);
}
/* internally exported function documented in widget.h */
int
fbtk_set_ptr(fbtk_widget_t *widget, fbtk_callback_info *cbi)
{
fbtk_widget_t *root = fbtk_get_root_widget(widget);
struct fbtk_bitmap *bm = cbi->context;
nsfb_cursor_set(root->u.root.fb,
(nsfb_colour_t *)bm->pixdata,
bm->width,
bm->height,
bm->width,
bm->hot_x,
bm->hot_y);
return 0;
}
/* internally exported function documented in widget.h */
fbtk_widget_t *
fbtk_get_root_widget(fbtk_widget_t *widget)
{
while (widget->parent != NULL)
widget = widget->parent;
/* check root widget was found */
if (widget->type != FB_WIDGET_TYPE_ROOT) {
LOG("Widget with null parent that is not the root widget!");
return NULL;
}
return widget;
}
/* exported function documented in fbtk.h */
int
fbtk_get_absx(fbtk_widget_t *widget)
{
int x = widget->x;
while (widget->parent != NULL) {
widget = widget->parent;
x += widget->x;
}
return x;
}
/* exported function documented in fbtk.h */
int
fbtk_get_absy(fbtk_widget_t *widget)
{
int y = widget->y;
while (widget->parent != NULL) {
widget = widget->parent;
y += widget->y;
}
return y;
}
/* exported function documented in fbtk.h */
int
fbtk_get_height(fbtk_widget_t *widget)
{
return widget->height;
}
/* exported function documented in fbtk.h */
int
fbtk_get_width(fbtk_widget_t *widget)
{
return widget->width;
}
/* exported function documented in fbtk.h */
bool
fbtk_get_bbox(fbtk_widget_t *widget, nsfb_bbox_t *bbox)
{
bbox->x0 = widget->x;
bbox->y0 = widget->y;
bbox->x1 = widget->x + widget->width;
bbox->y1 = widget->y + widget->height;
widget = widget->parent;
while (widget != NULL) {
bbox->x0 += widget->x;
bbox->y0 += widget->y;
bbox->x1 += widget->x;
bbox->y1 += widget->y;
widget = widget->parent;
}
return true;
}
bool
fbtk_get_caret(fbtk_widget_t *widget, int *x, int *y, int *height)
{
fbtk_widget_t *root = fbtk_get_root_widget(widget);
if (root->u.root.caret.owner == widget) {
*x = root->u.root.caret.x;
*y = root->u.root.caret.y;
*height = root->u.root.caret.height;
return true;
} else {
*x = 0;
*y = 0;
*height = 0;
return false;
}
}
/* exported function documented in fbtk.h */
fbtk_widget_t *
fbtk_get_widget_at(fbtk_widget_t *nwid, int x, int y)
{
fbtk_widget_t *widget = NULL; /* found widget */
/* require the root widget to start */
nwid = fbtk_get_root_widget(nwid);
while (nwid != NULL) {
if ((nwid->mapped) &&
(x >= nwid->x) &&
(y >= nwid->y) &&
(x < (nwid->x + nwid->width)) &&
(y < (nwid->y + nwid->height))) {
widget = nwid;
x -= nwid->x;
y -= nwid->y;
nwid = nwid->first_child;
} else {
nwid = nwid->next;
}
}
return widget;
}
/* internally exported function documented in widget.h */
fbtk_widget_t *
fbtk_widget_new(fbtk_widget_t *parent,
enum fbtk_widgettype_e type,
int x,
int y,
int width,
int height)
{
fbtk_widget_t *neww; /* new widget */
if (parent == NULL)
return NULL;
neww = calloc(1, sizeof(fbtk_widget_t));
if (neww == NULL)
return NULL;
#ifdef FBTK_LOGGING
LOG("creating %p %d,%d %d,%d", neww, x, y, width, height);
#endif
/* make new window fit inside parent */
if (width == 0) {
width = parent->width - x;
} else if (width < 0) {
width = parent->width + width - x;
}
if ((width + x) > parent->width) {
width = parent->width - x;
}
if (height == 0) {
height = parent->height - y;
} else if (height < 0) {
height = parent->height + height - y;
}
if ((height + y) > parent->height) {
height = parent->height - y;
}
#ifdef FBTK_LOGGING
LOG("using %p %d,%d %d,%d", neww, x, y, width, height);
#endif
/* set values */
neww->type = type;
neww->x = x;
neww->y = y;
neww->width = width;
neww->height = height;
/* insert into widget heiarchy */
neww->parent = parent;
if (parent->first_child == NULL) {
/* no child widgets yet */
parent->last_child = neww;
} else {
/* add new widget to front of sibling chain */
neww->next = parent->first_child;
neww->next->prev = neww;
}
parent->first_child = neww;
return neww;
}
/* exported function documented in fbtk.h */
bool
fbtk_get_redraw_pending(fbtk_widget_t *widget)
{
fbtk_widget_t *root;
/* ensure we have the root widget */
root = fbtk_get_root_widget(widget);
return root->redraw.needed | root->redraw.child;
}
/** Perform a depth-first tree-walk, calling the redraw callback of the widgets in turn.
*
* This function makes no decisions of its own and simply walks the
* widget tree depth first calling widgets redraw callbacks if flagged
* to do so.
* The tree search is optimised with a flag to indicate wether the
* children of a node should be considered.
*/
static int
do_redraw(nsfb_t *nsfb, fbtk_widget_t *widget)
{
nsfb_bbox_t plot_ctx;
fbtk_widget_t *cwidget; /* child widget */
/* check if the widget requires redrawing */
if (widget->redraw.needed == true) {
plot_ctx.x0 = fbtk_get_absx(widget) + widget->redraw.x;
plot_ctx.y0 = fbtk_get_absy(widget) + widget->redraw.y;
plot_ctx.x1 = plot_ctx.x0 + widget->redraw.width;
plot_ctx.y1 = plot_ctx.y0 + widget->redraw.height;
#ifdef FBTK_LOGGING
LOG("clipping %p %d,%d %d,%d", widget, plot_ctx.x0, plot_ctx.y0, plot_ctx.x1, plot_ctx.y1);
#endif
if (nsfb_plot_set_clip(nsfb, &plot_ctx) == true) {
fbtk_post_callback(widget, FBTK_CBT_REDRAW);
}
widget->redraw.needed = false;
}
/* walk the widgets children if child flag is set */
if (widget->redraw.child) {
cwidget = widget->last_child;
while (cwidget != NULL) {
do_redraw(nsfb, cwidget);
cwidget = cwidget->prev;
}
widget->redraw.child = false;
}
return 1;
}
/* exported function documented in fbtk.h */
int
fbtk_redraw(fbtk_widget_t *widget)
{
fbtk_widget_t *root;
/* ensure we have the root widget */
root = fbtk_get_root_widget(widget);
return do_redraw(root->u.root.fb, root);
}
/* exported function documented in fbtk.h */
fbtk_callback
fbtk_get_handler(fbtk_widget_t *widget, fbtk_callback_type cbt)
{
if ((cbt <= FBTK_CBT_START) || (cbt >= FBTK_CBT_END)) {
/* type out of range, no way to report error so return NULL */
return NULL;
}
return widget->callback[cbt];
}
/* exported function documented in fbtk.h */
fbtk_callback
fbtk_set_handler(fbtk_widget_t *widget,
fbtk_callback_type cbt,
fbtk_callback cb,
void *context)
{
fbtk_callback prevcb;
if ((cbt <= FBTK_CBT_START) || (cbt >= FBTK_CBT_END)) {
/* type out of range, no way to report error so return NULL */
return NULL;
}
prevcb = widget->callback[cbt];
widget->callback[cbt] = cb;
widget->callback_context[cbt] = context;
return prevcb;
}
/* exported function docuemnted in fbtk.h */
int
fbtk_post_callback(fbtk_widget_t *widget, fbtk_callback_type cbt, ...)
{
fbtk_callback_info cbi;
int ret = 0;
va_list ap;
if (widget == NULL)
return -1;
/* if the widget is not mapped do not attempt to post any
* events to it
*/
if (widget->mapped == false)
return ret;
if (widget->callback[cbt] != NULL) {
cbi.type = cbt;
cbi.context = widget->callback_context[cbt];
va_start(ap, cbt);
switch (cbt) {
case FBTK_CBT_SCROLLX:
cbi.x = va_arg(ap,int);
break;
case FBTK_CBT_SCROLLY:
cbi.y = va_arg(ap,int);
break;
case FBTK_CBT_CLICK:
cbi.event = va_arg(ap, void *);
cbi.x = va_arg(ap, int);
cbi.y = va_arg(ap, int);
break;
case FBTK_CBT_INPUT:
cbi.event = va_arg(ap, void *);
break;
case FBTK_CBT_POINTERMOVE:
cbi.x = va_arg(ap, int);
cbi.y = va_arg(ap, int);
break;
case FBTK_CBT_REDRAW:
break;
case FBTK_CBT_USER:
break;
case FBTK_CBT_STRIP_FOCUS:
break;
default:
break;
}
va_end(ap);
ret = (widget->callback[cbt])(widget, &cbi);
}
return ret;
}
/* exported function docuemnted in fbtk.h */
void
fbtk_set_focus(fbtk_widget_t *widget)
{
fbtk_widget_t *root;
/* ensure we have the root widget */
root = fbtk_get_root_widget(widget);
if (root->u.root.input != NULL &&
root->u.root.input != widget) {
/* inform previous holder of focus that it's being stripped
* of focus */
fbtk_post_callback(root->u.root.input, FBTK_CBT_STRIP_FOCUS);
}
root->u.root.input = widget;
}
/* exported function docuemnted in fbtk.h */
nsfb_t *
fbtk_get_nsfb(fbtk_widget_t *widget)
{
fbtk_widget_t *root;
/* ensure we have the root widget */
root = fbtk_get_root_widget(widget);
return root->u.root.fb;
}
/* exported function docuemnted in fbtk.h */
fbtk_widget_t *
fbtk_init(nsfb_t *fb)
{
fbtk_widget_t *root;
/* create and configure root widget */
root = calloc(1, sizeof(fbtk_widget_t));
if (root == NULL)
return NULL;
root->type = FB_WIDGET_TYPE_ROOT;
root->u.root.fb = fb;
root->u.root.caret.owner = NULL;
nsfb_get_geometry(fb, &root->width, &root->height, NULL);
root->mapped = true;
return root;
}
/*
* Local Variables:
* c-basic-offset:8
* End:
*/