diff --git a/base b/base index af763e714..5a83d4aad 160000 --- a/base +++ b/base @@ -1 +1 @@ -Subproject commit af763e714395a9bf1a2356f7fb99a020c0387684 +Subproject commit 5a83d4aaded90420306380613ccdc4613a53a9ce diff --git a/frontend/apps/reader/modules/readerhighlight.lua b/frontend/apps/reader/modules/readerhighlight.lua index b1b5829d0..530a8a91c 100644 --- a/frontend/apps/reader/modules/readerhighlight.lua +++ b/frontend/apps/reader/modules/readerhighlight.lua @@ -111,19 +111,24 @@ function ReaderHighlight:onSetDimensions(dimen) end function ReaderHighlight:clear() + if self.ui.document.info.has_pages then + self.view.highlight.temp = {} + else + self.ui.document:clearSelection() + end + UIManager:setDirty(self.dialog, "partial") if self.hold_pos then - if self.ui.document.info.has_pages then - self.view.highlight.temp[self.hold_pos.page] = nil - else - self.ui.document:clearSelection() - end self.hold_pos = nil self.selected_text = nil - UIManager:setDirty(self.dialog, "partial") return true end end +function ReaderHighlight:onClearHighlight() + self:clear() + return true +end + function ReaderHighlight:onTap(arg, ges) if not self:clear() then if self.ui.document.info.has_pages then @@ -333,11 +338,10 @@ function ReaderHighlight:onHoldRelease() }, { { - text = _("Share"), - enabled = false, + text = _("Search"), callback = function() - self:shareHighlight() - self:onClose() + self:onHighlightSearch() + UIManager:close(self.highlight_dialog) end, }, { @@ -357,16 +361,20 @@ function ReaderHighlight:onHoldRelease() return true end -function ReaderHighlight:onHighlight() +function ReaderHighlight:highlightFromHoldPos() if self.hold_pos then if not self.selected_text then self.selected_text = self.ui.document:getTextFromPositions(self.hold_pos, self.hold_pos) DEBUG("selected text:", self.selected_text) end - self:saveHighlight() end end +function ReaderHighlight:onHighlight() + self:highlightFromHoldPos() + self:saveHighlight() +end + function ReaderHighlight:saveHighlight() DEBUG("save highlight") local page = self.hold_pos.page @@ -427,6 +435,14 @@ function ReaderHighlight:lookupWikipedia() end end +function ReaderHighlight:onHighlightSearch() + DEBUG("search highlight") + self:highlightFromHoldPos() + if self.selected_text then + self.ui:handleEvent(Event:new("ShowSearchDialog", self.selected_text.text)) + end +end + function ReaderHighlight:shareHighlight() DEBUG("share highlight") end diff --git a/frontend/apps/reader/modules/readermenu.lua b/frontend/apps/reader/modules/readermenu.lua index bc1e3f285..ea499c083 100644 --- a/frontend/apps/reader/modules/readermenu.lua +++ b/frontend/apps/reader/modules/readermenu.lua @@ -208,7 +208,11 @@ function ReaderMenu:onShowReaderMenu() end main_menu.close_callback = function () - UIManager:close(menu_container) + self.ui:handleEvent(Event:new("CloseReaderMenu")) + end + + main_menu.touch_menu_callback = function () + self.ui:handleEvent(Event:new("CloseConfigMenu")) end menu_container[1] = main_menu diff --git a/frontend/apps/reader/modules/readersearch.lua b/frontend/apps/reader/modules/readersearch.lua new file mode 100644 index 000000000..ce35ea661 --- /dev/null +++ b/frontend/apps/reader/modules/readersearch.lua @@ -0,0 +1,100 @@ +local InputContainer = require("ui/widget/container/inputcontainer") +local ButtonDialog = require("ui/widget/buttondialog") +local UIManager = require("ui/uimanager") +local Geom = require("ui/geometry") +local Screen = require("ui/screen") +local DEBUG = require("dbg") +local _ = require("gettext") + +local ReaderSearch = InputContainer:new{ + direction = 0, -- 0 for search forward, 1 for search backward + case_insensitive = 1, -- default to case insensitive +} + +function ReaderSearch:init() + self.ui.menu:registerToMainMenu(self) +end + +function ReaderSearch:addToMainMenu(tab_item_table) + table.insert(tab_item_table.plugins, { + text = _("Fulltext search"), + tap_input = { + title = _("Input text to search for"), + type = "text", + callback = function(input) + self:onShowSearchDialog(input) + end, + }, + }) +end + +function ReaderSearch:onShowSearchDialog(text) + local do_search = function(search_func, text, param) + return function() + local res = search_func(self, text, param) + if res then + self.ui.link:onGotoLink(res[1].start) + end + end + end + self.search_dialog = ButtonDialog:new{ + alpha = 0.5, + buttons = { + { + { + text = "|<", + callback = do_search(self.searchFromStart, text), + }, + { + text = "<", + callback = do_search(self.searchNext, text, 1), + }, + { + text = ">", + callback = do_search(self.searchNext, text, 0), + }, + { + text = ">|", + callback = do_search(self.searchFromEnd, text), + }, + } + }, + tap_close_callback = function() + DEBUG("highlight clear") + self.ui.highlight:clear() + end, + } + local res = do_search(self.searchFromCurrent, text, 0)() + UIManager:show(self.search_dialog) + UIManager:setDirty(self.dialog, "partial") + return true +end + +function ReaderSearch:search(pattern, origin) + local direction = self.direction + local case = self.case_insensitive + return self.ui.document:findText(pattern, origin, direction, case) +end + +function ReaderSearch:searchFromStart(pattern) + self.direction = 0 + return self:search(pattern, -1) +end + +function ReaderSearch:searchFromEnd(pattern) + self.direction = 1 + return self:search(pattern, -1) +end + +function ReaderSearch:searchFromCurrent(pattern, direction) + self.direction = direction + return self:search(pattern, 0) +end + +-- ignore current page and search next occurrence +function ReaderSearch:searchNext(pattern, direction) + self.direction = direction + return self:search(pattern, 1) +end + +return ReaderSearch diff --git a/frontend/apps/reader/readerui.lua b/frontend/apps/reader/readerui.lua index 2cd9d5c5c..cf73ece7e 100644 --- a/frontend/apps/reader/readerui.lua +++ b/frontend/apps/reader/readerui.lua @@ -37,9 +37,10 @@ local ReaderDictionary = require("apps/reader/modules/readerdictionary") local ReaderWikipedia = require("apps/reader/modules/readerwikipedia") local ReaderHyphenation = require("apps/reader/modules/readerhyphenation") local ReaderActivityIndicator = require("apps/reader/modules/readeractivityindicator") +local FileManagerHistory = require("apps/filemanager/filemanagerhistory") +local ReaderSearch = require("apps/reader/modules/readersearch") local ReaderLink = require("apps/reader/modules/readerlink") local PluginLoader = require("apps/reader/pluginloader") -local FileManagerHistory = require("apps/filemanager/filemanagerhistory") --[[ This is an abstraction for a reader interface @@ -280,6 +281,12 @@ function ReaderUI:init() ui = self }) end + -- fulltext search + self:registerModule("search", ReaderSearch:new{ + dialog = self.dialog, + view = self.view, + ui = self + }) -- koreader plugins for _,module in ipairs(PluginLoader:loadPlugins()) do diff --git a/frontend/document/credocument.lua b/frontend/document/credocument.lua index 6e471615b..c644696cf 100644 --- a/frontend/document/credocument.lua +++ b/frontend/document/credocument.lua @@ -312,6 +312,7 @@ function CreDocument:setFontFace(new_font_face) end function CreDocument:clearSelection() + DEBUG("clear selection") self._document:clearSelection() end @@ -418,6 +419,11 @@ function CreDocument:setStatusLineProp(prop) self._document:setStringProperty("window.status.line", prop) end +function CreDocument:findText(pattern, origin, reverse, caseInsensitive) + DEBUG("CreDocument: find text", pattern, origin, reverse, caseInsensitive) + return self._document:findText(pattern, origin, reverse, caseInsensitive) +end + function CreDocument:register(registry) registry:addProvider("txt", "application/txt", self) registry:addProvider("epub", "application/epub", self) diff --git a/frontend/document/document.lua b/frontend/document/document.lua index f274b6dda..c77de744d 100644 --- a/frontend/document/document.lua +++ b/frontend/document/document.lua @@ -217,6 +217,10 @@ function Document:getCoverPageImage() return nil end +function Document:findText() + return nil +end + function Document:getFullPageHash(pageno, zoom, rotation, gamma, render_mode) return "renderpg|"..self.file.."|"..self.mod_time.."|"..pageno.."|" ..zoom.."|"..rotation.."|"..gamma.."|"..render_mode diff --git a/frontend/ui/uimanager.lua b/frontend/ui/uimanager.lua index 829fdcb0b..2656d9f66 100644 --- a/frontend/ui/uimanager.lua +++ b/frontend/ui/uimanager.lua @@ -384,12 +384,14 @@ function UIManager:run() if force_fast_refresh then waveform_mode = self.fast_waveform_mode end - if self.update_region_func then - local update_region = self.update_region_func() - -- in some rare cases update region has 1 pixel offset - Screen:refresh(refresh_type, waveform_mode, - update_region.x-1, update_region.y-1, - update_region.w+2, update_region.h+2) + if self.update_regions_func then + local update_regions = self.update_regions_func() + for _, update_region in ipairs(update_regions) do + -- in some rare cases update region has 1 pixel offset + Screen:refresh(refresh_type, waveform_mode, + update_region.x-1, update_region.y-1, + update_region.w+2, update_region.h+2) + end else Screen:refresh(refresh_type, waveform_mode) end @@ -398,7 +400,7 @@ function UIManager:run() elseif not force_partial_refresh and not force_full_refresh then self.refresh_count = (self.refresh_count + 1)%self.FULL_REFRESH_COUNT end - self.update_region_func = nil + self.update_regions_func = nil end self:checkTasks() diff --git a/frontend/ui/widget/dictquicklookup.lua b/frontend/ui/widget/dictquicklookup.lua index dd9b1d0de..ec2e247fb 100644 --- a/frontend/ui/widget/dictquicklookup.lua +++ b/frontend/ui/widget/dictquicklookup.lua @@ -187,10 +187,10 @@ function DictQuickLookup:update() end, }, { - text = _("More"), - enabled = false, + text = _("Search"), callback = function() - self.ui:handleEvent(Event:new("HighlightMore")) + self.ui:handleEvent(Event:new("HighlightSearch")) + UIManager:close(self) end, }, }, @@ -286,10 +286,10 @@ function DictQuickLookup:changeDictionary(index) local orig_dimen = self.dict_frame and self.dict_frame.dimen or Geom:new{} self:update() - UIManager.update_region_func = function() + UIManager.update_regions_func = function() local update_region = self.dict_frame.dimen:combine(orig_dimen) - DEBUG("update region", update_region) - return update_region + DEBUG("update dict region", update_region) + return {update_region} end end diff --git a/frontend/ui/widget/touchmenu.lua b/frontend/ui/widget/touchmenu.lua index e68537277..501ccc557 100644 --- a/frontend/ui/widget/touchmenu.lua +++ b/frontend/ui/widget/touchmenu.lua @@ -465,6 +465,9 @@ function TouchMenu:netToggle() end function TouchMenu:switchMenuTab(tab_num) + if self.touch_menu_callback then + self.touch_menu_callback() + end if self.tab_item_table[tab_num].callback then self.tab_item_table[tab_num].callback() end @@ -516,6 +519,9 @@ function TouchMenu:onSwipe(arg, ges_ev) end function TouchMenu:onMenuSelect(item) + if self.touch_menu_callback then + self.touch_menu_callback() + end if item.tap_input then self:closeMenu() self:onMenuInput(item.tap_input) @@ -547,6 +553,9 @@ function TouchMenu:onMenuSelect(item) end function TouchMenu:onMenuHold(item) + if self.touch_menu_callback then + self.touch_menu_callback() + end if item.hold_input then self:closeMenu() self:onMenuInput(item.hold_input) diff --git a/frontend/ui/widget/virtualkeyboard.lua b/frontend/ui/widget/virtualkeyboard.lua index 1dd73b90c..0de815cd3 100644 --- a/frontend/ui/widget/virtualkeyboard.lua +++ b/frontend/ui/widget/virtualkeyboard.lua @@ -95,27 +95,47 @@ function VirtualKey:init() end end +function VirtualKey:update_keyboard() + UIManager.update_regions_func = function() + DEBUG("update key region", self[1].dimen) + return {self[1].dimen} + end + UIManager:setDirty(self.keyboard, "partial") +end + +function VirtualKey:update_keyboard_inputbox() + local inputbox = self.keyboard.inputbox + UIManager.update_regions_func = function() + DEBUG("update keyboard and inputbox", self[1].dimen, inputbox.dimen) + return {self[1].dimen, inputbox.dimen} + end + UIManager:setDirty(inputbox, "partial") + UIManager:setDirty(self.keyboard, "partial") +end + function VirtualKey:onTapSelect() self[1].invert = true + self:update_keyboard_inputbox() if self.callback then self.callback() end - UIManager:scheduleIn(0.02, function() self:invert(false) end) + UIManager:scheduleIn(0.2, function() self:invert(false) end) return true end function VirtualKey:onHoldSelect() self[1].invert = true + self:update_keyboard_inputbox() if self.hold_callback then self.hold_callback() end - UIManager:scheduleIn(0.5, function() self:invert(false) end) + UIManager:scheduleIn(0.2, function() self:invert(false) end) return true end function VirtualKey:invert(invert) self[1].invert = invert - UIManager:setDirty(self.keyboard, "partial") + self:update_keyboard() end local VirtualKeyboard = InputContainer:new{ @@ -298,28 +318,23 @@ function VirtualKeyboard:setLayout(key) if self.utf8mode then self.umlautmode = false end end self:initLayout() + UIManager.update_regions_func = nil UIManager:setDirty(self, "partial") end function VirtualKeyboard:addChar(key) DEBUG("add char", key) self.inputbox:addChar(key) - UIManager:setDirty(self, "partial") - UIManager:setDirty(self.inputbox, "partial") end function VirtualKeyboard:delChar() DEBUG("delete char") self.inputbox:delChar() - UIManager:setDirty(self, "partial") - UIManager:setDirty(self.inputbox, "partial") end function VirtualKeyboard:clear() DEBUG("clear input") self.inputbox:clear() - UIManager:setDirty(self, "partial") - UIManager:setDirty(self.inputbox, "partial") end return VirtualKeyboard diff --git a/spec/unit/readersearch_spec.lua b/spec/unit/readersearch_spec.lua new file mode 100644 index 000000000..7c982277c --- /dev/null +++ b/spec/unit/readersearch_spec.lua @@ -0,0 +1,94 @@ +require("commonrequire") +local DocumentRegistry = require("document/documentregistry") +local ReaderUI = require("apps/reader/readerui") +local DEBUG = require("dbg") + +local sample_epub = "spec/front/unit/data/juliet.epub" + +describe("Readersearch module", function() + describe("search API for EPUB documents", function() + local doc, search, rolling + setup(function() + local readerui = ReaderUI:new{ + document = DocumentRegistry:openDocument(sample_epub), + } + doc = readerui.document + search = readerui.search + rolling = readerui.rolling + end) + it("should search backward", function() + rolling:gotoPage(10) + assert.truthy(search:searchFromCurrent("Verona", 1)) + for i = 1, 100, 10 do + rolling:gotoPage(i) + local words = search:searchFromCurrent("Verona", 1) + if words then + for _, word in ipairs(words) do + local pageno = doc:getPageFromXPointer(word.start) + --DEBUG("found at pageno", pageno) + assert.truthy(pageno <= i) + end + end + end + end) + it("should search forward", function() + rolling:gotoPage(10) + assert.truthy(search:searchFromCurrent("Verona", 0)) + for i = 1, 100, 10 do + rolling:gotoPage(i) + local words = search:searchFromCurrent("Verona", 0) + if words then + for _, word in ipairs(words) do + local pageno = doc:getPageFromXPointer(word.start) + --DEBUG("found at pageno", pageno) + assert.truthy(pageno >= i) + end + end + end + end) + it("should find the first occurrence", function() + for i = 10, 100, 10 do + rolling:gotoPage(i) + local words = search:searchFromStart("Verona") + assert.truthy(words) + local pageno = doc:getPageFromXPointer(words[1].start) + assert.are.equal(8, pageno) + end + for i = 1, 5, 1 do + rolling:gotoPage(i) + local words = search:searchFromStart("Verona") + assert(words == nil) + end + end) + it("should find the last occurrence", function() + for i = 100, 200, 10 do + rolling:gotoPage(i) + local words = search:searchFromEnd("Verona") + assert.truthy(words) + local pageno = doc:getPageFromXPointer(words[1].start) + assert.are.equal(208, pageno) + end + for i = 230, 235, 1 do + rolling:gotoPage(i) + local words = search:searchFromEnd("Verona") + assert(words == nil) + end + end) + it("should find all occurrences", function() + local count = 0 + rolling:gotoPage(1) + local words = search:searchFromCurrent("Verona", 0) + while words do + count = count + #words + for _, word in ipairs(words) do + --DEBUG("found word", word.start) + end + doc:gotoXPointer(words[1].start) + words = search:searchNext("Verona", 0) + end + assert.are.equal(13, count) + end) + end) + describe("search API for PDF documents", function() + end) +end)