The paintable canvas can be used as an overlay to provide this effect without any regard to z-order or component positioning. Simply overlay the entire container with a paintable canvas, and use the canvas's mouse moved event to detect when the cursor intersects a component, so a top level outline can be painted around it:
Example:
Add the following custom properties to the canvas:
Add the following custom methods to the canvas:
passMouseEvent
#def passMouseEvent(self, event):
"""
Passes mouse event to the underlying component
"""
from java.awt import Rectangle
from java.awt.event import MouseEvent
for component in event.source.parent.components:
if component != event.source:
# Components that use relative coordinates must be handled differently
if not hasattr(component, 'relX') and component.bounds.contains(event.point):
componentEvent = MouseEvent(
component,
event.getID(),
event.when,
event.modifiers,
event.x - component.x,
event.y - component.y,
event.getXOnScreen(),
event.getYOnScreen(),
event.clickCount,
event.isPopupTrigger(),
event.button)
component.dispatchEvent(componentEvent)
return
elif hasattr(component, 'relX') and Rectangle(int(component.relX), int(component.relY), int(component.relWidth), int(component.relHeight)).contains(event.point):
componentEvent = MouseEvent(
component,
event.getID(),
event.when,
event.modifiers,
event.x - int(component.relX),
event.y - int(component.relY),
event.getXOnScreen(),
event.getYOnScreen(),
event.clickCount,
event.isPopupTrigger(),
event.button)
component.dispatchEvent(componentEvent)
return
mapOutline
def mapOutline(self, event):
"""
Sets the custom properties on the paintable canvas that define an outline
...and in doing so, triggers the repaint event
"""
from java.awt import Rectangle
for component in event.source.parent.components:
# Filter out components that shouldn't be outlined
if component.__class__.__name__ not in ['PMIPaintableCanvas', 'PMILabel'] or 'Polygon' in component.name:
# Handle components that use relative coordinates differently than standard components
if not hasattr(component, 'relX') and component.bounds.contains(event.point):
event.source.outlineX = component.x - event.source.outlineGap
event.source.outlineY = component.y - event.source.outlineGap
event.source.outlineWidth = component.width + (2 * event.source.outlineGap)
event.source.outlineHeight = component.height + (2 * event.source.outlineGap)
return
elif hasattr(component, 'relX') and Rectangle(int(component.relX), int(component.relY), int(component.relWidth), int(component.relHeight)).contains(event.point):
event.source.outlineX = component.relX - event.source.outlineGap
event.source.outlineY = component.relY - event.source.outlineGap
event.source.outlineWidth = component.relWidth + (2 * event.source.outlineGap)
event.source.outlineHeight = component.relHeight + (2 * event.source.outlineGap)
return
event.source.outlineX = None
Both custom methods will take a single parameter called event. The
passMouseEvent
will be used to provide to the underlying components any mouse event that would otherwise be consumed by the overlay.
The mapOutline
will be used to set the custom properties on the canvas that define the outline. The top level if
statement in the mapOutline
method should be used to determine which components receive the outline.
Call the passMouseEvent
from all paintable canvas mouse event handlers in this way:
event.source.passMouseEvent(event)
call the mapOutline
method from the mouseMoved
event handler in this way:
# All mouse events will have passMouseEvent,
#...but only mouseMoved will have mapOutline
event.source.passMouseEvent(event)
event.source.mapOutline(event)
Finally, use the following script on the canvas's repaint event handler to paint the outlines:
if event.source.outlineX is not None:
from java.awt import BasicStroke
graphics = event.graphics
graphics.setStroke(BasicStroke(5))
graphics.setColor(system.gui.color('yellow'))
x = event.source.outlineX
y = event.source.outlineY
width = event.source.outlineWidth
height = event.source.outlineHeight
radius = event.source.outlineGap
graphics.drawRoundRect(x, y, width, height, radius, radius)
Result: