2019-12-06 21:55:39 +00:00
|
|
|
local BD = require("ui/bidi")
|
2014-11-05 04:28:11 +00:00
|
|
|
local ButtonDialog = require("ui/widget/buttondialog")
|
2017-04-07 13:20:57 +00:00
|
|
|
local InputContainer = require("ui/widget/container/inputcontainer")
|
2021-02-28 03:13:05 +00:00
|
|
|
local InputDialog = require("ui/widget/inputdialog")
|
2021-04-01 11:19:36 +00:00
|
|
|
local Notification = require("ui/widget/notification")
|
2014-11-05 04:28:11 +00:00
|
|
|
local UIManager = require("ui/uimanager")
|
2016-12-29 08:10:38 +00:00
|
|
|
local logger = require("logger")
|
2014-11-05 04:28:11 +00:00
|
|
|
local _ = require("gettext")
|
|
|
|
|
|
|
|
local ReaderSearch = InputContainer:new{
|
|
|
|
direction = 0, -- 0 for search forward, 1 for search backward
|
2014-11-17 09:58:25 +00:00
|
|
|
case_insensitive = true, -- default to case insensitive
|
2018-02-04 19:57:13 +00:00
|
|
|
|
|
|
|
-- internal: whether we expect results on previous pages
|
|
|
|
-- (can be different from self.direction, if, from a page in the
|
|
|
|
-- middle of a book, we search forward from start of book)
|
|
|
|
_expect_back_results = false,
|
2014-11-05 04:28:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function ReaderSearch:init()
|
|
|
|
self.ui.menu:registerToMainMenu(self)
|
|
|
|
end
|
|
|
|
|
2017-03-04 13:46:38 +00:00
|
|
|
function ReaderSearch:addToMainMenu(menu_items)
|
|
|
|
menu_items.fulltext_search = {
|
2014-11-05 04:28:11 +00:00
|
|
|
text = _("Fulltext search"),
|
2019-03-02 20:36:30 +00:00
|
|
|
callback = function()
|
|
|
|
self:onShowFulltextSearchInput()
|
|
|
|
end,
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
function ReaderSearch:onShowFulltextSearchInput()
|
2019-12-06 21:55:39 +00:00
|
|
|
local backward_text = "◁"
|
|
|
|
local forward_text = "▷"
|
|
|
|
if BD.mirroredUILayout() then
|
|
|
|
backward_text, forward_text = forward_text, backward_text
|
|
|
|
end
|
2021-02-28 03:13:05 +00:00
|
|
|
|
|
|
|
self.input_dialog = InputDialog:new{
|
2019-03-02 20:36:30 +00:00
|
|
|
title = _("Enter text to search for"),
|
2019-08-30 11:27:03 +00:00
|
|
|
buttons = {
|
|
|
|
{
|
|
|
|
{
|
|
|
|
text = _("Cancel"),
|
|
|
|
callback = function()
|
2021-02-28 03:13:05 +00:00
|
|
|
UIManager:close(self.input_dialog)
|
2019-08-30 11:27:03 +00:00
|
|
|
end,
|
|
|
|
},
|
|
|
|
{
|
2019-12-06 21:55:39 +00:00
|
|
|
text = backward_text,
|
2019-08-30 11:27:03 +00:00
|
|
|
callback = function()
|
2021-04-04 15:27:17 +00:00
|
|
|
if self.input_dialog:getInputText() == "" then return end
|
2021-02-28 03:13:05 +00:00
|
|
|
UIManager:close(self.input_dialog)
|
2019-08-30 11:27:03 +00:00
|
|
|
self:onShowSearchDialog(self.input_dialog:getInputText(), 1)
|
|
|
|
end,
|
|
|
|
},
|
|
|
|
{
|
2019-12-06 21:55:39 +00:00
|
|
|
text = forward_text,
|
2019-08-30 11:27:03 +00:00
|
|
|
is_enter_default = true,
|
|
|
|
callback = function()
|
2021-04-04 15:27:17 +00:00
|
|
|
if self.input_dialog:getInputText() == "" then return end
|
2021-02-28 03:13:05 +00:00
|
|
|
UIManager:close(self.input_dialog)
|
2019-08-30 11:27:03 +00:00
|
|
|
self:onShowSearchDialog(self.input_dialog:getInputText(), 0)
|
|
|
|
end,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2017-02-28 21:46:32 +00:00
|
|
|
}
|
2021-02-28 03:13:05 +00:00
|
|
|
UIManager:show(self.input_dialog)
|
|
|
|
self.input_dialog:onShowKeyboard()
|
2014-11-05 04:28:11 +00:00
|
|
|
end
|
|
|
|
|
2019-08-30 11:27:03 +00:00
|
|
|
function ReaderSearch:onShowSearchDialog(text, direction)
|
2018-01-31 12:28:26 +00:00
|
|
|
local neglect_current_location = false
|
2018-02-04 19:57:13 +00:00
|
|
|
local current_page
|
2016-06-27 16:43:23 +00:00
|
|
|
local do_search = function(search_func, _text, param)
|
2014-11-05 04:28:11 +00:00
|
|
|
return function()
|
2021-04-01 11:19:36 +00:00
|
|
|
local no_results = true -- for notification
|
2016-06-27 16:43:23 +00:00
|
|
|
local res = search_func(self, _text, param)
|
2014-11-05 04:28:11 +00:00
|
|
|
if res then
|
2014-11-17 09:58:25 +00:00
|
|
|
if self.ui.document.info.has_pages then
|
2021-04-01 11:19:36 +00:00
|
|
|
no_results = false
|
2018-01-31 12:28:26 +00:00
|
|
|
self.ui.link:onGotoLink({page = res.page - 1}, neglect_current_location)
|
2014-11-17 09:58:25 +00:00
|
|
|
self.view.highlight.temp[res.page] = res
|
|
|
|
else
|
2018-02-04 19:57:13 +00:00
|
|
|
-- Was previously just:
|
|
|
|
-- self.ui.link:onGotoLink(res[1].start, neglect_current_location)
|
|
|
|
|
|
|
|
-- To avoid problems with edge cases, crengine may now give us links
|
|
|
|
-- that are on previous/next page of the page we should show. And
|
|
|
|
-- sometimes even xpointers that resolve to no page.
|
|
|
|
-- We need to loop thru all the results until we find one suitable,
|
|
|
|
-- to follow its link and go to the next/prev page with occurences.
|
|
|
|
local valid_link
|
|
|
|
-- If backward search, results are already in a reversed order, so we'll
|
|
|
|
-- start from the nearest to current page one.
|
|
|
|
for _, r in ipairs(res) do
|
|
|
|
-- result's start and end may be on different pages, we must
|
|
|
|
-- consider both
|
|
|
|
local r_start = r["start"]
|
|
|
|
local r_end = r["end"]
|
|
|
|
local r_start_page = self.ui.document:getPageFromXPointer(r_start)
|
|
|
|
local r_end_page = self.ui.document:getPageFromXPointer(r_end)
|
|
|
|
logger.dbg("res.start page & xpointer:", r_start_page, r_start)
|
|
|
|
logger.dbg("res.end page & xpointer:", r_end_page, r_end)
|
|
|
|
local bounds = {}
|
|
|
|
if self._expect_back_results then
|
|
|
|
-- Process end of occurence first, which is nearest to current page
|
|
|
|
table.insert(bounds, {r_end, r_end_page})
|
|
|
|
table.insert(bounds, {r_start, r_start_page})
|
|
|
|
else
|
|
|
|
table.insert(bounds, {r_start, r_start_page})
|
|
|
|
table.insert(bounds, {r_end, r_end_page})
|
|
|
|
end
|
|
|
|
for _, b in ipairs(bounds) do
|
|
|
|
local xpointer = b[1]
|
|
|
|
local page = b[2]
|
|
|
|
-- Look if it is valid for us
|
|
|
|
if page then -- it should resolve to a page
|
|
|
|
if not current_page then -- initial search
|
|
|
|
-- We can (and should if there are) display results on current page
|
|
|
|
current_page = self.ui.document:getCurrentPage()
|
|
|
|
if (self._expect_back_results and page <= current_page) or
|
|
|
|
(not self._expect_back_results and page >= current_page) then
|
|
|
|
valid_link = xpointer
|
|
|
|
end
|
|
|
|
else -- subsequent searches
|
|
|
|
-- We must change page, so only consider results from
|
|
|
|
-- another page, in the adequate search direction
|
|
|
|
current_page = self.ui.document:getCurrentPage()
|
|
|
|
if (self._expect_back_results and page < current_page) or
|
|
|
|
(not self._expect_back_results and page > current_page) then
|
|
|
|
valid_link = xpointer
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if valid_link then
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if valid_link then
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if valid_link then
|
2021-04-01 11:19:36 +00:00
|
|
|
no_results = false
|
2018-02-09 16:06:16 +00:00
|
|
|
self.ui.link:onGotoLink({xpointer=valid_link}, neglect_current_location)
|
2018-02-04 19:57:13 +00:00
|
|
|
end
|
2014-11-17 09:58:25 +00:00
|
|
|
end
|
2018-01-31 12:28:26 +00:00
|
|
|
-- Don't add result pages to location ("Go back") stack
|
|
|
|
neglect_current_location = true
|
2014-11-05 04:28:11 +00:00
|
|
|
end
|
2021-04-01 11:19:36 +00:00
|
|
|
if no_results then
|
|
|
|
local notification_text
|
|
|
|
if self._expect_back_results then
|
|
|
|
notification_text = _("No results on preceding pages")
|
|
|
|
else
|
|
|
|
notification_text = _("No results on following pages")
|
|
|
|
end
|
|
|
|
UIManager:show(Notification:new{
|
|
|
|
text = notification_text,
|
|
|
|
})
|
|
|
|
end
|
2014-11-05 04:28:11 +00:00
|
|
|
end
|
|
|
|
end
|
2019-12-06 21:55:39 +00:00
|
|
|
local from_start_text = "▕◁"
|
|
|
|
local backward_text = "◁"
|
|
|
|
local forward_text = "▷"
|
|
|
|
local from_end_text = "▷▏"
|
|
|
|
if BD.mirroredUILayout() then
|
|
|
|
backward_text, forward_text = forward_text, backward_text
|
|
|
|
-- Keep the LTR order of |< and >|:
|
|
|
|
from_start_text, from_end_text = BD.ltr(from_end_text), BD.ltr(from_start_text)
|
|
|
|
end
|
2014-11-05 04:28:11 +00:00
|
|
|
self.search_dialog = ButtonDialog:new{
|
2018-01-29 20:27:24 +00:00
|
|
|
-- alpha = 0.7,
|
2014-11-05 04:28:11 +00:00
|
|
|
buttons = {
|
|
|
|
{
|
|
|
|
{
|
2019-12-06 21:55:39 +00:00
|
|
|
text = from_start_text,
|
Assorted fixes after #7118 (#7161)
* I'd failed to notice that ButtonTable *also* instantiates seven billion Buttons on each update. Unfortunately, that one is way trickier to fix properly, so, work around its behavior in Button. (This fixes multiple issues with stuff using ButtonTable, which is basically anything with a persistent set of buttons. A good and easy test-case is the dictionary popup, e.g., the Highlight button changes text, and the next/prev dic buttons change state. All that, and more, was broken ;p).
* Handle corner-cases related to VirtualKeyboard (e.g., Terminal & Text Editor), which screwed with both TouchMenu & Button heuristics because it's weird.
* Flag a the dictionary switch buttons as vsync
(They trigger a partial repaint of the dictionary content).
* Flag the ReaderSearch buttons as vsync
They very obviously trigger a partial repaint, much like SkimTo ;p.
2021-01-18 15:51:25 +00:00
|
|
|
vsync = true,
|
2014-11-05 04:28:11 +00:00
|
|
|
callback = do_search(self.searchFromStart, text),
|
|
|
|
},
|
|
|
|
{
|
2019-12-06 21:55:39 +00:00
|
|
|
text = backward_text,
|
Assorted fixes after #7118 (#7161)
* I'd failed to notice that ButtonTable *also* instantiates seven billion Buttons on each update. Unfortunately, that one is way trickier to fix properly, so, work around its behavior in Button. (This fixes multiple issues with stuff using ButtonTable, which is basically anything with a persistent set of buttons. A good and easy test-case is the dictionary popup, e.g., the Highlight button changes text, and the next/prev dic buttons change state. All that, and more, was broken ;p).
* Handle corner-cases related to VirtualKeyboard (e.g., Terminal & Text Editor), which screwed with both TouchMenu & Button heuristics because it's weird.
* Flag a the dictionary switch buttons as vsync
(They trigger a partial repaint of the dictionary content).
* Flag the ReaderSearch buttons as vsync
They very obviously trigger a partial repaint, much like SkimTo ;p.
2021-01-18 15:51:25 +00:00
|
|
|
vsync = true,
|
2014-11-05 04:28:11 +00:00
|
|
|
callback = do_search(self.searchNext, text, 1),
|
|
|
|
},
|
|
|
|
{
|
2019-12-06 21:55:39 +00:00
|
|
|
text = forward_text,
|
Assorted fixes after #7118 (#7161)
* I'd failed to notice that ButtonTable *also* instantiates seven billion Buttons on each update. Unfortunately, that one is way trickier to fix properly, so, work around its behavior in Button. (This fixes multiple issues with stuff using ButtonTable, which is basically anything with a persistent set of buttons. A good and easy test-case is the dictionary popup, e.g., the Highlight button changes text, and the next/prev dic buttons change state. All that, and more, was broken ;p).
* Handle corner-cases related to VirtualKeyboard (e.g., Terminal & Text Editor), which screwed with both TouchMenu & Button heuristics because it's weird.
* Flag a the dictionary switch buttons as vsync
(They trigger a partial repaint of the dictionary content).
* Flag the ReaderSearch buttons as vsync
They very obviously trigger a partial repaint, much like SkimTo ;p.
2021-01-18 15:51:25 +00:00
|
|
|
vsync = true,
|
2014-11-05 04:28:11 +00:00
|
|
|
callback = do_search(self.searchNext, text, 0),
|
|
|
|
},
|
|
|
|
{
|
2019-12-06 21:55:39 +00:00
|
|
|
text = from_end_text,
|
Assorted fixes after #7118 (#7161)
* I'd failed to notice that ButtonTable *also* instantiates seven billion Buttons on each update. Unfortunately, that one is way trickier to fix properly, so, work around its behavior in Button. (This fixes multiple issues with stuff using ButtonTable, which is basically anything with a persistent set of buttons. A good and easy test-case is the dictionary popup, e.g., the Highlight button changes text, and the next/prev dic buttons change state. All that, and more, was broken ;p).
* Handle corner-cases related to VirtualKeyboard (e.g., Terminal & Text Editor), which screwed with both TouchMenu & Button heuristics because it's weird.
* Flag a the dictionary switch buttons as vsync
(They trigger a partial repaint of the dictionary content).
* Flag the ReaderSearch buttons as vsync
They very obviously trigger a partial repaint, much like SkimTo ;p.
2021-01-18 15:51:25 +00:00
|
|
|
vsync = true,
|
2014-11-05 04:28:11 +00:00
|
|
|
callback = do_search(self.searchFromEnd, text),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
},
|
|
|
|
tap_close_callback = function()
|
2016-12-29 08:10:38 +00:00
|
|
|
logger.dbg("highlight clear")
|
2014-11-05 04:28:11 +00:00
|
|
|
self.ui.highlight:clear()
|
2018-03-26 19:10:46 +00:00
|
|
|
UIManager:setDirty(self.dialog, "ui")
|
2014-11-05 04:28:11 +00:00
|
|
|
end,
|
|
|
|
}
|
2019-08-30 11:27:03 +00:00
|
|
|
do_search(self.searchFromCurrent, text, direction)()
|
2014-11-05 04:28:11 +00:00
|
|
|
UIManager:show(self.search_dialog)
|
2019-08-23 17:53:53 +00:00
|
|
|
--- @todo regional
|
2014-11-05 04:28:11 +00:00
|
|
|
UIManager:setDirty(self.dialog, "partial")
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
function ReaderSearch:search(pattern, origin)
|
2016-12-29 08:10:38 +00:00
|
|
|
logger.dbg("search pattern", pattern)
|
2014-11-05 04:28:11 +00:00
|
|
|
local direction = self.direction
|
|
|
|
local case = self.case_insensitive
|
2014-11-17 09:58:25 +00:00
|
|
|
local page = self.view.state.page
|
|
|
|
return self.ui.document:findText(pattern, origin, direction, case, page)
|
2014-11-05 04:28:11 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function ReaderSearch:searchFromStart(pattern)
|
|
|
|
self.direction = 0
|
2018-02-04 19:57:13 +00:00
|
|
|
self._expect_back_results = true
|
2014-11-05 04:28:11 +00:00
|
|
|
return self:search(pattern, -1)
|
|
|
|
end
|
|
|
|
|
|
|
|
function ReaderSearch:searchFromEnd(pattern)
|
|
|
|
self.direction = 1
|
2018-02-04 19:57:13 +00:00
|
|
|
self._expect_back_results = false
|
2014-11-05 04:28:11 +00:00
|
|
|
return self:search(pattern, -1)
|
|
|
|
end
|
|
|
|
|
|
|
|
function ReaderSearch:searchFromCurrent(pattern, direction)
|
|
|
|
self.direction = direction
|
2018-02-04 19:57:13 +00:00
|
|
|
self._expect_back_results = direction == 1
|
2014-11-05 04:28:11 +00:00
|
|
|
return self:search(pattern, 0)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- ignore current page and search next occurrence
|
|
|
|
function ReaderSearch:searchNext(pattern, direction)
|
|
|
|
self.direction = direction
|
2018-02-04 19:57:13 +00:00
|
|
|
self._expect_back_results = direction == 1
|
2014-11-05 04:28:11 +00:00
|
|
|
return self:search(pattern, 1)
|
|
|
|
end
|
|
|
|
|
|
|
|
return ReaderSearch
|