Bug #6167
Mouse event processing refactoring/improvement
0%
Related issues
History
#2 Updated by Vladimir Tsichevski over 2 years ago
- File 6013-mouse-up.p
added
- File issue6013-final-14.03.2022.diff
added
- File 6013-mouse-click.p
added
This issue is created after #6013, all customer-specific material removed.
The main issue.¶
In 4gl, the MOUSE_RELEASED
and MOUSE_CLICKED
events are dispatched to (fire corresponding triggers for) widgets, which are under mouse pointer at the moment mouse button was released. In FWD this is not so: in most cases, these events are dispatched to the same widget as was under mouse pointer at the moment mouse button was pressed.
Test programs:
1. 6013-mouse-click.p
. In this example program, two rectangle widgets are DEFINEd: outerRect
and innerRect
. The innerRect
is located inside the outerRect
and overlaps it almost entirely. The innerRect
is initially hidden (innerRect:HIDDEN = TRUE). When outerRect
receives a mouse down event, the innerRect
becomes visible. When innerRect
receives a mouse down event, the innerRect
becomes hidden again. MOUSE-DOWN
and MOUSE_CLICKED
events are reported with corresponding MESSAGEs for both widgets.
The expected behavior is as follows:
When the user click the mouse:
outerRect
receivesMOUSE_PRESSED
innerRect
receives theMOUSE_CLICKED
event.
When the user click the mouse second time (above the innerRect
):
innerRect
receivesMOUSE_PRESSED
outerRect
receives theMOUSE_CLICKED
event.
2. 6013-mouse-up.p
. Same as 6013-mouse-click.p
, but for MOUSE_RELEASED
event instead of MOUSE_CLICKED
.
I see the problem as follows:
The WEB and Swing clients use different approaches when choosing the widget to dispatch mouse click event to:
- In Swing the event target is selected internally by Swing in
sun.awt.X11.XWindow:handleButtonPressRelease(XEvent xev)
. For mouse click Swing selects the same target as the widget the mouse press event was dispatched to. - In WEB the mouse click target is first selected by JavaScript code at the browser side, then, at the Java client side, if this target is a container, calls
findMouseSource(NativePoint)
to select by default the child widget at the given point.
Usually these two approaches give the same result, except in our case when a new widget is created between mouse press and mouse release events.
Other attachments:
issue6013-final-14.03.2022.diff
the cumulative patch for all issues listed in #6013.
#3 Updated by Vladimir Tsichevski over 2 years ago
WEB event slippage problem¶
I noticed the following problem with WEB mouse events synchronization:
Situation 1. Slow user, fast client/network. Expected program behavior.¶
- The user presses mouse button above
widget1
, theMOUSE_PRESSED
browser queues event forwidget1
. - Java client receives the
MOUSE_PRESSED
event and createswidget2
overwidget1
as the result, and sends updates to browser. - Browser receives updates, and registers
widget2
in its widget lists. - After awhile, the user releases mouse button at the same point, the
MOUSE_RELEASED
browser queues event for the newwidget2
. widget2
receives theMOUSE_RELEASED
event, as expected.
Situation 2. Fast user, slow client/network. Wrong program behavior.¶
- The user presses mouse button above
widget1
, theMOUSE_PRESSED
browser queues event forwidget1
- With minimal delay, the user releases mouse button at the same point.
- Browser queues the
MOUSE_RELEASED
event forwidget1
- Java client receives the
MOUSE_PRESSED
event and createswidget2
overwidget1
as the result, and sends updates to browser. - Browser receives updates, and registers
widget2
in its widget lists. widget1
receives theMOUSE_RELEASED
event, which is not expected.
I noticed this problem manifestation after I set a few conditional BPs in the Java client code, so my client gets very slow.
Moving the mouse event source calculation to the Java side solves this problem.
#4 Updated by Vladimir Tsichevski over 2 years ago
- Related to Bug #6074: Movable widget problem added
#5 Updated by Vladimir Tsichevski over 2 years ago
- Related to Bug #6077: Movable widget problem, when using both mouse buttons added
#6 Updated by Vladimir Tsichevski over 2 years ago
Another problem: in WEB, when the mouse enters a window area, the MOUSE_ENTER
event for the window is sent twice from the browser side.
#7 Updated by Vladimir Tsichevski over 2 years ago
Another issue: in WEB, while no mouse buttons is pressed, all mouse events generated have button=1
instead of expected button=0
.
#8 Updated by Vladimir Tsichevski over 2 years ago
The patch description¶
Main ideas¶
- The mouse released event must be dispatched to the widget which is currently under mouse pointer at the moment the mouse button was released (this fixes the original #6013 issue).
- Both Swing and WEB drivers must produce exactly the same output events, before this change this was not so:
- The WEB driver operation deeply depends on the values returned by widget
mouseActions
method, the Swing driver ignored these. - The Swing driver produces meaningful values of the
button
event field for all mouse-related event. Values set by WEB driver for events other than press/release/click are undefined. Meanwhile, these values are references in FWD outside the press/release/click mouse handlers. - mouse direct operations must operate on widgets other than that used to dispatch events. For all drag-type operation the mouse release event should be dispatched to the same widget which reseived the correspondent mouse release event. Meanwhile, mouse direct operations implementations in Swing and WEB drivers are based on principally different principles: in WEB completely independent sets of system-wide event handlers are used for "normal" mouse event processing and mouse direct operations. In Swing both kinds of processing are based on mouse actions, which already attached to the same widget.
- The WEB driver operation deeply depends on the values returned by widget
- Widgets must be capable to participate in defining the final mouse source when events are dispatched. The decision must be taken not only based on the mouse coordinates, but either on the event type.
Main changes¶
- The mouse released event is now dispatched to the widget which is currently under mouse pointer at the moment the mouse button was released both for WEB and Swing driver.
- Swing driver operation now depends on the values returned by widget
mouseActions
method, in the same manner as the WEB driver. - The WEB driver produces meaningful values of the
button
event field for all mouse-related event using the algorithm similar to that the Swing (and hence Swing driver) uses. - Both Swing and WEB driver now use the same
findMouseSource(eventId, MouseEvt)
when calculating the widget to dispatch mouse events to. The selection is based on:- the event type (a new feature)
- the mouse pointer coordinates (an old feature)
- widget mouse event subscription (the values returned by widget
mouseActions
method)(a new feature)
- in Swing mouse direct operations now operate on widgets other than that used to dispatch events. This change is implemented in
MouseHandler.handleMouseEvent(int sourceID, MouseEvent evt)
. The original Swind driver mouse actions-based architecture was not destroyed, since this would be a change too huge for one task. - Widgets are now capable to participate in defining the final mouse source when events are dispatched.
Downsides¶
- In WEB, mouse source widgets computed at the browser side are ignored at the Java side in favor of the widgets computed at the Java side. So, part of JS code is currently is not used.
- The mouse actions-based code in Swing driver should be probably re-designed. IMO this architecture is not very suitable to the task.
Other changes¶
Multiple small fixes, see the descriptions below, which are also placed as the history entries in corresponding classes.
Changes by classes¶
src/com/goldencode/p2j/ui/chui/ThinClient.java¶
- Method processProgressEvent: results of calls to event.id(), and
event.source() extracted to local variables (otherwise these calls are
performed >40 times in the method code).
src/com/goldencode/p2j/ui/client/TopLevelWindow.java¶
- A new private field
mousePressedSource
is introduced. This field remembers the widget the
mouse was pressed upon, and the value is used as the mouse event target during drag operation. findMouseSource(MouseEvt)
implementation added. It usesmousePressedSource
for drag
operations, otherwise callsfindMouseSource(evtID, mousePoint)
.
src/com/goldencode/p2j/ui/client/WindowManager.java¶
getNextActiveWindow
code refactored, so compilator complains of "Unlikely argument type" no
more.
processWindowEvent
: # the mouse source stored with the event by the driver used instead of running
window.findMouseSource(evt)
second time. Note: after this change, the call to
findMouseSource
is executed only once by the driver (check this!) (previously
findMouseSource
was called multiple times during the event dispatching and processing, which
is waste of CPU time, and probably may result in incorrect returned value)- Local variable
parent
renamed tomouseSourceWithId
, since the old name was misleading.
- Local variable
src/com/goldencode/p2j/ui/client/gui/ModalWindow.java¶
findMouseSource
overloaded: delegates request towindowPane
.
src/com/goldencode/p2j/ui/client/gui/driver/MouseDirectManipulation.java¶
processDrag(MouseEvent, boolean)
: unused second argument removed.- Unused
selectSingleWidget(false)
removed - selectSingleWidget(boolean): method argument inlined and removed (always
true
), assertion
added to check this widget is selected at the moment of the call. - Unused private method removed:
deselectEverythingExcept(Widget)
- Methods
deselectFrameWidgets(Frame)
deselectFrameWidgetsExcept(Frame, Widget)
refactored
into onedeselectFrameWidgets(Frame)
(the second "except" parameter is not used anymore). - Unused private method
deselectEverythingExcept(Widget)
removed. - Private method
deselectEverything()
inlined (only single use). - Unused private methods
deselectNestedFrames(Frame)
,deselectNestedFramesExcept(Frame)
removed. mousePressed
refactored:if (!widgetResizing)
replaced by more meaningfulif
; if/else replaced by early return; two conditional block with
(rowResizing)!isExtended(e)
as part of condition replaced by one block.
src/com/goldencode/p2j/ui/client/gui/driver/MouseEmptySelector.java¶
- mousePressed() refactored to eliminate repeatable calls and unneeded Java casts and instanceof
statements.
src/com/goldencode/p2j/ui/client/gui/driver/MouseHandler.java¶
- Method
findMouseSource(MouseEvent)
pulled up fromSwingMouseHandler
to make it accessible
also fromWebMouseHandler
. - Methods
processWidgetActions
,handleMouseEventIfMovable
,handleMouseEventIfSelectable
inlined - Method
applyMouseEvent(Object mouseAdapter, MouseEvent evt)
now accepts all suitably types of mouse handlers as the first argument. - Method
canProcessMouse
declared static - Field
mousePressedSourceID
added to local context to track the last mouse pressed widget.
src/com/goldencode/p2j/ui/client/gui/driver/swing/SwingMouseHandler.java¶
mouseEntered
made no-op: all necessary actions are taken in the first mouse move event. The
reason is that when a mouse pointer enters a widget, two events are generated simultaneously:
theENTER
and theMOVE
events.mouseExited
: call tofindMouseSource
eliminated, the correct mouse source is in
lastHoveredWidget
field already.mouseDragged
,mouseWheelMoved
: the mouse source widget is explicitly passed to
postMouseEvent
.mouseMoved
: # where is no need to execute code as aRunnable exit
lambda, so the lambda body was inlined. # the complex method logic made simple: # if the widget under mouse was not changed, then do nothing in this method (just call super method) and exit. # otherwise if previous widget (lastHoveredWidget
) is not null, then sendEXIT
event to it. # sendENTER
event to the new widget.findMouseSource(MouseEvent)
: the local variableparent
renamed tomouseSourceWithId
,
since the old name was misleading.processAction(MouseEvent)
: the value returned byfindMouseSource(e)
used instead of
null
.processAction
: the argument is asserted never benull
; method made static and renamed to
processActions
.- Method
findMouseSource(MouseEvent)
pulled up to MouseHandler to make it accessible from
WebMouseHandler
.
src/com/goldencode/p2j/ui/client/gui/driver/web/WebMouseHandler.java¶
raiseMouseEvent
: # MethodMouseHandler.findMouseSource(MouseEvent)
is used instead of
findMouseSource(w.findMouseSource(eventID))
. # The unusedwidgetId
arguments eliminated.
src/com/goldencode/p2j/ui/client/widget/Widget.java¶
- new
findMouseSource(w.findMouseSource(eventID, NativePoint))
method introduced instead of
obsoletefindMouseSource(w.findMouseSource(eventID)) along with default implementation. This
is returned.
method returns 'this' iff widget is visible and enabled and mouse event type matches mouse
actions defined in this widget. Otherwise @null
src/com/goldencode/p2j/ui/client/gui/driver/web/GuiWebDriver.java¶
raiseMouseEvent
: an additionalwidgetId
parameter is passed to windowraiseMouseEvent
.- Multiple cosmetic fixes: missing @Override annotations added, methods made static when possible, closable resource access wrapped with try() statement, unneeded Java casts removed.
src/com/goldencode/p2j/ui/client/widget/AbstractContainer.java¶
findMouseSource(w.findMouseSource(eventID, NativePoint))
method is overriden.
src/com/goldencode/p2j/ui/client/gui/WindowGuiImpl.java¶
findMouseSource(MouseEvent)
moved toTopLevelWindow
.init()
: now that true mouse source isWindowTitle
calculated instead ofWindowTitleBar
,WindowTitle
is registered withgd.registerMoveableWidget
instead ofWindowTitleBar
.
ButtonListGuiImpl¶
- Added overrides for
findMouseSource(eventID, NativePoint)
for 3 subclasses made after the correspondingfindMouseSource(NativePoint)
methods. processKeyEvent(KeyInput keyEvent)
: removed series of if/ifelse, which does nothing.ItemsControl.findMouseSource(MouseEvent)
: removedif (w == null)
condition, which is never met.
src/com/goldencode/p2j/ui/client/gui/WindowTitleBar.java¶
- unused import statement(s) removed
- duplicate interfaces removed from the class definition
- The
allButtons
field made constant (final). - The class fixed in static method call: TopLevelWindow.getWindowDefaultIcon() (was Window).
- Missing @Override annotations were added
toString
8 lines of code refactored to 1.loadIcon
method: access to a closable resource surrounded bytry/catch
block.- Internal
WindowTitle
class access restriction weakened to allow access from outside of this class - Overridden
findMouseSource(int eventId, NativePoint p)
introduced in the previous release was removed: special treatment is no more needed for this container widget children.
src/com/goldencode/p2j/ui/client/gui/ButtonListGuiImpl.java¶
processKeyEvent
: piece of code doing nothing removed.findMouseSource(NativePoint)
fixed.findMouseSource(eventId, NativePoint)
was overriden.setSelected
: missing Javadocs added
src/com/goldencode/p2j/ui/client/gui/CaptionButton.java¶
mouseActions
:MouseEvent.MOUSE_MOVED
was added to the list of actions.
src/com/goldencode/p2j/ui/client/gui/EditorGuiImpl.java¶
- findMouseSource(eventId, NativePoint) was overriden.
src/com/goldencode/p2j/ui/client/gui/driver/web/WebMouseDirectManipulation.java¶
- Javadocs and other cosmetic fixes.
src/com/goldencode/p2j/ui/client/gui/driver/web/res/p2j.mouse.js¶
- Event listeners removed for "mouseenter" and "mouseleave": they were duplicating "mouseout" and "mouseover" listeners in wrong order and resulted in duplicate events sent to Java.
- Function inlined:
sendMouseExit
,sendMouseEnter
sendMouseEvent
renamed tosendMouseOrOSEvent
, Javadocs fixedprocessMouseMove
simplified and fixed: See #6013-94 for the description.
src/com/goldencode/p2j/ui/client/gui/driver/web/res/p2j.screen.js¶
- The
mouseButtonDownStack
variable added to keep last mouse button pressed/released state stack. evt.preventSendToApplication
is no more used to prevent some event handlers. evt.preventDefault() is used instead.- event listeners are now not registered for "mouseenter", "mouseleave", "mouseover" and "mouselout" events.
- trivial sendExplicitMouseEvent function inlined.
sendMouseEvent
: a correctbutton
field of output event is now calculated for all events (not only press/release/click events).getModifiersBitMask
inlinedcheckMessageBlasterEvent
: unusedmop
argument removed.