From 7b10d66e1b812359be8db285732180288633a499 Mon Sep 17 00:00:00 2001 From: chrox Date: Wed, 20 Aug 2014 14:41:45 +0800 Subject: [PATCH] enable Wikipedia support Since wikipedia server is language specific we should first detect language of the text by querying Google Translate. Make sure you have access to both Google Translate and Wikipedia service to use this function. --- .../apps/reader/modules/readerdictionary.lua | 27 ++---- .../apps/reader/modules/readerhighlight.lua | 50 +++++++---- .../apps/reader/modules/readerwikipedia.lua | 51 +++++++++++ frontend/apps/reader/readerui.lua | 8 ++ frontend/ui/translator.lua | 85 +++++++++++++++++++ frontend/ui/widget/dictquicklookup.lua | 38 ++++++++- frontend/ui/wikipedia.lua | 84 ++++++++++++++++++ 7 files changed, 301 insertions(+), 42 deletions(-) create mode 100644 frontend/apps/reader/modules/readerwikipedia.lua create mode 100644 frontend/ui/translator.lua create mode 100644 frontend/ui/wikipedia.lua diff --git a/frontend/apps/reader/modules/readerdictionary.lua b/frontend/apps/reader/modules/readerdictionary.lua index ecabd4eee..6f5119283 100644 --- a/frontend/apps/reader/modules/readerdictionary.lua +++ b/frontend/apps/reader/modules/readerdictionary.lua @@ -12,6 +12,7 @@ local ReaderDictionary = EventListener:new{} function ReaderDictionary:onLookupWord(word, box, highlight) self.highlight = highlight self:stardictLookup(word, box) + return true end function ReaderDictionary:stardictLookup(word, box) @@ -47,24 +48,7 @@ end function ReaderDictionary:showDict(results, box) if results and results[1] then - DEBUG("showing quick lookup dictionary window") - local align = "center" - local region = Geom:new{ - x = 0, y = 0, - w = Screen:getWidth(), - h = Screen:getHeight(), - } - if box then - if box.y + box.h/2 < Screen:getHeight()/2 then - region.y = box.y + box.h - region.h = Screen:getHeight() - box.y - box.h - align = "top" - else - region.y = 0 - region.h = box.y - align = "bottom" - end - end + DEBUG("showing quick lookup window") UIManager:show(DictQuickLookup:new{ ui = self.ui, highlight = self.highlight, @@ -72,9 +56,9 @@ function ReaderDictionary:showDict(results, box) results = results, dictionary = self.default_dictionary, width = Screen:getWidth() - Screen:scaleByDPI(80), - height = math.min(region.h*0.7, Screen:getHeight()*0.5), - region = region, - align = align, + word_box = box, + -- differentiate between dict and wiki + wiki = self.wiki, }) end end @@ -82,6 +66,7 @@ end function ReaderDictionary:onUpdateDefaultDict(dict) DEBUG("make default dictionary:", dict) self.default_dictionary = dict + return true end function ReaderDictionary:onReadSettings(config) diff --git a/frontend/apps/reader/modules/readerhighlight.lua b/frontend/apps/reader/modules/readerhighlight.lua index 494abd8e9..c6d869e9f 100644 --- a/frontend/apps/reader/modules/readerhighlight.lua +++ b/frontend/apps/reader/modules/readerhighlight.lua @@ -294,8 +294,7 @@ function ReaderHighlight:onHoldRelease() text = _("Highlight"), callback = function() self:saveHighlight() - UIManager:close(self.highlight_dialog) - self:handleEvent(Event:new("Tap")) + self:onClose() end, }, { @@ -303,39 +302,44 @@ function ReaderHighlight:onHoldRelease() enabled = false, callback = function() self:addNote() - UIManager:close(self.highlight_dialog) - self:handleEvent(Event:new("Tap")) + self:onClose() end, }, }, { + { + text = _("Wikipedia"), + callback = function() + UIManager:scheduleIn(0.1, function() + self:lookupWikipedia() + self:onClose() + end) + end, + }, { text = _("Translate"), enabled = false, callback = function() self:translate(self.selected_text) - UIManager:close(self.highlight_dialog) - self:handleEvent(Event:new("Tap")) - end, - }, - { - text = _("Share"), - enabled = false, - callback = function() - self:shareHighlight() - UIManager:close(self.highlight_dialog) - self:handleEvent(Event:new("Tap")) + self:onClose() end, }, }, { + { + text = _("Share"), + enabled = false, + callback = function() + self:shareHighlight() + self:onClose() + end, + }, { text = _("More"), enabled = false, callback = function() self:moreAction() - UIManager:close(self.highlight_dialog) - self:handleEvent(Event:new("Tap")) + self:onClose() end, }, }, @@ -411,6 +415,12 @@ function ReaderHighlight:addNote() DEBUG("add Note") end +function ReaderHighlight:lookupWikipedia() + if self.selected_text then + self.ui:handleEvent(Event:new("LookupWikipedia", self.selected_text.text)) + end +end + function ReaderHighlight:shareHighlight() DEBUG("share highlight") end @@ -436,4 +446,10 @@ function ReaderHighlight:onSaveSettings() self.ui.doc_settings:saveSetting("highlight_drawer", self.view.highlight.saved_drawer) end +function ReaderHighlight:onClose() + UIManager:close(self.highlight_dialog) + -- clear highlighted text + self:handleEvent(Event:new("Tap")) +end + return ReaderHighlight diff --git a/frontend/apps/reader/modules/readerwikipedia.lua b/frontend/apps/reader/modules/readerwikipedia.lua new file mode 100644 index 000000000..17821e181 --- /dev/null +++ b/frontend/apps/reader/modules/readerwikipedia.lua @@ -0,0 +1,51 @@ +local ReaderDictionary = require("apps/reader/modules/readerdictionary") +local EventListener = require("ui/widget/eventlistener") +local Translator = require("ui/translator") +local Wikipedia = require("ui/wikipedia") +local UIManager = require("ui/uimanager") +local Geom = require("ui/geometry") +local Screen = require("ui/screen") +local JSON = require("JSON") +local DEBUG = require("dbg") +local _ = require("gettext") + +-- Wikipedia as a special dictionary +local ReaderWikipedia = ReaderDictionary:new{ + -- identify itself + wiki = true, + no_page = _("No wiki page found."), +} + +function ReaderWikipedia:onLookupWikipedia(word, box) + local lang = Translator:detect(word) or "en" + -- convert "zh-CN" and "zh-TW" to "zh" + lang = lang:match("(.*)-") or lang + local results = {} + local ok, pages = pcall(Wikipedia.wikintro, Wikipedia, word, lang) + if ok and pages then + for pageid, page in pairs(pages) do + local result = { + dict = _("Wikipedia"), + word = page.title, + definition = page.extract or self.no_page, + } + table.insert(results, result) + end + DEBUG("lookup result:", word, results) + self:showDict(results, box) + else + DEBUG("error:", pages) + -- dummy results + results = { + { + dict = _("Wikipedia"), + word = word, + definition = self.no_page, + } + } + DEBUG("dummy result table:", word, results) + self:showDict(results, box) + end +end + +return ReaderWikipedia diff --git a/frontend/apps/reader/readerui.lua b/frontend/apps/reader/readerui.lua index 9bc50c61e..352da8887 100644 --- a/frontend/apps/reader/readerui.lua +++ b/frontend/apps/reader/readerui.lua @@ -29,6 +29,7 @@ local ReaderHighlight = require("apps/reader/modules/readerhighlight") local ReaderScreenshot = require("apps/reader/modules/readerscreenshot") local ReaderFrontLight = require("apps/reader/modules/readerfrontlight") 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 ReaderLink = require("apps/reader/modules/readerlink") @@ -142,6 +143,13 @@ function ReaderUI:init() ui = self, document = self.document, }) + -- wikipedia + table.insert(self, ReaderWikipedia:new{ + dialog = self.dialog, + view = self[1], + ui = self, + document = self.document, + }) -- screenshot controller table.insert(self.active_widgets, ReaderScreenshot:new{ dialog = self.dialog, diff --git a/frontend/ui/translator.lua b/frontend/ui/translator.lua new file mode 100644 index 000000000..357d8c83b --- /dev/null +++ b/frontend/ui/translator.lua @@ -0,0 +1,85 @@ +local socket = require('socket') +local url = require('socket.url') +local http = require('socket.http') +local https = require('ssl.https') +local ltn12 = require('ltn12') +local JSON = require("JSON") +local DEBUG = require("dbg") + +--[[ +-- Translate text using Google Translate. +-- http://translate.google.com/translate_a/t?client=z&ie=UTF-8&oe=UTF-8&hl=en&tl=en&text=hello +--]] + +local Translator = { + trans_servers = { + "http://translate.google.cn", + "http://translate.google.com", + }, + trans_path = "/translate_a/t", + trans_params = { + client = "z", -- client z returns normal JSON result + ie = "UTF-8", + oe = "UTF-8", + hl = "en", + tl = "en", + sl = nil, -- we don't specify source languagae to detect language + }, + default_lang = "en", +} + +function Translator:getTransServer() + return G_reader_settings:readSetting("trans_server") or self.trans_servers[1] +end + +--[[ +-- return decoded JSON table from translate server +--]] +function Translator:loadPage(target_lang, source_lang, text) + local request, sink = {}, {} + local query = "" + self.trans_params.tl = target_lang + self.trans_params.sl = source_lang + for k,v in pairs(self.trans_params) do + query = query .. k .. '=' .. v .. '&' + end + local parsed = url.parse(self:getTransServer()) + parsed.path = self.trans_path + parsed.query = query .. "text=" .. url.escape(text) + + -- HTTP request + request['url'] = url.build(parsed) + request['method'] = 'GET' + request['sink'] = ltn12.sink.table(sink) + DEBUG("request", request) + http.TIMEOUT, https.TIMEOUT = 10, 10 + local httpRequest = parsed.scheme == 'http' and http.request or https.request + local code, headers, status = socket.skip(1, httpRequest(request)) + + -- raise error message when network is unavailable + if headers == nil then + error("Network is unreachable") + end + + local content = table.concat(sink) + if content ~= "" then + local ok, result = pcall(JSON.decode, JSON, content) + if ok and result then + --DEBUG("translate result", result) + return result + else + DEBUG("error:", result) + end + end +end + +function Translator:detect(text) + local result = self:loadPage("en", nil, text) + if result then + local src_lang = result.src + DEBUG("detected language:", src_lang) + return src_lang + end +end + +return Translator diff --git a/frontend/ui/widget/dictquicklookup.lua b/frontend/ui/widget/dictquicklookup.lua index 071b0e827..fd232affc 100644 --- a/frontend/ui/widget/dictquicklookup.lua +++ b/frontend/ui/widget/dictquicklookup.lua @@ -37,6 +37,8 @@ local DictQuickLookup = InputContainer:new{ content_face = Font:getFace("cfont", DDICT_FONT_SIZE), width = nil, height = nil, + -- box of highlighted word, quick lookup window tries to not hide the word + word_box = nil, title_padding = Screen:scaleByDPI(5), title_margin = Screen:scaleByDPI(2), @@ -85,6 +87,26 @@ function DictQuickLookup:init() end function DictQuickLookup:update() + -- calculate window dimension and try to not hide highlighted word + self.align = "center" + self.region = Geom:new{ + x = 0, y = 0, + w = Screen:getWidth(), + h = Screen:getHeight(), + } + if self.word_box then + local box = self.word_box + if box.y + box.h/2 < Screen:getHeight()/2 then + self.region.y = box.y + box.h + self.region.h = Screen:getHeight() - box.y - box.h + self.align = "top" + else + self.region.y = 0 + self.region.h = box.y + self.align = "bottom" + end + end + self.height = math.min(self.region.h*0.7, Screen:getHeight()*0.5) -- dictionary title self.dict_title = FrameContainer:new{ padding = self.title_padding, @@ -150,9 +172,11 @@ function DictQuickLookup:update() { { text = _("Wikipedia"), - enabled = false, callback = function() - self.ui:handleEvent(Event:new("HighlightWiki")) + UIManager:scheduleIn(0.1, function() + self:lookupWikipedia() + self:onClose() + end) end, }, { @@ -225,7 +249,7 @@ function DictQuickLookup:update() } self[1] = WidgetContainer:new{ align = self.align, - dimen = self.region:copy(), + dimen = self.region, FrameContainer:new{ bordersize = 0, padding = Screen:scaleByDPI(5), @@ -336,6 +360,7 @@ function DictQuickLookup:lookupInputWord(hint) self:onClose() self.input_dialog = InputDialog:new{ title = _("Input lookup word"), + input = hint, input_hint = hint or "", input_type = "text", buttons = { @@ -369,7 +394,8 @@ end function DictQuickLookup:inputLookup() local word = self.input_dialog:getInputText() if word and word ~= "" then - self.ui:handleEvent(Event:new("LookupWord", word)) + local event = self.wiki and "LookupWikipedia" or "LookupWord" + self.ui:handleEvent(Event:new(event, word)) end end @@ -378,4 +404,8 @@ function DictQuickLookup:closeInputDialog() UIManager:close(self.input_dialog) end +function DictQuickLookup:lookupWikipedia() + self.ui:handleEvent(Event:new("LookupWikipedia", self.lookupword, self.word_box)) +end + return DictQuickLookup diff --git a/frontend/ui/wikipedia.lua b/frontend/ui/wikipedia.lua new file mode 100644 index 000000000..b75bd9c05 --- /dev/null +++ b/frontend/ui/wikipedia.lua @@ -0,0 +1,84 @@ +local socket = require('socket') +local url = require('socket.url') +local http = require('socket.http') +local https = require('ssl.https') +local ltn12 = require('ltn12') +local JSON = require("JSON") +local DEBUG = require("dbg") + +--[[ +-- Query wikipedia using Wikimedia Web API. +-- http://en.wikipedia.org/w/api.php?action=query&prop=extracts&format=json&exintro=&explaintext=&redirects=&titles=hello +--]] + +local Wikipedia = { + wiki_server = "http://%s.wikipedia.org", + wiki_path = "/w/api.php", + wiki_params = { + action = "query", + prop = "extracts", + format = "json", + exintro = "", + explaintext = "", + redirects = "", + }, + default_lang = "en", +} + +function Wikipedia:getWikiServer(lang) + return string.format(self.wiki_server, lang or self.default_lang) +end + +--[[ +-- return decoded JSON table from Wikipedia +--]] +function Wikipedia:loadPage(text, lang, intro, plain) + local request, sink = {}, {} + local query = "" + self.wiki_params.exintro = intro and "" or nil + self.wiki_params.explaintext = plain and "" or nil + for k,v in pairs(self.wiki_params) do + query = query .. k .. '=' .. v .. '&' + end + local parsed = url.parse(self:getWikiServer(lang)) + parsed.path = self.wiki_path + parsed.query = query .. "titles=" .. url.escape(text) + + -- HTTP request + request['url'] = url.build(parsed) + request['method'] = 'GET' + request['sink'] = ltn12.sink.table(sink) + DEBUG("request", request) + http.TIMEOUT, https.TIMEOUT = 10, 10 + local httpRequest = parsed.scheme == 'http' and http.request or https.request + local code, headers, status = socket.skip(1, httpRequest(request)) + + -- raise error message when network is unavailable + if headers == nil then + error("Network is unreachable") + end + + local content = table.concat(sink) + if content ~= "" then + local ok, result = pcall(JSON.decode, JSON, content) + if ok and result then + DEBUG("wiki result", result) + return result + else + DEBUG("error:", result) + end + end +end + +-- extract intro passage in wiki page +function Wikipedia:wikintro(text, lang) + local result = self:loadPage(text, lang, true, true) + if result then + local query = result.query + if query then + return query.pages + end + end +end + +return Wikipedia