mirror of
https://github.com/koreader/koreader
synced 2024-10-31 21:20:20 +00:00
130f7b0773
Fix innacurate language query and rotation when back from nested lookups. More informative lookup message (langage used, search or full page) Allow for screen refresh with diagonal swipe (like in readerpaging).
661 lines
22 KiB
Lua
661 lines
22 KiB
Lua
local InputContainer = require("ui/widget/container/inputcontainer")
|
|
local WidgetContainer = require("ui/widget/container/widgetcontainer")
|
|
local FrameContainer = require("ui/widget/container/framecontainer")
|
|
local CenterContainer = require("ui/widget/container/centercontainer")
|
|
local LeftContainer = require("ui/widget/container/leftcontainer")
|
|
local ScrollTextWidget = require("ui/widget/scrolltextwidget")
|
|
local VerticalGroup = require("ui/widget/verticalgroup")
|
|
local OverlapGroup = require("ui/widget/overlapgroup")
|
|
local CloseButton = require("ui/widget/closebutton")
|
|
local ButtonTable = require("ui/widget/buttontable")
|
|
local InputDialog = require("ui/widget/inputdialog")
|
|
local TextWidget = require("ui/widget/textwidget")
|
|
local LineWidget = require("ui/widget/linewidget")
|
|
local GestureRange = require("ui/gesturerange")
|
|
local Button = require("ui/widget/button")
|
|
local UIManager = require("ui/uimanager")
|
|
local Screen = require("device").screen
|
|
local Device = require("device")
|
|
local Geom = require("ui/geometry")
|
|
local Event = require("ui/event")
|
|
local Font = require("ui/font")
|
|
local logger = require("logger")
|
|
local _ = require("gettext")
|
|
local T = require("ffi/util").template
|
|
local Blitbuffer = require("ffi/blitbuffer")
|
|
|
|
--[[
|
|
Display quick lookup word definition
|
|
]]
|
|
local DictQuickLookup = InputContainer:new{
|
|
results = nil,
|
|
lookupword = nil,
|
|
dictionary = nil,
|
|
definition = nil,
|
|
displayword = nil,
|
|
is_wiki = false,
|
|
is_fullpage = false,
|
|
dict_index = 1,
|
|
title_face = Font:getFace("tfont", 22),
|
|
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,
|
|
-- allow for disabling justification
|
|
dict_justify = G_reader_settings:nilOrTrue("dict_justify"),
|
|
|
|
title_padding = Screen:scaleBySize(5),
|
|
title_margin = Screen:scaleBySize(2),
|
|
word_padding = Screen:scaleBySize(5),
|
|
word_margin = Screen:scaleBySize(2),
|
|
-- alt padding/margin for wiki to compensate for reduced font size
|
|
wiki_word_padding = Screen:scaleBySize(7),
|
|
wiki_word_margin = Screen:scaleBySize(3),
|
|
definition_padding = Screen:scaleBySize(2),
|
|
definition_margin = Screen:scaleBySize(2),
|
|
button_padding = Screen:scaleBySize(14),
|
|
}
|
|
|
|
function DictQuickLookup:init()
|
|
self:changeToDefaultDict()
|
|
if Device:hasKeys() then
|
|
self.key_events = {
|
|
Close = { {"Back"}, doc = "close quick lookup" }
|
|
}
|
|
end
|
|
if Device:isTouchDevice() then
|
|
self.ges_events = {
|
|
TapCloseDict = {
|
|
GestureRange:new{
|
|
ges = "tap",
|
|
range = Geom:new{
|
|
x = 0, y = 0,
|
|
w = Screen:getWidth(),
|
|
h = Screen:getHeight(),
|
|
}
|
|
},
|
|
},
|
|
Swipe = {
|
|
GestureRange:new{
|
|
ges = "swipe",
|
|
range = Geom:new{
|
|
x = 0, y = 0,
|
|
w = Screen:getWidth(),
|
|
h = Screen:getHeight(),
|
|
}
|
|
},
|
|
},
|
|
-- This was for selection of a single word with simple hold
|
|
-- HoldWord = {
|
|
-- GestureRange:new{
|
|
-- ges = "hold",
|
|
-- range = function()
|
|
-- return self.region
|
|
-- end,
|
|
-- },
|
|
-- -- callback function when HoldWord is handled as args
|
|
-- args = function(word)
|
|
-- self.ui:handleEvent(
|
|
-- -- don't pass self.highlight to subsequent lookup, we want
|
|
-- -- the first to be the only one to unhighlight selection
|
|
-- -- when closed
|
|
-- Event:new("LookupWord", word, self.word_box))
|
|
-- end
|
|
-- },
|
|
-- Allow selection of one or more words (see textboxwidget.lua) :
|
|
HoldStartText = {
|
|
GestureRange:new{
|
|
ges = "hold",
|
|
range = function()
|
|
return self.region
|
|
end,
|
|
},
|
|
},
|
|
HoldReleaseText = {
|
|
GestureRange:new{
|
|
ges = "hold_release",
|
|
range = function()
|
|
return self.region
|
|
end,
|
|
},
|
|
-- callback function when HoldReleaseText is handled as args
|
|
args = function(text, hold_duration)
|
|
local lookup_target
|
|
if hold_duration < 2.0 then
|
|
-- do this lookup in the same domain (dict/wikipedia)
|
|
lookup_target = self.is_wiki and "LookupWikipedia" or "LookupWord"
|
|
else
|
|
-- but allow switching domain with a long hold
|
|
lookup_target = self.is_wiki and "LookupWord" or "LookupWikipedia"
|
|
end
|
|
if lookup_target == "LookupWikipedia" then
|
|
self:resyncWikiLanguages()
|
|
end
|
|
self.ui:handleEvent(
|
|
-- don't pass self.highlight to subsequent lookup, we want
|
|
-- the first to be the only one to unhighlight selection
|
|
-- when closed
|
|
Event:new(lookup_target, text)
|
|
)
|
|
end
|
|
},
|
|
}
|
|
end
|
|
end
|
|
|
|
-- Whether currently DictQuickLookup is working without a document.
|
|
function DictQuickLookup:isDocless()
|
|
return self.ui == nil or self.ui.highlight == nil
|
|
end
|
|
|
|
function DictQuickLookup:update()
|
|
local orig_dimen = self.dict_frame and self.dict_frame.dimen or Geom:new{}
|
|
-- calculate window dimension
|
|
self.align = "center"
|
|
self.region = Geom:new{
|
|
x = 0, y = 0,
|
|
w = Screen:getWidth(),
|
|
h = Screen:getHeight(),
|
|
}
|
|
if self.is_fullpage then
|
|
-- bigger window if fullpage being shown - this will let
|
|
-- some room anyway for footer display (time, battery...)
|
|
self.height = Screen:getHeight()
|
|
self.width = Screen:getWidth() - Screen:scaleBySize(40)
|
|
else
|
|
-- smaller window otherwise
|
|
-- try to not hide highlighted word
|
|
if self.word_box then
|
|
local box = self.word_box
|
|
if box.y + box.h/2 < Screen:getHeight()*0.3 then
|
|
self.region.y = box.y + box.h
|
|
self.region.h = Screen:getHeight() - box.y - box.h
|
|
self.align = "top"
|
|
elseif box.y + box.h/2 > Screen:getHeight()*0.7 then
|
|
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)
|
|
end
|
|
-- dictionary title
|
|
local dict_title_text = TextWidget:new{
|
|
text = self.dictionary,
|
|
face = self.title_face,
|
|
bold = true,
|
|
width = self.width,
|
|
}
|
|
-- Some different UI tweaks for dict or wiki
|
|
local lookup_word_font_size, lookup_word_padding, lookup_word_margin
|
|
if self.is_wiki then
|
|
-- visual hint : dictionary title left adjusted, Wikipedia title centered
|
|
dict_title_text = CenterContainer:new{
|
|
dimen = Geom:new{
|
|
w = self.width,
|
|
h = dict_title_text:getSize().h,
|
|
},
|
|
dict_title_text
|
|
}
|
|
-- Wikipedia has longer titles, so use a smaller font
|
|
lookup_word_font_size = 18
|
|
lookup_word_padding = self.wiki_word_padding
|
|
lookup_word_margin = self.wiki_word_margin
|
|
-- Keep a copy of self.wiki_languages for use
|
|
-- by DictQuickLookup:resyncWikiLanguages()
|
|
self.wiki_languages_copy = self.wiki_languages and {unpack(self.wiki_languages)} or nil
|
|
else
|
|
-- Usual font size for dictionary
|
|
lookup_word_font_size = 22
|
|
lookup_word_padding = self.word_padding
|
|
lookup_word_margin = self.word_margin
|
|
end
|
|
self.dict_title = FrameContainer:new{
|
|
padding = self.title_padding,
|
|
margin = self.title_margin,
|
|
bordersize = 0,
|
|
dict_title_text
|
|
}
|
|
-- lookup word
|
|
local lookup_word = Button:new{
|
|
padding = lookup_word_padding,
|
|
margin = lookup_word_margin,
|
|
bordersize = 0,
|
|
text = self.displayword,
|
|
text_font_face = "tfont",
|
|
text_font_size = lookup_word_font_size,
|
|
hold_callback = function() self:lookupInputWord(self.lookupword) end,
|
|
}
|
|
-- word definition
|
|
local definition = FrameContainer:new{
|
|
padding = self.definition_padding,
|
|
margin = self.definition_margin,
|
|
bordersize = 0,
|
|
ScrollTextWidget:new{
|
|
text = self.definition,
|
|
face = self.content_face,
|
|
width = self.width,
|
|
-- get a bit more height for definition as wiki has one less button raw
|
|
height = self.is_fullpage and self.height*0.75 or self.height*0.7,
|
|
dialog = self,
|
|
justified = self.dict_justify,
|
|
},
|
|
}
|
|
-- Different sets of buttons if fullpage or not
|
|
local buttons
|
|
if self.is_fullpage then
|
|
-- Only a single wide close button, get a little more room for
|
|
-- closing by taping at bottom (on footer or on this button)
|
|
buttons = {
|
|
{
|
|
{
|
|
text = "Close",
|
|
callback = function()
|
|
UIManager:close(self)
|
|
end,
|
|
},
|
|
},
|
|
}
|
|
else
|
|
buttons = {
|
|
{
|
|
{
|
|
text = "<<",
|
|
enabled = self:isPrevDictAvaiable(),
|
|
callback = function()
|
|
self:changeToPrevDict()
|
|
end,
|
|
},
|
|
{
|
|
text = self:getHighlightText(),
|
|
enabled = select(2, self:getHighlightText()),
|
|
callback = function()
|
|
self.ui:handleEvent(Event:new("Highlight"))
|
|
self:update()
|
|
end,
|
|
},
|
|
{
|
|
text = ">>",
|
|
enabled = self:isNextDictAvaiable(),
|
|
callback = function()
|
|
self:changeToNextDict()
|
|
end,
|
|
},
|
|
},
|
|
{
|
|
{
|
|
-- if dictionary result, do the same search on wikipedia
|
|
-- if already wiki, get the full page for the current result
|
|
text = self.is_wiki and _("Wikipedia full") or _("Wikipedia"),
|
|
callback = function()
|
|
UIManager:scheduleIn(0.1, function()
|
|
self:lookupWikipedia(self.is_wiki) -- will get_fullpage if is_wiki
|
|
end)
|
|
end,
|
|
},
|
|
-- Rotate thru available wikipedia languages (disabled if dictionary window)
|
|
-- (replace previous unimplemented "Add Note")
|
|
{
|
|
-- if more than one language, enable it and display "current lang > next lang"
|
|
-- otherwise, just display current lang
|
|
text = self.is_wiki and ( #self.wiki_languages > 1 and self.wiki_languages[1].." > "..self.wiki_languages[2] or self.wiki_languages[1] ) or "-",
|
|
enabled = self.is_wiki and #self.wiki_languages > 1,
|
|
callback = function()
|
|
self:resyncWikiLanguages(true) -- rotate & resync them
|
|
UIManager:close(self)
|
|
self:lookupWikipedia()
|
|
end,
|
|
},
|
|
{
|
|
text = (self.is_wiki or self:isDocless()) and _("Close") or _("Search"),
|
|
callback = function()
|
|
if not self.is_wiki then
|
|
self.ui:handleEvent(Event:new("HighlightSearch"))
|
|
end
|
|
UIManager:close(self)
|
|
end,
|
|
},
|
|
},
|
|
}
|
|
end
|
|
|
|
local button_table = ButtonTable:new{
|
|
width = math.max(self.width, definition:getSize().w),
|
|
button_font_face = "cfont",
|
|
button_font_size = 20,
|
|
buttons = buttons,
|
|
zero_sep = true,
|
|
show_parent = self,
|
|
}
|
|
local title_bar = LineWidget:new{
|
|
dimen = Geom:new{
|
|
w = button_table:getSize().w + self.button_padding,
|
|
h = Screen:scaleBySize(2),
|
|
}
|
|
}
|
|
|
|
self.dict_bar = OverlapGroup:new{
|
|
dimen = {
|
|
w = button_table:getSize().w + self.button_padding,
|
|
h = self.dict_title:getSize().h
|
|
},
|
|
self.dict_title,
|
|
CloseButton:new{ window = self, },
|
|
}
|
|
|
|
self.dict_frame = FrameContainer:new{
|
|
radius = 8,
|
|
bordersize = 3,
|
|
padding = 0,
|
|
margin = 0,
|
|
background = Blitbuffer.COLOR_WHITE,
|
|
VerticalGroup:new{
|
|
align = "left",
|
|
self.dict_bar,
|
|
title_bar,
|
|
-- word
|
|
LeftContainer:new{
|
|
dimen = Geom:new{
|
|
w = title_bar:getSize().w,
|
|
h = lookup_word:getSize().h,
|
|
},
|
|
lookup_word,
|
|
},
|
|
-- definition
|
|
CenterContainer:new{
|
|
dimen = Geom:new{
|
|
w = title_bar:getSize().w,
|
|
h = definition:getSize().h,
|
|
},
|
|
definition,
|
|
},
|
|
-- buttons
|
|
CenterContainer:new{
|
|
dimen = Geom:new{
|
|
w = title_bar:getSize().w,
|
|
h = button_table:getSize().h,
|
|
},
|
|
button_table,
|
|
}
|
|
}
|
|
}
|
|
self[1] = WidgetContainer:new{
|
|
align = self.align,
|
|
dimen = self.region,
|
|
FrameContainer:new{
|
|
bordersize = 0,
|
|
padding = Screen:scaleBySize(5),
|
|
self.dict_frame,
|
|
}
|
|
}
|
|
UIManager:setDirty("all", function()
|
|
local update_region = self.dict_frame.dimen:combine(orig_dimen)
|
|
logger.dbg("update dict region", update_region)
|
|
return "partial", update_region
|
|
end)
|
|
end
|
|
|
|
function DictQuickLookup:onCloseWidget()
|
|
UIManager:setDirty(nil, function()
|
|
return "partial", self.dict_frame.dimen
|
|
end)
|
|
return true
|
|
end
|
|
|
|
function DictQuickLookup:onShow()
|
|
UIManager:setDirty(self, function()
|
|
return "ui", self.dict_frame.dimen
|
|
end)
|
|
return true
|
|
end
|
|
|
|
function DictQuickLookup:getHighlightedItem()
|
|
if self:isDocless() then return end
|
|
return self.ui.highlight:getHighlightBookmarkItem()
|
|
end
|
|
|
|
function DictQuickLookup:getHighlightText()
|
|
local item = self:getHighlightedItem()
|
|
if not item then
|
|
return _("Highlight"), false
|
|
elseif self.ui.bookmark:isBookmarkAdded(item) then
|
|
return _("Unhighlight"), false
|
|
else
|
|
return _("Highlight"), true
|
|
end
|
|
end
|
|
|
|
function DictQuickLookup:isPrevDictAvaiable()
|
|
return self.dict_index > 1
|
|
end
|
|
|
|
function DictQuickLookup:isNextDictAvaiable()
|
|
return self.dict_index < #self.results
|
|
end
|
|
|
|
function DictQuickLookup:changeToPrevDict()
|
|
if self:isPrevDictAvaiable() then
|
|
self:changeDictionary(self.dict_index - 1)
|
|
elseif #self.results > 1 then -- restart at end if first reached
|
|
self:changeDictionary(#self.results)
|
|
end
|
|
end
|
|
|
|
function DictQuickLookup:changeToNextDict()
|
|
if self:isNextDictAvaiable() then
|
|
self:changeDictionary(self.dict_index + 1)
|
|
elseif #self.results > 1 then -- restart at first if end reached
|
|
self:changeDictionary(1)
|
|
end
|
|
end
|
|
|
|
function DictQuickLookup:changeDictionary(index)
|
|
if not self.results[index] then return end
|
|
self.dict_index = index
|
|
self.dictionary = self.results[index].dict
|
|
self.lookupword = self.results[index].word
|
|
self.definition = self.results[index].definition
|
|
self.is_fullpage = self.results[index].is_fullpage
|
|
if self.is_fullpage then
|
|
self.displayword = self.lookupword
|
|
else
|
|
-- add "dict_index / nbresults" to displayword, so we know where
|
|
-- we're at and what's yet to see
|
|
self.displayword = self.lookupword.." "..index.." / "..#self.results
|
|
-- add queried word to 1st result's definition, so we can see
|
|
-- what was the selected text and if we selected wrong
|
|
if index == 1 then
|
|
self.definition = self.definition.."\n_______\n"..T(_("(query : %1)"), self.word)
|
|
end
|
|
end
|
|
|
|
self:update()
|
|
end
|
|
|
|
function DictQuickLookup:changeToDefaultDict()
|
|
if self.dictionary then
|
|
-- dictionaries that have definition of the first word(accurate word)
|
|
-- excluding Fuzzy queries.
|
|
local n_accurate_dicts = nil
|
|
local default_word = self.results[1].word
|
|
for i=1, #self.results do
|
|
if self.results[i].word == default_word then
|
|
n_accurate_dicts = i
|
|
else
|
|
break
|
|
end
|
|
end
|
|
-- change to dictionary specified by self.dictionary
|
|
for i=1, n_accurate_dicts do
|
|
if self.results[i].dict == self.dictionary then
|
|
self:changeDictionary(i)
|
|
break
|
|
end
|
|
-- cannot find definition in default dictionary
|
|
if i == n_accurate_dicts then
|
|
self:changeDictionary(1)
|
|
end
|
|
end
|
|
else
|
|
self:changeDictionary(1)
|
|
end
|
|
end
|
|
|
|
function DictQuickLookup:onAnyKeyPressed()
|
|
-- triggered by our defined key events
|
|
UIManager:close(self)
|
|
return true
|
|
end
|
|
|
|
function DictQuickLookup:onTapCloseDict(arg, ges_ev)
|
|
if ges_ev.pos:notIntersectWith(self.dict_frame.dimen) then
|
|
self:onClose()
|
|
return true
|
|
elseif not ges_ev.pos:notIntersectWith(self.dict_title.dimen) and not self.is_wiki then
|
|
self.ui:handleEvent(Event:new("UpdateDefaultDict", self.dictionary))
|
|
return true
|
|
end
|
|
-- Allow for changing dict with tap (tap event will be first
|
|
-- processed for scrolling definition by ScrollTextWidget, which
|
|
-- will pop it up for us here when it can't scroll anymore).
|
|
-- This allow for continuous reading of results' definitions with tap.
|
|
if ges_ev.pos.x < Screen:getWidth()/2 then
|
|
self:changeToPrevDict()
|
|
else
|
|
self:changeToNextDict()
|
|
end
|
|
return true
|
|
end
|
|
|
|
function DictQuickLookup:onClose()
|
|
UIManager:close(self)
|
|
for i = #self.window_list, 1, -1 do
|
|
local window = self.window_list[i]
|
|
if window == self then
|
|
table.remove(self.window_list, i)
|
|
end
|
|
end
|
|
if self.highlight then
|
|
-- delay unhighlight of selection, so we can see where we stopped when
|
|
-- back from our journey into dictionary or wikipedia
|
|
UIManager:scheduleIn(1, function()
|
|
self.highlight:clear()
|
|
end)
|
|
end
|
|
return true
|
|
end
|
|
|
|
function DictQuickLookup:onHoldClose()
|
|
self:onClose()
|
|
for i = #self.window_list, 1, -1 do
|
|
local window = self.window_list[i]
|
|
-- if one holds a highlight, let's clear it like in onClose()
|
|
if window.highlight then
|
|
UIManager:scheduleIn(1, function()
|
|
window.highlight:clear()
|
|
end)
|
|
end
|
|
UIManager:close(window)
|
|
table.remove(self.window_list, i)
|
|
end
|
|
return true
|
|
end
|
|
|
|
function DictQuickLookup:onSwipe(arg, ges)
|
|
if ges.direction == "west" then
|
|
self:changeToNextDict()
|
|
elseif ges.direction == "east" then
|
|
self:changeToPrevDict()
|
|
else
|
|
-- trigger full refresh
|
|
UIManager:setDirty(nil, "full")
|
|
end
|
|
return true
|
|
end
|
|
|
|
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 = {
|
|
{
|
|
{
|
|
text = _("Cancel"),
|
|
callback = function()
|
|
self:closeInputDialog()
|
|
end,
|
|
},
|
|
{
|
|
text = _("Lookup"),
|
|
is_enter_default = true,
|
|
callback = function()
|
|
self:closeInputDialog()
|
|
self:inputLookup()
|
|
end,
|
|
},
|
|
}
|
|
},
|
|
}
|
|
self.input_dialog:onShowKeyboard()
|
|
UIManager:show(self.input_dialog)
|
|
end
|
|
|
|
function DictQuickLookup:inputLookup()
|
|
local word = self.input_dialog:getInputText()
|
|
if word and word ~= "" then
|
|
local event
|
|
if self.is_wiki then
|
|
event = "LookupWikipedia"
|
|
self:resyncWikiLanguages()
|
|
else
|
|
event = "LookupWord"
|
|
end
|
|
self.ui:handleEvent(Event:new(event, word))
|
|
end
|
|
end
|
|
|
|
function DictQuickLookup:closeInputDialog()
|
|
UIManager:close(self.input_dialog)
|
|
end
|
|
|
|
function DictQuickLookup:resyncWikiLanguages(rotate)
|
|
-- Resync the current language or rotate it from its state when
|
|
-- this window was created (we may have rotated it later in other
|
|
-- wikipedia windows that we closed and went back here, and its
|
|
-- state would not be what the wikipedia language button is showing.
|
|
if not self.wiki_languages_copy then
|
|
return
|
|
end
|
|
if rotate then
|
|
-- rotate our saved wiki_languages copy
|
|
local current_lang = table.remove(self.wiki_languages_copy, 1)
|
|
table.insert(self.wiki_languages_copy, current_lang)
|
|
end
|
|
-- re-set self.wiki_languages with original (possibly rotated) items
|
|
for i, lang in ipairs(self.wiki_languages_copy) do
|
|
self.wiki_languages[i] = lang
|
|
end
|
|
end
|
|
|
|
function DictQuickLookup:lookupWikipedia(get_fullpage)
|
|
local word
|
|
if get_fullpage then
|
|
-- we use the word of the displayed result's definition, which
|
|
-- is the exact title of the full wikipedia page
|
|
word = self.lookupword
|
|
else
|
|
-- we use the original word that was querried
|
|
word = self.word
|
|
end
|
|
self:resyncWikiLanguages()
|
|
-- strange : we need to pass false instead of nil if word_box is nil,
|
|
-- otherwise get_fullpage is not passed
|
|
self.ui:handleEvent(Event:new("LookupWikipedia", word, self.word_box and self.word_box or false, get_fullpage))
|
|
end
|
|
|
|
return DictQuickLookup
|