netsurf/riscos/wimp_event.c
Vincent Sanders c105738fa3 Change LOG() macro to be varadic
This changes the LOG macro to be varadic removing the need for all
callsites to have double bracketing and allows for future improvement
on how we use the logging macros.

The callsites were changed with coccinelle and the changes checked by
hand. Compile tested for several frontends but not all.

A formatting annotation has also been added which allows the compiler
to check the parameters and types passed to the logging.
2015-05-28 16:08:46 +01:00

1816 lines
46 KiB
C

/*
* Copyright 2005 Richard Wilson <info@tinct.net>
* Copyright 2010, 2011 Stephen Fryatt <stevef@netsurf-browser.org>
*
* 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
* Automated RISC OS WIMP event handling (implementation).
*/
#include <assert.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "oslib/os.h"
#include "oslib/osbyte.h"
#include "oslib/serviceinternational.h"
#include "oslib/wimp.h"
#include "utils/log.h"
#include "utils/utils.h"
#include "riscos/gui.h"
#include "riscos/dialog.h"
#include "riscos/menus.h"
#include "riscos/ucstables.h"
#include "riscos/wimp.h"
#include "riscos/wimp_event.h"
#include "riscos/wimputils.h"
#define WIN_HASH_SIZE 32
#define WIN_HASH(w) (((unsigned)(w) >> 5) % WIN_HASH_SIZE)
typedef enum {
EVENT_NUMERIC_FIELD,
EVENT_TEXT_FIELD,
EVENT_UP_ARROW,
EVENT_DOWN_ARROW,
EVENT_MENU_GRIGHT,
EVENT_CHECKBOX,
EVENT_RADIO,
EVENT_BUTTON,
EVENT_CANCEL,
EVENT_OK
} event_type;
struct event_data_numeric_field {
int stepping;
int min;
int max;
int decimal_places;
};
struct event_data_menu_gright {
wimp_i field;
wimp_menu *menu;
};
struct icon_event {
event_type type;
wimp_i i;
union {
struct event_data_numeric_field numeric_field;
struct event_data_menu_gright menu_gright;
wimp_i linked_icon;
int radio_group;
void (*callback)(wimp_pointer *pointer);
} data;
union {
char *textual;
bool boolean;
} previous_value;
bool previous_shaded;
struct icon_event *next;
};
struct event_window {
wimp_w w;
bool (*ok_click)(wimp_w w);
bool (*mouse_click)(wimp_pointer *pointer);
bool (*keypress)(wimp_key *key);
void (*open_window)(wimp_open *open);
void (*close_window)(wimp_w w);
void (*redraw_window)(wimp_draw *redraw);
void (*scroll_window)(wimp_scroll *scroll);
void (*entering_window)(wimp_entering *entering);
bool (*menu_prepare)(wimp_w w, wimp_i i, wimp_menu *m,
wimp_pointer *p);
bool (*menu_selection)(wimp_w w, wimp_i i, wimp_menu *m,
wimp_selection *s, menu_action a);
void (*menu_warning)(wimp_w w, wimp_i i, wimp_menu *m,
wimp_selection *s, menu_action a);
void (*menu_close)(wimp_w w, wimp_i i, wimp_menu *m);
wimp_menu *window_menu;
bool window_menu_auto;
bool window_menu_iconbar;
const char *help_prefix;
const char *(*get_help_suffix)(wimp_w w, wimp_i i, os_coord *pos,
wimp_mouse_state buttons);
void *user_data;
struct icon_event *first;
struct event_window *next;
int max_radio_group;
};
static void ro_gui_wimp_event_ok_click(struct event_window *window,
wimp_mouse_state state);
static struct event_window *ro_gui_wimp_event_get_window(wimp_w w);
static struct event_window *ro_gui_wimp_event_find_window(wimp_w w);
static struct icon_event *ro_gui_wimp_event_get_event(wimp_w w, wimp_i i,
event_type type);
static void ro_gui_wimp_event_prepare_gright_menu(wimp_w w, struct icon_event *event);
static struct event_window *ro_gui_wimp_event_remove_window(wimp_w w);
static struct event_window *ro_gui_wimp_event_windows[WIN_HASH_SIZE];
static wimp_w ro_gui_wimp_event_submenu;
/**
* Memorises the current state of any registered components in a window.
*
* \param w the window to memorise
* \return true on success, false on memory exhaustion or for an unknown window
*/
bool ro_gui_wimp_event_memorise(wimp_w w)
{
struct event_window *window;
struct icon_event *event;
bool error = false;
window = ro_gui_wimp_event_find_window(w);
if (!window)
return false;
for (event = window->first; event; event = event->next) {
switch (event->type) {
case EVENT_NUMERIC_FIELD:
case EVENT_TEXT_FIELD:
if (event->previous_value.textual)
free(event->previous_value.textual);
event->previous_value.textual = strdup(
ro_gui_get_icon_string(window->w, event->i));
if (!event->previous_value.textual) {
error = true;
LOG("Unable to store state for icon %i", event->i);
}
break;
case EVENT_CHECKBOX:
case EVENT_RADIO:
event->previous_value.boolean =
ro_gui_get_icon_selected_state(window->w, event->i);
break;
default:
break;
}
if (event->type != EVENT_MENU_GRIGHT)
event->previous_shaded = ro_gui_get_icon_shaded_state(window->w,
event->i);
}
return !error;
}
/**
* Restore the state of any registered components in a window to their memorised state.
*
* \param w the window to restore
* \return true on success, false for an unknown window
*/
bool ro_gui_wimp_event_restore(wimp_w w)
{
struct event_window *window;
struct icon_event *event;
window = ro_gui_wimp_event_find_window(w);
if (!window)
return false;
for (event = window->first; event; event = event->next) {
switch (event->type) {
case EVENT_NUMERIC_FIELD:
case EVENT_TEXT_FIELD:
if (event->previous_value.textual)
ro_gui_set_icon_string(window->w, event->i,
event->previous_value.textual, true);
break;
case EVENT_CHECKBOX:
case EVENT_RADIO:
ro_gui_set_icon_selected_state(window->w, event->i,
event->previous_value.boolean);
break;
default:
break;
}
if (event->type != EVENT_MENU_GRIGHT)
ro_gui_set_icon_shaded_state(window->w, event->i,
event->previous_shaded);
}
return true;
}
/**
* Ensures all values are within pre-determined boundaries.
*
* \param w the window to memorise
* \return true on success, false for an unknown window
*/
bool ro_gui_wimp_event_validate(wimp_w w)
{
struct event_window *window;
struct icon_event *event;
int value;
window = ro_gui_wimp_event_find_window(w);
if (!window)
return false;
for (event = window->first; event; event = event->next) {
switch (event->type) {
case EVENT_NUMERIC_FIELD:
value = ro_gui_get_icon_decimal(window->w, event->i,
event->data.numeric_field.decimal_places);
if (value < event->data.numeric_field.min)
value = event->data.numeric_field.min;
else if (value > event->data.numeric_field.max)
value = event->data.numeric_field.max;
ro_gui_set_icon_decimal(window->w, event->i, value,
event->data.numeric_field.decimal_places);
break;
default:
break;
}
}
return true;
}
/**
* Transfer event data from one window to another. This can be used as an
* alternative to ro_gui_wimp_event_finalise() and re-registering, if
* events need to continue across a change of window handle.
*
* All aspects of the registered events MUST remain the same in the new
* window!
*
* \param from The current window, which is to be deleted.
* \param to The window to which the events should transfer.
* \return true on success; false for an unknown window.
*/
bool ro_gui_wimp_event_transfer(wimp_w from, wimp_w to)
{
struct event_window *window;
int h;
LOG("Transferring all events from window 0x%x to window 0x%x", (unsigned int)from, (unsigned int)to);
window = ro_gui_wimp_event_remove_window(from);
if (window == NULL || window->w != from)
return false;
h = WIN_HASH(to);
window->w = to;
window->next = ro_gui_wimp_event_windows[h];
ro_gui_wimp_event_windows[h] = window;
ro_gui_menu_window_changed(from, to);
return true;
}
/**
* Free any resources associated with a window.
*
* \param w the window to free resources for
*/
void ro_gui_wimp_event_finalise(wimp_w w)
{
struct event_window *window;
struct icon_event *event;
LOG("Removing all events for window 0x%x", (unsigned int)w);
window = ro_gui_wimp_event_remove_window(w);
if (!window)
return;
while (window->first) {
event = window->first;
window->first = event->next;
switch (event->type) {
case EVENT_NUMERIC_FIELD:
case EVENT_TEXT_FIELD:
if (event->previous_value.textual)
free(event->previous_value.textual);
event->previous_value.textual = NULL;
break;
default:
break;
}
free(event);
}
free(window);
return;
}
/**
* Free any resources associated with a specific icon in a window.
*
* \param w The window containing the icon.
* \param i The icon to free resources for.
*/
void ro_gui_wimp_event_deregister(wimp_w w, wimp_i i)
{
struct event_window *window;
struct icon_event *event, *parent, *child;
LOG("Removing all events for window 0x%x, icon %d", (unsigned int)w, (int)i);
window = ro_gui_wimp_event_get_window(w);
if (!window)
return;
/* Remove any events that apply to the given icon. */
event = window->first;
parent = NULL;
while (event != NULL) {
child = event->next;
if (event->i == i) {
LOG("Removing event 0x%x", (unsigned int)event);
if (parent == NULL)
window->first = child;
else
parent->next = child;
switch (event->type) {
case EVENT_NUMERIC_FIELD:
case EVENT_TEXT_FIELD:
if (event->previous_value.textual)
free(event->previous_value.textual);
event->previous_value.textual = NULL;
break;
default:
break;
}
free(event);
} else {
parent = event;
}
event = child;
}
}
/**
* Set the associated help prefix for a given window.
*
* \param w the window to get the prefix for
* \param help_prefix the prefix to associate with the window (used directly)
* \return true on success, or NULL for memory exhaustion
*/
bool ro_gui_wimp_event_set_help_prefix(wimp_w w, const char *help_prefix)
{
struct event_window *window;
window = ro_gui_wimp_event_get_window(w);
if (!window)
return false;
window->help_prefix = help_prefix;
return true;
}
/**
* Get the associated help prefix.
*
* \param w the window to get the prefix for
* \return the associated prefix, or NULL
*/
const char *ro_gui_wimp_event_get_help_prefix(wimp_w w)
{
struct event_window *window;
window = ro_gui_wimp_event_find_window(w);
if (window)
return window->help_prefix;
return NULL;
}
/**
* Register a handler to decode help suffixes for a given window.
*
*/
bool ro_gui_wimp_event_register_help_suffix(wimp_w w,
const char *(*get_help_suffix)(wimp_w w, wimp_i i,
os_coord *pos, wimp_mouse_state buttons))
{
struct event_window *window;
window = ro_gui_wimp_event_get_window(w);
if (!window)
return false;
window->get_help_suffix = get_help_suffix;
return true;
}
/**
* Get the associated help suffix.
*
* \param w The window to get the suffix for
* \param i The icon
* \param pos The os coordinates
* \param buttons The button state.
* \return The associated prefix, or NULL
*/
const char *ro_gui_wimp_event_get_help_suffix(wimp_w w, wimp_i i,
os_coord *pos, wimp_mouse_state buttons)
{
struct event_window *window;
window = ro_gui_wimp_event_find_window(w);
if (window == NULL || window->get_help_suffix == NULL)
return NULL;
return window->get_help_suffix(w, i, pos, buttons);
}
/**
* Sets the user data associated with a window.
*
* \param w the window to associate the data with
* \param user the data to associate
*/
bool ro_gui_wimp_event_set_user_data(wimp_w w, void *user)
{
struct event_window *window;
window = ro_gui_wimp_event_get_window(w);
if (!window)
return false;
window->user_data = user;
return true;
}
/**
* Gets the user data associated with a window.
*
* \param w the window to retrieve the data for
* \return the associated data, or NULL
*/
void *ro_gui_wimp_event_get_user_data(wimp_w w)
{
struct event_window *window;
window = ro_gui_wimp_event_find_window(w);
if (window)
return window->user_data;
return NULL;
}
/**
* Handles a menu selection event.
*
* (At present, this is tied to being called from menus.c and relies on that
* module decoding the menu into an action code. If menus.c loses its
* menu handling in the future, such decoding might need to move here.)
*
* The order of execution is:
*
* 1. Try to match the menu to a pop-up menu. If successful, handle it as
* this.
* 2. Try to match the menu to a window menu. If successful, pass control to
* the menu's registered _select handler.
* 3. Return event as unhandled.
*
* \param w the window to owning the menu
* \param i the icon owning the menu
* \param menu the menu that has been selected
* \param selection the selection information
* \param action the menu action info from menus.c
* \return true if the menu is OK for an Adjust re-open;
* else false.
*/
bool ro_gui_wimp_event_menu_selection(wimp_w w, wimp_i i, wimp_menu *menu,
wimp_selection *selection, menu_action action)
{
struct event_window *window;
struct icon_event *event;
wimp_menu_entry *menu_entry;
wimp_key key;
os_error *error;
wimp_caret caret;
wimp_icon_state ic;
unsigned int button_type;
bool prepared;
window = ro_gui_wimp_event_find_window(w);
if (window == NULL)
return false;
/* Start by looking for an icon event that matches. If there isn't one,
* then return details for an unconnected menu. It's up to the
* event recipient to sort out if this is a window menu or not, based
* on the menu handle passed back.
*/
for (event = window->first; event; event = event->next)
if ((event->type == EVENT_MENU_GRIGHT) && (event->i == i))
break;
if (!event) {
if (window->menu_selection)
window->menu_selection(window->w, wimp_ICON_WINDOW,
menu, selection, action);
/* Prepare the menu pending a possible Adjust click. */
if (window->menu_prepare)
if (!window->menu_prepare(window->w, wimp_ICON_WINDOW,
menu, NULL))
return false;
return true;
}
menu_entry = &menu->entries[selection->items[0]];
for (i = 1; selection->items[i] != -1; i++)
menu_entry = &menu_entry->sub_menu->
entries[selection->items[i]];
/* if the entry is already ticked then we do nothing */
if (menu_entry->menu_flags & wimp_MENU_TICKED)
return true;
ro_gui_set_icon_string(window->w, event->data.menu_gright.field,
menu_entry->data.indirected_text.text, false);
if (window->menu_selection)
window->menu_selection(window->w, event->i, menu,
selection, action);
prepared = true;
if (window->menu_prepare)
prepared = window->menu_prepare(window->w, event->i,
menu, NULL);
if (prepared)
ro_gui_wimp_event_prepare_gright_menu(window->w, event);
/* set the caret for writable icons and send a CTRL+U keypress to
* stimulate activity if needed */
ic.w = window->w;
ic.i = event->data.menu_gright.field;
error = xwimp_get_icon_state(&ic);
if (error) {
LOG("xwimp_get_icon_state: 0x%x: %s", error->errnum, error->errmess);
warn_user("WimpError", error->errmess);
return false;
}
button_type = (ic.icon.flags & wimp_ICON_BUTTON_TYPE) >> wimp_ICON_BUTTON_TYPE_SHIFT;
if ((button_type != wimp_BUTTON_WRITABLE) &&
(button_type != wimp_BUTTON_WRITE_CLICK_DRAG))
return prepared;
error = xwimp_get_caret_position(&caret);
if (error) {
LOG("xwimp_get_caret_position: 0x%x: %s", error->errnum, error->errmess);
warn_user("WimpError", error->errmess);
return false;
}
if ((caret.w != window->w) || (caret.i != event->data.menu_gright.field)) {
error = xwimp_set_caret_position(window->w, event->data.menu_gright.field,
-1, -1, -1, strlen(menu_entry->data.indirected_text.text));
if (error) {
LOG("xwimp_set_caret_position: 0x%x: %s", error->errnum, error->errmess);
warn_user("WimpError", error->errmess);
}
}
if (window->keypress) {
key.w = window->w;
key.c = 21; // ctrl+u
window->keypress(&key);
}
return prepared;
}
/**
* Handles a mouse click event in a registered window.
*
* The order of execution is:
*
* 1. If a menu click, and the window has an automatic window menu, this is
* processed immediately.
* 2. Any registered mouse_click routine (see ro_gui_wimp_register_mouse_click())
* 3. If the current icon is not registered with a type then it is assumed that no
* action is necessary, and the click is deemed to have been handled.
* 4. If the registered mouse_click routine returned false, or there was no registered
* routine then the automated action for the registered icon type is performed
*
* \param pointer the current pointer state
* \return true if the event was handled, false otherwise
*/
bool ro_gui_wimp_event_mouse_click(wimp_pointer *pointer)
{
struct event_window *window;
struct icon_event *event;
wimp_w w;
struct icon_event *search;
int current, step, stepping, min, max, decimal_places;
wimp_window_state open;
wimp_caret caret;
bool prepared;
w = pointer->w;
window = ro_gui_wimp_event_find_window(w);
if (!window)
return false;
/* Menu clicks take priority if there is an auto menu defined. */
if ((pointer->buttons == wimp_CLICK_MENU) &&
(window->window_menu != NULL) &&
(window->window_menu_auto)) {
ro_gui_wimp_event_process_window_menu_click(pointer);
return true;
}
/* registered routines take next priority */
if ((window->mouse_click) && (window->mouse_click(pointer)))
return true;
for (event = window->first; event; event = event->next)
if (event->i == pointer->i)
break;
if (!event)
return true;
switch (event->type) {
case EVENT_NUMERIC_FIELD:
case EVENT_TEXT_FIELD:
break;
case EVENT_UP_ARROW:
case EVENT_DOWN_ARROW:
for (search = window->first; search; search = search->next)
if (search->i == event->data.linked_icon) break;
if (!search) {
LOG("Incorrect reference.");
return false;
}
stepping = search->data.numeric_field.stepping;
min = search->data.numeric_field.min;
max = search->data.numeric_field.max;
decimal_places = search->data.numeric_field.decimal_places;
if (pointer->buttons & wimp_CLICK_ADJUST)
step = -stepping;
else if (pointer->buttons & wimp_CLICK_SELECT)
step = stepping;
else
return true;
if (event->type == EVENT_DOWN_ARROW)
step = -step;
current = ro_gui_get_icon_decimal(pointer->w, event->data.linked_icon,
decimal_places);
current += step;
if (current < min)
current = min;
if (current > max)
current = max;
ro_gui_set_icon_decimal(pointer->w, event->data.linked_icon, current,
decimal_places);
break;
case EVENT_MENU_GRIGHT:
/* if there's already a menu open then we assume that we are part of it.
* to follow the standard RISC OS behaviour we add a 'send to the back'
* button, then close the menu (which closes us) and then finally
* re-open ourselves. ugh! */
if (current_menu != NULL) {
os_error *error;
open.w = pointer->w;
error = xwimp_get_window_state(&open);
if (error) {
LOG("xwimp_get_window_state: 0x%x: %s", error->errnum, error->errmess);
warn_user("WimpError", error->errmess);
return false;
}
error = xwimp_get_caret_position(&caret);
if (error) {
LOG("xwimp_get_caret_position: 0x%x: %s", error->errnum, error->errmess);
warn_user("WimpError", error->errmess);
return false;
}
ro_gui_dialog_add_persistent(current_menu_window,
pointer->w);
ro_gui_menu_destroy();
error = xwimp_open_window(PTR_WIMP_OPEN(&open));
if (error) {
LOG("xwimp_open_window: 0x%x: %s", error->errnum, error->errmess);
warn_user("WimpError", error->errmess);
return false;
}
if (caret.w == pointer->w) {
error = xwimp_set_caret_position(caret.w,
caret.i,
caret.pos.x, caret.pos.y,
-1, caret.index);
if (error) {
LOG("xwimp_set_caret_position: 0x%x: %s", error->errnum, error->errmess);
warn_user("WimpError", error->errmess);
}
}
}
/* display the menu */
prepared = true;
if (window->menu_prepare != NULL)
prepared = window->menu_prepare(pointer->w, pointer->i,
event->data.menu_gright.menu, pointer);
if (prepared) {
ro_gui_wimp_event_prepare_gright_menu(pointer->w, event);
ro_gui_popup_menu(event->data.menu_gright.menu, pointer->w, pointer->i);
}
break;
case EVENT_CHECKBOX:
break;
case EVENT_RADIO:
for (search = window->first; search; search = search->next)
if ((search->type == EVENT_RADIO) &&
(search->data.radio_group ==
event->data.radio_group))
ro_gui_set_icon_selected_state(pointer->w,
search->i, (search == event));
break;
case EVENT_BUTTON:
if (event->data.callback)
event->data.callback(pointer);
break;
case EVENT_CANCEL:
if (pointer->buttons & wimp_CLICK_SELECT) {
ro_gui_dialog_close(pointer->w);
ro_gui_wimp_event_close_window(pointer->w);
ro_gui_menu_destroy();
} else {
ro_gui_wimp_event_restore(pointer->w);
}
break;
case EVENT_OK:
ro_gui_wimp_event_ok_click(window, pointer->buttons);
break;
}
return true;
}
/**
* Prepare a menu ready for use
*
* /param w the window owning the menu
* /param event the icon event owning the menu
*/
void ro_gui_wimp_event_prepare_gright_menu(wimp_w w, struct icon_event *event)
{
int i;
const char *text;
unsigned int button_type;
wimp_icon_state ic;
wimp_menu *menu;
os_error *error;
/* if the linked icon is not writable then we set the ticked state
* of the menu item that matches the contents */
ic.w = w;
ic.i = event->data.menu_gright.field;
error = xwimp_get_icon_state(&ic);
if (error) {
LOG("xwimp_get_icon_state: 0x%x: %s", error->errnum, error->errmess);
warn_user("WimpError", error->errmess);
return;
}
button_type = (ic.icon.flags & wimp_ICON_BUTTON_TYPE)
>> wimp_ICON_BUTTON_TYPE_SHIFT;
if ((button_type == wimp_BUTTON_WRITABLE) ||
(button_type == wimp_BUTTON_WRITE_CLICK_DRAG))
return;
text = ro_gui_get_icon_string(w, event->data.menu_gright.field);
menu = event->data.menu_gright.menu;
i = 0;
do {
if (!strcmp(menu->entries[i].data.indirected_text.text, text))
menu->entries[i].menu_flags |= wimp_MENU_TICKED;
else
menu->entries[i].menu_flags &= ~wimp_MENU_TICKED;
} while (!(menu->entries[i++].menu_flags & wimp_MENU_LAST));
}
/**
* Perform the necessary actions following a click on the OK button.
*
* /param window the window to perform the action on
* /param state the mouse button state
*/
void ro_gui_wimp_event_ok_click(struct event_window *window,
wimp_mouse_state state)
{
struct icon_event *search;
for (search = window->first; search; search = search->next)
if (search->type == EVENT_OK) {
if (ro_gui_get_icon_shaded_state(window->w, search->i))
return;
break;
}
ro_gui_wimp_event_validate(window->w);
if (window->ok_click)
if (!window->ok_click(window->w))
return;
if (state & wimp_CLICK_SELECT) {
ro_gui_dialog_close(window->w);
ro_gui_wimp_event_close_window(window->w);
ro_gui_menu_destroy();
} else {
ro_gui_wimp_event_memorise(window->w);
}
}
/**
* Handle any registered keypresses, and the standard RISC OS ones
*
* \param key the key state
* \return true if keypress handled, false otherwise
*/
bool ro_gui_wimp_event_keypress(wimp_key *key)
{
static const int *ucstable = NULL;
static int alphabet = 0;
static uint32_t wc = 0; /* buffer for UTF8 alphabet */
static int shift = 0;
struct event_window *window;
struct icon_event *event;
wimp_pointer pointer;
wimp_key k;
uint32_t c = (uint32_t) key->c;
int t_alphabet;
os_error *error;
window = ro_gui_wimp_event_find_window(key->w);
if (!window)
return false;
/* copy key state so we can corrupt it safely */
memcpy(&k, key, sizeof(wimp_key));
/* In order to make sensible use of the 0x80->0xFF ranges specified
* in the RISC OS 8bit alphabets, we must do the following:
*
* + Read the currently selected alphabet
* + Acquire a pointer to the UCS conversion table for this alphabet:
* + Try using ServiceInternational 8 to get the table
* + If that fails, use our internal table (see ucstables.c)
* + If the alphabet is not UTF8 and the conversion table exists:
* + Lookup UCS code in the conversion table
* + If code is -1 (i.e. undefined):
* + Use codepoint 0xFFFD instead
* + If the alphabet is UTF8, we must buffer input, thus:
* + If the keycode is < 0x80:
* + Handle it directly
* + If the keycode is a UTF8 sequence start:
* + Initialise the buffer appropriately
* + Otherwise:
* + OR in relevant bits from keycode to buffer
* + If we've received an entire UTF8 character:
* + Handle UCS code
* + Otherwise:
* + Simply handle the keycode directly, as there's no easy way
* of performing the mapping from keycode -> UCS4 codepoint.
*/
error = xosbyte1(osbyte_ALPHABET_NUMBER, 127, 0, &t_alphabet);
if (error) {
LOG("failed reading alphabet: 0x%x: %s", error->errnum, error->errmess);
/* prevent any corruption of ucstable */
t_alphabet = alphabet;
}
if (t_alphabet != alphabet) {
void *ostable;
osbool unclaimed;
/* Alphabet has changed, so read UCS table location */
alphabet = t_alphabet;
error = xserviceinternational_get_ucs_conversion_table(
alphabet, &unclaimed, &ostable);
if (error != NULL) {
LOG("failed reading UCS conversion table: 0x%x: %s", error->errnum, error->errmess);
/* Try using our own table instead */
ucstable = ucstable_from_alphabet(alphabet);
} else if (unclaimed) {
/* Service wasn't claimed so use our own ucstable */
ucstable = ucstable_from_alphabet(alphabet);
} else {
/* Use the table provided by the OS */
ucstable = ostable;
}
}
if (c < 256) {
if (alphabet != 111 /* UTF8 */ && ucstable != NULL) {
/* defined in this alphabet? */
if (ucstable[c] == -1)
return true;
/* read UCS4 value out of table */
k.c = ucstable[c];
}
else if (alphabet == 111 /* UTF8 */) {
if ((c & 0x80) == 0x00 || (c & 0xC0) == 0xC0) {
/* UTF8 start sequence */
if ((c & 0xE0) == 0xC0) {
wc = ((c & 0x1F) << 6);
shift = 1;
return true;
}
else if ((c & 0xF0) == 0xE0) {
wc = ((c & 0x0F) << 12);
shift = 2;
return true;
}
else if ((c & 0xF8) == 0xF0) {
wc = ((c & 0x07) << 18);
shift = 3;
return true;
}
/* These next two have been removed
* from RFC3629, but there's no
* guarantee that RISC OS won't
* generate a UCS4 value outside the
* UTF16 plane, so we handle them
* anyway. */
else if ((c & 0xFC) == 0xF8) {
wc = ((c & 0x03) << 24);
shift = 4;
}
else if ((c & 0xFE) == 0xFC) {
wc = ((c & 0x01) << 30);
shift = 5;
}
else if (c >= 0x80) {
/* If this ever happens,
* RISC OS' UTF8 keyboard
* drivers are broken */
LOG("unexpected UTF8 start"" byte %x (ignoring)", c);
return true;
}
/* Anything else is ASCII, so just
* handle it directly. */
}
else {
if ((c & 0xC0) != 0x80) {
/* If this ever happens,
* RISC OS' UTF8 keyboard
* drivers are broken */
LOG("unexpected keycode: ""%x (ignoring)", c);
return true;
}
/* Continuation of UTF8 character */
wc |= ((c & 0x3F) << (6 * --shift));
if (shift > 0)
/* partial character */
return true;
else
/* got entire character, so
* fetch from buffer and
* handle it */
k.c = wc;
}
}
} else {
k.c |= IS_WIMP_KEY;
}
/* registered routines take priority */
if (window->keypress)
if (window->keypress(&k))
return true;
switch (key->c) {
/* Escape performs the CANCEL action (simulated click) */
case wimp_KEY_ESCAPE:
for (event = window->first; event; event = event->next) {
switch (event->type) {
case EVENT_CANCEL:
pointer.w = key->w;
pointer.i = event->i;
pointer.buttons = wimp_CLICK_SELECT;
ro_gui_wimp_event_mouse_click(&pointer);
return true;
default:
break;
}
}
return false;
/* CTRL+F2 closes a window with a close icon */
case wimp_KEY_CONTROL + wimp_KEY_F2:
if (!ro_gui_wimp_check_window_furniture(key->w,
wimp_WINDOW_CLOSE_ICON))
return false;
ro_gui_dialog_close(key->w);
ro_gui_wimp_event_close_window(key->w);
ro_gui_menu_destroy();
return true;
/* Return performs the OK action */
case wimp_KEY_RETURN:
if (!window->ok_click)
return false;
/* todo: check we aren't greyed out */
ro_gui_wimp_event_ok_click(window, wimp_CLICK_SELECT);
return true;
}
return false;
}
/**
* Handle any open window requests
*
* \param open the window open request
*/
bool ro_gui_wimp_event_open_window(wimp_open *open)
{
struct event_window *window;
window = ro_gui_wimp_event_find_window(open->w);
if ((window) && (window->open_window)) {
window->open_window(open);
return true;
}
return false;
}
/**
* Service any close window handlers
*
* \param w the window being closed
*/
bool ro_gui_wimp_event_close_window(wimp_w w)
{
struct event_window *window;
LOG("Close event received for window 0x%x", (unsigned int)w);
if (w == ro_gui_wimp_event_submenu)
ro_gui_wimp_event_submenu = 0;
window = ro_gui_wimp_event_find_window(w);
if ((window) && (window->close_window)) {
window->close_window(w);
return true;
}
return false;
}
/**
* Handle any redraw window requests
*
* \param redraw the window redraw request
*/
bool ro_gui_wimp_event_redraw_window(wimp_draw *redraw)
{
struct event_window *window;
window = ro_gui_wimp_event_find_window(redraw->w);
if ((window) && (window->redraw_window)) {
window->redraw_window(redraw);
return true;
}
return false;
}
/**
* Handle any scroll window requests
*
* \param scroll the window scroll request
*/
bool ro_gui_wimp_event_scroll_window(wimp_scroll *scroll)
{
struct event_window *window;
window = ro_gui_wimp_event_find_window(scroll->w);
if ((window) && (window->scroll_window)) {
window->scroll_window(scroll);
return true;
}
return false;
}
/**
* Handle any pointer entering window requests
*
* \param entering the pointer entering window request
*/
bool ro_gui_wimp_event_pointer_entering_window(wimp_entering *entering)
{
struct event_window *window;
window = ro_gui_wimp_event_find_window(entering->w);
if ((window) && (window->entering_window)) {
window->entering_window(entering);
return true;
}
return false;
}
/**
* Process a Menu click in a window, by checking for a registered window
* menu and opening it if one is found.
*
* \param pointer The pointer block from the mouse click event.
* \return true if the click was actioned; else false.
*/
bool ro_gui_wimp_event_process_window_menu_click(wimp_pointer *pointer)
{
struct event_window *window;
window = ro_gui_wimp_event_find_window(pointer->w);
if ((window) && (window->window_menu)
&& (pointer->buttons == wimp_CLICK_MENU)) {
int xpos, ypos;
if (window->menu_prepare)
if (!window->menu_prepare(window->w, wimp_ICON_WINDOW,
window->window_menu, pointer))
return false;
if (window->window_menu_iconbar) {
int entry = 0;
int line_height = window->window_menu->height +
window->window_menu->gap;
int gap_height = 24; /* The fixed dotted line height */
xpos = pointer->pos.x;
ypos = 96;
do {
ypos += line_height;
if ((window->window_menu->
entries[entry].menu_flags &
wimp_MENU_SEPARATE) != 0)
ypos += gap_height;
} while ((window->window_menu->
entries[entry++].menu_flags &
wimp_MENU_LAST) == 0);
} else {
xpos = pointer->pos.x;
ypos = pointer->pos.y;
}
ro_gui_menu_create(window->window_menu, xpos, ypos, window->w);
return true;
}
return false;
}
/**
* Trigger a window's Prepare Menu event.
*
* \param w The window to use.
* \param i The icon to use.
* \param *menu The menu handle to use.
* \return true if the affected menu was prepared OK; else
* false.
*/
bool ro_gui_wimp_event_prepare_menu(wimp_w w, wimp_i i, wimp_menu *menu)
{
struct event_window *window;
window = ro_gui_wimp_event_find_window(w);
if (window == NULL)
return false;
if (window->menu_prepare)
return window->menu_prepare(w, i, menu, NULL);
/* The menu is always OK if there's no event handler. */
return true;
}
/**
* Register a window menu to be (semi-)automatically handled.
*
* \param w The window to attach the menu to.
* \param m The menu to be attached.
* \param menu_auto true if the menu should be opened autimatically on
* Menu clicks with no task intervention; false to pass
* clicks to the window's Mouse Event handler and leave
* that to pass the menu click back to us for handling
* and menu opening.
* \param position_ibar true if the menu should open in an iconbar
* position; false to open at the pointer.
* \return true if the menu was registed ok; else false.
*/
bool ro_gui_wimp_event_register_menu(wimp_w w, wimp_menu *m,
bool menu_auto, bool position_ibar)
{
struct event_window *window;
window = ro_gui_wimp_event_get_window(w);
if (!window)
return false;
window->window_menu = m;
window->window_menu_auto = menu_auto;
window->window_menu_iconbar = position_ibar;
return true;
}
/**
* Register a numeric field to be automatically handled
*/
bool ro_gui_wimp_event_register_numeric_field(wimp_w w, wimp_i i,
wimp_i up, wimp_i down,
int min, int max, int stepping, int decimal_places)
{
struct icon_event *event;
event = ro_gui_wimp_event_get_event(w, i, EVENT_NUMERIC_FIELD);
if (!event)
return false;
event->data.numeric_field.min = min;
event->data.numeric_field.max = max;
event->data.numeric_field.stepping = stepping;
event->data.numeric_field.decimal_places = decimal_places;
event = ro_gui_wimp_event_get_event(w, up, EVENT_UP_ARROW);
if (!event)
return false;
event->data.linked_icon = i;
event = ro_gui_wimp_event_get_event(w, down, EVENT_DOWN_ARROW);
if (!event)
return false;
event->data.linked_icon = i;
return true;
}
/**
* Register a text field to be automatically handled
*/
bool ro_gui_wimp_event_register_text_field(wimp_w w, wimp_i i) {
struct icon_event *event;
event = ro_gui_wimp_event_get_event(w, i, EVENT_TEXT_FIELD);
if (!event)
return false;
return true;
}
/**
* Register an icon menu to be automatically handled
*/
bool ro_gui_wimp_event_register_menu_gright(wimp_w w, wimp_i i,
wimp_i gright, wimp_menu *menu)
{
struct icon_event *event;
event = ro_gui_wimp_event_get_event(w, gright, EVENT_MENU_GRIGHT);
if (!event)
return false;
event->data.menu_gright.field = i;
event->data.menu_gright.menu = menu;
return ro_gui_wimp_event_register_text_field(w, i);
}
/**
* Register a checkbox to be automatically handled
*/
bool ro_gui_wimp_event_register_checkbox(wimp_w w, wimp_i i)
{
struct icon_event *event;
event = ro_gui_wimp_event_get_event(w, i, EVENT_CHECKBOX);
if (!event)
return false;
return true;
}
/**
* Register a group of radio icons to be automatically handled
*/
bool ro_gui_wimp_event_register_radio(wimp_w w, wimp_i *i)
{
struct event_window *window;
window = ro_gui_wimp_event_get_window(w);
if (!window)
return false;
window->max_radio_group++;
while (*i != -1) {
struct icon_event *event = ro_gui_wimp_event_get_event(w, *i,
EVENT_RADIO);
if (!event)
return false;
event->data.radio_group = window->max_radio_group;
i++;
}
return true;
}
/**
* Register a function to be called when a particular button is pressed.
*/
bool ro_gui_wimp_event_register_button(wimp_w w, wimp_i i,
void (*callback)(wimp_pointer *pointer))
{
struct icon_event *event;
event = ro_gui_wimp_event_get_event(w, i, EVENT_BUTTON);
if (!event)
return false;
event->data.callback = callback;
return true;
}
/**
* Register a function to be called for the Cancel action on a window.
*/
bool ro_gui_wimp_event_register_cancel(wimp_w w, wimp_i i)
{
struct icon_event *event;
event = ro_gui_wimp_event_get_event(w, i, EVENT_CANCEL);
if (!event)
return false;
return true;
}
/**
* Register a function to be called for the OK action on a window.
*/
bool ro_gui_wimp_event_register_ok(wimp_w w, wimp_i i,
bool (*callback)(wimp_w w))
{
struct event_window *window;
struct icon_event *event;
window = ro_gui_wimp_event_get_window(w);
if (!window)
return false;
window->ok_click = callback;
event = ro_gui_wimp_event_get_event(w, i, EVENT_OK);
if (!event)
return false;
return true;
}
/**
* Register a function to be called for all mouse-clicks to icons
* in a window that don't have registered actions.
*/
bool ro_gui_wimp_event_register_mouse_click(wimp_w w,
bool (*callback)(wimp_pointer *pointer))
{
struct event_window *window;
window = ro_gui_wimp_event_get_window(w);
if (!window)
return false;
window->mouse_click = callback;
return true;
}
/**
* Register a function to be called for all keypresses within a
* particular window.
*
* Important: the character code passed to the callback in key->c
* is UTF-32 (i.e. in the range [0, &10ffff]). WIMP keys (e.g. F1)
* will have bit 31 set.
*
*/
bool ro_gui_wimp_event_register_keypress(wimp_w w,
bool (*callback)(wimp_key *key))
{
struct event_window *window;
window = ro_gui_wimp_event_get_window(w);
if (!window)
return false;
window->keypress = callback;
return true;
}
/**
* Register a function to be called for all window opening requests.
*/
bool ro_gui_wimp_event_register_open_window(wimp_w w,
void (*callback)(wimp_open *open))
{
struct event_window *window;
window = ro_gui_wimp_event_get_window(w);
if (!window)
return false;
window->open_window = callback;
return true;
}
/**
* Register a function to be called after the window has been closed.
*/
bool ro_gui_wimp_event_register_close_window(wimp_w w,
void (*callback)(wimp_w w))
{
struct event_window *window;
window = ro_gui_wimp_event_get_window(w);
if (!window)
return false;
window->close_window = callback;
return true;
}
/**
* Register a function to be called for all window redraw operations.
*/
bool ro_gui_wimp_event_register_redraw_window(wimp_w w,
void (*callback)(wimp_draw *redraw))
{
struct event_window *window;
window = ro_gui_wimp_event_get_window(w);
if (!window)
return false;
window->redraw_window = callback;
return true;
}
/**
* Register a function to be called for all window scroll requests.
*/
bool ro_gui_wimp_event_register_scroll_window(wimp_w w,
void (*callback)(wimp_scroll *scroll))
{
struct event_window *window;
window = ro_gui_wimp_event_get_window(w);
if (!window)
return false;
window->scroll_window = callback;
return true;
}
/**
* Register a function to be called for all pointer entering window requests.
*/
bool ro_gui_wimp_event_register_pointer_entering_window(wimp_w w,
void (*callback)(wimp_entering *entering))
{
struct event_window *window;
window = ro_gui_wimp_event_get_window(w);
if (!window)
return false;
window->entering_window = callback;
return true;
}
/**
* Register a function to be called before a menu is (re-)opened.
*
* \param *w The window for which events should be returned.
* \param *callback A function to be called beofre the menu is
* (re-)opened.
* \return true if the menu was registed ok; else false.
*/
bool ro_gui_wimp_event_register_menu_prepare(wimp_w w,
bool (*callback)(wimp_w w, wimp_i i, wimp_menu *m,
wimp_pointer *p))
{
struct event_window *window;
window = ro_gui_wimp_event_get_window(w);
if (!window)
return false;
window->menu_prepare = callback;
return true;
}
/**
* Register a function to be called following a menu selection.
*
* \param *w The window for which events should be returned.
* \param *callback A function to be called when a selection is
* made.
* \return true if the menu was registed ok; else false.
*/
bool ro_gui_wimp_event_register_menu_selection(wimp_w w,
bool (*callback)(wimp_w w, wimp_i i, wimp_menu *m,
wimp_selection *s, menu_action a))
{
struct event_window *window;
window = ro_gui_wimp_event_get_window(w);
if (!window)
return false;
window->menu_selection = callback;
return true;
}
/**
* Register a function to be called when a sub-menu warning is received.
*
* \param *w The window for which events should be returned.
* \param *callback A function to be called whenever a submenu
* warning is received for the menu.
* \return true if the menu was registed ok; else false.
*/
bool ro_gui_wimp_event_register_menu_warning(wimp_w w,
void (*callback)(wimp_w w, wimp_i i, wimp_menu *m,
wimp_selection *s, menu_action a))
{
struct event_window *window;
window = ro_gui_wimp_event_get_window(w);
if (!window)
return false;
window->menu_warning = callback;
return true;
}
/**
* Register a function to be called before a menu is finally closed.
*
* \param *w The window for which events should be returned.
* \param *callback A function to be called when the menu is closed.
* \return true if the menu was registed ok; else false.
*/
bool ro_gui_wimp_event_register_menu_close(wimp_w w,
void (*callback)(wimp_w w, wimp_i i, wimp_menu *m))
{
struct event_window *window;
window = ro_gui_wimp_event_get_window(w);
if (!window)
return false;
window->menu_close = callback;
return true;
}
/**
* Finds the event data associated with a given window handle, or creates a
* new one.
*
* \param w the window to find data for
*/
struct event_window *ro_gui_wimp_event_get_window(wimp_w w)
{
struct event_window *window;
int h;
assert((int)w != 0);
window = ro_gui_wimp_event_find_window(w);
if (window)
return window;
LOG("Creating structure for window 0x%x", (unsigned int)w);
window = calloc(1, sizeof(struct event_window));
if (!window)
return NULL;
h = WIN_HASH(w);
window->w = w;
window->next = ro_gui_wimp_event_windows[h];
ro_gui_wimp_event_windows[h] = window;
return window;
}
/**
* Removes the event data associated with a given handle from the hash tables,
* but does not delete it.
*
* \param w the window to be removed
* \return pointer to the event data or NULL if not found
*/
struct event_window *ro_gui_wimp_event_remove_window(wimp_w w)
{
struct event_window **prev;
int h = WIN_HASH(w);
/* search hash chain for the window */
prev = &ro_gui_wimp_event_windows[h];
while (*prev) {
struct event_window *window = *prev;
if (window->w == w) {
/* remove from chain */
*prev = window->next;
return window;
}
prev = &window->next;
}
/* not found */
return NULL;
}
/**
* Find the event data associated with a given window handle
*
* \param w the window to find data for
*/
struct event_window *ro_gui_wimp_event_find_window(wimp_w w)
{
struct event_window *window;
int h = WIN_HASH(w);
/* search hash chain for window */
for (window = ro_gui_wimp_event_windows[h]; window; window = window->next) {
if (window->w == w)
return window;
}
return NULL;
}
struct icon_event *ro_gui_wimp_event_get_event(wimp_w w, wimp_i i,
event_type type)
{
struct event_window *window;
struct icon_event *event;
window = ro_gui_wimp_event_get_window(w);
if (!window)
return NULL;
for (event = window->first; event; event = event->next) {
if (event->i == i) {
event->type = type;
return event;
}
}
event = calloc(1, sizeof(struct icon_event));
if (!event)
return NULL;
event->i = i;
event->type = type;
event->next = window->first;
window->first = event;
return event;
}
/* Handle sumbenu warnings. This is called from ro_gui_menu_warning(), and
* returns to that function to have the submenu opened correctly.
*
* \param w the window to owning the menu
* \param i the icon owning the menu
* \param menu the menu that has been selected
* \param selection the selection information
* \param action the menu action info from menus.c
* \return true if the event was handled, false otherwise
*/
bool ro_gui_wimp_event_submenu_warning(wimp_w w, wimp_i i, wimp_menu *menu,
wimp_selection *selection, menu_action action)
{
struct event_window *window;
struct icon_event *event;
ro_gui_wimp_event_register_submenu(0);
/* Process the event for any window menus. Find the window data, then
* try and match to an icon event. If we can, then there isn't anything
* to do.
*/
window = ro_gui_wimp_event_find_window(w);
if (!window)
return false;
for (event = window->first; event; event = event->next)
if ((event->type == EVENT_MENU_GRIGHT) && (event->i == i))
break;
if (event) {
if (window->menu_close != NULL &&
event->type == EVENT_MENU_GRIGHT &&
event->data.menu_gright.menu == menu) {
window->menu_close(w, i, menu);
return true;
}
return false;
}
/* If the warning is for a window menu, then pass the event on to it. */
if ((window->window_menu) && (window->window_menu == menu)) {
if (window->menu_warning) {
window->menu_warning(w, wimp_ICON_WINDOW, menu,
selection, action);
return true;
}
}
return false;
}
/**
* Handle menus being closed. This is called from the menus modules, in
* every scenario when one of our own menus is open.
*
* \param w the window to owning the menu
* \param i the icon owning the menu
* \param menu the menu that has been selected
*/
void ro_gui_wimp_event_menus_closed(wimp_w w, wimp_i i, wimp_menu *menu)
{
struct event_window *window;
struct icon_event *event;
ro_gui_wimp_event_register_submenu(0);
/* Process the event for any window menus. Find the window data, then
* try and match to an icon event. If we can, then GRight menus are
* sent the event; otherwise, we do nothing.
*/
window = ro_gui_wimp_event_find_window(w);
if (!window)
return;
for (event = window->first; event; event = event->next)
if ((event->type == EVENT_MENU_GRIGHT) && (event->i == i))
break;
if (event) {
if (window->menu_close != NULL &&
event->type == EVENT_MENU_GRIGHT &&
event->data.menu_gright.menu == menu)
window->menu_close(w, i, menu);
return;
}
/* If the close is for a window menu, then pass the event on to it. */
if ((window->window_menu) && (window->window_menu == menu) &&
(window->menu_close))
window->menu_close(w, wimp_ICON_WINDOW, menu);
}
/**
* Register a submenu as being opened
*/
void ro_gui_wimp_event_register_submenu(wimp_w w)
{
if (ro_gui_wimp_event_submenu)
ro_gui_wimp_event_close_window(ro_gui_wimp_event_submenu);
ro_gui_wimp_event_submenu = w;
}