Skip to content

[HtmlBoxWidget, DictQuickLookup] add capability to search within dictionary definitions#15001

Merged
Commodore64user merged 9 commits into
koreader:masterfrom
Commodore64user:find-in-defi
Mar 21, 2026
Merged

[HtmlBoxWidget, DictQuickLookup] add capability to search within dictionary definitions#15001
Commodore64user merged 9 commits into
koreader:masterfrom
Commodore64user:find-in-defi

Conversation

@Commodore64user

@Commodore64user Commodore64user commented Feb 18, 2026

Copy link
Copy Markdown
Member

what's new

Find-in-definition feature:

  • Added a new in_definition_search state flag to DictQuickLookup, and implemented methods to search for text within a definition, highlight matches, and navigate between them (searchInDefinition, findInDefinitionNextOrPreviousPage, and UI integration).

  • Updated the lookup input dialog to include a "Find in definition" button, which triggers the search within the current definition.

  • Added search state and methods to HtmlBoxWidget to find text across pages, highlight matches, and navigate between matched pages (findText, findTextNextPage, _highlightSearchOnCurrentPage, clearSearch).

  • Ensured search highlights are updated when changing pages and cleared when starting text selection or when appropriate.

  • Refactored key event handling to distinguish between closing with keys and normal closing, and to allow navigation between search matches using keyboard shortcuts.

  • Added onFindInTextOrMoveSelectorIndicator to handle navigation between matches or text selection movement, depending on context.

  • Consolidated widget access with a new _getScrollAndContentWidgets helper, replacing the previous _getSelectionWidget function for more consistent access to scroll/content widgets.

  • Updated scroll and tap handling in ScrollHtmlWidget to respect the new ignore_taps flag, disabling tap-to-scroll when in search mode.

These changes collectively enable a robust "find in definition" experience for users, with integrated UI, keyboard, and touch navigation.

screenshots

related issues

bump me! ;)


This change is Reviewable

@jonnyl2

jonnyl2 commented Feb 18, 2026

Copy link
Copy Markdown
Contributor

Suggest calling it "record", "article", or "body". There are usually multiple definitions, sample sentences, etymology, etc., per returned record.

Comment thread frontend/ui/widget/scrollhtmlwidget.lua Outdated
Comment on lines +189 to +190
function ScrollHtmlWidget:onTapScrollText(arg, ges)
if self.dialog and self.dialog.in_definition_search then return false end

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't do that, it's an ugly hack: ScrollHtmlWidget is generic and does not have to know about what happens in its users. Remember your principles (reaching across the dinner table and grabbing someone else's wine).
You'll have to find another idea.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On mobile right now, we can talk to our friends not to strangers though. I'm grabbing your wine, but that's fine because we're friends. Self.dialog is our friend too, the other idea would be to add Scroll class variable that DictQuick flips ... better?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, even a method, ScrollHtmlWidget:enableScrollByTap(toggle) (or some other name/param).

Comment thread frontend/ui/widget/dictquicklookup.lua Outdated
self.key_events.ReadPrevResult = { { Input.group.PgBack } }
self.key_events.ReadNextResult = { { Input.group.PgFwd } }
self.key_events.Close = { { Input.group.Back } }
self.key_events.CloseWithButton = { { Input.group.Back } }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why "CloseWithButton", if it's only associated to closing with... keys ? :)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Back is a button, esc is a key. I suppose is a matter of preference at this point

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A mysterious entity once used Key so I'd rather it be consistent. ;p
672be80

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For most of us, a Button would be a ButtonWidget.

Comment thread frontend/ui/widget/dictquicklookup.lua Outdated

function DictQuickLookup:findInDefinitionNextOrPreviousPage(direction)
if not self.in_definition_search then return false end
local scroll_widget, inner_widget = self:_getScrollAndInnerWidget()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe more specific naming than "inner" ? And plural if it returns 2 widgets :)
local scroll_widget, content_widget = self:_getScrollAndContentWidgets()

Maybe now that many stuff access these widgets (I remember some hw/tw by @NiLuJe ), we could have the changeDictionary method, that creates/swaps/reuses them, just set them as properties on self.
self.cur_scroll_widget, self.cur_content_widget ?

