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")
|
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
|
2019-03-02 20:36:30 +00:00
|
|
|
self:onInput{
|
|
|
|
title = _("Enter text to search for"),
|
|
|
|
type = "text",
|
2019-08-30 11:27:03 +00:00
|
|
|
buttons = {
|
|
|
|
{
|
|
|
|
{
|
|
|
|
text = _("Cancel"),
|
|
|
|
callback = function()
|
|
|
|
self:closeInputDialog()
|
|
|
|
end,
|
|
|
|
},
|
|
|
|
{
|
2019-12-06 21:55:39 +00:00
|
|
|
text = backward_text,
|
2019-08-30 11:27:03 +00:00
|
|
|
callback = function()
|
|
|
|
self:onShowSearchDialog(self.input_dialog:getInputText(), 1)
|
|
|
|
self:closeInputDialog()
|
|
|
|
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()
|
|
|
|
self:onShowSearchDialog(self.input_dialog:getInputText(), 0)
|
|
|
|
self:closeInputDialog()
|
|
|
|
end,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2017-02-28 21:46:32 +00:00
|
|
|
}
|
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()
|
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
|
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
|
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
|
|
|
|
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,
|
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,
|
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,
|
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,
|
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)
|
2016-06-14 18:34:46 +00:00
|
|
|
if pattern == nil or pattern == '' then return end
|
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
|