Bug #8673
Deduce the logic behind OE focus management and implement in FWD
30%
Related issues
History
#1 Updated by Vladimir Tsichevski about 2 years ago
- File 8376-with-focusable.p
added
There is a feature related to how OE manages focus I cannot understand.
I wrote the 8376-with-focusable.p example to learn focus-related stuff.
In this example there is a number of frames, most of them nested, which contain a number of buttons.
When I click with mouse in any frame, the first button in this frame is focused.
But there is a single exception: the frame named outerFrame has two enabled buttons, but clicking inside this frame does not move focus to one of the frame buttons.
I am stuck, since I cannot find out, why is this frame so special.
Probably, I am missing something obvious?
The logic behind this feature should be discovered in order to be implemented in FWD.
#3 Updated by Vladimir Tsichevski about 2 years ago
- Status changed from New to WIP
- % Done changed from 0 to 20
When the target is a frame, and there is no focus hint set in this frame (something roughly similar to the AbstractContainer#current field in FWD),
OE searches for the suitable field in its field group. This is known.
The new hypothesis: the search is aborted on the first non-field item (a frame) found in the field group.
In the 8376-with-focusable.p example the outerFrame is enabled with the following statement:
ENABLE focusable4 focusable6 WITH FRAME outerFrame.
This way results in that the focusable4 and focusable6 buttons move to the end of the field group widgets, and the innerFrame is the first item in the field group.
So, when the user click the outerFrame with mouse the search for the child suitable as focus target, aborts on the innerFrame, and the outerFrame stays as the focus target.
#4 Updated by Vladimir Tsichevski about 2 years ago
- Related to Bug #8376: Focus transfer problem with frames having no focusable contents added
#5 Updated by Vladimir Tsichevski about 2 years ago
- Related to Bug #8095: LEAVE trigger fires in no focus change situation added
#6 Updated by Vladimir Tsichevski about 2 years ago
- Related to Bug #7786: FOCUS handle must return only field-level widgets added
#7 Updated by Vladimir Tsichevski about 2 years ago
- Related to Bug #7685: LEAVE/ENTRY events are raised before MOUSE-UP event. added
#8 Updated by Vladimir Tsichevski about 2 years ago
- Related to Bug #7684: ENTRY event is emitted twice added
#9 Updated by Vladimir Tsichevski about 2 years ago
- Related to Support #7339: UI - Focus added
#10 Updated by Vladimir Tsichevski about 2 years ago
- Related to Bug #8711: TAB-STOP attribute wrong implementation for RECTANGLE, IMAGE and TEXT widgets added
#11 Updated by Vladimir Tsichevski about 2 years ago
Focus data model¶
Focus state consists of the following data:
Window.focus
Field-level focused widget, which receives keyboard input, initiallynull.
The OE FOCUS handle contains the value of this field for the currently focused window.
Window.rawFocus.
The last widget the user selected by mouse. Initiallynull. Can be field-level or frame. This field cannot be directly accessed from 4gl.
Frame.currentFocus
The preferred FOCUS candidate field-level.
This field is eithernullor points to one of field-level members of the frame field group, which was last selected forWindow.focus.
Focus change by mouse events control flow¶
Focus state can be affected by MOUSE DOWN and MOUSE UP actions.
The control flow looks as follows:
- Decide if this mouse type/button combination can cause focus-related actions, return if not.
- Calculate the effective target focus (which can be either a field-level or a frame).
- If the resulting target focus is the same as the current
Window.rawFocusvalue, then return, do nothing. - Fire necessary
LEAVE/ENTRYevent triggers. - If no trigger returned
NO_APPLY: # If the target focus is a field-level, assign it toWindow.focusand updateFrame.currentFocusfor the field frame. - Save the target focus to
Window.rawFocus.
Which mouse event may initiate a focus change¶
Warning: this section describes the FWD implementation, the information needs probably be checked with original OE.
- The mouse button is the LEFT button for any widget or the mouse button is the RIGHT button for any widgets, except the following FWD widget classes:
BorderedPanelGuiImplEditorGuiImplFillInGuiImplScrollbarGuiButtonScrollbarGuiImpl
- event is one of
UP,DOWNorCLICK. - widget is any, except:
ScrollbarGuiButtonScrollbarGuiImpl
Effective target focus calculation¶
- Initially use the event source as the target focus.
- If the target focus is a widget which cannot be a target focus (either a disabled field-level, or a widget which by its nature does not accept keyboard input), then assign widget's frame to target focus.
- If target focus is not a frame, return the target focus value
- If the
Frame.currentFocusis a field-level, then return theFrame.currentFocusvalue - Search the target focus field group tab ring for a suitable field: search until either an enabled field-level found, and we use it as the new target, or a frame found, which aborts the search.
Fire LEAVE/ENTRY event triggers¶
Given a target focus, fire ENTRY and LEAVE events. Abort the operation if any trigger returns NO-APPLY.
- If target focus differs from the
Window.rawFocus:- If
Window.rawFocusis set:- Fire
LEAVEtrigger forWindow.rawFocus - If
Window.rawFocusis a field-level:- If target focus is a field-level:
- If the frame of the target focus differs from the frame of
Window.rawFocus:- fire
LEAVEtrigger for the frame ofWindow.rawFocus - if the
Frame.currentFocusfield of the frame of the target focus is set or theFrame.currentFocusfield of the frame of the target focus differs from the target focus:- fire
ENTRYtrigger for the frame of the target focus
- fire
- fire
- If the frame of the target focus differs from the frame of
- Otherwise (target focus is a frame):
- fire
LEAVEtrigger for the frame ofWindow.rawFocus
- fire
- If target focus is a field-level:
- Otherwise (
Window.rawFocusis a frame):- If target focus is field-level and the frame of the target focus differs from
Window.rawFocus
and (the frame of the target focus.currentFocus is not set
or theFrame.currentFocusfield of the frame of the target focus differs from the target focus):- fire
ENTRYtrigger for the frame of the target focus
- fire
- If target focus is field-level and the frame of the target focus differs from
- Fire
- Otherwise (
Window.rawFocusis not set):- if target focus is a field-level and (
Frame.currentFocusfield of the frame of the target focus is not set
orFrame.currentFocusfield of the frame of the target focus differs from target focus): # fireENTRYtrigger for the frame of the target focus
- if target focus is a field-level and (
- If
- fire
ENTRYtrigger for the target focus
#12 Updated by Vladimir Tsichevski about 2 years ago
Question to gurus:
A Progress trigger can return a value. If the NO-APPLY is returned, the further event processing is aborted.
The question: are values other than the NO-APPLY ever used by Progress?
#13 Updated by Greg Shah about 2 years ago
First, I want to record some of our older documentation:
https://proj.goldencode.com/artifacts/javadoc/latest/api/com/goldencode/p2j/ui/chui/package-summary.html#Focus_Management
https://proj.goldencode.com/artifacts/javadoc/latest/api/com/goldencode/p2j/convert/package-summary.html#Transaction_Processing_and_Block
The first reference is not 100% complete. It especially was only looked at in ChUI and the influence of mouse processing, drag and drop, multiple windows and child frames are not considered.
The second reference is based on 9.x and thus is not up to date with the additional block processing of OO, structured error handling or the more recent transaction processing related changes in 10.x and 11.x. It also doesn't consider anything related to appserver/REST/SOAP agent session processing, nor anything about persistent/super procedures.
Even with these caveats, these are useful resources.
The question: are values other than the NO-APPLY ever used by Progress?
Limiting this question to triggers, the answer is yes and no.
This may help (it is the javadoc from BlockManager.returnWorker()):
Return from the nearest enclosing top-level block. This will process differently based on the type of the top-level block (internal/external procedures, triggers or user-defined functions), the type of return processing that is requested (see BlockManager.ReturnType) and the presence or absence of a value to return.
The following behavior is provided:
Codes Meaning ------ ----------------------------------------------------- C Invoke LogicalTerminal.consume() E Invoke TransactionManager.triggerErrorInCaller() V Invoke ControlFlowOps.setReturnValue(val) N Invoke ControlFlowOps.setReturnValue("") F Invoke setFuncReturn(val) U Invoke setFuncReturn(null) (set return value as unknown) I Invalid (compile error in Progress) Return Return Internal External Trigger Function Value Type Proc Proc -------- --------- -------- -------- ------- -------- null NORMAL N N N U non-null NORMAL V V V F null CONSUME N N NC U non-null CONSUME V V VC F null ERROR NE NE I U non-null ERROR VE VE I UIn all cases, a
ReturnUnwindExceptionwill be thrown and this will cause the stack to unwind to the nearest enclosing top-level block. That block will then handle the return in a block specific manner. For example, in a user-defined function, the given value or by default the unknown value will be returned to the caller. Procedures and triggers have void return values.
The part about the ReturnUnwindException is no longer 100% correct but otherwise the details are quite accurate and match the 4GL behavior. The way to interpret (for triggers) is as follows:
- A
RETURN.("NORMAL" return without a value) in a trigger sets theRETURN-VALUEto""and then returns, allowing default processing to occur. - A
RETURN <expression>.("NORMAL" return with a value) in a trigger sets theRETURN-VALUEto the evaluated value of the<expression>and then returns, allowing default processing to occur. - A
RETURN NO-APPLY.("CONSUME" return without a value) in a trigger sets theRETURN-VALUEto""and then returns, suppressing the default processing. - A
RETURN NO-APPLY <expression>.("CONSUME" return with a value) in a trigger sets theRETURN-VALUEto the evaluated value of the<expression>and then returns, suppressing the default processing. - A
RETURN ERROR.("ERROR" return without a value) in a trigger causes a compilation error. - A
RETURN ERROR <expression>.("ERROR" return with a value) in a trigger causes a compilation error.
So these other constructions can affect the behavior but in no case does the value actually get returned as in a function call. In the "NORMAL" return with a value and "CONSUME" return with a value, the value can only be found in RETURN-VALUE. Relying upon this is tricky at best, because of the very idea of a trigger. It is happening in response to an event occurring, so how do you check the RETURN-VALUE just after a trigger executes BUT BEFORE any other procedure or trigger processing overwrites the RETURN-VALUE. Since there is just one global RETURN-VALUE, it is a pretty poor idea to use it from a trigger, in my opinion.
I hope this helps.
Marian: If I've gotten anything wrong here, please do let us know.
#14 Updated by Vladimir Tsichevski about 2 years ago
Thanks, Greg.
One question:
The following two RETURN <expression>. statements look the same:
- A
RETURN <expression>.("NORMAL" return with a value) in a trigger sets theRETURN-VALUEto the evaluated value of the<expression>and then returns, allowing default processing to occur.- A
RETURN <expression>.("CONSUME" return with a value) in a trigger sets theRETURN-VALUEto the evaluated value of the<expression>and then returns, suppressing the default processing.
How can the "CONSUME" return with a value be expressed in 4gl?
#15 Updated by Greg Shah about 2 years ago
I edited it. It should have been RETURN NO-APPLY <expression>..
#16 Updated by Vladimir Tsichevski about 2 years ago
- File 8376-with-focusable.p
added - File functions.i added
The #8673-11 now contains the focus data model and the focus change by mouse flow. Please, comment.
The demo program used to test the model is 8376-with-focusable.p (with the include file functions.i).
#17 Updated by Vladimir Tsichevski about 2 years ago
- % Done changed from 20 to 30
#18 Updated by Vladimir Tsichevski about 2 years ago
- File printEntryLeave.i added
The missing printEntryLeave.i required to run the 8376-with-focusable.p uploaded.
#19 Updated by Vladimir Tsichevski about 2 years ago
- Related to Bug #8770: ENTRY event is not sent for a frame when window receives focus added
#20 Updated by Vladimir Tsichevski about 2 years ago
- Related to Bug #8809: Incorrect next enabled widget calculation added
#21 Updated by Vladimir Tsichevski about 2 years ago
- Related to Bug #8586: Fields are not in FIELD-GROUP until they are enabled added
#22 Updated by Vladimir Tsichevski about 2 years ago
A strange thing in OE.
The test application:
- Defines a frame
outerFramewith a buttonfocusable4. - The button
ENTRYtrigger returnsNO-APPLY, so the button cannot be selected for focus.
When the application starts, and the window receives focus for the first time, the order of ENTRY events is as this:
focusable4outerFrameDEFAULT-WINDOW
When the window receives focus next times, the order of events is different:
outerFramefocusable4DEFAULT-WINDOW
I wonder why is this, and shall we implement this behavior verbatim?
#24 Updated by Vladimir Tsichevski about 2 years ago
- Related to Bug #8872: WAIT-FOR with FOCUS explicitly specified: NO-APPLY return from ENTRY trigger must be ignored added
#25 Updated by Vladimir Tsichevski about 2 years ago
- Related to Bug #8909: WAIT-FOR with PAUSE and no explicit focus: no default focus resolution should be done added
#26 Updated by Vladimir Tsichevski about 2 years ago
- Related to Bug #8911: Default focus resolution should NOT be done until window receives focus added
#27 Updated by Vladimir Tsichevski about 2 years ago
OE focus management feature I cannot explain: the default focus resolution differs in the DEFAULT-WINDOW and dynamic windows.
- in the
DEFAULT-WINDOW, if the root frame has no focusable fields, inner frames are searched for such. - in a dynamically created window, if the root frame has no focusable fields, no further search happens.
Can anyone provide me with any hints?
#28 Updated by Vladimir Tsichevski about 2 years ago
- Related to Bug #8917: Mouse focus management: focus update must happen on mouse down, not on mouse up added
#31 Updated by Greg Shah over 1 year ago
- Status changed from WIP to Review
- reviewer Hynek Cihlar added
#32 Updated by Hynek Cihlar over 1 year ago
Vladimir Tsichevski wrote:
OE focus management feature I cannot explain: the default focus resolution differs in the
DEFAULT-WINDOWand dynamic windows.
- in the
DEFAULT-WINDOW, if the root frame has no focusable fields, inner frames are searched for such.- in a dynamically created window, if the root frame has no focusable fields, no further search happens.
Can anyone provide me with any hints?
If this is how OE works, then we will have to implement this. I suggest you create good set of tests to confirm and document this behavior.
#33 Updated by Hynek Cihlar over 1 year ago
- Status changed from Review to WIP
#35 Updated by Vladimir Tsichevski 10 months ago
- Related to Bug #10499: LEAVE event missing for top-level window added