Comment thread frontend/ui/widget/dictquicklookup.lua Outdated
function DictQuickLookup:findInDefinitionNextOrPreviousPage(direction)
if not self.in_definition_search then return false end
local scroll_widget, inner_widget = self:_getScrollAndInnerWidget()
if inner_widget and inner_widget.findTextNextPage then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this check and maybe some other convolutions can go once you have done it for TextWidget.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of these are there to stop text from crashing yes

Comment thread frontend/ui/widget/dictquicklookup.lua Outdated
if not self.in_definition_search then return false end
local scroll_widget, inner_widget = self:_getScrollAndInnerWidget()
if inner_widget and inner_widget.findTextNextPage then
inner_widget:findTextNextPage(direction)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You were always using the word "pages" in your description of this feature, which feels a bit odd to me. Not sure what a better word could be, "view" ?
(There is no occurence of the word "page" in DictQuickLookup (except for Wikipedia full page) and neither in TextWidget, haven't reread it if we have a common word for the slice of the... article/definition/...page :))

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's MuPDF the method is getPages or something like that

Comment on lines +1492 to +1498
UIManager:show(InfoMessage:new{
text = T(_("No matches for '%1' were found."), text),
timeout = 2,
})
if self.in_definition_search then
inner_widget:clearSearch(true)
end

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this happen ? When you do a 2nd seach while the results for a first search are still active?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You just perfectly described when it can happen ;)

Comment on lines 1502 to +1509
function DictQuickLookup:onLookupInputWord(hint)
if self.allow_key_text_selection and self.nt_text_selector_indicator then
self:onStopTextSelectorIndicator(true)
end
self:lookupInputWord(hint)
end

