summaryrefslogtreecommitdiff
path: root/render/form.c
diff options
context:
space:
mode:
authorMichael Drake <tlsa@netsurf-browser.org>2009-08-14 10:37:33 +0000
committerMichael Drake <tlsa@netsurf-browser.org>2009-08-14 10:37:33 +0000
commit13afa0ff4c591e7800dc4c11a81d04c335418c05 (patch)
tree017f315584621f418a8bab5627aee8f8364d377b /render/form.c
parent3549846a9aec635bea82a919c5c1e38c7163731a (diff)
downloadnetsurf-13afa0ff4c591e7800dc4c11a81d04c335418c05.tar.gz
netsurf-13afa0ff4c591e7800dc4c11a81d04c335418c05.tar.bz2
Merge Paul Blokus' selectscroll branch. Adds core select menu widget for forms and core scrollbar widget.
svn path=/trunk/netsurf/; revision=9289
Diffstat (limited to 'render/form.c')
-rw-r--r--render/form.c492
1 files changed, 492 insertions, 0 deletions
diff --git a/render/form.c b/render/form.c
index 01e5e1d2f..968397a21 100644
--- a/render/form.c
+++ b/render/form.c
@@ -2,6 +2,7 @@
* Copyright 2004 James Bursa <bursa@users.sourceforge.net>
* Copyright 2003 Phil Mellor <monkeyson@users.sourceforge.net>
* Copyright 2005-9 John-Mark Bell <jmb@netsurf-browser.org>
+ * Copyright 2009 Paul Blokus <paul_pl@users.sourceforge.net>
*
* This file is part of NetSurf, http://www.netsurf-browser.org/
*
@@ -29,18 +30,60 @@
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
+#include "css/css.h"
+#include "css/utils.h"
+#include "desktop/gui.h"
+#include "desktop/knockout.h"
+#include "desktop/plot_style.h"
+#include "desktop/plotters.h"
+#include "desktop/scroll.h"
#include "render/box.h"
+#include "render/font.h"
#include "render/form.h"
+#include "render/layout.h"
#include "utils/log.h"
+#include "utils/messages.h"
#include "utils/url.h"
#include "utils/utf8.h"
#include "utils/utils.h"
+#define MAX_SELECT_HEIGHT 210
+#define SELECT_LINE_SPACING 0.2
+#define SELECT_BORDER_WIDTH 1
+#define SELECT_SELECTED_COLOUR 0xDB9370
+
+struct form_select_menu {
+ int line_height;
+ int width, height;
+ struct scroll *scroll;
+ int f_size;
+ bool scroll_capture;
+ select_menu_redraw_callback callback;
+ void *client_data;
+ struct browser_window *bw;
+};
+
+static plot_style_t plot_style_fill_selected = {
+ .fill_type = PLOT_OP_TYPE_SOLID,
+ .fill_colour = SELECT_SELECTED_COLOUR,
+};
+
+static plot_font_style_t plot_fstyle_entry = {
+ .family = PLOT_FONT_FAMILY_SANS_SERIF,
+ .weight = 400,
+ .flags = FONTF_NONE,
+ .background = 0xffffff,
+ .foreground = 0x000000,
+};
static char *form_textarea_value(struct form_control *textarea);
static char *form_acceptable_charset(struct form *form);
static char *form_encode_item(const char *item, const char *charset,
const char *fallback);
+static void form_select_menu_clicked(struct form_control *control,
+ int x, int y);
+static void form_select_menu_scroll_callback(void *client_data,
+ struct scroll_msg_data *scroll_data);
/**
* Create a struct form.
@@ -199,6 +242,8 @@ void form_free_control(struct form_control *control)
free(option->value);
free(option);
}
+ if (control->data.select.menu != NULL)
+ form_free_select_menu(control);
}
free(control);
@@ -812,3 +857,450 @@ char *form_encode_item(const char *item, const char *charset,
return ret;
}
+/**
+ * Open a select menu for a select form control, creating it if necessary.
+ *
+ * \param client_data data passed to the redraw callback
+ * \param control the select form control for which the menu is being
+ * opened
+ * \param callback redraw callback for the select menu
+ * \param bw the browser window in which the select menu is being
+ * opened
+ * \return false on memory exhaustion, true otherwise
+ */
+bool form_open_select_menu(void *client_data,
+ struct form_control *control,
+ select_menu_redraw_callback callback,
+ struct browser_window *bw)
+{
+ int i, line_height_with_spacing, scroll;
+ struct form_option *option;
+ struct box *box;
+ plot_font_style_t fstyle;
+ int total_height;
+ struct form_select_menu *menu;
+
+
+ /* if the menu is opened for the first time */
+ if (control->data.select.menu == NULL) {
+
+ menu = calloc(1, sizeof (struct form_select_menu));
+ if (menu == NULL) {
+ warn_user("NoMemory", 0);
+ return false;
+ }
+
+ control->data.select.menu = menu;
+
+ box = control->box;
+
+ menu->width = box->width +
+ box->border[RIGHT].width +
+ box->border[LEFT].width +
+ box->padding[RIGHT] + box->padding[LEFT];
+
+ font_plot_style_from_css(control->box->style,
+ &fstyle);
+ menu->f_size = fstyle.size;
+
+ menu->line_height =
+ FIXTOINT(FDIVI((FMUL(FLTTOFIX(1.2),
+ FMULI(nscss_screen_dpi,
+ (fstyle.size / FONT_SIZE_SCALE)))), 72));
+
+ line_height_with_spacing = menu->line_height +
+ menu->line_height *
+ SELECT_LINE_SPACING;
+
+ total_height = control->data.select.num_items *
+ line_height_with_spacing;
+ menu->height = total_height;
+
+ scroll = 0;
+ if (menu->height > MAX_SELECT_HEIGHT) {
+
+ menu->height = MAX_SELECT_HEIGHT;
+
+ if (control->data.select.num_selected > 0) {
+ i = 0;
+ option = control->data.select.items;
+ while (!option->selected) {
+ option = option->next;
+ i++;
+ }
+
+ if ((i + 1) * line_height_with_spacing >
+ MAX_SELECT_HEIGHT)
+ scroll = (i + 1) *
+ line_height_with_spacing
+ - MAX_SELECT_HEIGHT;
+ }
+ }
+ menu->client_data = client_data;
+ menu->callback = callback;
+ if (!scroll_create(false,
+ menu->height,
+ total_height,
+ menu->height,
+ control,
+ form_select_menu_scroll_callback,
+ &(menu->scroll))) {
+ free(menu);
+ return false;
+ }
+ menu->bw = bw;
+ }
+ else menu = control->data.select.menu;
+
+ menu->callback(client_data, 0, 0, menu->width, menu->height);
+
+ return true;
+}
+
+
+/**
+ * Destroy a select menu and free allocated memory.
+ *
+ * \param control the select form control owning the select menu being
+ * destroyed
+ */
+void form_free_select_menu(struct form_control *control)
+{
+ if (control->data.select.menu->scroll != NULL)
+ scroll_destroy(control->data.select.menu->scroll);
+ free(control->data.select.menu);
+ control->data.select.menu = NULL;
+}
+
+/**
+ * Redraw an opened select menu.
+ *
+ * \param control the select menu being redrawn
+ * \param x the X coordinate to draw the menu at
+ * \param x the Y coordinate to draw the menu at
+ * \param scale current redraw scale
+ * \param clip_x0 minimum x of clipping rectangle
+ * \param clip_y0 minimum y of clipping rectangle
+ * \param clip_x1 maximum x of clipping rectangle
+ * \param clip_y1 maximum y of clipping rectangle
+ * \return true on success, false otherwise
+ */
+bool form_redraw_select_menu(struct form_control *control, int x, int y,
+ float scale, int clip_x0, int clip_y0, int clip_x1, int clip_y1)
+{
+ struct box *box;
+ struct form_select_menu *menu = control->data.select.menu;
+ struct form_option *option;
+ int line_height, line_height_with_spacing;
+ int width, height;
+ int x0, y0, x1, scrollbar_x, y1, y2, y3;
+ int item_y;
+ int text_pos_offset, text_x;
+ int scrollbar_width = SCROLLBAR_WIDTH;
+ int i;
+ int scroll;
+ int x_cp, y_cp;
+
+ box = control->box;
+
+ x_cp = x;
+ y_cp = y;
+ width = menu->width;
+ height = menu->height;
+ line_height = menu->line_height;
+
+ line_height_with_spacing = line_height +
+ line_height * SELECT_LINE_SPACING;
+ scroll = scroll_get_offset(menu->scroll);
+
+ if (scale != 1.0) {
+ x *= scale;
+ y *= scale;
+ width *= scale;
+ height *= scale;
+ scrollbar_width *= scale;
+
+ i = scroll / line_height_with_spacing;
+ scroll -= i * line_height_with_spacing;
+ line_height *= scale;
+ line_height_with_spacing *= scale;
+ scroll *= scale;
+ scroll += i * line_height_with_spacing;
+ }
+
+
+ x0 = x;
+ y0 = y;
+ x1 = x + width - 1;
+ y1 = y + height - 1;
+ scrollbar_x = x1 - scrollbar_width;
+
+ if (!plot.clip(x0, y0, x1 + 1, y1 + 1))
+ return false;
+ if (!plot.rectangle(x0, y0, x1, y1 ,plot_style_stroke_darkwbasec))
+ return false;
+
+
+ x0 = x0 + SELECT_BORDER_WIDTH;
+ y0 = y0 + SELECT_BORDER_WIDTH;
+ x1 = x1 - SELECT_BORDER_WIDTH;
+ y1 = y1 - SELECT_BORDER_WIDTH;
+ height = height - 2 * SELECT_BORDER_WIDTH;
+
+ if (!plot.clip(x0, y0, x1 + 1, y1 + 1))
+ return false;
+ if (!plot.rectangle(x0, y0, x1 + 1, y1 + 1,
+ plot_style_fill_lightwbasec))
+ return false;
+ option = control->data.select.items;
+ item_y = line_height_with_spacing;
+
+ while (item_y < scroll) {
+ option = option->next;
+ item_y += line_height_with_spacing;
+ }
+ item_y -= line_height_with_spacing;
+ text_pos_offset = y - scroll +
+ (int) (line_height * (0.75 + SELECT_LINE_SPACING));
+ text_x = x + (box->border[LEFT].width + box->padding[LEFT]) * scale;
+
+ plot_fstyle_entry.size = menu->f_size;
+
+ while (option && item_y - scroll < height) {
+
+ if (option->selected) {
+ y2 = y + item_y - scroll;
+ y3 = y + item_y + line_height_with_spacing - scroll;
+ if (!plot.rectangle(x0, (y0 > y2 ? y0 : y2),
+ scrollbar_x + 1,
+ (y3 < y1 + 1 ? y3 : y1 + 1),
+ &plot_style_fill_selected))
+ return false;
+ }
+
+ y2 = text_pos_offset + item_y;
+ if (!plot.text(text_x, y2, option->text,
+ strlen(option->text), &plot_fstyle_entry))
+ return false;
+
+ item_y += line_height_with_spacing;
+ option = option->next;
+ }
+
+ if (!scroll_redraw(menu->scroll,
+ x_cp + menu->width - SCROLLBAR_WIDTH,
+ y_cp,
+ clip_x0, clip_y0, clip_x1, clip_y1, scale))
+ return false;
+
+ return true;
+}
+
+/**
+ * Check whether a clipping rectangle is completely contained in the
+ * select menu.
+ *
+ * \param control the select menu to check the clipping rectangle for
+ * \param scale the current browser window scale
+ * \param clip_x0 minimum x of clipping rectangle
+ * \param clip_y0 minimum y of clipping rectangle
+ * \param clip_x1 maximum x of clipping rectangle
+ * \param clip_y1 maximum y of clipping rectangle
+ * \return true if inside false otherwise
+ */
+bool form_clip_inside_select_menu(struct form_control *control, float scale,
+ int clip_x0, int clip_y0, int clip_x1, int clip_y1)
+{
+ struct form_select_menu *menu = control->data.select.menu;
+ int width, height;
+
+
+ width = menu->width;
+ height = menu->height;
+
+ if (scale != 1.0) {
+ width *= scale;
+ height *= scale;
+ }
+
+ if (clip_x0 >= 0 && clip_x1 <= width &&
+ clip_y0 >= 0 && clip_y1 <= height)
+ return true;
+
+ return false;
+}
+
+/**
+ * Handle a click on the area of the currently opened select menu.
+ *
+ * \param control the select menu which received the click
+ * \param x X coordinate of click
+ * \param y Y coordinate of click
+ */
+void form_select_menu_clicked(struct form_control *control, int x, int y)
+{
+ struct form_select_menu *menu = control->data.select.menu;
+ struct form_option *option;
+ int line_height, line_height_with_spacing;
+ int item_bottom_y;
+ int scroll, i;
+
+ scroll = scroll_get_offset(menu->scroll);
+
+ line_height = menu->line_height;
+ line_height_with_spacing = line_height +
+ line_height * SELECT_LINE_SPACING;
+
+ option = control->data.select.items;
+ item_bottom_y = line_height_with_spacing;
+ i = 0;
+ while (option && item_bottom_y < scroll + y) {
+ item_bottom_y += line_height_with_spacing;
+ option = option->next;
+ i++;
+ }
+
+ if (option != NULL)
+ browser_window_form_select(menu->bw, control, i);
+
+ menu->callback(menu->client_data, 0, 0, menu->width, menu->height);
+}
+
+/**
+ * Handle mouse action for the currently opened select menu.
+ *
+ * \param control the select menu which received the mouse action
+ * \param mouse current mouse state
+ * \param x X coordinate of click
+ * \param y Y coordinate of click
+ * \return text for the browser status bar or NULL if the menu has
+ * to be closed
+ */
+const char *form_select_mouse_action(struct form_control *control,
+ browser_mouse_state mouse, int x, int y)
+{
+ struct form_select_menu *menu = control->data.select.menu;
+ int x0, y0, x1, y1, scrollbar_x;
+ const char *status = NULL;
+ bool multiple = control->data.select.multiple;
+
+ x0 = 0;
+ y0 = 0;
+ x1 = menu->width;
+ y1 = menu->height;
+ scrollbar_x = x1 - SCROLLBAR_WIDTH;
+
+ if (menu->scroll_capture ||
+ (x > scrollbar_x && x < x1 && y > y0 && y < y1)) {
+ /* The scroll is currently capturing all events or the mouse
+ * event is taking place on the scrollbar widget area
+ */
+ x -= scrollbar_x;
+ return scroll_mouse_action(menu->scroll,
+ mouse, x, y);
+ }
+
+
+ if (x > x0 && x < scrollbar_x && y > y0 && y < y1) {
+ /* over option area */
+
+ if (mouse & (BROWSER_MOUSE_CLICK_1 | BROWSER_MOUSE_CLICK_2))
+ /* button 1 or 2 click */
+ form_select_menu_clicked(control, x, y);
+
+ if (!(mouse & BROWSER_MOUSE_CLICK_1 && !multiple))
+ /* anything but a button 1 click over a single select
+ menu */
+ status = messages_get(control->data.select.multiple ?
+ "SelectMClick" : "SelectClick");
+
+ } else if (!(mouse & (BROWSER_MOUSE_CLICK_1 | BROWSER_MOUSE_CLICK_2)))
+ /* if not a button 1 or 2 click*/
+ status = messages_get("SelectClose");
+
+ return status;
+}
+
+/**
+ * Handle mouse drag end for the currently opened select menu.
+ *
+ * \param control the select menu which received the mouse drag end
+ * \param mouse current mouse state
+ * \param x X coordinate of drag end
+ * \param y Y coordinate of drag end
+ */
+void form_select_mouse_drag_end(struct form_control *control,
+ browser_mouse_state mouse, int x, int y)
+{
+ int x0, y0, x1, y1;
+ struct form_select_menu *menu = control->data.select.menu;
+
+ if (menu->scroll_capture) {
+ x -= menu->width - SCROLLBAR_WIDTH;
+ scroll_mouse_drag_end(menu->scroll,
+ mouse, x, y);
+ return;
+ }
+
+ x0 = 0;
+ y0 = 0;
+ x1 = menu->width;
+ y1 = menu->height;
+
+
+ if (x > x0 && x < x1 - SCROLLBAR_WIDTH && y > y0 && y < y1)
+ /* handle drag end above the option area like a regular click */
+ form_select_menu_clicked(control, x, y);
+}
+
+/**
+ * Callback for the select menus scroll
+ */
+void form_select_menu_scroll_callback(void *client_data,
+ struct scroll_msg_data *scroll_data)
+{
+ struct form_control *control = client_data;
+ struct form_select_menu *menu = control->data.select.menu;
+
+ switch (scroll_data->msg) {
+ case SCROLL_MSG_REDRAW:
+ menu->callback(menu->client_data,
+ menu->width -
+ SCROLLBAR_WIDTH + scroll_data->x0,
+ scroll_data->y0,
+ scroll_data->x1 - scroll_data->x0,
+ scroll_data->y1 - scroll_data->y0);
+ break;
+ case SCROLL_MSG_MOVED:
+ menu->callback(menu->client_data,
+ 0, 0,
+ menu->width - SCROLLBAR_WIDTH,
+ menu->height);
+ break;
+ case SCROLL_MSG_SCROLL_START:
+ menu->scroll_capture = true;
+ gui_window_box_scroll_start(menu->bw->window,
+ scroll_data->x0, scroll_data->y0,
+ scroll_data->x1, scroll_data->y1);
+ break;
+ case SCROLL_MSG_SCROLL_FINISHED:
+ menu->scroll_capture = false;
+ break;
+ default:
+ break;
+ }
+}
+
+/**
+ * Get the dimensions of a select menu.
+ *
+ * \param control the select menu to get the dimensions of
+ * \param width gets updated to menu width
+ * \param height gets updated to menu height
+ */
+void form_select_get_dimensions(struct form_control *control,
+ int *width, int *height)
+{
+ *width = control->data.select.menu->width;
+ *height = control->data.select.menu->height;
+}