Change contextual content retrieval to browser features.

Update the API which allows frontends to acquire the page features
(images, link urls or form elements) present at the given coordinates
within a browser window.

By making this an explicit browser_window API and using the browser.h
header for the associated data structure with a more appropriate API
naming the usage is much more obvious and contained.

Additionally the link url is now passed around as a nsurl stopping it
being converted from nsurl to text and back again several times.
This commit is contained in:
Vincent Sanders 2014-11-02 15:46:42 +00:00
parent 1794ac0d33
commit c31c4babe1
14 changed files with 190 additions and 194 deletions

View File

@ -585,7 +585,7 @@ void ami_context_menu_show(struct gui_window_2 *gwin,int x,int y)
struct hlcache_handle *cc = gwin->bw->current_content;
bool no_more_menus = false;
bool menuhascontent = false;
struct contextual_content ccdata;
struct browser_window_features ccdata;
if(!cc) return;
if(ctxmenuobj) DisposeObject(ctxmenuobj);
@ -648,7 +648,7 @@ void ami_context_menu_show(struct gui_window_2 *gwin,int x,int y)
{
if(no_more_menus == false)
{
browser_window_get_contextual_content(gwin->bw, x, y, &ccdata);
browser_window_get_features(gwin->bw, x, y, &ccdata);
ami_context_menu_add_submenu(ctxmenuobj, CMSUB_PAGE, cc);
menuhascontent = true;
@ -659,9 +659,9 @@ void ami_context_menu_show(struct gui_window_2 *gwin,int x,int y)
menuhascontent = true;
}
if(ccdata.link_url)
if(ccdata.link)
{
ami_context_menu_add_submenu(ctxmenuobj, CMSUB_URL, (char *)ccdata.link_url);
ami_context_menu_add_submenu(ctxmenuobj, CMSUB_URL, (char *)nsurl_access(ccdata.link));
menuhascontent = true;
}

View File

@ -5032,7 +5032,7 @@ bool ami_text_box_at_point(struct gui_window_2 *gwin, ULONG *x, ULONG *y)
{
struct IBox *bbox;
ULONG xs, ys, width, height;
struct contextual_content data;
struct browser_window_features data;
GetAttr(SPACE_AreaBox, (Object *)gwin->objects[GID_BROWSER],
(ULONG *)&bbox);
@ -5046,7 +5046,7 @@ bool ami_text_box_at_point(struct gui_window_2 *gwin, ULONG *x, ULONG *y)
width=bbox->Width;
height=bbox->Height;
browser_window_get_contextual_content(gwin->bw, *x, *y, &data);
browser_window_get_features(gwin->bw, *x, *y, &data);
if (data.form_features == CTX_FORM_TEXT)
return true;

View File

@ -58,7 +58,7 @@ bool gui_window_get_scroll(struct gui_window *w, int *sx, int *sy);
struct s_context_info {
unsigned long flags;
struct contextual_content ccdata;
struct browser_window_features ccdata;
};
struct s_context_info ctxinfo;
@ -86,14 +86,12 @@ static struct s_context_info * get_context_info( struct gui_window * gw, short m
ctxinfo.flags |= CNT_BROWSER;
memset( &ctxinfo.ccdata, sizeof(struct contextual_content), 0 );
gui_window_get_scroll(gw, &sx, &sy);
browser_window_get_contextual_content( gw->browser->bw, mx+sx, my+sy,
(struct contextual_content*)&ctxinfo.ccdata);
browser_window_get_features( gw->browser->bw, mx+sx, my+sy,
&ctxinfo.ccdata);
if( ctxinfo.ccdata.link_url ){
if( ctxinfo.ccdata.link ){
ctxinfo.flags |= CNT_HREF;
}
if( ctxinfo.ccdata.object) {
@ -242,23 +240,18 @@ void context_popup(struct gui_window * gw, short x, short y)
}
case POP_CTX_SAVE_LINK_AS:
if (ctx->ccdata.link_url != NULL) {
nsurl *url;
if (ctx->ccdata.link != NULL) {
nserror error;
error = nsurl_create(ctx->ccdata.link_url, &url);
if (error == NSERROR_OK) {
error = browser_window_navigate(
gw->browser->bw,
url,
browser_window_get_url(gw->browser->bw),
BW_NAVIGATE_DOWNLOAD,
NULL,
NULL,
NULL
);
nsurl_unref(url);
}
error = browser_window_navigate(
gw->browser->bw,
ctx->ccdata.link,
browser_window_get_url(gw->browser->bw),
BW_NAVIGATE_DOWNLOAD,
NULL,
NULL,
NULL);
if (error != NSERROR_OK) {
warn_user(messages_get_errorcode(error), 0);
}
@ -276,27 +269,23 @@ void context_popup(struct gui_window * gw, short x, short y)
break;
case POP_CTX_COPY_LINK:
if ((ctx->flags & CNT_HREF) && ctx->ccdata.link_url != NULL) {
scrap_txt_write((char*)ctx->ccdata.link_url);
if ((ctx->flags & CNT_HREF) &&
(ctx->ccdata.link != NULL)) {
scrap_txt_write((char*)nsurl_access(ctx->ccdata.link));
}
break;
case POP_CTX_OPEN_NEW:
if ((ctx->flags & CNT_HREF) && ctx->ccdata.link_url) {
nsurl *url;
if ((ctx->flags & CNT_HREF) &&
(ctx->ccdata.link != NULL)) {
nserror error;
error = nsurl_create(ctx->ccdata.link_url, &url);
if (error == NSERROR_OK) {
error = browser_window_create(
BW_CREATE_HISTORY | BW_CREATE_CLONE,
url,
browser_window_get_url(gw->browser->bw),
gw->browser->bw,
NULL
);
nsurl_unref(url);
}
error = browser_window_create(
BW_CREATE_HISTORY | BW_CREATE_CLONE,
ctx->ccdata.link,
browser_window_get_url(gw->browser->bw),
gw->browser->bw,
NULL);
if (error != NSERROR_OK) {
warn_user(messages_get_errorcode(error), 0);
}

View File

@ -490,9 +490,9 @@ static browser_mouse_state cocoa_mouse_flags_for_event( NSEvent *evt )
NSMenu *popupMenu = [[NSMenu alloc] initWithTitle: @""];
NSPoint point = [self convertMousePoint: event];
struct contextual_content cont;
struct browser_window_features;
browser_window_get_contextual_content( browser, point.x, point.y, &cont);
browser_window_get_features(browser, point.x, point.y, &cont);
if (cont.object != NULL) {
NSString *imageURL = [NSString stringWithUTF8String: nsurl_access(hlcache_handle_get_url( cont.object ))];
@ -513,8 +513,8 @@ static browser_mouse_state cocoa_mouse_flags_for_event( NSEvent *evt )
[popupMenu addItem: [NSMenuItem separatorItem]];
}
if (cont.link_url != NULL) {
NSString *target = [NSString stringWithUTF8String: cont.link_url];
if (cont.link != NULL) {
NSString *target = [NSString stringWithUTF8String: nsurl_access(cont.link)];
[[popupMenu addItemWithTitle: NSLocalizedString( @"Open link in new tab", @"Context menu" )
action: @selector(cmOpenURLInTab:)

View File

@ -795,20 +795,19 @@ char * content_get_selection(hlcache_handle *h)
return NULL;
}
void content_get_contextual_content(struct hlcache_handle *h,
int x, int y, struct contextual_content *data)
/* exported interface documented in content/content.h */
nserror content_get_contextual_content(struct hlcache_handle *h,
int x, int y, struct browser_window_features *data)
{
struct content *c = hlcache_handle_get_content(h);
assert(c != 0);
if (c->handler->get_contextual_content != NULL) {
c->handler->get_contextual_content(c, x, y, data);
return;
} else {
data->object = h;
return;
return c->handler->get_contextual_content(c, x, y, data);
}
data->object = h;
return NSERROR_OK;
}

View File

@ -35,7 +35,6 @@
#include "utils/errors.h"
#include "utils/http.h"
#include "utils/nsurl.h"
#include "utils/types.h"
#include "content/content_factory.h"
#include "content/content_type.h"
#include "desktop/search.h"
@ -43,6 +42,7 @@
#include "desktop/plot_style.h"
struct browser_window;
struct browser_window_features;
struct content;
struct llcache_handle;
struct hlcache_handle;
@ -50,7 +50,6 @@ struct object_params;
struct rect;
struct redraw_context;
/** Status of a content */
typedef enum {
CONTENT_STATUS_LOADING, /**< Content is being fetched or
@ -271,8 +270,18 @@ void content_open(struct hlcache_handle *h, struct browser_window *bw,
void content_close(struct hlcache_handle *h);
void content_clear_selection(struct hlcache_handle *h);
char * content_get_selection(struct hlcache_handle *h);
void content_get_contextual_content(struct hlcache_handle *h,
int x, int y, struct contextual_content *data);
/**
* Get positional contextural information for a content.
*
* \param[in] h Handle to content to examine.
* \param[in] x The x coordinate to examine.
* \param[in] y The y coordinate to examine.
* \param[out] data The context structure to fill in.
*/
nserror content_get_contextual_content(struct hlcache_handle *h,
int x, int y, struct browser_window_features *data);
bool content_scroll_at_point(struct hlcache_handle *h,
int x, int y, int scrx, int scry);
bool content_drop_file_at_point(struct hlcache_handle *h,

View File

@ -67,8 +67,8 @@ struct content_handler {
void (*close)(struct content *c);
void (*clear_selection)(struct content *c);
char * (*get_selection)(struct content *c);
void (*get_contextual_content)(struct content *c, int x, int y,
struct contextual_content *data);
nserror (*get_contextual_content)(struct content *c, int x, int y,
struct browser_window_features *data);
bool (*scroll_at_point)(struct content *c, int x, int y,
int scrx, int scry);
bool (*drop_file_at_point)(struct content *c, int x, int y,

View File

@ -560,18 +560,28 @@ void browser_window_set_scroll(struct browser_window *bw, int x, int y)
}
/**
* Internal helper for browser_window_get_contextual_content
* Internal helper for getting the positional features
*
* \param[in] bw browser window to examine.
* \param[in] x x-coordinate of point of interest
* \param[in] y y-coordinate of point of interest
* \param[out] data Feature structure to update.
* \return NSERROR_OK or appropriate error code on faliure.
*/
static void browser_window__get_contextual_content(struct browser_window *bw,
int x, int y, struct contextual_content *data)
static nserror
browser_window__get_contextual_content(struct browser_window *bw,
int x, int y, struct browser_window_features *data)
{
nserror ret = NSERROR_OK;
/* Handle (i)frame scroll offset (core-managed browser windows only) */
x += scrollbar_get_offset(bw->scroll_x);
y += scrollbar_get_offset(bw->scroll_y);
if (bw->children) {
/* Browser window has children, so pass request on to
* appropriate child */
* appropriate child.
*/
struct browser_window *bwc;
int cur_child;
int children = bw->rows * bw->cols;
@ -582,39 +592,41 @@ static void browser_window__get_contextual_content(struct browser_window *bw,
bwc = &bw->children[cur_child];
/* Skip this frame if (x, y) coord lies outside */
if (x < bwc->x || bwc->x + bwc->width < x ||
y < bwc->y || bwc->y + bwc->height < y)
if ((x < bwc->x) ||
(bwc->x + bwc->width < x) ||
(y < bwc->y) ||
(bwc->y + bwc->height < y)) {
continue;
}
/* Pass request into this child */
browser_window__get_contextual_content(bwc,
return browser_window__get_contextual_content(bwc,
(x - bwc->x), (y - bwc->y), data);
return;
}
/* Coordinate not contained by any frame */
return;
} else if (bw->current_content != NULL) {
/* Pass request to content */
ret = content_get_contextual_content(bw->current_content,
x, y, data);
data->main = bw->current_content;
}
if (bw->current_content == NULL)
/* No content; nothing to set */
return;
/* Pass request to content */
content_get_contextual_content(bw->current_content, x, y, data);
data->main = bw->current_content;
return ret;
}
/* exported interface, documented in browser.h */
void browser_window_get_contextual_content(struct browser_window *bw,
int x, int y, struct contextual_content *data)
nserror browser_window_get_features(struct browser_window *bw,
int x, int y, struct browser_window_features *data)
{
data->link_url = NULL;
/* clear the features structure to empty values */
data->link = NULL;
data->object = NULL;
data->main = NULL;
data->form_features = CTX_FORM_NONE;
browser_window__get_contextual_content(bw, x, y, data);
return browser_window__get_contextual_content(bw, x, y, data);
}
/* exported interface, documented in browser.h */

View File

@ -116,6 +116,27 @@ enum browser_window_nav_flags {
BW_NAVIGATE_UNVERIFIABLE = (1 << 2)
};
/**
* Page features at a specific spatial location.
*/
struct browser_window_features {
/** URL of a link or NULL. */
struct nsurl *link;
/** Object at position or NULL. */
struct hlcache_handle *object;
/** handle of top level content. */
struct hlcache_handle *main;
/** type of form feature. */
enum {
CTX_FORM_NONE,
CTX_FORM_TEXT,
CTX_FORM_FILE
} form_features;
};
/**
* Create and open a new root browser window with the given page.
*
@ -302,17 +323,22 @@ void browser_window_set_scale(struct browser_window *bw, float scale, bool all);
float browser_window_get_scale(struct browser_window *bw);
/**
* Get access to any content, link URLs and objects (images) currently
* at the given (x, y) coordinates.
* Get access to any page features at the given coordinates.
*
* \param bw browser window to look inside
* \param x x-coordinate of point of interest
* \param y y-coordinate of point of interest
* \param data pointer to contextual_content struct. Its fields are updated
* with pointers to any relevent content, or set to NULL if none.
* Fetches page features like content, link URLs and objects (images)
* at the specified co-ordinates within the browsing context.
*
* Fields within the supplied features structure are updated with
* pointers to any relevent content, or set to NULL if none.
*
* \param[in] bw browser window to examine.
* \param[in] x x-coordinate of point of interest
* \param[in] y y-coordinate of point of interest
* \param[out] data Feature structure to update.
* \return NSERROR_OK or appropriate error code on faliure.
*/
void browser_window_get_contextual_content(struct browser_window *bw,
int x, int y, struct contextual_content *data);
nserror browser_window_get_features(struct browser_window *bw,
int x, int y, struct browser_window_features *data);
/**
* Send a scroll request to a browser window at a particular point. The

View File

@ -27,6 +27,8 @@
#include <stdint.h>
#include <stdbool.h>
#include "utils/types.h"
struct textarea;
/* Text area flags */

View File

@ -143,8 +143,9 @@ static struct nsgtk_scaffolding *scaf_current;
static struct nsgtk_scaffolding *scaf_list = NULL;
/** holds the context data for what's under the pointer, when the contextual
* menu is opened. */
static struct contextual_content current_menu_ctx;
* menu is opened.
*/
static struct browser_window_features current_menu_features;
/**
@ -917,23 +918,23 @@ MULTIHANDLER(quit)
MENUHANDLER(savelink)
{
nsurl *url;
struct nsgtk_scaffolding *g = (struct nsgtk_scaffolding *) data;
struct gui_window *gui = g->top_level;
struct browser_window *bw = nsgtk_get_browser_window(gui);
nserror err;
if (current_menu_ctx.link_url == NULL)
if (current_menu_features.link == NULL)
return FALSE;
if (nsurl_create(current_menu_ctx.link_url, &url) == NSERROR_OK) {
browser_window_navigate(bw,
url,
NULL,
BW_NAVIGATE_DOWNLOAD,
NULL,
NULL,
NULL);
nsurl_unref(url);
err = browser_window_navigate(bw,
current_menu_features.link,
NULL,
BW_NAVIGATE_DOWNLOAD,
NULL,
NULL,
NULL);
if (err != NSERROR_OK) {
warn_user(messages_get_errorcode(err), 0);
}
return TRUE;
@ -947,21 +948,15 @@ MENUHANDLER(link_openwin)
struct nsgtk_scaffolding *g = (struct nsgtk_scaffolding *) data;
struct gui_window *gui = g->top_level;
struct browser_window *bw = nsgtk_get_browser_window(gui);
nsurl *url;
nserror error;
nserror err;
if (current_menu_ctx.link_url == NULL)
if (current_menu_features.link == NULL)
return FALSE;
error = nsurl_create(current_menu_ctx.link_url, &url);
if (error == NSERROR_OK) {
error = browser_window_create(
BW_CREATE_CLONE | BW_CREATE_HISTORY,
url, NULL, bw, NULL);
nsurl_unref(url);
}
if (error != NSERROR_OK) {
warn_user(messages_get_errorcode(error), 0);
err = browser_window_create(BW_CREATE_CLONE | BW_CREATE_HISTORY,
current_menu_features.link, NULL, bw, NULL);
if (err != NSERROR_OK) {
warn_user(messages_get_errorcode(err), 0);
}
return TRUE;
@ -975,23 +970,19 @@ MENUHANDLER(link_opentab)
struct nsgtk_scaffolding *g = (struct nsgtk_scaffolding *) data;
struct gui_window *gui = g->top_level;
struct browser_window *bw = nsgtk_get_browser_window(gui);
nsurl *url;
nserror error;
nserror err;
if (current_menu_ctx.link_url == NULL)
if (current_menu_features.link == NULL)
return FALSE;
temp_open_background = 1;
error = nsurl_create(current_menu_ctx.link_url, &url);
if (error == NSERROR_OK) {
error = browser_window_create(BW_CREATE_CLONE |
BW_CREATE_HISTORY | BW_CREATE_TAB,
url, NULL, bw, NULL);
nsurl_unref(url);
}
if (error != NSERROR_OK) {
warn_user(messages_get_errorcode(error), 0);
err = browser_window_create(BW_CREATE_CLONE |
BW_CREATE_HISTORY |
BW_CREATE_TAB,
current_menu_features.link, NULL, bw, NULL);
if (err != NSERROR_OK) {
warn_user(messages_get_errorcode(err), 0);
}
temp_open_background = -1;
@ -1004,20 +995,10 @@ MENUHANDLER(link_opentab)
*/
MENUHANDLER(link_bookmark)
{
nsurl *url;
nserror error;
if (current_menu_ctx.link_url == NULL)
if (current_menu_features.link == NULL)
return FALSE;
error = nsurl_create(current_menu_ctx.link_url, &url);
if (error == NSERROR_OK) {
hotlist_add_url(url);
nsurl_unref(url);
}
if (error != NSERROR_OK) {
warn_user(messages_get_errorcode(error), 0);
}
hotlist_add_url(current_menu_features.link);
return TRUE;
}
@ -1029,11 +1010,13 @@ MENUHANDLER(link_copy)
{
GtkClipboard *clipboard;
if (current_menu_ctx.link_url == NULL)
if (current_menu_features.link == NULL)
return FALSE;
clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
gtk_clipboard_set_text(clipboard, current_menu_ctx.link_url, -1);
gtk_clipboard_set_text(clipboard,
nsurl_access(current_menu_features.link), -1);
return TRUE;
}
@ -2692,13 +2675,11 @@ void nsgtk_scaffolding_context_menu(struct nsgtk_scaffolding *g,
{
GtkMenu *gtkmenu;
/* update the global current_menu_ctx */
browser_window_get_contextual_content(
nsgtk_get_browser_window(g->top_level),
x, y, &current_menu_ctx);
/* update the global context menu features */
browser_window_get_features(nsgtk_get_browser_window(g->top_level),
x, y, &current_menu_features);
if (current_menu_ctx.link_url != NULL) {
if (current_menu_features.link != NULL) {
/* menu is opening over a link */
gtkmenu = g->link_menu->link_menu;
} else {

View File

@ -36,7 +36,6 @@
#include "utils/messages.h"
#include "utils/talloc.h"
#include "utils/utf8.h"
#include "utils/utils.h"
#include "utils/nsoption.h"
#include "content/content_protected.h"
#include "content/fetch.h"
@ -1617,17 +1616,16 @@ static char *html_get_selection(struct content *c)
* Get access to any content, link URLs and objects (images) currently
* at the given (x, y) coordinates.
*
* \param c html content to look inside
* \param x x-coordinate of point of interest
* \param y y-coordinate of point of interest
* \param data pointer to contextual_content struct. Its fields are updated
* with pointers to any relevent content, or set to NULL if none.
* \param[in] c html content to look inside
* \param[in] x x-coordinate of point of interest
* \param[in] y y-coordinate of point of interest
* \param[out] data Positional features struct to be updated with any
* relevent content, or set to NULL if none.
* \return NSERROR_OK on success else appropriate error code.
*/
static void
html_get_contextual_content(struct content *c,
int x,
int y,
struct contextual_content *data)
static nserror
html_get_contextual_content(struct content *c, int x, int y,
struct browser_window_features *data)
{
html_content *html = (html_content *) c;
@ -1638,13 +1636,16 @@ html_get_contextual_content(struct content *c,
while ((next = box_at_point(box, x, y, &box_x, &box_y)) != NULL) {
box = next;
if (box->style && css_computed_visibility(box->style) ==
CSS_VISIBILITY_HIDDEN)
/* hidden boxes are ignored */
if ((box->style != NULL) &&
css_computed_visibility(box->style) == CSS_VISIBILITY_HIDDEN) {
continue;
}
if (box->iframe)
browser_window_get_contextual_content(box->iframe,
if (box->iframe) {
browser_window_get_features(box->iframe,
x - box_x, y - box_y, data);
}
if (box->object)
content_get_contextual_content(box->object,
@ -1654,7 +1655,7 @@ html_get_contextual_content(struct content *c,
data->object = box->object;
if (box->href)
data->link_url = nsurl_access(box->href);
data->link = box->href;
if (box->usemap) {
const char *target = NULL;
@ -1663,7 +1664,7 @@ html_get_contextual_content(struct content *c,
/* Box might have imagemap, but no actual link area
* at point */
if (url != NULL)
data->link_url = nsurl_access(url);
data->link = url;
}
if (box->gadget) {
switch (box->gadget->type) {
@ -1683,6 +1684,7 @@ html_get_contextual_content(struct content *c,
}
}
}
return NSERROR_OK;
}

View File

@ -1868,7 +1868,7 @@ bool ro_gui_window_toolbar_keypress(void *data, wimp_key *key)
bool ro_gui_window_handle_local_keypress(struct gui_window *g, wimp_key *key,
bool is_toolbar)
{
struct contextual_content cont;
struct browser_window_features cont;
os_error *ro_error;
wimp_pointer pointer;
os_coord pos;
@ -1892,7 +1892,7 @@ bool ro_gui_window_handle_local_keypress(struct gui_window *g, wimp_key *key,
if (!ro_gui_window_to_window_pos(g, pointer.pos.x, pointer.pos.y, &pos))
return false;
browser_window_get_contextual_content(g->bw, pos.x, pos.y, &cont);
browser_window_get_features(g->bw, pos.x, pos.y, &cont);
switch (c) {
case IS_WIMP_KEY + wimp_KEY_F1: /* Help. */
@ -2144,7 +2144,7 @@ bool ro_gui_window_menu_prepare(wimp_w w, wimp_i i, wimp_menu *menu,
struct gui_window *g;
struct browser_window *bw;
struct toolbar *toolbar;
struct contextual_content cont;
struct browser_window_features cont;
bool export_sprite, export_draw, have_content;
os_coord pos;
browser_editor_flags editor_flags;
@ -2176,21 +2176,15 @@ bool ro_gui_window_menu_prepare(wimp_w w, wimp_i i, wimp_menu *menu,
current_menu_main = NULL;
current_menu_object = NULL;
if (current_menu_url != NULL) {
nsurl_unref(current_menu_url);
current_menu_url = NULL;
}
current_menu_url = NULL;
if (ro_gui_window_to_window_pos(g, pointer->pos.x,
pointer->pos.y, &pos)) {
browser_window_get_contextual_content(bw,
pos.x, pos.y, &cont);
browser_window_get_features(bw, pos.x, pos.y, &cont);
current_menu_main = cont.main;
current_menu_object = cont.object;
if (cont.link_url != NULL) {
nsurl_create(cont.link_url, &current_menu_url);
}
current_menu_url = cont.link;
}
}
@ -3049,10 +3043,7 @@ void ro_gui_window_menu_close(wimp_w w, wimp_i i, wimp_menu *menu)
{
if (menu == ro_gui_browser_window_menu) {
current_menu_object = NULL;
if (current_menu_url != NULL) {
nsurl_unref(current_menu_url);
current_menu_url = NULL;
}
current_menu_url = NULL;
} else if (menu == gui_form_select_menu) {
gui_form_select_control = NULL;
}

View File

@ -49,20 +49,5 @@ struct redraw_context {
};
/**
* Content located at a specific spatial location.
*
* \todo This structure should contain a nsurl not a string.
*/
struct contextual_content {
const char *link_url;
struct hlcache_handle *object;
struct hlcache_handle *main;
enum {
CTX_FORM_NONE,
CTX_FORM_TEXT,
CTX_FORM_FILE
} form_features;
};
#endif