function DictQuickLookup:lookupInputWord(hint)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this?
Looks like there is no other use of :lookupInputWord(), so why this wrapper?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To not sully what clearly is meant to be a declaration of a widget and nothing more. Besides, principle dictates event handlers trigger methods

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not really how most handlers do. Most of them do the full work. We even call these self:onSomething() directly as methods of the object in the linear code. We even renamed them from :doSomething to :onDoSomething just because they may now be called as an action/event.
We can have these 2 names (and :onDoSomething calling :doSomething) when there are actually some work in :onDoSomething that are event-related, but have nothing to do in the :doSomething(), which may be called in other contexts than events.
Here, your split is gratuitous (it wasn't there before, it does nothing more, it is not required.)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but it is doing something extra

 if self.allow_key_text_selection and self.nt_text_selector_indicator then
        self:onStopTextSelectorIndicator(true)
    end

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra to what? To what other use that doesn't need that extra?

(I won't fight on this one if you like it like that - but seeing it like this, I, or little Timmy when he'll be older, would go look for some other call site that would call lookupInputWord() directly - because it it has been splitted, there must be a reason and something that needed it without the need for onStopTextSelectorIndicator() - and find nothing, and wonder.)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the idea of the extra is simple, this mode and text selection are natural enemies, so we can't have both coexist at the same time. otherwise the shift+left/right won't work properly

Comment thread frontend/ui/widget/dictquicklookup.lua Outdated
Comment thread frontend/ui/widget/scrollhtmlwidget.lua
@poire-z

poire-z commented Feb 19, 2026

Copy link
Copy Markdown
Contributor

Pinging @TnS-hun for some quick review about the html widgets.

Comment thread frontend/ui/widget/scrollhtmlwidget.lua Outdated
Comment on lines +198 to +202
function ScrollHtmlWidget:disableTapScrollText()
return self.ignore_taps
function ScrollHtmlWidget:disableTapScrollText(force_block)
-- If an argument is provided, update the external block state
if force_block ~= nil then
self._blocked_by_parent = force_block
end

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry to insist on this little thing.

scroll_widget:disableTapScrollText(false) : it's a bit hard to get that the double negative will actually enable taps.
No matter the initial state, you can just use a positive one:
scroll_widget:enableTapScrollText(true)
And scroll_widget:enableTapScrollText(false) will read easier that it does not enable it = it disables it.

Also, inside the function:
self._blocked_by_parent = force_block
Why mention it is blocked by parent ? In here, you don't know who is calling you.
Be simple and generic, don't assume the current use case is the single one (it might be your parent, tomorrow your cousing, next month your neighbour, next year me!), just use self.ignore_taps as initially.
tight coupling, don't listen to strangers

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should make it (for real men)

function ScrollHtmlWidget:is_not_un_un_enabled(isnt_not_un_disabled)
    -- If the parent provides a value, we store the inverse of the negation
    if isnt_not_un_disabled ~= nil then
        self.isnt_un_dis_ignored = not isnt_not_un_disabled
    end
    return not (not (not self.isnt_un_dis_ignored))
end

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-if isnt_not_un_disabled ~= nil then
+if isnt_not_un_disabled ~= innit then

@TnS-hun

TnS-hun commented Feb 21, 2026

Copy link
Copy Markdown
Contributor

I did not check the code, just the functionality.

Looks like you cannot cancel the find in definition functionality without switching to the next dictionary (which you might not have) or cancel the dictionary lookup?

What if you use the find in definition functionality, the result is in the last line, but you want to read the next line too?

Would not be better to show a temporary row of buttons (for example: previous result, cancel find, next result) to control the find in definition functionality when using it? And keep the tap for turning pages.

(I don't like the main search functionality for the exact same reason, it "breaks" page turning, I cannot look around the results.)

@Commodore64user

Commodore64user commented Feb 21, 2026

Copy link
Copy Markdown
Member Author

it "breaks" page turning, I cannot look around the results.)

this doesn't break anything.

all answers to your questions can be found in #3022. but in short, it was deemed undesirable to use the x button to exit said mode, swipes still let you move freely through the definition and no, widgets on top are a no go.

I did not check the code,

that is kind of the point, could you have a look at that please!

@Commodore64user Commodore64user force-pushed the find-in-defi branch 3 times, most recently from da8a8d9 to bcf524d Compare February 22, 2026 01:59
@TnS-hun

TnS-hun commented Feb 22, 2026

Copy link
Copy Markdown
Contributor

this doesn't break anything.

It hijacks taps. They no longer do the same page turning that they always do. Just like when using the regular search. Why repeat the same mistake here?

@Commodore64user

Copy link
Copy Markdown
Member Author

this doesn't break anything.

It hijacks taps. They no longer do the same page turning that they always do. Just like when using the regular search. Why repeat the same mistake here?

There is no mistake, you can still swipe up/down to move through the definition as normal.

self.ignore_taps = not enabled
end
return not self.ignore_taps
end

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: I would keep this just a setter and either add a simple getter or just use ignore_taps directly.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setter and getter does seem more appropriate

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you don't use your getter externally, it's not needed, just use ignore_taps directly.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please lets not play this game again, that is doing a full 360 to what I was doing originally (kind of)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't care for this one, but I'd like you to get that: we don't have to have getter and setter for the beauty (or kludgility) of it.
It's good when it's actually called from the outside, to not expose the internals - but it's useless for a class itself which can use its own internals (unless the check for the internal is complex and a function avoids code duplication).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have any other comments?

@TnS-hun

TnS-hun commented Feb 22, 2026

Copy link
Copy Markdown
Contributor

There is no mistake, you can still swipe up/down to move through the definition as normal.

Normally I would never use swipes though. :)

that is kind of the point, could you have a look at that please!

The changes looks good to me. (But it has been a while since I last worked on this code.)

@Commodore64user

Copy link
Copy Markdown
Member Author

@TnS-hun incidentally you might remember #13276 (comment), interestingly these coordinates are actually correct... not sure if that helps fix that yet to be fixed problem

Comment thread frontend/ui/widget/dictquicklookup.lua Outdated
highlight_text_selection = true,
on_clear_search = function()
self.in_definition_search = false
self.stw_widget:setTapScrollEnabled(true)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this one exist on the scroll text widget ?
(Something tells me you have done the scroll/search on non-HTML dicts, and you forgot to push these changes ! :))

@Commodore64user Commodore64user Feb 22, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will exist in textbox. Essentially all the stuff is ready on dictquick for both, but textbox won't be ready for this PR (as i already mentioned) so when it is ready it'll be a matter of removing the enabled_func and PR'ing the textbox side.

