mirror of
https://github.com/koreader/koreader.git
synced 2025-08-10 00:52:38 +00:00
* FocusManager: Fix `focus_flags` check in `moveFocusTo` (0 is truthy in Lua, can't do AND checks like in C ;).) * FileManager+FileChooser: Pass our custom title bar directly to FileChooser (which also means we can now use FC's FocusManager layout directly). * FileChooser/Menu: Get rid of the weird `outer_title_bar` hack, and simply take a `custom_title_bar` pointer to an actual TitleBar instance instead. * FileManager/Menu/ListMenu/CoverMenu: Fix content height computations in `_recalculateDimen` (all the non-FM cases were including an old and now unused padding value, `self.header_padding`, leading to more blank space at the bottom than necessary, and, worse, leading to different item heights between FM views, possibly leading to unnecessary thumbnail scaling !) * ButtonDialog: Proper focus management when the ButtonTable is wrapped in a ScrollableContainer. * ConfigDialog: Implement a stupid workaround for a weird FocusManager issue when going back from `[⋮]` buttons. * ConfigDialog: Don't move the visual focus in `update` (i.e., we use `NOT_FOCUS` now that it works as intended). * DictQuickLookup: Ensures the `Menu` key bind does the exact same thing as the hamburger icon. * DictQuickLookup: Ensure we refocus after having mangled the FocusManager layout (prevents an old focus highlight from lingering on the wrong button). * FileChooser: Stop flagging it as no_title, because it is *never* without a title. (This behavior was a remnant of the previous FM-specific title bar hacks, which are no longer a thing). * FileChooser: Stop calling `mergeTitleBarIntoLayout` twice in `updateItems`. We already call Menu's, which handles it. (Prevents the title bar from being added twice to the FocusManager layout). * FocusManager: Relax the `Unfocus` checks in `moveFocusTo` to ensure we *always* unfocus something (if unfocusing was requested), even if we have to blast the whole widget tree to do so. This ensures callers that mangle self.layout can expect things to work after calling it regardless of how borked the current focus is. * FocusManager: Allow passing `focus_flags` to `refocusWidget`, so that it can be forwarded to the internal `moveFocusTo` call. * FocusManager: The above also allows us to enforce a default that ensures we do *not* send a Focus event on Touch devices, even if they have the hasDPad devcap. This essentially restores the previous/current behavior of not showing the visual feedback from such focus "events" sent programmatically, given the `focus_flags` check fix at the root of this PR ;). * InputDialog: Fix numerous issues relating to double/ghost instances of both InputText and VirtualKeyboard, ensuring we only ever have a single InputText & VK instance live. * InputDialog: Make sure every way we have of hiding the VK play nice together, especially when the `toggleKeyboard` button (shown w/ `add_nav_bar`) is at play. And doubly so when we're `fullscreen`, as hiding the VK implies resizing the widget. * InputText: Make sure we're flagged as in-focus when tapping inside the text field. * InputText: Make sure we don't attempt to show an already-visible VK in the custom `hasDPad` `onFocus` handler. * Menu: Get rid of an old and no longer used (nor meaningful) hack in `onFocus` about the initial/programmatically-sent Focus event. * Menu: Get rid of the unused `header_padding` field mentioned earlier in the FM/FC fixes. * Menu: Use `FOCUS_ONLY_ON_NT` in the explicit `moveFocusTo` call in `updatePageInfo`, so as to keep the current behavior of not showing the visual feedback of this focus on Touch devices. * Menu: Make sure *all* the `moveFocusTo` calls are gated behind the `hasDPad` devcap (previously, that was only the case for `updatePageInfo`, but not `mergeTitleBarIntoLayout` (which is called by `updateItems`). * MultiInputDialog: Actively get rid of the InputText & VK instances from the base class's constructor that we do not use. * MultiInputDialog: Ensure the FocusManager layout is *slightly* less broken (password fields can still be a bit weird, though). * TextViewer: Get rid of the unfocus -> layout mangling -> refocus hack now that `refocusWidget` handles this case sanely. * VirtualKeyboard: Notify our parent InputDialog when we get closed, so it can act accordingly (e.g., resize itself when `fullscreen`). * ScrollableContainer: Implement the necessary machinery for focus handling inside ButtonDialog (specifically, when scrolling via PgUp/PgDwn). * TextEditor: Given the above fixes, the plugin is no longer disabled on non-touch devices. * ReaderBookMark: Make sure we request a full refresh when closing the "Edit note" dialog, as CRe highlights may extend past its dimensions, and if it's closed separately from VK, the refresh would have been limited to its own dimensions, leaving a neat InputDialog-sized hole in the highlights ;).
1588 lines
63 KiB
Lua
1588 lines
63 KiB
Lua
local BD = require("ui/bidi")
|
|
local Blitbuffer = require("ffi/blitbuffer")
|
|
local ButtonDialog = require("ui/widget/buttondialog")
|
|
local ButtonTable = require("ui/widget/buttontable")
|
|
local CenterContainer = require("ui/widget/container/centercontainer")
|
|
local Device = require("device")
|
|
local Geom = require("ui/geometry")
|
|
local Event = require("ui/event")
|
|
local Font = require("ui/font")
|
|
local FrameContainer = require("ui/widget/container/framecontainer")
|
|
local GestureRange = require("ui/gesturerange")
|
|
local IconButton = require("ui/widget/iconbutton")
|
|
local InputContainer = require("ui/widget/container/inputcontainer")
|
|
local InputDialog = require("ui/widget/inputdialog")
|
|
local Math = require("optmath")
|
|
local MovableContainer = require("ui/widget/container/movablecontainer")
|
|
local OverlapGroup = require("ui/widget/overlapgroup")
|
|
local ScrollHtmlWidget = require("ui/widget/scrollhtmlwidget")
|
|
local ScrollTextWidget = require("ui/widget/scrolltextwidget")
|
|
local Size = require("ui/size")
|
|
local TextWidget = require("ui/widget/textwidget")
|
|
local TitleBar = require("ui/widget/titlebar")
|
|
local Translator = require("ui/translator")
|
|
local UIManager = require("ui/uimanager")
|
|
local VerticalGroup = require("ui/widget/verticalgroup")
|
|
local VerticalSpan = require("ui/widget/verticalspan")
|
|
local WidgetContainer = require("ui/widget/container/widgetcontainer")
|
|
local filemanagerutil = require("apps/filemanager/filemanagerutil")
|
|
local lfs = require("libs/libkoreader-lfs")
|
|
local logger = require("logger")
|
|
local util = require("util")
|
|
local _ = require("gettext")
|
|
local C_ = _.pgettext
|
|
local Input = Device.input
|
|
local Screen = Device.screen
|
|
local T = require("ffi/util").template
|
|
local time = require("ui/time")
|
|
|
|
--[[
|
|
Display quick lookup word definition
|
|
]]
|
|
local DictQuickLookup = InputContainer:extend{
|
|
results = nil,
|
|
lookupword = nil,
|
|
dictionary = nil,
|
|
definition = nil,
|
|
displayword = nil,
|
|
images = nil,
|
|
is_wiki = false,
|
|
is_wiki_fullpage = false,
|
|
is_html = false,
|
|
dict_index = 1,
|
|
width = nil,
|
|
height = nil,
|
|
-- sboxes containing highlighted text, quick lookup window tries to not hide the word
|
|
word_boxes = nil,
|
|
|
|
-- refresh_callback will be called before we trigger full refresh in onSwipe
|
|
refresh_callback = nil,
|
|
html_dictionary_link_tapped_callback = nil,
|
|
|
|
-- Static class member, holds a ref to the currently opened widgets (in instantiation order).
|
|
window_list = {},
|
|
-- Static class member, used by ReaderWiktionary to communicate state from a closed widget to the next opened one.
|
|
rotated_update_wiki_languages_on_close = nil,
|
|
}
|
|
|
|
function DictQuickLookup.getWikiSaveEpubDefaultDir()
|
|
local dir = G_reader_settings:readSetting("home_dir") or filemanagerutil.getDefaultDir()
|
|
if dir:sub(-1) ~= "/" then
|
|
dir = dir .. "/"
|
|
end
|
|
return dir .. "Wikipedia"
|
|
end
|
|
|
|
function DictQuickLookup:canSearch()
|
|
if self.is_wiki then
|
|
-- In the Wiki variant of this widget, the Search button is coopted to cycle between enabled languages.
|
|
if #self.wiki_languages > 1 then
|
|
return true
|
|
end
|
|
else
|
|
if self:isDocless() then
|
|
return false
|
|
end
|
|
-- This is to prevent an ineffective button when we're launched from the Reader's menu.
|
|
if self.highlight then
|
|
return true
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
function DictQuickLookup:init()
|
|
self.dict_font_size = G_reader_settings:readSetting("dict_font_size") or 20
|
|
self.content_face = Font:getFace("cfont", self.dict_font_size)
|
|
local font_size_alt = self.dict_font_size - 4
|
|
if font_size_alt < 8 then
|
|
font_size_alt = 8
|
|
end
|
|
self.image_alt_face = Font:getFace("cfont", font_size_alt)
|
|
if Device:hasKeys() then
|
|
self.key_events.ReadPrevResult = { { Input.group.PgBack } }
|
|
self.key_events.ReadNextResult = { { Input.group.PgFwd } }
|
|
self.key_events.Close = { { Input.group.Back } }
|
|
self.key_events.MenuKeyPress = { { "Menu" } }
|
|
if Device:hasKeyboard() then
|
|
self.key_events.ChangeToPrevDict = { { "Shift", "Left" } }
|
|
self.key_events.ChangeToNextDict = { { "Shift", "Right" } }
|
|
elseif Device:hasScreenKB() then
|
|
self.key_events.ChangeToPrevDict = { { "ScreenKB", "Left" } }
|
|
self.key_events.ChangeToNextDict = { { "ScreenKB", "Right" } }
|
|
end
|
|
end
|
|
if Device:isTouchDevice() then
|
|
local range = Geom:new{
|
|
x = 0, y = 0,
|
|
w = Screen:getWidth(),
|
|
h = Screen:getHeight(),
|
|
}
|
|
self.ges_events = {
|
|
Tap = {
|
|
GestureRange:new{
|
|
ges = "tap",
|
|
range = range,
|
|
},
|
|
},
|
|
Swipe = {
|
|
GestureRange:new{
|
|
ges = "swipe",
|
|
range = range,
|
|
},
|
|
},
|
|
-- 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, true, {self.word_box}))
|
|
-- end
|
|
-- },
|
|
-- Allow selection of one or more words (see textboxwidget.lua) :
|
|
HoldStartText = {
|
|
GestureRange:new{
|
|
ges = "hold",
|
|
range = range,
|
|
},
|
|
},
|
|
HoldPanText = {
|
|
GestureRange:new{
|
|
ges = "hold",
|
|
range = range,
|
|
},
|
|
},
|
|
HoldReleaseText = {
|
|
GestureRange:new{
|
|
ges = "hold_release",
|
|
range = range,
|
|
},
|
|
-- callback function when HoldReleaseText is handled as args
|
|
args = function(text, hold_duration)
|
|
-- do this lookup in the same domain (dict/wikipedia)
|
|
local lookup_wikipedia = self.is_wiki
|
|
if hold_duration >= time.s(3) then
|
|
-- but allow switching domain with a long hold
|
|
lookup_wikipedia = not lookup_wikipedia
|
|
end
|
|
-- We don't pass self.highlight to subsequent lookup, we want the
|
|
-- first to be the only one to unhighlight selection when closed
|
|
if lookup_wikipedia then
|
|
self:lookupWikipedia(false, text)
|
|
else
|
|
self.ui:handleEvent(Event:new("LookupWord", text))
|
|
end
|
|
end
|
|
},
|
|
-- These will be forwarded to MovableContainer after some checks
|
|
ForwardingTouch = { GestureRange:new{ ges = "touch", range = range, }, },
|
|
ForwardingPan = { GestureRange:new{ ges = "pan", range = range, }, },
|
|
ForwardingPanRelease = { GestureRange:new{ ges = "pan_release", range = range, }, },
|
|
}
|
|
end
|
|
|
|
-- We no longer support setting a default dict with Tap on title.
|
|
-- self:changeToDefaultDict()
|
|
-- Now, dictionaries can be ordered (although not yet per-book), so trust the order set
|
|
self:changeDictionary(1, true) -- don't call update
|
|
|
|
-- And here comes the initial widget layout...
|
|
if self.is_wiki then
|
|
-- Get a copy of ReaderWikipedia.wiki_languages, with the current result
|
|
-- lang first (rotated, or added)
|
|
self.wiki_languages, self.update_wiki_languages_on_close = self.ui.wikipedia:getWikiLanguages(self.lang)
|
|
end
|
|
|
|
-- Bigger window if fullpage Wikipedia article being shown,
|
|
-- or when large windows for dict requested
|
|
local is_large_window = self.is_wiki_fullpage or G_reader_settings:isTrue("dict_largewindow")
|
|
if is_large_window then
|
|
self.width = Screen:getWidth() - 2*Size.margin.default
|
|
else
|
|
self.width = Screen:getWidth() - Screen:scaleBySize(80)
|
|
end
|
|
local frame_bordersize = Size.border.window
|
|
local inner_width = self.width - 2*frame_bordersize
|
|
-- Height will be computed below, after we build top an bottom
|
|
-- components, when we know how much height they are taking.
|
|
|
|
-- Dictionary title
|
|
self.dict_title = TitleBar:new{
|
|
width = inner_width,
|
|
title = self.displaydictname,
|
|
with_bottom_line = true,
|
|
bottom_v_padding = 0, -- padding handled below
|
|
close_callback = function() self:onClose() end,
|
|
close_hold_callback = function() self:onHoldClose() end,
|
|
-- visual hint: title left aligned for dict, centered for Wikipedia
|
|
align = self.is_wiki and "center" or "left",
|
|
show_parent = self,
|
|
lang = self.lang_out,
|
|
left_icon = "appbar.menu",
|
|
left_icon_tap_callback = function()
|
|
if self.is_wiki then
|
|
self:showWikiResultsMenu()
|
|
else
|
|
self:onShowResultsMenu()
|
|
end
|
|
end,
|
|
left_icon_hold_callback = not self.is_wiki and function() self:showResultsAltMenu() end or nil,
|
|
}
|
|
-- Scrollable offsets of the various showResults* menus and submenus,
|
|
-- so we can reopen them in the same state they were when closed.
|
|
self.menu_scrolled_offsets = {}
|
|
-- We'll also need to close any opened such menu when closing this DictQuickLookup
|
|
-- (needed if closing all DictQuickLookups via long-press on Close on the top one)
|
|
self.menu_opened = {}
|
|
|
|
-- This padding and the resulting width apply to the content
|
|
-- below the title: lookup word and definition
|
|
local content_padding_h = Size.padding.large
|
|
local content_padding_v = Size.padding.large -- added via VerticalSpan
|
|
self.content_width = inner_width - 2*content_padding_h
|
|
|
|
-- Spans between components
|
|
local top_to_word_span = VerticalSpan:new{ width = content_padding_v }
|
|
local word_to_definition_span = VerticalSpan:new{ width = content_padding_v }
|
|
local definition_to_bottom_span = VerticalSpan:new{ width = content_padding_v }
|
|
|
|
-- Lookup word
|
|
local word_font_face = "tfont"
|
|
-- Ensure this word doesn't get smaller than its definition
|
|
local word_font_size = math.max(22, self.dict_font_size)
|
|
-- Get the line height of the normal font size, as a base for sizing this component
|
|
if not self.word_line_height then
|
|
local test_widget = TextWidget:new{
|
|
text = "z",
|
|
face = Font:getFace(word_font_face, word_font_size),
|
|
}
|
|
self.word_line_height = test_widget:getSize().h
|
|
test_widget:free()
|
|
end
|
|
if self.is_wiki then
|
|
-- Wikipedia has longer titles, so use a smaller font,
|
|
word_font_size = math.max(18, self.dict_font_size)
|
|
end
|
|
local icon_size = Screen:scaleBySize(32)
|
|
local lookup_height = math.max(self.word_line_height, icon_size)
|
|
-- Edit button
|
|
local lookup_edit_button = IconButton:new{
|
|
icon = "edit",
|
|
width = icon_size,
|
|
height = icon_size,
|
|
padding = 0,
|
|
padding_left = Size.padding.small,
|
|
callback = function()
|
|
-- allow adjusting the queried word
|
|
self:lookupInputWord(self.word)
|
|
end,
|
|
hold_callback = function()
|
|
-- allow adjusting the current result word
|
|
self:lookupInputWord(self.lookupword)
|
|
end,
|
|
overlap_align = "right",
|
|
show_parent = self,
|
|
}
|
|
local lookup_edit_button_w = lookup_edit_button:getSize().w
|
|
-- Nb of results (if set)
|
|
local lookup_word_nb
|
|
local lookup_word_nb_w = 0
|
|
if self.displaynb then
|
|
self.displaynb_text = TextWidget:new{
|
|
text = self.displaynb,
|
|
face = Font:getFace("cfont", word_font_size),
|
|
padding = 0, -- smaller height for better aligmnent with icon
|
|
}
|
|
|
|
lookup_word_nb = FrameContainer:new{
|
|
margin = 0,
|
|
bordersize = 0,
|
|
padding = 0,
|
|
padding_left = Size.padding.small,
|
|
padding_right = lookup_edit_button_w + Size.padding.default,
|
|
overlap_align = "right",
|
|
self.displaynb_text,
|
|
}
|
|
lookup_word_nb_w = lookup_word_nb:getSize().w
|
|
end
|
|
-- Lookup word
|
|
self.lookup_word_text = TextWidget:new{
|
|
text = self.displayword,
|
|
face = Font:getFace(word_font_face, word_font_size),
|
|
bold = true,
|
|
max_width = self.content_width - math.max(lookup_edit_button_w, lookup_word_nb_w),
|
|
padding = 0, -- to be aligned with lookup_word_nb
|
|
lang = self.lang_in
|
|
}
|
|
-- Group these 3 widgets
|
|
local lookup_word = OverlapGroup:new{
|
|
dimen = {
|
|
w = self.content_width,
|
|
h = lookup_height,
|
|
},
|
|
self.lookup_word_text,
|
|
lookup_edit_button,
|
|
lookup_word_nb, -- last, as this might be nil
|
|
}
|
|
|
|
-- Different sets of buttons whether fullpage or not
|
|
local buttons
|
|
if self.is_wiki_fullpage then
|
|
-- A save and a close button
|
|
buttons = {
|
|
{
|
|
{
|
|
id = "save",
|
|
text = _("Save as EPUB"),
|
|
callback = function()
|
|
local InfoMessage = require("ui/widget/infomessage")
|
|
local ConfirmBox = require("ui/widget/confirmbox")
|
|
-- if forced_lang was specified, it may not be in our wiki_languages,
|
|
-- but ReaderWikipedia will have put it in result.lang
|
|
local lang = self.lang or self.wiki_languages[1]
|
|
-- Find a directory to save file into
|
|
local dir
|
|
if G_reader_settings:isTrue("wikipedia_save_in_book_dir") and not self:isDocless() then
|
|
local last_file = G_reader_settings:readSetting("lastfile")
|
|
dir = last_file and last_file:match("(.*)/")
|
|
end
|
|
dir = dir or G_reader_settings:readSetting("wikipedia_save_dir") or DictQuickLookup.getWikiSaveEpubDefaultDir()
|
|
if not util.pathExists(dir) then
|
|
lfs.mkdir(dir)
|
|
end
|
|
-- Just to be safe (none of the invalid chars, except ':' for uninteresting
|
|
-- Portal: or File: wikipedia pages, should be in lookupword)
|
|
local filename = self.lookupword .. "."..string.upper(lang)..".epub"
|
|
filename = util.getSafeFilename(filename, dir):gsub("_", " ")
|
|
local epub_path = dir .. "/" .. filename
|
|
UIManager:show(ConfirmBox:new{
|
|
text = T(_("Save as %1?"), BD.filename(filename)),
|
|
ok_callback = function()
|
|
UIManager:scheduleIn(0.1, function()
|
|
local Wikipedia = require("ui/wikipedia")
|
|
Wikipedia:createEpubWithUI(epub_path, self.lookupword, lang, function(success)
|
|
if success then
|
|
UIManager:show(ConfirmBox:new{
|
|
text = T(_("Article saved to:\n%1\n\nWould you like to read the downloaded article now?"), BD.filepath(epub_path)),
|
|
ok_callback = function()
|
|
-- close all dict/wiki windows, without scheduleIn(highlight.clear())
|
|
self:onHoldClose(true)
|
|
-- close current ReaderUI in 1 sec, and create a new one
|
|
UIManager:scheduleIn(1.0, function()
|
|
UIManager:broadcastEvent(Event:new("SetupShowReader"))
|
|
|
|
if self.ui then
|
|
-- close Highlight menu if any still shown
|
|
if self.ui.highlight and self.ui.highlight.highlight_dialog then
|
|
self.ui.highlight:onClose()
|
|
end
|
|
self.ui:onClose()
|
|
end
|
|
|
|
local ReaderUI = require("apps/reader/readerui")
|
|
ReaderUI:showReader(epub_path)
|
|
end)
|
|
end,
|
|
})
|
|
else
|
|
UIManager:show(InfoMessage:new{
|
|
text = _("Saving Wikipedia article failed or interrupted."),
|
|
})
|
|
end
|
|
end)
|
|
end)
|
|
end
|
|
})
|
|
end,
|
|
},
|
|
{
|
|
id = "close",
|
|
text = _("Close"),
|
|
callback = function()
|
|
self:onClose()
|
|
end,
|
|
hold_callback = function()
|
|
self:onHoldClose()
|
|
end,
|
|
},
|
|
},
|
|
}
|
|
else
|
|
local prev_dict_text = "◁◁"
|
|
local next_dict_text = "▷▷"
|
|
if BD.mirroredUILayout() then
|
|
prev_dict_text, next_dict_text = next_dict_text, prev_dict_text
|
|
end
|
|
buttons = {
|
|
{
|
|
{
|
|
id = "prev_dict",
|
|
text = prev_dict_text,
|
|
vsync = true,
|
|
enabled = self:isPrevDictAvaiable(),
|
|
callback = function()
|
|
self:onChangeToPrevDict()
|
|
end,
|
|
hold_callback = function()
|
|
self:changeToFirstDict()
|
|
end,
|
|
},
|
|
{
|
|
id = "highlight",
|
|
text = _("Highlight"),
|
|
enabled = not self:isDocless() and self.highlight ~= nil,
|
|
callback = function()
|
|
self.save_highlight = not self.save_highlight
|
|
-- Just update, repaint and refresh *this* button
|
|
local this = self.button_table:getButtonById("highlight")
|
|
this:setText(self.save_highlight and _("Unhighlight") or _("Highlight"), this.width)
|
|
this:refresh()
|
|
end,
|
|
},
|
|
{
|
|
id = "next_dict",
|
|
text = next_dict_text,
|
|
vsync = true,
|
|
enabled = self:isNextDictAvaiable(),
|
|
callback = function()
|
|
self:onChangeToNextDict()
|
|
end,
|
|
hold_callback = function()
|
|
self:changeToLastDict()
|
|
end,
|
|
},
|
|
},
|
|
{
|
|
{
|
|
id = "wikipedia",
|
|
-- if dictionary result, do the same search on wikipedia
|
|
-- if already wiki, get the full page for the current result
|
|
text_func = function()
|
|
if self.is_wiki then
|
|
-- @translators Full Wikipedia article.
|
|
return C_("Button", "Wikipedia full")
|
|
else
|
|
return _("Wikipedia")
|
|
end
|
|
end,
|
|
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, or Search in book if dict window
|
|
{
|
|
id = "search",
|
|
-- 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 BD.wrap(self.wiki_languages[1]).." > "..BD.wrap(self.wiki_languages[2])
|
|
or self.wiki_languages[1] ) -- (this " > " will be auro-mirrored by bidi)
|
|
or _("Search"),
|
|
enabled = self:canSearch(),
|
|
callback = function()
|
|
if self.is_wiki then
|
|
-- We're rotating: forward this flag from the one we're closing so
|
|
-- that ReaderWikipedia can give it to the one we'll be showing
|
|
DictQuickLookup.rotated_update_wiki_languages_on_close = self.update_wiki_languages_on_close
|
|
self:lookupWikipedia(false, nil, nil, self.wiki_languages[2])
|
|
self:onClose(true)
|
|
else
|
|
self.ui:handleEvent(Event:new("HighlightSearch"))
|
|
self:onClose(true) -- don't unhighlight (or we might erase a search hit)
|
|
end
|
|
end,
|
|
},
|
|
{
|
|
id = "close",
|
|
text = _("Close"),
|
|
callback = function()
|
|
self:onClose()
|
|
end,
|
|
hold_callback = function()
|
|
self:onHoldClose()
|
|
end,
|
|
},
|
|
},
|
|
}
|
|
if not self.is_wiki and self.selected_link ~= nil then
|
|
-- If highlighting some word part of a link (which should be rare),
|
|
-- add a new first row with a single button to follow this link.
|
|
table.insert(buttons, 1, {
|
|
{
|
|
id = "link",
|
|
text = _("Follow Link"),
|
|
callback = function()
|
|
local link = self.selected_link.link or self.selected_link
|
|
self.ui.link:onGotoLink(link)
|
|
self:onClose()
|
|
end,
|
|
},
|
|
})
|
|
end
|
|
end
|
|
if self.ui then
|
|
self.ui:handleEvent(Event:new("DictButtonsReady", self, buttons))
|
|
end
|
|
-- Bottom buttons get a bit less padding so their line separators
|
|
-- reach out from the content to the borders a bit more
|
|
local buttons_padding = Size.padding.default
|
|
local buttons_width = inner_width - 2*buttons_padding
|
|
self.button_table = ButtonTable:new{
|
|
width = buttons_width,
|
|
buttons = buttons,
|
|
zero_sep = true,
|
|
show_parent = self,
|
|
}
|
|
|
|
-- Margin from screen edges
|
|
local margin_top = Size.margin.default
|
|
local margin_bottom = Size.margin.default
|
|
if self.ui and self.ui.view and self.ui.view.footer_visible then
|
|
-- We want to let the footer visible (as it can show time, battery level
|
|
-- and wifi state, which might be useful when spending time reading
|
|
-- definitions or wikipedia articles)
|
|
margin_bottom = margin_bottom + self.ui.view.footer:getHeight()
|
|
end
|
|
local avail_height = Screen:getHeight() - margin_top - margin_bottom
|
|
-- Region in which the window will be aligned center/top/bottom:
|
|
self.region = Geom:new{
|
|
x = 0,
|
|
y = margin_top,
|
|
w = Screen:getWidth(),
|
|
h = avail_height,
|
|
}
|
|
self.align = "center"
|
|
|
|
local others_height = frame_bordersize * 2 -- DictQuickLookup border
|
|
+ self.dict_title:getHeight()
|
|
+ top_to_word_span:getSize().h
|
|
+ lookup_word:getSize().h
|
|
+ word_to_definition_span:getSize().h
|
|
+ definition_to_bottom_span:getSize().h
|
|
+ self.button_table:getSize().h
|
|
|
|
-- To properly adjust the definition to the height of text, we need
|
|
-- the line height a ScrollTextWidget will use for the current font
|
|
-- size (we'll then use this perfect height for ScrollTextWidget,
|
|
-- but also for ScrollHtmlWidget, where it doesn't matter).
|
|
if not self.definition_line_height then
|
|
local test_widget = ScrollTextWidget:new{
|
|
text = "z",
|
|
face = self.content_face,
|
|
width = self.content_width,
|
|
height = self.definition_height,
|
|
for_measurement_only = true, -- flag it as a dummy, so it won't trigger any bogus repaint/refresh...
|
|
}
|
|
self.definition_line_height = test_widget:getLineHeight()
|
|
test_widget:free(true)
|
|
end
|
|
|
|
if is_large_window then
|
|
-- Available height for definition + components
|
|
self.height = avail_height
|
|
self.definition_height = self.height - others_height
|
|
local nb_lines = math.floor(self.definition_height / self.definition_line_height)
|
|
self.definition_height = nb_lines * self.definition_line_height
|
|
local pad = self.height - others_height - self.definition_height
|
|
-- put that unused height on the above span
|
|
word_to_definition_span.width = word_to_definition_span.width + pad
|
|
else
|
|
-- Definition height was previously computed as 0.5*0.7*screen_height, so keep
|
|
-- it that way. Components will add themselves to that.
|
|
self.definition_height = math.floor(avail_height * 0.5 * 0.7)
|
|
-- But we want it to fit to the lines that will show, to avoid
|
|
-- any extra padding
|
|
local nb_lines = Math.round(self.definition_height / self.definition_line_height)
|
|
self.definition_height = nb_lines * self.definition_line_height
|
|
self.height = self.definition_height + others_height
|
|
if self.word_boxes and #self.word_boxes > 0 then
|
|
-- Try to not hide the highlighted word. We don't want to always
|
|
-- get near it if we can stay center, so that more context around
|
|
-- the word is still visible with the dict result.
|
|
-- But if we were to be drawn over the word, move a bit if possible.
|
|
|
|
-- In most cases boxes will be a single sbox, but handle multiple
|
|
-- sboxes by taking the top and bottom y values.
|
|
local word_box_top
|
|
local word_box_bottom
|
|
for _, box in ipairs(self.word_boxes) do
|
|
local box_top = box.y
|
|
local box_bottom = box.y + box.h
|
|
if not word_box_top or word_box_top > box_top then
|
|
word_box_top = box_top
|
|
end
|
|
if not word_box_bottom or word_box_bottom < box_bottom then
|
|
word_box_bottom = box_bottom
|
|
end
|
|
end
|
|
|
|
-- Don't stick to the box, ensure a minimal padding between box and
|
|
-- window.
|
|
word_box_top = word_box_top - Size.padding.small
|
|
word_box_bottom = word_box_bottom + Size.padding.small
|
|
|
|
local half_visible_height = (avail_height - self.height) / 2
|
|
if word_box_bottom > half_visible_height and word_box_top <= half_visible_height + self.height then
|
|
-- word would be covered by our centered window
|
|
if word_box_bottom <= avail_height - self.height then
|
|
-- Window can be moved just below word
|
|
self.region.y = word_box_bottom
|
|
self.region.h = self.region.h - word_box_bottom
|
|
self.align = "top"
|
|
elseif word_box_top > self.height then
|
|
-- Window can be moved just above word
|
|
self.region.y = 0
|
|
self.region.h = word_box_top
|
|
self.align = "bottom"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Instantiate self.text_widget
|
|
self:_instantiateScrollWidget()
|
|
|
|
-- word definition
|
|
self.definition_widget = FrameContainer:new{
|
|
padding = 0,
|
|
padding_left = content_padding_h,
|
|
padding_right = content_padding_h,
|
|
margin = 0,
|
|
bordersize = 0,
|
|
self.text_widget,
|
|
}
|
|
|
|
self.dict_frame = FrameContainer:new{
|
|
radius = Size.radius.window,
|
|
bordersize = frame_bordersize,
|
|
padding = 0,
|
|
margin = 0,
|
|
background = Blitbuffer.COLOR_WHITE,
|
|
VerticalGroup:new{
|
|
align = "left",
|
|
self.dict_title,
|
|
top_to_word_span,
|
|
-- word
|
|
CenterContainer:new{
|
|
dimen = Geom:new{
|
|
w = inner_width,
|
|
h = lookup_word:getSize().h,
|
|
},
|
|
lookup_word,
|
|
},
|
|
word_to_definition_span,
|
|
-- definition
|
|
CenterContainer:new{
|
|
dimen = Geom:new{
|
|
w = inner_width,
|
|
h = self.definition_widget:getSize().h,
|
|
},
|
|
self.definition_widget,
|
|
},
|
|
definition_to_bottom_span,
|
|
-- buttons
|
|
CenterContainer:new{
|
|
dimen = Geom:new{
|
|
w = inner_width,
|
|
h = self.button_table:getSize().h,
|
|
},
|
|
self.button_table,
|
|
}
|
|
}
|
|
}
|
|
|
|
self.movable = MovableContainer:new{
|
|
-- We'll handle these events ourselves, and call appropriate
|
|
-- MovableContainer's methods when we didn't process the event
|
|
ignore_events = {
|
|
-- These have effects over the definition widget, and may
|
|
-- or may not be processed by it
|
|
"swipe", "hold", "hold_release", "hold_pan",
|
|
-- These do not have direct effect over the definition widget,
|
|
-- but may happen while selecting text: we need to check
|
|
-- a few things before forwarding them
|
|
"touch", "pan", "pan_release",
|
|
},
|
|
self.dict_frame,
|
|
}
|
|
|
|
self[1] = WidgetContainer:new{
|
|
align = self.align,
|
|
dimen = self.region,
|
|
self.movable,
|
|
}
|
|
|
|
-- NT: add dict_title.left_button and lookup_edit_button to FocusManager.
|
|
-- It is better to add these two buttons into self.movable, but it is not a FocusManager.
|
|
-- Only self.button_table is a FocusManager, so the workaround is inserting these two buttons into self.button_table.layout.
|
|
if Device:hasDPad() then
|
|
table.insert(self.button_table.layout, 1, { self.dict_title.left_button })
|
|
table.insert(self.button_table.layout, 2, { lookup_edit_button })
|
|
-- Refocus on the updated layout
|
|
self.button_table:refocusWidget()
|
|
end
|
|
|
|
-- We're a new window
|
|
table.insert(DictQuickLookup.window_list, self)
|
|
end
|
|
|
|
-- Whether currently DictQuickLookup is working without a document.
|
|
function DictQuickLookup:isDocless()
|
|
return self.ui == nil or self.ui.highlight == nil
|
|
end
|
|
|
|
function DictQuickLookup:getHtmlDictionaryCss()
|
|
-- Using Noto Sans because Nimbus doesn't contain the IPA symbols.
|
|
-- 'line-height: 1.3' to have it similar to textboxwidget,
|
|
-- and follow user's choice on justification
|
|
local css_justify = G_reader_settings:nilOrTrue("dict_justify") and "text-align: justify;" or ""
|
|
local css = [[
|
|
@page {
|
|
margin: 0;
|
|
font-family: 'Noto Sans';
|
|
}
|
|
|
|
body {
|
|
margin: 0;
|
|
line-height: 1.3;
|
|
]]..css_justify..[[
|
|
}
|
|
|
|
blockquote, dd {
|
|
margin: 0 1em;
|
|
}
|
|
|
|
ol, ul, menu {
|
|
margin: 0; padding: 0 1.7em;
|
|
}
|
|
]]
|
|
-- For reference, MuPDF declarations with absolute units:
|
|
-- "blockquote{margin:1em 40px}"
|
|
-- "dd{margin:0 0 0 40px}"
|
|
-- "ol,ul,menu {margin:1em 0;padding:0 0 0 30pt}"
|
|
-- "hr{border-width:1px;}"
|
|
-- "td,th{padding:1px}"
|
|
--
|
|
-- MuPDF doesn't currently scale CSS pixels, so we have to use a font-size based measurement.
|
|
-- Unfortunately MuPDF doesn't properly support `rem` either, which it bases on a hard-coded
|
|
-- value of `16px`, so we have to go with `em` (or `%`).
|
|
--
|
|
-- These `em`-based margins can vary slightly, but it's the best available compromise.
|
|
--
|
|
-- We also keep left and right margin the same so it'll display as expected in RTL.
|
|
-- Because MuPDF doesn't currently support `margin-start`, this results in a slightly
|
|
-- unconventional but hopefully barely noticeable right margin for <dd>.
|
|
--
|
|
-- For <ul> and <ol>, bullets and numbers are displayed in the margin/padding, so
|
|
-- we need a bit more for them to not get truncated (1.7em allows for 2 digits list
|
|
-- item numbers). Unfortunately, because we want this also for RTL, this space is
|
|
-- wasted on the other side...
|
|
|
|
if self.css then
|
|
return css .. self.css
|
|
end
|
|
return css
|
|
end
|
|
|
|
-- Used in init & update to instantiate the Scroll*Widget that self.text_widget points to
|
|
function DictQuickLookup:_instantiateScrollWidget()
|
|
if self.is_html then
|
|
self.shw_widget = ScrollHtmlWidget:new{
|
|
html_body = self.definition,
|
|
css = self:getHtmlDictionaryCss(),
|
|
default_font_size = Screen:scaleBySize(self.dict_font_size),
|
|
width = self.content_width,
|
|
height = self.definition_height,
|
|
dialog = self,
|
|
html_link_tapped_callback = function(link)
|
|
self.html_dictionary_link_tapped_callback(self.dictionary, link)
|
|
end,
|
|
}
|
|
self.text_widget = self.shw_widget
|
|
else
|
|
self.stw_widget = ScrollTextWidget:new{
|
|
text = self.definition,
|
|
face = self.content_face,
|
|
width = self.content_width,
|
|
height = self.definition_height,
|
|
dialog = self,
|
|
justified = G_reader_settings:nilOrTrue("dict_justify"), -- allow for disabling justification
|
|
lang = self.lang and self.lang:lower() or self.lang_out,
|
|
para_direction_rtl = self.rtl_lang, -- only available on wikipedia results
|
|
auto_para_direction = not self.is_wiki, -- only for dict results (we don't know their lang)
|
|
image_alt_face = self.image_alt_face,
|
|
images = self.images,
|
|
}
|
|
self.text_widget = self.stw_widget
|
|
end
|
|
end
|
|
|
|
function DictQuickLookup:update()
|
|
-- self[1] is a WidgetContainer, its free method will call free on each of its child widget with a free method.
|
|
-- Here, that's the definitions' TextBoxWidget & HtmlBoxWidget,
|
|
-- to release their bb, MuPDF instance, and scheduled image_update_action.
|
|
self[1]:free()
|
|
|
|
-- Update TextWidgets
|
|
self.dict_title:setTitle(self.displaydictname)
|
|
if self.displaynb then
|
|
self.displaynb_text:setText(self.displaynb)
|
|
end
|
|
self.lookup_word_text:setText(self.displayword)
|
|
self.lookup_word_text.lang = self.lang_in
|
|
|
|
-- Update Buttons
|
|
if not self.is_wiki_fullpage then
|
|
local prev_dict_btn = self.button_table:getButtonById("prev_dict")
|
|
if prev_dict_btn then
|
|
prev_dict_btn:enableDisable(self:isPrevDictAvaiable())
|
|
end
|
|
local next_dict_btn = self.button_table:getButtonById("next_dict")
|
|
if next_dict_btn then
|
|
next_dict_btn:enableDisable(self:isNextDictAvaiable())
|
|
end
|
|
end
|
|
|
|
-- Update main text widgets
|
|
if self.is_html and self.shw_widget then
|
|
-- Re-use our ScrollHtmlWidget (self.shw_widget)
|
|
-- NOTE: The recursive free via our WidgetContainer (self[1]) above already released the previous MµPDF document instance ;)
|
|
self.text_widget.htmlbox_widget:setContent(self.definition, self:getHtmlDictionaryCss(), Screen:scaleBySize(self.dict_font_size))
|
|
-- Scroll back to top
|
|
self.text_widget:resetScroll()
|
|
elseif not self.is_html and self.stw_widget then
|
|
-- Re-use our ScrollTextWidget (self.stw_widget)
|
|
-- Update properties that may change across results (as done in DictQuickLookup:_instantiateScrollWidget())
|
|
self.text_widget.text_widget.text = self.definition
|
|
self.text_widget.text_widget.charlist = nil -- (required when use_xtext=false for proper re-init)
|
|
self.text_widget.text_widget.lang = self.lang and self.lang:lower() or self.lang_out
|
|
self.text_widget.text_widget.para_direction_rtl = self.rtl_lang
|
|
self.text_widget.text_widget.images = self.images
|
|
-- Scroll back to the top, àla TextBoxWidget:scrollToTop
|
|
self.text_widget.text_widget.virtual_line_num = 1
|
|
-- NOTE: The recursive free via our WidgetContainer (self[1]) above already free'd us ;)
|
|
self.text_widget.text_widget:init()
|
|
-- Reset the scrollbar's state
|
|
self.text_widget:resetScroll()
|
|
else
|
|
-- We jumped from HTML to Text (or vice-versa), we need a new widget instance
|
|
self:_instantiateScrollWidget()
|
|
-- Update *all* the references to self.text_widget
|
|
self.definition_widget[1] = self.text_widget
|
|
-- Destroy the previous "opposite type" widget
|
|
if self.is_html then
|
|
self.stw_widget = nil
|
|
else
|
|
self.shw_widget = nil
|
|
end
|
|
end
|
|
|
|
-- If we're translucent, reset alpha to make the new definition actually readable.
|
|
if self.movable.alpha then
|
|
self.movable.alpha = nil
|
|
end
|
|
|
|
UIManager:setDirty(self, function()
|
|
return "partial", self.dict_frame.dimen
|
|
end)
|
|
end
|
|
|
|
function DictQuickLookup:getInitialVisibleArea()
|
|
-- Some positionning happens only at paintTo() time, but we want
|
|
-- to know this before. So, do a bit like WidgetContainer does
|
|
-- (without any MovableContainer offset)
|
|
local dict_size = self.dict_frame:getSize()
|
|
local area = Geom:new{
|
|
w = dict_size.w,
|
|
h = dict_size.h,
|
|
x = self.region.x + math.floor((self.region.w - dict_size.w)/2)
|
|
}
|
|
if self.align == "top" then
|
|
area.y = self.region.y
|
|
elseif self.align == "bottom" then
|
|
area.y = self.region.y + self.region.h - dict_size.h
|
|
elseif self.align == "center" then
|
|
area.x = self.region.y + math.floor((self.region.h - dict_size.h)/2)
|
|
end
|
|
return area
|
|
end
|
|
|
|
function DictQuickLookup:onCloseWidget()
|
|
-- Our TextBoxWidget/HtmlBoxWidget/TextWidget/ImageWidget are proper child widgets,
|
|
-- so this event will propagate to 'em, and they'll free their resources.
|
|
|
|
-- What's left is stuff that isn't directly in our widget tree...
|
|
if self.images_cleanup_needed then
|
|
logger.dbg("freeing lookup results images blitbuffers")
|
|
for _, r in ipairs(self.results) do
|
|
if r.images and #r.images > 0 then
|
|
for _, im in ipairs(r.images) do
|
|
if im.bb then im.bb:free() end
|
|
if im.hi_bb then im.hi_bb:free() end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Drop our ref from the static class member
|
|
for i = #DictQuickLookup.window_list, 1, -1 do
|
|
local window = DictQuickLookup.window_list[i]
|
|
-- We should only find a single match, but, better safe than sorry...
|
|
if window == self then
|
|
table.remove(DictQuickLookup.window_list, i)
|
|
end
|
|
end
|
|
|
|
-- NOTE: Drop region to make it a full-screen flash
|
|
UIManager:setDirty(nil, function()
|
|
return "flashui", nil
|
|
end)
|
|
end
|
|
|
|
function DictQuickLookup:onShow()
|
|
UIManager:setDirty(self, function()
|
|
return "flashui", self.dict_frame.dimen
|
|
end)
|
|
return true
|
|
end
|
|
|
|
function DictQuickLookup:isPrevDictAvaiable()
|
|
return self.dict_index > 1
|
|
end
|
|
|
|
function DictQuickLookup:isNextDictAvaiable()
|
|
return self.dict_index < #self.results
|
|
end
|
|
|
|
function DictQuickLookup:onChangeToPrevDict()
|
|
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:onChangeToNextDict()
|
|
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:changeToFirstDict()
|
|
if self:isPrevDictAvaiable() then
|
|
self:changeDictionary(1)
|
|
end
|
|
end
|
|
|
|
function DictQuickLookup:changeToLastDict()
|
|
if self:isNextDictAvaiable() then
|
|
self:changeDictionary(#self.results)
|
|
end
|
|
end
|
|
|
|
function DictQuickLookup:changeDictionary(index, skip_update)
|
|
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_wiki_fullpage = self.results[index].is_wiki_fullpage
|
|
self.is_html = self.results[index].is_html
|
|
self.css = self.results[index].css
|
|
self.lang = self.results[index].lang
|
|
local ifo_lang = self.results[index].ifo_lang
|
|
self.lang_in = ifo_lang and ifo_lang.lang_in or nil
|
|
self.lang_out = ifo_lang and ifo_lang.lang_out or nil
|
|
self.rtl_lang = self.results[index].rtl_lang
|
|
self.images = self.results[index].images
|
|
if self.images and #self.images > 0 then
|
|
-- We'll be giving some images to textboxwidget that will
|
|
-- load and display them. We'll need to free these blitbuffers
|
|
-- when we're done.
|
|
self.images_cleanup_needed = true
|
|
end
|
|
if self.is_wiki_fullpage then
|
|
self.displayword = self.lookupword
|
|
self.displaynb = nil
|
|
else
|
|
self.displayword = self.lookupword
|
|
-- show "dict_index / nbresults" so we know where we're at and what's yet to see
|
|
self.displaynb = T("%1 / %2", 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
|
|
if self.is_html then
|
|
self.definition = self.definition.."<br/>_______<br/>"
|
|
else
|
|
self.definition = self.definition.."\n_______\n"
|
|
end
|
|
self.definition = self.definition..T(_("(query : %1)"), self.word)
|
|
end
|
|
end
|
|
self.displaydictname = self.dictionary
|
|
if self.preferred_dictionaries then
|
|
-- If current result is from a preferred dictionary, prepend dict name
|
|
-- (shown in the window title) with its preference number
|
|
for idx, name in ipairs(self.preferred_dictionaries) do
|
|
if self.dictionary == name then
|
|
-- Use number in circle symbol (U+2460...2473)
|
|
local symbol = util.unicodeCodepointToUtf8(0x245F + (idx < 20 and idx or 20))
|
|
self.displaydictname = symbol .. " " .. self.displaydictname
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Don't call update when called from init
|
|
if not skip_update then
|
|
self:update()
|
|
end
|
|
end
|
|
|
|
--[[ No longer used
|
|
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:onReadNextResult()
|
|
self:onChangeToNextDict()
|
|
return true
|
|
end
|
|
|
|
function DictQuickLookup:onReadPrevResult()
|
|
local prev_index = self.dict_index
|
|
self:onChangeToPrevDict()
|
|
if self.dict_index ~= prev_index then
|
|
-- Jump directly to bottom of previous dict definition
|
|
-- to keep "continuous reading with tap" consistent
|
|
self.definition_widget[1]:scrollToRatio(1) -- 1 = 100% = bottom
|
|
end
|
|
return true
|
|
end
|
|
|
|
function DictQuickLookup:onMenuKeyPress()
|
|
return self.dict_title.left_icon_tap_callback()
|
|
end
|
|
|
|
function DictQuickLookup:onTap(arg, ges_ev)
|
|
if ges_ev.pos:notIntersectWith(self.dict_frame.dimen) then
|
|
self:onClose()
|
|
return true
|
|
end
|
|
if ges_ev.pos:intersectWith(self.dict_title.dimen) and not self.is_wiki then
|
|
self.ui:handleEvent(Event:new("TogglePreferredDict", self.dictionary))
|
|
-- Re-display current result, with title bar updated
|
|
self:changeDictionary(self.dict_index)
|
|
return true
|
|
end
|
|
if ges_ev.pos:intersectWith(self.definition_widget.dimen) then
|
|
-- 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 BD.flipIfMirroredUILayout(ges_ev.pos.x < Screen:getWidth()/2) then
|
|
self:onReadPrevResult()
|
|
else
|
|
self:onReadNextResult()
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
function DictQuickLookup:onClose(no_clear)
|
|
for menu, _ in pairs(self.menu_opened) do
|
|
UIManager:close(menu)
|
|
end
|
|
self.menu_opened = {}
|
|
|
|
UIManager:close(self)
|
|
|
|
if self.update_wiki_languages_on_close then
|
|
-- except if we got no result for current language
|
|
if not self.results.no_result then
|
|
self.ui:handleEvent(Event:new("UpdateWikiLanguages", self.wiki_languages))
|
|
end
|
|
end
|
|
|
|
if self.save_highlight then
|
|
self.highlight:saveHighlight()
|
|
self.highlight:clear()
|
|
else
|
|
if self.highlight and not no_clear then
|
|
-- delay unhighlight of selection, so we can see where we stopped when
|
|
-- back from our journey into dictionary or wikipedia
|
|
local clear_id = self.highlight:getClearId()
|
|
UIManager:scheduleIn(0.5, function()
|
|
self.highlight:clear(clear_id)
|
|
end)
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
function DictQuickLookup:onHoldClose(no_clear)
|
|
-- Pop the windows FILO
|
|
for i = #DictQuickLookup.window_list, 1, -1 do
|
|
local window = DictQuickLookup.window_list[i]
|
|
window:onClose(no_clear)
|
|
end
|
|
return true
|
|
end
|
|
|
|
function DictQuickLookup:onSwipe(arg, ges)
|
|
if ges.pos:intersectWith(self.definition_widget.dimen) then
|
|
-- if we want changeDict to still work with swipe outside window :
|
|
-- or not ges.pos:intersectWith(self.dict_frame.dimen) then
|
|
local direction = BD.flipDirectionIfMirroredUILayout(ges.direction)
|
|
if direction == "west" then
|
|
self:onChangeToNextDict()
|
|
elseif direction == "east" then
|
|
self:onChangeToPrevDict()
|
|
else
|
|
if self.refresh_callback then self.refresh_callback() end
|
|
-- update footer (time & battery)
|
|
self.ui:handleEvent(Event:new("UpdateFooter", true))
|
|
-- trigger a full-screen HQ flashing refresh
|
|
UIManager:setDirty(nil, "full")
|
|
-- a long diagonal swipe may also be used for taking a screenshot,
|
|
-- so let it propagate
|
|
return false
|
|
end
|
|
return true
|
|
end
|
|
-- Let our MovableContainer handle swipe outside of definition
|
|
return self.movable:onMovableSwipe(arg, ges)
|
|
end
|
|
|
|
function DictQuickLookup:onHoldStartText(_, ges)
|
|
-- Forward Hold events not processed by TextBoxWidget event handler
|
|
-- to our MovableContainer
|
|
return self.movable:onMovableHold(_, ges)
|
|
end
|
|
|
|
function DictQuickLookup:onHoldPanText(_, ges)
|
|
-- Forward Hold events not processed by TextBoxWidget event handler
|
|
-- to our MovableContainer
|
|
-- We only forward it if we did forward the Touch
|
|
if self.movable._touch_pre_pan_was_inside then
|
|
return self.movable:onMovableHoldPan(arg, ges)
|
|
end
|
|
end
|
|
|
|
function DictQuickLookup:onHoldReleaseText(_, ges)
|
|
-- Forward Hold events not processed by TextBoxWidget event handler
|
|
-- to our MovableContainer
|
|
return self.movable:onMovableHoldRelease(_, ges)
|
|
end
|
|
|
|
-- These 3 event processors are just used to forward these events
|
|
-- to our MovableContainer, under certain conditions, to avoid
|
|
-- unwanted moves of the window while we are selecting text in
|
|
-- the definition widget.
|
|
function DictQuickLookup:onForwardingTouch(arg, ges)
|
|
-- This Touch may be used as the Hold we don't get (for example,
|
|
-- when we start our Hold on the bottom buttons)
|
|
if not ges.pos:intersectWith(self.definition_widget.dimen) then
|
|
return self.movable:onMovableTouch(arg, ges)
|
|
else
|
|
-- Ensure this is unset, so we can use it to not forward HoldPan
|
|
self.movable._touch_pre_pan_was_inside = false
|
|
end
|
|
end
|
|
|
|
function DictQuickLookup:onForwardingPan(arg, ges)
|
|
-- We only forward it if we did forward the Touch or are currently moving
|
|
if self.movable._touch_pre_pan_was_inside or self.movable._moving then
|
|
return self.movable:onMovablePan(arg, ges)
|
|
end
|
|
end
|
|
|
|
function DictQuickLookup:onForwardingPanRelease(arg, ges)
|
|
-- Allow scrolling with the mousewheel
|
|
if ges.from_mousewheel and ges.relative and ges.relative.y then
|
|
if ges.relative.y < 0 then
|
|
if not self.definition_widget[1]:onScrollDown() then
|
|
self:onReadNextResult()
|
|
end
|
|
elseif ges.relative.y > 0 then
|
|
if not self.definition_widget[1]:onScrollUp() then
|
|
self:onReadPrevResult()
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
-- We can forward onMovablePanRelease() does enough checks
|
|
return self.movable:onMovablePanRelease(arg, ges)
|
|
end
|
|
|
|
function DictQuickLookup:lookupInputWord(hint)
|
|
self.input_dialog = InputDialog:new{
|
|
title = _("Enter a word or phrase to look up"),
|
|
input = hint,
|
|
input_hint = hint,
|
|
buttons = {
|
|
{
|
|
{
|
|
text = _("Translate"),
|
|
callback = function()
|
|
local text = self.input_dialog:getInputText()
|
|
if text ~= "" then
|
|
UIManager:close(self.input_dialog)
|
|
Translator:showTranslation(text, true)
|
|
end
|
|
end,
|
|
},
|
|
{
|
|
text = _("Search Wikipedia"),
|
|
is_enter_default = self.is_wiki,
|
|
callback = function()
|
|
local text = self.input_dialog:getInputText()
|
|
if text ~= "" then
|
|
UIManager:close(self.input_dialog)
|
|
self.is_wiki = true
|
|
self:lookupWikipedia(false, text, true)
|
|
end
|
|
end,
|
|
},
|
|
},
|
|
{
|
|
{
|
|
text = _("Cancel"),
|
|
id = "close",
|
|
callback = function()
|
|
UIManager:close(self.input_dialog)
|
|
end,
|
|
},
|
|
{
|
|
text = _("Search dictionary"),
|
|
is_enter_default = not self.is_wiki,
|
|
callback = function()
|
|
local text = self.input_dialog:getInputText()
|
|
if text ~= "" then
|
|
UIManager:close(self.input_dialog)
|
|
self.is_wiki = false
|
|
self.ui:handleEvent(Event:new("LookupWord", text, true))
|
|
end
|
|
end,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
UIManager:show(self.input_dialog)
|
|
self.input_dialog:onShowKeyboard()
|
|
end
|
|
|
|
function DictQuickLookup:lookupWikipedia(get_fullpage, word, is_sane, lang)
|
|
if not lang then
|
|
-- Use the lang of the current or nearest is_wiki DictQuickLookup.
|
|
-- Otherwise, first lang in ReaderWikipedia.wiki_languages will be used.
|
|
for i = #DictQuickLookup.window_list, 1, -1 do
|
|
local window = DictQuickLookup.window_list[i]
|
|
if window.is_wiki and window.lang then
|
|
lang = window.lang
|
|
break
|
|
end
|
|
end
|
|
end
|
|
if not word then
|
|
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
|
|
is_sane = true
|
|
else
|
|
-- we use the original word that was querried
|
|
word = self.word
|
|
is_sane = false
|
|
end
|
|
end
|
|
-- Keep providing self.word_boxes so new windows keep being positionned to not hide it
|
|
self.ui:handleEvent(Event:new("LookupWikipedia", word, is_sane, self.word_boxes, get_fullpage, lang))
|
|
end
|
|
|
|
function DictQuickLookup:onShowResultsMenu()
|
|
-- Show one row: "| word | dict |" for each result
|
|
local width = math.floor(self.width * 0.75)
|
|
local right_width = math.floor(width * 0.5)
|
|
local font_size = 18
|
|
local button_dialog
|
|
local buttons = {}
|
|
for idx, result in ipairs(self.results) do
|
|
-- Show in bold the currently displayed result
|
|
local bold = idx == self.dict_index
|
|
local row = {
|
|
{
|
|
text = result.word,
|
|
lang = result.ifo_lang and result.ifo_lang.lang_in or nil,
|
|
font_size = font_size,
|
|
font_bold = bold,
|
|
align = "left",
|
|
callback = function()
|
|
self.menu_scrolled_offsets["main"] = button_dialog:getScrolledOffset()
|
|
self.menu_opened[button_dialog] = nil
|
|
UIManager:close(button_dialog)
|
|
self:changeDictionary(idx)
|
|
end,
|
|
hold_callback = function()
|
|
-- Allow doing another lookup with this result word
|
|
self.ui:handleEvent(Event:new("LookupWord", result.word))
|
|
end,
|
|
},
|
|
{
|
|
text = result.dict,
|
|
lang = result.ifo_lang and result.ifo_lang.lang_out or nil,
|
|
font_size = font_size,
|
|
font_bold = bold,
|
|
width = right_width,
|
|
callback = function()
|
|
self.menu_scrolled_offsets["main"] = button_dialog:getScrolledOffset()
|
|
self.menu_opened[button_dialog] = nil
|
|
UIManager:close(button_dialog)
|
|
self:changeDictionary(idx)
|
|
end,
|
|
},
|
|
}
|
|
table.insert(buttons, row)
|
|
end
|
|
button_dialog = ButtonDialog:new{
|
|
width = width,
|
|
-- We don't provide shrink_unneeded_width=true as it's ugly with small words
|
|
buttons = buttons,
|
|
anchor = function()
|
|
return self.dict_title.left_button.image.dimen, true -- pop down
|
|
end,
|
|
tap_close_callback = function()
|
|
self.menu_scrolled_offsets["main"] = button_dialog:getScrolledOffset()
|
|
self.menu_opened[button_dialog] = nil
|
|
end
|
|
}
|
|
button_dialog:setScrolledOffset(self.menu_scrolled_offsets["main"])
|
|
self.menu_opened[button_dialog] = true
|
|
UIManager:show(button_dialog)
|
|
return true
|
|
end
|
|
|
|
function DictQuickLookup:showResultsAltMenu()
|
|
-- Alternative listing with long-press:
|
|
-- Show one row: "| dict | word or N results |" for each dictionary returning results
|
|
local dicts = {}
|
|
local dict_results = {}
|
|
for idx, result in ipairs(self.results) do
|
|
local dict = result.dict
|
|
if not dict_results[dict] then
|
|
dict_results[dict] = { idx }
|
|
table.insert(dicts, dict)
|
|
else
|
|
table.insert(dict_results[dict], idx)
|
|
end
|
|
end
|
|
local max_width = math.floor(self.width * 0.75)
|
|
local right_width = math.floor(max_width * 0.25)
|
|
local font_size = 18
|
|
local button_dialog
|
|
local buttons = {}
|
|
for dictnum, dict in ipairs(dicts) do
|
|
local results = dict_results[dict]
|
|
local first_result = self.results[results[1]]
|
|
-- Show in bold only the currently displayed result's dict
|
|
local bold = util.arrayContains(results, self.dict_index)
|
|
local row = {{
|
|
text = dict,
|
|
lang = first_result.ifo_lang and first_result.ifo_lang.lang_out or nil,
|
|
font_size = font_size,
|
|
font_bold = bold,
|
|
align = "left",
|
|
callback = function()
|
|
self.menu_scrolled_offsets["alt"] = button_dialog:getScrolledOffset()
|
|
self.menu_opened[button_dialog] = nil
|
|
UIManager:close(button_dialog)
|
|
self:changeDictionary(results[1])
|
|
end,
|
|
}}
|
|
-- Right button
|
|
local button_id = "button"..dictnum
|
|
local text, lang, avoid_text_truncation, is_single_result, hold_callback
|
|
if #results == 1 then
|
|
-- Show the headword, possibly truncated (otherwise, long words
|
|
-- would get displayed in a really small font size).
|
|
-- If truncated, we'll show it full in a popup
|
|
text = first_result.word
|
|
lang = first_result.ifo_lang and first_result.ifo_lang.lang_in or nil
|
|
avoid_text_truncation = false
|
|
is_single_result = true
|
|
hold_callback = function()
|
|
-- Allow doing another lookup with this result word
|
|
self.ui:handleEvent(Event:new("LookupWord", first_result.word))
|
|
end
|
|
else
|
|
text = T(_("%1 results"), #results)
|
|
end
|
|
local callback = function()
|
|
local source_button = button_dialog:getButtonById(button_id)
|
|
if is_single_result and not source_button.label_widget:isTruncated() then
|
|
-- Not truncated: jump directly to the result
|
|
self.menu_scrolled_offsets["alt"] = button_dialog:getScrolledOffset()
|
|
self.menu_opened[button_dialog] = nil
|
|
UIManager:close(button_dialog)
|
|
self:changeDictionary(results[1])
|
|
return
|
|
end
|
|
local button_dialog2
|
|
local buttons2 = {}
|
|
local lang2 = first_result.ifo_lang and first_result.ifo_lang.lang_in or nil -- same for all results
|
|
for res=1, #results do
|
|
table.insert(buttons2, {{
|
|
text = self.results[results[res]].word,
|
|
lang = lang2,
|
|
font_size = font_size,
|
|
font_bold = results[res] == self.dict_index,
|
|
callback = function()
|
|
self.menu_scrolled_offsets["alt_sub"..dictnum] = button_dialog2:getScrolledOffset()
|
|
self.menu_opened[button_dialog2] = nil
|
|
UIManager:close(button_dialog2)
|
|
self.menu_scrolled_offsets["alt"] = button_dialog:getScrolledOffset()
|
|
self.menu_opened[button_dialog] = nil
|
|
UIManager:close(button_dialog)
|
|
self:changeDictionary(results[res])
|
|
end,
|
|
hold_callback = function()
|
|
-- Allow doing another lookup with this result word
|
|
self.ui:handleEvent(Event:new("LookupWord", self.results[results[res]].word))
|
|
end,
|
|
}})
|
|
end
|
|
button_dialog2 = ButtonDialog:new{
|
|
width = right_width*2, -- larger, to have room for long words
|
|
buttons = buttons2,
|
|
anchor = function()
|
|
return source_button.dimen, true -- pop down
|
|
end,
|
|
tap_close_callback = function()
|
|
self.menu_scrolled_offsets["alt_sub"..dictnum] = button_dialog2:getScrolledOffset()
|
|
self.menu_opened[button_dialog2] = nil
|
|
end
|
|
}
|
|
button_dialog2:setScrolledOffset(self.menu_scrolled_offsets["alt_sub"..dictnum])
|
|
self.menu_opened[button_dialog2] = true
|
|
UIManager:show(button_dialog2)
|
|
end
|
|
table.insert(row, {
|
|
text = text,
|
|
lang = lang,
|
|
avoid_text_truncation = avoid_text_truncation,
|
|
font_size = font_size,
|
|
font_bold = bold,
|
|
width = right_width,
|
|
callback = callback,
|
|
hold_callback = hold_callback,
|
|
id = button_id,
|
|
})
|
|
table.insert(buttons, row)
|
|
end
|
|
button_dialog = ButtonDialog:new{
|
|
width = max_width,
|
|
shrink_unneeded_width = true,
|
|
buttons = buttons,
|
|
anchor = function()
|
|
return self.dict_title.left_button.image.dimen, true -- pop down
|
|
end,
|
|
tap_close_callback = function()
|
|
self.menu_scrolled_offsets["alt"] = button_dialog:getScrolledOffset()
|
|
self.menu_opened[button_dialog] = nil
|
|
end
|
|
}
|
|
button_dialog:setScrolledOffset(self.menu_scrolled_offsets["alt"])
|
|
self.menu_opened[button_dialog] = true
|
|
UIManager:show(button_dialog)
|
|
end
|
|
|
|
function DictQuickLookup:showWikiResultsMenu()
|
|
-- Show one row with each result's article title
|
|
local max_width = math.floor(self.width * 0.75)
|
|
local font_size = 18
|
|
local button_dialog
|
|
local buttons = {}
|
|
for idx, result in ipairs(self.results) do
|
|
local bold = idx == self.dict_index
|
|
local row = {{
|
|
text = result.word,
|
|
lang = result.lang,
|
|
font_size = font_size,
|
|
font_bold = bold,
|
|
align = "left",
|
|
callback = function()
|
|
self.menu_scrolled_offsets["wiki"] = button_dialog:getScrolledOffset()
|
|
self.menu_opened[button_dialog] = nil
|
|
UIManager:close(button_dialog)
|
|
self:changeDictionary(idx)
|
|
end,
|
|
hold_callback = function()
|
|
-- Allow doing another lookup with this result title
|
|
self:lookupWikipedia(false, result.word)
|
|
end,
|
|
}}
|
|
table.insert(buttons, row)
|
|
end
|
|
button_dialog = ButtonDialog:new{
|
|
width = max_width,
|
|
shrink_unneeded_width = true,
|
|
buttons = buttons,
|
|
-- We requested 30 results, so we will probably be scrolling.
|
|
-- If we do, ensure we use these values (they will all make full pages
|
|
-- if we get 30 results), depending on what the screen height allows.
|
|
rows_per_page = { 15, 10, 6 },
|
|
anchor = function()
|
|
return self.dict_title.left_button.image.dimen, true -- pop down
|
|
end,
|
|
tap_close_callback = function()
|
|
self.menu_scrolled_offsets["wiki"] = button_dialog:getScrolledOffset()
|
|
self.menu_opened[button_dialog] = nil
|
|
end
|
|
}
|
|
button_dialog:setScrolledOffset(self.menu_scrolled_offsets["wiki"])
|
|
self.menu_opened[button_dialog] = true
|
|
UIManager:show(button_dialog)
|
|
end
|
|
|
|
return DictQuickLookup
|