Field in a group receiving focus without having focus... ?
Posted: Wed Apr 03, 2024 3:06 am
Hi all, this is a weird one I decided to repost here because it will probably get lost in in translation where this is.
In short: I created a group widget that functions as a combobox, but with added features like placeholder text and keyboard navigation.
https://forums.livecode.com/viewtopic.php?f=9&t=39005
The group consists of an SVG widget icon and 2 fields (a normal 1-line field and a listField). When using the group, the 1-line field receives the focus.
All works quite well. Using the up/down arrows or scroll, I can navigate the dropdown without having to use the mouse etc. The entire script is in the group script and of course arrow keys and scroll are trapped in the group's rawKeyDown handler.
Now the weird part which I can't explain:
When clicking out of the field and removing the focus ring from the 1-line field and the group, the rawKeyDown handler still traps the arrow keys!!!
If a click out of the group a 2nd time, that stops happening!!!
If I put a line in the rawKeyDown to put the focusedObject, then it returns the 1-line field even though that visually has lost the focus!!!
I know that's a lot of exclamation marks, but this broke my mind a bit
I was able to address this by adding focus on nothing to closeField and exitField handlers.
But this seems wrong to me... Is this expected behaviour or a bug?
Why should a group's rawKeyDown handler fire even though it's not in focus?
Or more accurately, why does the group report that the focusedObject is the group's text entry field after clicking out of said field and group and the field no longer displays a cursor and has lost it's focus ring?
The entire group script is below:
Many thanks
Stam
In short: I created a group widget that functions as a combobox, but with added features like placeholder text and keyboard navigation.
https://forums.livecode.com/viewtopic.php?f=9&t=39005
The group consists of an SVG widget icon and 2 fields (a normal 1-line field and a listField). When using the group, the 1-line field receives the focus.
All works quite well. Using the up/down arrows or scroll, I can navigate the dropdown without having to use the mouse etc. The entire script is in the group script and of course arrow keys and scroll are trapped in the group's rawKeyDown handler.
Now the weird part which I can't explain:
When clicking out of the field and removing the focus ring from the 1-line field and the group, the rawKeyDown handler still traps the arrow keys!!!
If a click out of the group a 2nd time, that stops happening!!!
If I put a line in the rawKeyDown to put the focusedObject, then it returns the 1-line field even though that visually has lost the focus!!!
I know that's a lot of exclamation marks, but this broke my mind a bit