As for the current state of textbox... coming soon ;) it currently "works" but it is not ready

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But currently, there is no ScrollTextWidget:setTapScrollEnabled.
I guess that on_clear_search can't currently be called when on a text dict.
But it feels dirty (even as an interim) to have references to stuff that doesn't exist.
I would comment it or leave it out (you'll anyway have to edit dictquicklookup when you'll implement search for text dicts.

(I also wouldn't merge this without its text dict search counterpart in a stable.)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds a lot like coercion "do my bidding or else" ;)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay let's look closer at what you are saying...

(I also wouldn't merge this without its text dict search counterpart in a stable.)

two options available to us here, option 1 this PR won't be merged until there is a second one with TextBox, or option 2, it can be merged but there won't be a stable release ever again until TextBox is merged.

of the two, only one is enforceable and realistic, so we are left with option 1 only, which begs the question, why comment anything out? also, I don't know how much longer it will take for TextBox to be ready, so will this sit here for months? (assuming it took months for it to be ready)

so what are you saying? because it does sound like coercion, a lot or extortion ;)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's option 3: wait until after next stable (which is promised next week, since weeks :) I know) to merge this.
There's no hurry to have it merged, it doesn't fix any issue, it's a feature, next stable can be without it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why comment anything out?

I would say the same if anybody introduced:
if False then UIManager:blowItOut() end
blowItOut doesn't exist, it's not because it's never called that we have to have it in the code.

@Commodore64user Commodore64user Feb 22, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But that still assumes that textbox will be ready before 2026.05 (or whatever) which i am not happy to guarantee, it will be ready when its ready, so we are back to option 1… i am happy to delay until after 2026.02 just not under thinly veiled threats ;), it goes live in 26.05 with or without textbox… agreed?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not a threat.
You bring a feature, half done because you just need it for your Oxford HTML dict.
And somehow promise you'll bring it for TEXT dicts (but without pressure because you don't need it).
Some kind of engagement that you will deliver for v2026.05 (with that pressure) would be welcome, and would somehow reassure me that I won't be the one that will have to finish it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but without pressure because you don't need it

only, this isn’t true.

Is not half finished, is not my fault one has to double the work here, nowhere else this is the case (i might regret saying that)

You could have simply said, let’s delay till next release, and that would have been fine. Instead you went, well give me the other or else… see the difference?

@Commodore64user

Copy link
Copy Markdown
Member Author

There is one little issue we are currently not dealing with, straddling matches, is it worth pursuing potential matches that span across different pages?

@Frenzie

Frenzie commented Feb 24, 2026

Copy link
Copy Markdown
Member

What's the problem precisely? The highlights?

@Commodore64user

Copy link
Copy Markdown
Member Author

MuPDF doesn't find them since we give it the text on a per page basis. So it can only match full strings within that page. Searching for 'set apart' where set is in page 3 and apart on page 4, fails to match.

@Frenzie

Frenzie commented Feb 24, 2026

Copy link
Copy Markdown
Member

Oh right, in that case I wouldn't worry about it too much. I'd check if they don't have a whole document search.

@jonnyl2

jonnyl2 commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

that is TextBox which is a bitch and it will be shipped at a later date.

👍. I ignorantly ass/u/me-d that Wiki would use HtmlBoxWidget.

@Commodore64user

Commodore64user commented Mar 21, 2026

Copy link
Copy Markdown
Member Author

Merging, unless you think is still think is to close to release...?

@Frenzie

Frenzie commented Mar 21, 2026

Copy link
Copy Markdown
Member

Should be fine. Please squash it and clean up the commit message.

@Commodore64user Commodore64user merged commit 80cb27d into koreader:master Mar 21, 2026
4 checks passed
@poire-z

poire-z commented Mar 21, 2026

Copy link
Copy Markdown
Contributor
image

The (possibly single) large button in the middle (while above and below are 2 buttons) is odd - as is the distance between the Search dict and Search Wikipedia.
Feels like Find in definition and Search with preset (in your top post screenshot) should be aove the others?

@Commodore64user Commodore64user deleted the find-in-defi branch March 21, 2026 13:43
@Commodore64user

Copy link
Copy Markdown
Member Author

and you feel like the best time to mention it is, exactly 5 minutes after it was merged? ;)

