gSetHandler
Definition: |
include xpGUI.e
gSetHandler(gdx id, sequence name, rtn handler=NULL) |
Description: |
Associates one or more handlers for an event on one or more controls.
id: identifier of the interface element(s), can be a single integer or a (flat, NULL-free) sequence of them. name: a string identifying the handler, eg "KEY", or an even-length sequence of string,rtn pairs. handler: a (bare) function or procedure, or NULL. Must be NULL (/omitted) if name is pairs and not a string. A NULL rtn when name is a string, or any NULL even element of name, simply removes any association. Refer to each specific interface element for valid/meaningful names and their required routine signatures. |
pwa/p2js: | Supported. |
Notes: |
All elements with a parameter of type rtn permit/encourage an implit invocation of gSetHandler().
You can also invoke this as the simple alias gSetHandlers(), with the implication but not enforcement that id or name or both are non-unitary, or just stick with the non-s name for everything. Note however that is in stark contrast to (say) gSetAttribute() and gSetAttributes(), which are in fact quite different, although they too can both set something on one or more elements, likewise unconnected to a trailing s, though only the latter can set somethings in a single call. Handlers are used by the application to receive and process notifications from the system that the user or the system itself has interacted with the user interface of the application. In particular that means everything (directly and indirectly) from the mouse and keyboard, as well as things like timers and other alerts, such as "going idle" (ie/aka "msg queue is empty"). All handlers receive at least the element for which the event was triggerd as a parameter, almost always in the form of a gdx, though it may be a tad more opaque for some, such as BRANCHOPEN. Most handlers (except those defined as procedures) must return one of the following values:
NB: returning XPG_CLOSE does not automatically invoke CLOSE_CB handlers. Note: Avoid using XPG_IGNORE (and XPG_CLOSE) unless it is specifically documented for that handler. For example, it is and always was the documented behaviour for an IDLE_ACTION handler to be removed when it returns XPG_IGNORE. However, the act of IUP_IGNORE removing the callback on an IupButton in pGUI (which was in fact undocumented until I myself piped up) has not been replicated in xpGUI, you may be relieved to hear. An important detail when using handlers is that they are only triggered when the user physically performs an action on an element. In particular, a handler is not invoked when the programmer sets a value via gSetAttribute(). For instance: should the code change the selected item on a list, no handler is called and it is up to the programmer to ensure any additional appropriate processing occurs, by some more direct means. rtn handler = gGetHandler(gdx id, string name, integer dflt=0) can be used to retrieve a previously set handler, primarily intended for use inside xpGUI.e itself, and to be honest I cannot offhand think of any good reason user code should ever want to use it, but it costs absolutely nothing to be available anyway. The default is currently only used for the IDROP handler. |
Inheritance: |
rtn handler = gGetInheritedHandler(gdx id,
string name) is used on those handlers specifically marked/linked here, currently only KEY. This routine is intended for internal use only, and currently local/unavailable externally. Typically events propagate up the dialog hierarchy, so, for instance, the bEsc can be handled at the dialog level, even if that key (first) went to a child element. In some cases the return value from a handler depends on the event occuring on the right control, in which case not only must the handler be attached just so, but you may also need to force the issue with an explicit gSetFocus(). However some controls don’t even allow that (esp under GTK), and don’t depend on system-level default behaviour. One case in particular is a single gCanvas, or the canvas-derived controls gGraph, gList, and gTable, or a single gTreeView or gTabs, that occupies the whole of the gDialog, and without this a key handler on the canvas would only work after it had been clicked on, with there then being nowhere you could click or any other (simple) way that would re-disable it. The above routine implements a bi-directional handler inheritance model, for instance suppose you have gDialog(gHbox({gLabel(),gText()})) (and there isn’t an appropriate handler on the element that actually received the event). If the dialog has a key handler, but none such on any children, all key events on them are forwarded to that. Note however the return value from the key handler only has meaning when it is attached to the correct control. If only the hbox has a handler, all other three elements would pass along any key events they receive to that. The text field could inherit from the hbox or dialog (potentially with some of the issues just mentioned), but never under any circumstances from the sibling label (not that setting a key handler on that makes much sense). If there is one and only one such handler on the children of the dialog then it too will forward key events to that. However, a dialog with more than one child having a handler (and it not) simply does nothing, and future releases reserve the right to restrict such downward inheritance/propagation to those specific cases it actually benefits. In the example just given, a handler on the text field might not get triggered precisely because the return values might not be honoured correctly, effectively forcing an appropriate gSetFocus(), but as it stands that part has not yet been implemented (and should not upset any correctly written code if it ever is). Lastly, there is no problem putting the same handler all over the shop, as long as XPG_IGNORE is used approriately, to prevent the same event being processed multiple times. |
Examples: |
-- set one handler on one interface element: gSetHandler(canvas,"REDRAW",redraw) -- set the same handler on two elements: gSetHandler({canvas1,canvas2},"REDRAW",redraw) -- set two handlers on both elements: gSetHandler({canvas1,canvas2},{"REDRAW",redraw,"CLICK",click}) |
Common: |
The following handlers are common to several controls/interface elements: |
CLICK |
Event generated when any mouse button is pressed or released.
function/procedure click(gdx id, sequence status[, integer x, y]) id: identifies the interface element that activated the event. status: a sequence of two characters and three bools that qualify the precise event that just occurred: The first character is 'L', 'M', 'R' for Left, Middle, or Right button, or 'X', 'Y' for those under your thumb on the mouse. The second character is 'S', 'D', or 'R' for Single-click, Double-click (not XY), or Release events, see note. The third element is true if the ctrl key is depressed, false otherwise. The fourth element is true if the shift key is depressed, false otherwise. The fifth element is true if the alt key is depressed, false otherwise. You may prefer to code integer {button,pressed,ctrl,shift,alt} = status over using [1..5]s.x,y: (optional) pixel co-ordinates of the mouse cursor when the click/release occurred. The ACTION event is usually much more suitable for a gButton. The VALUE_CHANGED event is usually much more suitable for a gCheckbox. The HANDLER event is always much more suitable for a gMenu. In many/most other cases the system would naturally ignore a click and perform no further action anyway, hence if the handler is declared as a procedure it is treated the same as a function which always returns XPG_IGNORE. Returning XPG_IGNORE prevents the event from being propagated, all other values do not, though it is probably best to use XPG_CONTINUE to implicitly imply that propagation. Note that for a double click (and for clarity showing only the second character of 5/4 consecutive status codes), GTK sends "S", "R", "S", "D", "R", whereas WinAPI slightly more sensibly just sends: "S", "R", "D", "R" (ie no second "S"). [DEV I have yet to test what JavaScript does... Sending a fake second "S" under WinAPI seems silly, suppressing the second "S" under GTK would necessitate fetching the system double-click speed setting, and I simply don’t fancy doing that either.] Affects: All, except menus. |
? CLOSE_CB |
Called just before a dialog is closed when the user clicks the close button of the title bar or an equivalent action. NB: other handlers returning XPG_CLOSE do not invoke any CLOSE_CB handler, instead any required final processing, such as saving files or invoking IupConfigDialogClosed(), must be performed manually/explicitly, that is, just before returning XPG_CLOSE. Penning (say) my_shutdown() and invoking it from several places, including close_cb, is probably a wise move. Note that you are not allowed to (re-)use the name close() as a handler since that is already reserved for the builtin. function close_cb(Ihandle ih) ih: identifies the element that activated the event. Returns: an XPG_IGNORE prevents the dialog from being closed. Should you destroy the dialog in this handler, you must return XPG_IGNORE. XPG_CLOSE, XPG_CONTINUE, and XPG_DEFAULT will in the specific case of this handler all be treated identically. Affects: gDialog |
? IDLE_ACTION |
Predefined, generated when there are no events or messages to be processed. Often used to perform background operations.
function idle_action() Returns: if XPG_CLOSE is returned the current loop will be closed and the handler will be removed. If XPG_IGNORE is returned the handler is removed and normal processing continues. Notes The idle handler is called whenever there are no messages left to be processed. This may occur more frequently than expected, for example if you move the mouse over the application the idle handler will be called many times because the mouse move message is processed so fast that the Idle will be called before another mouse move message occurs. [No mouse(/user) is likely to generate 100 messages/s, which on a GHz box means ten million clocks between each in which to perform some idle processing.] So this handler changes the message loop to a more CPU consuming one. It is important it be set NULL when not used, otherwise the application consumes CPU even if the handler is doing nothing. ?? It can only be set using IupSetGlobalFunction("IDLE_ACTION", idle_action). Long Time Operations If you create a loop or an operation that takes a long time to complete inside a handler of your application then the user interface message loop processing is interrupted until the handler returns, so the user can not click on any control of the application. But there are ways to handle that:
For example, register a timer for a small time like 500ms, and reset the timer in all the mouse and keyboard handlers of the IupCanvas. If the timer is trigged then you are in idle state. If the IupCanvas loses its focus then stop the timer. pwa/p2js: Uses window.requestIdleCallback() which is marked on MDN as experimental, but that has not changed since 2015. Note that according to caniuse.com for this to work in Safari you (and/or you users) must enable requestIdleCallback in Experimental (Webkit) Features, and the same may be true for Chrome on macOS (not that desktop/Phix works on macOS, yet). Also note I am not quite sure what "current loop will be closed" above means for XPG_CLOSE returns, so XPG_CLOSE and XPG_IGNORE do the same thing, which is in fact don’t re-apply requestIdleCallback. If at all possible you should strive to return within 50ms/break up idle processing into chucks of work shorter than that, to avoid (otherwise fairly harmless) JavaScript violation messages being logged to the (hidden) console. A future version may incorporate a means to get (deadline.timeRemaining() > 0 || deadline.didTimeout) in pwa/p2js, with a matching
mechanism to get true on desktop/Phix, should that ever be desired/requested.
See Also: IupSetGlobalFunction, gTimer. |
KEY |
(inherited) Event generated when a keyboard event occurs.
function key_handler(gdx id, integer c, [[bool] ctrl[, shift[, alt ]]]) id: the interface element that had focus when the key event occurred, or maybe its parent. c: the typed key. Some possible (special) values are listed below. ctrl, shift, alt: (optional) any key modifiers that were also pressed. Note that, unlike pGUI, a key such as <Ctrl C> is passed to key_handler as 'c' or 'C' with ctrl=true, depending on the state of caps lock. While the latter is honoured, there is no forwarding of its state with every keypress, and obviously no guarantee of any correlation at all between shift and 'C' or 'c'. While pGUI/IUP defines literally several hundred key constants, xpGUI limits itself to a much more reasonable subset. You should, typically as and when the compiler complains, replace eg VK_a with 'a', and any specially prefixed keys such as VK_cC with (say) "ctrl and upper(c)='C'". Note that IUP allows a custom callback to be attached specifically to (eg) VK_cC, whereas xpGUI has just the one generic KEY handler. xpGUI defines the following key constants, in particular those that cannot (intuitively) be written as a normal character constant, like say 'X' can, plus, as you can see, a few/five that could be: VK_ESC, VK_F1, VK_F2, VK_F3, VK_F4, VK_F5, VK_F6, VK_F7, VK_F8, VK_F9, VK_F10, VK_F11, VK_F12, VK_SCROLL, VK_PAUSE, VK_NUMLOCK, VK_CAPSLOCK, VK_BS (or '\b'), VK_TAB (or '\t'), VK_CR (or '\r'), VK_LF (or '\n'), VK_SP (' '), VK_INS, VK_DEL, VK_HOME, VK_END, VK_PGUP, VK_PGDN, VK_UP, VK_RIGHT, VK_DOWN, VK_LEFT, VK_LCTRL, VK_RCTRL, VK_LSHIFT, VK_RSHIFT, VK_LALT, VK_RALT, VK_APPS, and VK_POUND (£, see below). Aside: instead of (re-)using say K_DEL from pGUI, the equivalent constant is VK_DEL (as per arwen), partly because I did not want to break pGUI help lookup too soon (which is neither a concern nor a reality with what little arwen docs there are in this file), but more importantly I certainly did not want to end up with K_C/K_D/K_E linking to pGUI but K_DEL linking to (this) xpGUI help section, and probably getting left in that mess pretty much forever. Note that (eg) VK_F1 is (the freshly made up, unsigned) #F1 (241) rather than the potentially confusing WinAPI value of 'p' (ie #70 or 112, or for that matter the GTK value of #FFBE which is 65470 or -66), and likewise most other keys. There is a relatively straightforward mapping table in xpGUI.e which should be fairly easy to tweak if ever needed, and some (hopefully) matching definitions/manual mapping in xpGUI.js. (obvs. just search for VK_F1) You can also use string res = gGetKeyName(integer c) to convert key codes into something more human-readable. An error occurs if c is defined as an atom: the original pGUI went to great lengths to enforce that and prevent a "time-bomb" routine signature error as I then dubbed it. Should such occur you would be advised to review and update the (legacy) code as necessary, at the same time as changing the c parameter to be an integer. In a similar fashion to paranormalised functions, only without any reshuffling of the parameters, you can assume that all of the following are potentially perfectly valid, with the fairly obvious point that the third parameter, if present, always contains the ctrl flag, and likewise the fouth contains shift and the fifth contains alt, in spite of whatever the parameters may actually be named, and even if you only wanted to check for the alt key, you would still be required to declare the full five parameters: key_handler(gdx id, integer c) key_handler(gdx id, integer c, ctrl) key_handler(gdx id, integer c, ctrl, shift) key_handler(gdx id, integer c, ctrl, shift, alt) key_handler(gdx id, integer c, bool ctrl) key_handler(gdx id, integer c, bool ctrl, shift) key_handler(gdx id, integer c, bool ctrl, shift, alt) When ctrl/shift/alt are absent, the backend avoids relaying characters and other keystrokes that it knows the handler cannot cope with or might mis-interpret. In the case (sic) of shift, you’ll still get '!' (#21) all the way to '~' (#7E) but not space or below, or VK_DEL and above (including all the function keys), that is when shift is down but there’s no shift parameter on your key_handler() routine (or whatever it is actually called), and you would of course still get all un-shifted keys. Should you want to treat say <F1>, <Ctrl F1>, <Shift F1>, <Alt F1>, and the other four possible combinations of multiple modifiers the same, you would have to declare all five parameters but then ignore the last three. Caps lock and Num lock do not of course alter which physical keys are relayed, just some of their values. Small differences in handling will almost certainly exist and quite likely remain forever, for instance in my testing of GTK on Windows, it did not generate the VK_CAPSLOCK key, and GTK2 did not generate the VK_PAUSE key. GTK also appears unable or unwilling to permit the capture of Alt-Space or Alt-F4. On the other hand, Windows does some things with the Alt key (alone) and F10 that sort of put it into a "special mode" until you hit them a second time, all of which I don’t completely grok. And of course a browser is going to do some browser-like things with some keystrokes and that may differ between browsers. Oh, and of course there is a "Windows" key one away from the spacebar, but that’s for the OS, not your app, OK? (ie/aka ymmv) However otherwise, more normal things are generally speaking pretty consistent, and avoiding the odd dicey one shouldn’t really be a problem when there are perhaps 252 other key combinations that you could certainly always use, as in 4 combinations of ctrl/shift times ~63 suitable keys, at least, and nearly but not quite as many again that involve the alt key. Note that VK_POUND is (currently) the pseudo-UTF-8 value #C2A3 representing the UTF-32 code point #00A3, aka £, and may well need special treatment, perhaps by being substituted with x"C2A3". In truth I accidentally made VK_DOWN #A3, but it would be trivial to change either or both VK_DOWN and VK_POUND to almost anything else, should doing so actually make anything any easier, just let me know. Finally, I own/use a UK keyboard, and haven’t done any testing of non-latin-alphabet input, but I strongly doubt changes that might yet be needed in that regard would be spectacularly difficult, at least assuming there is no need for tens of thousands of new vk_constants, and I will of course be happy to assist as best I can (once provided with suitable details/links/photos/whatever). Returns: If XPG_CONTINUE is returned the key is propagated to anything that might handle it. If XPG_DEFAULT is returned VK_ESC (close window as per bEsc) and VK_F5 (refresh browser when under pwa/p2js) are propagated, but other keys are not. If XPG_IGNORE is returned the key is ignored and not processed by the control and not propagated. There are cases where XPG_IGNORE may start disabling generic OS/backend-specific behaviour, and cases where it simply doesn’t get that chance, such as <Alt F4>, ymmv. XPG_CLOSE will be processed. Notes Keyboard handlers depend on the keyboard usage of the control with the focus. So if you return XPG_IGNORE the control will usually not process the key. But be aware that sometimes the control processes the key in another event so even when returning XPG_IGNORE the key can still get processed, although it will(/should) not be propagated, again ymmv. It may also be that a keystroke went to the wrong control (eg the containing dialog) which graciously invoked some child handler in case it could deal with it, but no amount of returning XPG_CONTINUE will make the key have been originally entered in the right control, that is should you be hoping/expecting the standard system behaviour to kick in. Should things only work as desired after manually clicking on a control, then maybe you should think about somehow forcing an appropriate gSetFocus(). If there is no handler attached to a control the event is automatically propagated to the parent of the element (aka inherited), or in some cases that can work downwards. Affects: All elements with keyboard interaction. |
MOUSEMOVE |
Event generated when the mouse moves.
procedure mousemove(gdx id, integer x, y[, [bool] left,middle,right[, ctrl,shift,alt]]) id: identifier of the interface element that activated the event. x, y: position in the canvas where the event has occurred, in pixels. left,middle,right: (optional) status of the mouse buttons, true for down and false for up. ctrl,shift,alt: (optional) status of the modifier keys, true for down and false for up. In a similar fashion to paranormalised functions, only without any reshuffling of the parameters, you can assume that all of the following are potentially perfectly valid (ie "in sets of 3"): mousemove(gdx id, integer x, y) mousemove(gdx id, integer x, y, bool left, middle, right) mousemove(gdx id, integer x, y, bool left, middle, right, ctrl, shift, alt) An error occurs if the handler does not have 3, 6, or 9 parameters, or the last 2..8 are not integer/bool. Since bool is a subset of integer, and in fact get_routine_info() returns 'I' for parameters of both, it doesn’t actually make any difference should the "bool" type specifier be omitted. On Windows, as similarly noted in KEY above, if you dab on the Alt key, then try moving the mouse, you may find it is effectively disabled until you tap the Alt key again. Holding the Alt key while moving however works fine. Affects: IupCanvas, ... |
MOUSEWHEEL |
Event generated when the mouse wheel is used - automatically taken care of on several/most controls, and in truth this is more
for documenting how that is done, not that I would try to stop you from using it.
procedure mouse_wheel(gdx id, integer direction, bool ctrl, shift, alt) ih: identifier of the element that activated the event. direction: -1 when rolled toward the user/down, +1 when rolled away/up. ctrl, shift, alt: true if that key is being held down, false otherwise. At the time of writing this has only been implemented/tested on a gCanvas, which has a totally fake scrollbar implementation, whereas gTreeView intead uses and relies on an implicit native scrollbar. The default builtin handler for a canvas, which automatically comes into effect when a SCROLLSIZE larger than SIZE is specified, uses ctrl to scroll horizontally and/or shift to scroll faster. Be warned that as per KEY, WinAPI does it’s usual strange things with the Alt key (alone). The direction is kinda backwards, but it’s the same sign as WinAPI, and simply replicated on the other backends. Note there is no inherent speed handling, which I would fundamentally disagree with (on scrollbars) anyway: it is far better for an application to gradually increase the (rate of) effect the longer a wheel is turned without pause, or just have shift be faster, than is is to expect an arthritic hand to spin it like a manic 12-year old. Besides, a mousewheel should be for fine control anyway: for fast just use the scrollbar directly. Setting a SCROLLSIZE on a gCanvas (and derived controls) automatically adds a mousewheel handler (which maps to keyboard commands) to control the scrollbars. Also, gSlider and gSpin rely on the native handling and do not actually use a mousewheel handler at all. There is in fact some speed handling within the latter for free, at least under WinAPI. Affects: gCanvas, gGraph, gList, gTable |
VALUE_CHANGED |
Event generated when the value of a control is changed.
function value_changed(gdx id) ih: identifier of the element that activated the event. Note this handler on a gCheckbox() also gets a bool bChecked parameter. Affects: gCheckbox, gDatePick, gList, gSlider, gSpin, gText |