I was able to address this by adding focus on nothing to closeField and exitField handlers.
But this seems wrong to me... Is this expected behaviour or a bug?
Why should a group's rawKeyDown handler fire even though it's not in focus?
Or more accurately, why does the group report that the focusedObject is the group's text entry field after clicking out of said field and group and the field no longer displays a cursor and has lost it's focus ring?
The entire group script is below:
Code: Select all
#< ---------------------- CONSTANTS --------------------------- >
constant kReturn = 65293
constant kEnter = 65421
constant kEscape = 65307
constant kTab = 65289
constant kBackspace = 65288
constant kDelete = 65535
constant kArrowDown = 65364
constant kArrowUp = 65362
constant kArrowRight = 65363
constant kArrowLeft = 65361
constant kScrollDown = 65309
constant kScrollUp = 65308
#</
#< ----------------------- HANDLERS ---------------------------- >
local sFieldID, sDiscloseID, sDropdownID
private function getFoundLine
local tData, tLine, tFilter
checkForIDs
if fieldIsEmpty() then return empty
return lineOffset(the text of control id sFieldID, control id sDropdownID )
end getFoundLine
private function fieldIsEmpty
checkForIDs
try
return the text of control id sFieldID = the placeholderText of me or the text of control id sFieldID is empty
end try
end fieldIsEmpty
private command setStyle
checkForIDs
set the textColor of control id sDropdownID to the normalColor of me
if fieldIsEmpty() then
set the textColor of control id sFieldID to the placeholderColor of me
set the textStyle of control id sFieldID to the placeholderStyle of me
set the text of control id sFieldID to the placeholderText of me
select before control id sFieldID
else
set the textColor of control id sFieldID to the normalColor of me
set the textStyle of control id sFieldID to the normalStyle of me
select after control id sFieldID
end if
centerVertically
end setStyle
command showDropdown
checkForIDs
local tRect
show control id sDropdownID
put item 1 of the rect of control id sFieldID - 4 into item 1 of tRect
put item 2 of the rect of control id sFieldID- 4 into item 2 of tRect
put item 3 of the rect of control id sFieldID + 4 into item 3 of tRect
put item 4 of the rect of control id sDropdownID + 4 into item 4 of tRect
set the rect of me to tRect
end showDropdown
command hideDropdown
checkForIDs
local tRect
hide control id sDropdownID
put item 1 of the rect of control id sFieldID - 4 into item 1 of tRect
put item 2 of the rect of control id sFieldID - 4 into item 2 of tRect
put item 3 of the rect of control id sFieldID + 4 into item 3 of tRect
put item 4 of the rect of control id sFieldID + 4 into item 4 of tRect
set the rect of me to tRect
end hideDropdown
command checkForIDs
if sFieldID is empty or sDropdownID is empty or sDiscloseID is empty then openControl
end checkForIDs
command centerVertically
local tFieldY, tFormatRect, tFormatheight, tFormatHalfHeight, tCurrFormatTop, tCenterField_To_TopTextDiff
put item 2 of the loc of control id sFieldID into tfieldY
put the formattedRect of line 1 to - 1 of control id sFieldID into tFormatRect
put item 4 of tFormatRect - item 2 of tFormatRect into tFormatHeight
put tFormatHeight div 2 into tFormatHalfHeight
put item 2 of tFormatRect into tCurrFormatTop
put tfieldY - tCurrFormatTop into tCenterField_To_TopTextDiff
set the topMargin of control id sFieldID to the topMargin of control id sFieldID + tCenterField_To_TopTextDiff - tFormatHalfHeight
end centerVertically
#</
#< ------------------------- EVENTS ------------------------------- >
on openControl
put the id of field "field" of me into sFieldID
put the id of field "dropdown" of me into sDropdownID
put the id of widget "disclose" of me into sDiscloseID
if the text of field "field" of me is empty then setStyle
end openControl
on resizeControl
checkForIDs
local tRect, tFieldRect, tDropdownRect, tExpanded
put the visible of control id sDropdownID into tExpanded
put the rect of me into tRect
// field "field"
if tExpanded then
put item 1 of tRect + 4 into item 1 of tFieldRect
put item 2 of tRect +4 into item 2 of tFieldRect
put item 3 of tRect - 4 into item 3 of tFieldRect
put item 2 of tFieldRect + the height of control id sFieldID into item 4 of tFieldRect
else
put item 1 of tRect + 4 into item 1 of tFieldRect
put item 2 of tRect +4 into item 2 of tFieldRect
put item 3 of tRect - 4 into item 3 of tFieldRect
put item 4 of tRect - 4 into item 4 of tFieldRect
centerVertically
end if
set the rect of control id sFieldID to tFieldRect
// widget "disclose"
set the height of control id sDiscloseID to the height of control id sFieldID - 4
set the loc of control id sDiscloseID to the loc of control id sFieldID
set the right of control id sDiscloseID to the right of control id sFieldID - 5
// field "dropdown"
if tExpanded then
put tFieldRect into tDropdownRect
put item 4 of tFieldRect + 2 into item 2 of tDropdownRect
put item 4 of tRect - 4 into item 4 of tDropdownRect
set the rect of control id sDropdownID to tDropdownRect
end if
if the formattedHeight of control id sDropdownID > the height of control id sDropdownID + 2 then
set the vScrollbar of control id sDropdownID to true
else
set the vScrollbar of control id sDropdownID to false
end if
set the dgColumnWidth["item"] of control id sDropdownID to the width of control id sDropdownID
end resizeControl
on rawKeyDown pKeyNum
checkForIDs
if the long id of control id sFieldID <> the focusedObject then pass rawKeyDown
local tData, tRow, tNumRows
switch pKeyNum
case kReturn
case kEnter
// show dropdown if hidden
if not the visible of control id sDropdownID then
showDropdown
set the hilitedLine of control id sDropdownID to getFoundLine()
// if dropdown visible but no selection, enter/retun commits the text in field
else if the visible of control id sDropdownID and the hilitedLine of control id sDropdownID is empty then
hideDropdown
set the textContent of me to the text of control id sFieldID
// if dropdown is visible and an option selected, select & commit
else if the visible of control id sDropdownID and the hilitedLine of control id sDropdownID is not empty then
set the textContent of me to line( the hilitedLine of control id sDropdownID) of control id sDropdownID
hideDropdown
end if
break
case kArrowDown
case kScrollDown
showDropdown
put the hilitedLine of control id sDropdownID into tRow
put the number of lines of control id sDropdownID into tNumRows
if tRow is empty or tRow = tNumRows then
set the hilitedLine of control id sDropdownID to 1
else
set the hilitedLine of control id sDropdownID to tRow + 1
end if
break
case kArrowUp
case kScrollUp
showDropdown
if the hilitedLine of control id sDropdownID = 1 or the hilitedLine of control id sDropdownID is empty then
set the hilitedLine of control id sDropdownID to the number of lines of control id sDropdownID
else
set the hilitedLine of control id sDropdownID to the hilitedLine of control id sDropdownID - 1
end if
break
case kEscape
if the visible of control id sDropdownID then
if the hilitedLines of control id sDropdownID is empty then
hideDropdown
else
set the hilitedLines of control id sDropdownID to empty
end if
end if
break
default
if fieldIsEmpty() then set the text of control id sFieldID to empty
pass rawKeyDown
end switch
end rawKeyDown
on rawKeyUp pKeyNum
checkForIDs
if pKeyNum is in (kArrowDown && kArrowUp && kScrollDown && kScrollUp && kEnter && kReturn && kEscape) then pass rawKeyUp
setStyle
set the hilitedLines of control id sDropdownID to getFoundLine()
if the hilitedLines of control id sDropdownID is not empty then showDropdown
# required for select-all. cmd-c/cmd-v/cmd-x all work as is, but not cmd-a (a = 97)
if the commandKey is down and pKeyNum = 97 then select char 1 to -1 of control id sFieldID
pass rawKeyUp
end rawKeyUp
on openField
checkForIDs
try
if fieldIsEmpty() then
select before control id sFieldID
else
select after control id sFieldID
end if
end try
end openField
on closeField
checkForIDs
if (the mouseloc is within the rect of control id sFieldID) or (the optionKey is down) then pass closeField
if the visible of control id sDropdownID is false then
set the textContent of me to the text of control id sFieldID
else
if the mouseloc is not within the rect of control id sDropdownID then
set the textContent of me to the text of control id sFieldID
hideDropdown
end if
end if
focus on nothing
end closeField
on exitField
checkForIDs
if (the mouseloc is within the rect of control id sFieldID) or (the optionKey is down) then pass exitField
if the visible of control id sDropdownID is false then
set the textContent of me to the text of control id sFieldID
else
if the mouseloc is not within the rect of control id sDropdownID then
set the textContent of me to the text of control id sFieldID
hideDropdown
end if
end if
end exitField
on mouseDown
checkForIDs
if the short name of the target is "disclose" then
set the hilite of control id sDiscloseID to true
focus on control id sFieldID
end if
end mouseDown
on mouseRelease
set the hilite of control id sDiscloseID to false
end mouseRelease
on mouseUp
if the the short name of the target is "disclose" then
set the hilite of control id sDiscloseID to false
if the visible of control id sDropdownID then
hideDropdown
else
showDropdown
end if
pass mouseUp
end if
local tData
if the clickloc is within the rect of control id sDropdownID then
if the hilitedLine of control id sDropdownID is not empty then
put line (the hilitedLine of control id sDropdownID) of control id sDropdownID into tData
set the text of control id sFieldID to tData
select after control id sFieldID
setStyle
hideDropdown
set the textContent of me to the text of control id sFieldID
select after control id sFieldID
end if
end if
end mouseUp
#</
#< ---------------------- PROPERTIES ---------------------------- >
command resetProps
set the placeholderText of me to empty
set the placeholderText of me to the placeholderText of me
set the placeholderColor of me to empty
set the placeholderColor of me to the placeholderColor of me
set the placeholderStyle of me to empty
set the placeholderStyle of me to the placeholderStyle of me
set the normalColor of me to empty
set the normalColor of me to the normalColor of me
set the normalStyle of me to empty
set the normalStyle of me to the normalStyle of me
set the groupColor of me to empty
set the groupColor of me to the groupColor of me
set the groupHIliteColor of me to empty
set the groupHiliteColor of me to the groupHiliteColor of me
setStyle
end resetProps
getProp placeholderText
if the placeholderText of me is empty then set the placeholderText of me to "Type or Select..."
return the placeholderText of me
end placeholderText
setProp placeholderText pText
checkForIDs
local tText
put the placeholderText of me into tText
set the placeholderText of me to pText
if the text of control id sFieldID is tText then set the text of control id sFieldID to pText
setStyle
end placeholderText
getProp textContent
checkForIDs
if fieldIsEmpty() then
return empty
else
return the text of control id sFieldID
end if
end textContent
setProp textContent pText
checkForIDs
if fieldIsEmpty() then
set the textContent of me to empty
else
set the textContent of me to pText
end if
if pText is empty then
set the text of control id sFieldID to the placeholderText of me
else
set the text of control id sFieldID to pText
end if
setStyle
dispatch "comboAction" with the long id of me, the textContent of me
end textContent
getProp listContent
checkForIDs
if the listContent of me is empty then set the listcontent of me to "Choice 1" & return & "Choice 2" & return & "Choice 3"
return the text of control id sDropdownID
end listContent
setProp listContent pList
checkForIDs
set the text of control id sDropdownID to pList
end listContent
getProp placeholderColor
if the placeholderColor of me is empty then set the placeholderColor of me to "180,180,180"
return the placeholderColor of me
end placeholderColor
setProp placeholderColor pColor
set the placeholderColor of me to pColor
setStyle
end placeholderColor
getProp normalColor
if the normalColor of me is empty then set the normalColor of me to "66,66,66"
return the normalColor of me
end normalColor
setProp normalColor pColor
set the normalColor of me to pColor
setStyle
end normalColor
getProp placeholderStyle
if the placeholderStyle of me is empty then set the placeholderStyle of me to "italic"
return the placeholderStyle of me
end placeholderStyle
setProp placeholderStyle pStyle
set the placeholderStyle of me to pStyle
setStyle
end placeholderStyle
getProp normalStyle
if the normalStyle of me is empty then set the normalStyle of me to "Plain"
return the normalStyle of me
end normalStyle
setProp normalStyle pStyle
set the normalStyle of me to pStyle
setStyle
end normalStyle
getProp groupColor
if the groupColor of me is empty then set the groupColor of me to "255,255,255"
return the groupColor of me
end groupColor
setProp groupColor pColor
checkForIDs
set the backgroundColor of control id sFieldID to pColor
set the backgroundColor of control id sDropdownID to pColor
set the groupColor of me to pColor
end groupColor
getProp groupHiliteColor
if the groupHiliteColor of me is empty then set the groupHiliteColor of me to "173,204,250"
return the groupHiliteColor of me
end groupHiliteColor
setProp groupHiliteColor pColor
checkForIDs
set the hiliteColor of control id sFieldID to pColor
set the hiliteColor of control id sDropdownID to pColor
set the groupHiliteColor of me to pColor
end groupHiliteColor
getProp fieldTextSize
if the fieldTextSize of me is empty then set the fieldTextSize of me to 13
return the fieldTextSize of me
end fieldTextSize
setProp fieldTextSize pSize
checkForIDs
set the textSize of control id sFieldID to pSize
set the textSize of control id sDropdownID to pSize -1
set the fieldTextSize of me to pSize
end fieldTextSize
getProp skComboBox
return true
end skComboBox
#</
Stam