weren't you the one who wanted "Search with preset" in the middle row? I honestly can't remember, so apologies if that is not the case.

@poire-z

poire-z commented Mar 21, 2026

Copy link
Copy Markdown
Contributor

Well, it's when it is merged that we can easily test it.
(I don't think it was me that would have requested "Search with preset" in the middle row - at least not in this widget.)

(Btw, how do you reach the pencil icon with keys to trigger this dialog on NT ? Some key combination?)

@Commodore64user

Copy link
Copy Markdown
Member Author

Well, it's when it is merged that we can easily test it.

"that is not what the development branch is for" - The poet (2024)

(Btw, how do you reach the pencil icon with keys to trigger this dialog on NT ? Some key combination?)

on K4, screenkb + back, on hasFewKeys is part of self.layout and on hasKeyboard, start typing any alphabet key to start afresh, or backspace/del to get the current search

@poire-z

poire-z commented Mar 21, 2026

Copy link
Copy Markdown
Contributor

or backspace/del to get the current search

These closes the dict window for me.
Btw, on the emulator, sometime these last months, when on a dict window and hiting Esc or Backspace (what I usually do to quit it), I get that "Enter a word or phrase to lookup". I can't reproduce it when I want, but I get it when I don't want :) Any idea what situation triggers that different behaviour?

@Commodore64user

Copy link
Copy Markdown
Member Author

What do you mean they close it? Like you added a patch so it does that?

What you describe makes absolutely no sense to me, i've never encountered such behaviour

@poire-z

poire-z commented Mar 21, 2026

Copy link
Copy Markdown
Contributor

No, but I have Gear > Navigation > [x] Backspace works as back button enabled.

I disabled it to check I got your dialog on backspace, and I did. Re-enabled and I still got it. Restarted, ensure it is checked, got it. Disabled and re-enabled, and I got my behaviour to close it working again... Feels like it may depend on some random ordering of events? (ie iterating an unordered hash table, where the iteration order may change - we had this with gestures, and we had to add the override= stuff to get a consistent ordering).

@poire-z

poire-z commented Mar 21, 2026

Copy link
Copy Markdown
Contributor

Probably because:

if G_reader_settings:isTrue("backspace_as_back") then
table.insert(self.group.Back, "Backspace")
end

self.key_events.CloseWithKeys = { { Input.group.Back } }
self.key_events.MenuKeyPress = { { "Menu" } }
if Device:hasScreenKB() or Device:hasKeyboard() then
local modifier = Device:hasScreenKB() and "ScreenKB" or "Shift"
self.key_events.ChangeToPrevDict = { { modifier, Input.group.PgBack } }
self.key_events.ChangeToNextDict = { { modifier, Input.group.PgFwd } }
self.key_events.SetTemporaryLargeWindowMode = { { modifier, "Home" } }
self.key_events.TextSelectorModifierPress = { { modifier, "Press" } }
self.key_events.StartOrUpTextSelectorIndicator = { { modifier, "Up" }, event = "StartOrMoveTextSelectorIndicator", args = { 0, -1, true } }
self.key_events.StartOrDownTextSelectorIndicator = { { modifier, "Down" }, event = "StartOrMoveTextSelectorIndicator", args = { 0, 1, true } }
self.key_events.FastLeftTextSelectorIndicator = { { modifier, "Left" }, event = "FindInTextOrMoveSelectorIndicator", args = { -1, 0, true } }
self.key_events.FastRightTextSelectorIndicator = { { modifier, "Right" }, event = "FindInTextOrMoveSelectorIndicator", args = { 1, 0, true } }
if Device:hasKeyboard() then
self.key_events.LookupInputWordClear = { { Input.group.Alphabet }, event = "LookupInputWord" }
-- We need to concat here so that the 'del' event press, which propagates to inputText (desirable for previous key_event,
-- i.e., LookupInputWordClear) does not remove the last char of self.word
self.key_events.LookupInputWord = { { Device:hasSymKey() and "Del" or "Backspace" }, args = self.word .." " }

and we have Backspace associated to CloseWithKeys and LookupInputWord.
A solution could be to check for if G_reader_settings:isTrue("backspace_as_back") on line 812 to not include Backspace ?
Do you remember if you associated Backspace to other actions elsewhere?

@Commodore64user

Copy link
Copy Markdown
Member Author

Do you remember if you associated Backspace to other actions elsewhere?

I believe that is the only one

A solution could be to check for if G_reader_settings:isTrue("backspace_as_back") on line 812 to not include Backspace?

I don't know, maybe...

incidentally, that backspace event on the emulator propagates to the next widget (i.e., it deletes the added space), but when you use the alphabet keys, those don't propagate on the emulator (they do on kindle)... why does it do it on one and not the others?

@poire-z

poire-z commented Mar 21, 2026

Copy link
Copy Markdown
Contributor

incidentally, that backspace event on the emulator propagates to the next widget (i.e., it deletes the added space), but when you use the alphabet keys, those don't propagate on the emulator (they do on kindle)... why does it do it on one and not the others?

I don't understand what you mean (by "next widget", and "propagate"). May be describe precisely one case.

@Commodore64user

Commodore64user commented Mar 21, 2026

Copy link
Copy Markdown
Member Author

Try both those events from dictquick, the alpha one and the backspace one 809 and 812. The backspace passes the backspace press forward and the whitespace is deleted. With alphabetical keys, those don't get handed down, that original press is ignored.

@poire-z

poire-z commented Mar 21, 2026

Copy link
Copy Markdown
Contributor

I still don't understand.
I disabled "Backspace as back" setting.
On a Dictquicklookup windows, I hit backspace, and I get that:
image

passes the backspace press forward and the whitespace is deleted
forward to what ? Which whitespace is deleted ? :/

@Commodore64user

Copy link
Copy Markdown
Member Author

To that inputtext, don't you see? It should be colored with a trailing whitespace. But that delete event was passed over there, not when you do it with another alpha key

@poire-z

poire-z commented Mar 21, 2026

Copy link
Copy Markdown
Contributor

Oh, I see, you appended a " " explicitely for it be the one deleted by this backspace (well, it was obvious to you as you did it - I didn't real the full snippet I pasted above, so not obvious to me).

Well, if doesn't propage if you add a return true at the end of DictQuickLookup:onLookupInputWord() (which would be the right thing to do anyway).

What is strange is that when onLookupInputWord() is done - and has created that new dialog, I would expect for it to propagate to lower widgets in the stack, and not to be fed to the newly created dialog on top of it !
I guess that's UIManager line 950 (from @NiLuJe before he himself jumped off the bridge :/ (why didn't you?:)):

-- If the event was not consumed (no handler returned true), active widgets (from top to bottom) can access it.
-- NOTE: _window_stack can shrink/grow when widgets are closed (CloseWidget & Close events) or opened.
-- Simply looping in reverse would only cover the list shrinking, and that only by a *single* element,
-- something we can't really guarantee, hence the more dogged iterator below,
-- which relies on a hash check of already processed widgets (LuaJIT actually hashes the table's GC reference),
-- rather than a simple loop counter, and will in fact iterate *at least* #items ^ 2 times.
-- Thankfully, that list should be very small, so the overhead should be minimal.
local i = #self._window_stack
while i > 0 do
local widget = self._window_stack[i].widget
if not checked_widgets[widget] then
checked_widgets[widget] = true
-- Widget's active widgets have precedence to handle this event
-- NOTE: ReaderUI & FileManager *may* optionally register their modules as such
-- (currently, they only do that for the Screenshot module).
if widget.active_widgets then
for _, active_widget in ipairs(widget.active_widgets) do
if active_widget:handleEvent(event) then
return
end
end
end
if widget.is_always_active then
-- Widget itself is flagged always active, let it handle the event
-- NOTE: is_always_active widgets are currently widgets that want to show a VirtualKeyboard or listen to Dispatcher events
if widget:handleEvent(event) then
return
end
end
-- As mentioned above, event handlers might have shown/closed widgets,
-- so all bets are off on our old window tally being accurate, so let's take it from the top again ;).
i = #self._window_stack
else
i = i - 1
end
end
end

@Commodore64user

Copy link
Copy Markdown
Member Author

But my beef isn't with the delete event propagating there. My real issue is that the alphabet key ones don't 😭

@poire-z

poire-z commented Mar 21, 2026

Copy link
Copy Markdown
Contributor

That's for the first "a":

03/21/26-22:40:24 WARN  key press {
  A = true,
  key = "A",
  modifiers = {
    Alt = false,
    Ctrl = false,
    Meta = false,
    ScreenKB = false,
    Shift = false,
    Sym = false
  } --[[table: 0x7fbc56608180]]
} --[[bad argument #1 to '?' (table expected, got nil)]]
03/21/26-22:40:24 DEBUG key event => code: 97 (A), value: 0, time: 565763.746145
03/21/26-22:40:24 DEBUG AutoTurn: onInputEvent

That's for the next "a" typed:

03/21/26-22:40:27 DEBUG key event => code: 97 (A), value: 1, time: 565767.254080
03/21/26-22:40:27 DEBUG AutoTurn: onInputEvent
03/21/26-22:40:27 WARN  key press {
  A = true,
  key = "A",
  modifiers = {
    Alt = false,
    Ctrl = false,
    Meta = false,
    ScreenKB = false,
    Shift = false,
    Sym = false
  } --[[table: 0x7fbc56608180]]
} --[[bad argument #1 to '?' (table expected, got nil)]]
03/21/26-22:40:27 DEBUG input event => type: 83 (EV_SDL), code: 771, value: a, time: 565767.254080
03/21/26-22:40:27 WARN  onTextInput a

Non-special keys are handled by InputText:onTextInput(), while special ones get processed by InputText:onKeyPress() (not really sure how all this is handled, it's in sdl/device.lua that I don't want to comprehend).

I would say that because you haven't yet done Device:startTextInput() when the first "a" is typed, it doesn't get to be a SDL event to be processed by onTextInput() and so does not end up in the inputtext.

@Commodore64user

Copy link
Copy Markdown
Member Author

couldn't we somehow capture that press and pass it as we do with self.word?

@Commodore64user

Copy link
Copy Markdown
Member Author

we could do

function DictQuickLookup:onLookupInputWord(hint, ev)
    if self.allow_key_text_selection and self.nt_text_selector_indicator then
        self:onStopTextSelectorIndicator(true)
    end
    if not hint and ev and ev.key then
        local k = tostring(ev.key)
        local is_shift = ev.modifiers and ev.modifiers.Shift
        hint = is_shift and k or k:lower()
    end
    self:lookupInputWord(hint)
end

?

@poire-z

poire-z commented Mar 21, 2026

Copy link
Copy Markdown
Contributor

That's a bit ugly (but ok if you don't find anything cleaner I guess).
If you can get the event (or manage to only pass it as arg when Alphabet), maybe you can just resend it (after startTextInput happened) with UIManager:nextTick() - but I don't know if it will be processed by onTextInput at it may not be a proper SDL "input event".

@Commodore64user

Copy link
Copy Markdown
Member Author
function DictQuickLookup:onLookupInputWord(hint, ev)
    if self.allow_key_text_selection and self.nt_text_selector_indicator then
        self:onStopTextSelectorIndicator(true)
    end

    -- Key-event path: open dialog now, then inject one TextInput next tick.
    if Device:isSDL() and ev and ev.key then
        local text = tostring(ev.key)
        if not (ev.modifiers and ev.modifiers.Shift) then
            text = text:lower()
        end

        self:lookupInputWord()

        UIManager:nextTick(function()
            if self.input_dialog then
                UIManager:sendEvent(Event:new("TextInput", text))
            end
        end)
        return true
    end
    self:lookupInputWord(hint)
end

?

@poire-z

poire-z commented Mar 22, 2026

Copy link
Copy Markdown
Contributor

If the ev can't be used as-is and you need your modifiers checks and :lower(), I prefer your shorted first idea.

@Commodore64user

Copy link
Copy Markdown
Member Author

well the idea is that both shift + A and A, can directly call the thing

0xstillb pushed a commit to 0xstillb/koreader-thai that referenced this pull request May 9, 2026
…ionary definitions (koreader#15001)

Adds in-definition search to DictQuickLookup relaying on MuPDF's word-matching implementation, uses tap handling for navigation by allowing temporary disable/restore of Tap events.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Search inside dictionary definition [Feature request]

5 participants