mirror of
https://github.com/koreader/koreader.git
synced 2025-12-18 12:02:09 +01: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 ;).
706 lines
24 KiB
Lua
706 lines
24 KiB
Lua
--[[--
|
|
Displays some text in a scrollable view.
|
|
|
|
@usage
|
|
local textviewer = TextViewer:new{
|
|
title = _("I can scroll!"),
|
|
text = _("I'll need to be longer than this example to scroll."),
|
|
}
|
|
UIManager:show(textviewer)
|
|
]]
|
|
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 CheckButton = require("ui/widget/checkbutton")
|
|
local Device = require("device")
|
|
local Geom = require("ui/geometry")
|
|
local Font = require("ui/font")
|
|
local FrameContainer = require("ui/widget/container/framecontainer")
|
|
local GestureRange = require("ui/gesturerange")
|
|
local InputContainer = require("ui/widget/container/inputcontainer")
|
|
local InputDialog = require("ui/widget/inputdialog")
|
|
local MovableContainer = require("ui/widget/container/movablecontainer")
|
|
local Notification = require("ui/widget/notification")
|
|
local ScrollTextWidget = require("ui/widget/scrolltextwidget")
|
|
local Size = require("ui/size")
|
|
local TextBoxWidget = require("ui/widget/textboxwidget")
|
|
local TitleBar = require("ui/widget/titlebar")
|
|
local UIManager = require("ui/uimanager")
|
|
local VerticalGroup = require("ui/widget/verticalgroup")
|
|
local WidgetContainer = require("ui/widget/container/widgetcontainer")
|
|
local T = require("ffi/util").template
|
|
local lfs = require("libs/libkoreader-lfs")
|
|
local util = require("util")
|
|
local _ = require("gettext")
|
|
local Screen = Device.screen
|
|
|
|
local TextViewer = InputContainer:extend{
|
|
title = nil,
|
|
text = nil,
|
|
charlist = nil, -- internal
|
|
width = nil,
|
|
height = nil,
|
|
buttons_table = nil,
|
|
-- See TextBoxWidget for details about these options
|
|
-- We default to justified and auto_para_direction to adapt
|
|
-- to any kind of text we are given (book descriptions,
|
|
-- bookmarks' text, translation results...).
|
|
-- When used to display more technical text (HTML, CSS,
|
|
-- application logs...), it's best to reset them to false.
|
|
alignment = "left",
|
|
lang = nil,
|
|
para_direction_rtl = nil,
|
|
auto_para_direction = true,
|
|
alignment_strict = false,
|
|
|
|
title_face = nil, -- use default from TitleBar
|
|
title_multilines = nil, -- see TitleBar for details
|
|
title_shrink_font_to_fit = nil, -- see TitleBar for details
|
|
|
|
show_menu = true, -- titlebar left icon
|
|
monospace_font = nil, -- internal
|
|
text_font_size = nil, -- internal
|
|
justified = nil, -- internal
|
|
|
|
fgcolor = Blitbuffer.COLOR_BLACK,
|
|
text_padding = Size.padding.large,
|
|
text_margin = Size.margin.small,
|
|
button_padding = Size.padding.default,
|
|
-- Bottom row with Close, Find buttons. Also added when no caller's buttons defined.
|
|
add_default_buttons = nil,
|
|
default_hold_callback = nil, -- on each default button
|
|
find_centered_lines_count = 5, -- line with find results to be not far from the center
|
|
|
|
text_type = "general",
|
|
text_types = {
|
|
general = { monospace_font = false, font_size = 20, justified = false },
|
|
file_content = { monospace_font = false, font_size = 20, justified = false },
|
|
book_info = { monospace_font = false, font_size = 20, justified = true },
|
|
bookmark = { monospace_font = false, font_size = 20, justified = true },
|
|
lookup = { monospace_font = false, font_size = 20, justified = false },
|
|
code = { monospace_font = true, font_size = 16, justified = false },
|
|
},
|
|
}
|
|
|
|
function TextViewer:init(reinit)
|
|
local screen_w = Screen:getWidth()
|
|
local screen_h = Screen:getHeight()
|
|
|
|
self.align = "center"
|
|
self.region = Geom:new{
|
|
w = screen_w,
|
|
h = screen_h,
|
|
}
|
|
self.width = self.width or screen_w - Screen:scaleBySize(30)
|
|
self.height = self.height or screen_h - Screen:scaleBySize(30)
|
|
|
|
if not reinit then
|
|
local text_types = G_reader_settings:readSetting("textviewer_text_types")
|
|
local text_settings = text_types and text_types[self.text_type] or self.text_types[self.text_type]
|
|
self.monospace_font = text_settings.monospace_font
|
|
self.text_font_size = text_settings.font_size
|
|
self.justified = text_settings.justified
|
|
end
|
|
local text_font_face = self.monospace_font and "smallinfont" or "x_smallinfofont"
|
|
|
|
self._find_next = false
|
|
self._find_next_button = false
|
|
self._old_virtual_line_num = 1
|
|
|
|
if Device:hasKeys() then
|
|
self.key_events.Close = { { Device.input.group.Back } }
|
|
self.key_events.ShowMenu = { { "Menu" } }
|
|
end
|
|
|
|
if Device:isTouchDevice() then
|
|
local range = Geom:new{
|
|
w = screen_w,
|
|
h = screen_h,
|
|
}
|
|
self.ges_events = {
|
|
TapClose = {
|
|
GestureRange:new{
|
|
ges = "tap",
|
|
range = range,
|
|
},
|
|
},
|
|
Swipe = {
|
|
GestureRange:new{
|
|
ges = "swipe",
|
|
range = range,
|
|
},
|
|
},
|
|
MultiSwipe = {
|
|
GestureRange:new{
|
|
ges = "multiswipe",
|
|
range = range,
|
|
},
|
|
},
|
|
-- 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, start_idx, end_idx, to_source_index_func)
|
|
self:handleTextSelection(text, hold_duration, start_idx, end_idx, to_source_index_func)
|
|
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
|
|
|
|
self.titlebar = TitleBar:new{
|
|
width = self.width,
|
|
align = "left",
|
|
with_bottom_line = true,
|
|
title = self.title,
|
|
title_face = self.title_face,
|
|
title_multilines = self.title_multilines,
|
|
title_shrink_font_to_fit = self.title_shrink_font_to_fit,
|
|
left_icon = self.show_menu and "appbar.menu",
|
|
left_icon_tap_callback = function() self:onShowMenu() end,
|
|
close_callback = function() self:onClose() end,
|
|
show_parent = self,
|
|
}
|
|
|
|
-- Callback to enable/disable buttons, for at-top/at-bottom feedback
|
|
local prev_at_top = false -- Buttons were created enabled
|
|
local prev_at_bottom = false
|
|
local function button_update(id, enable)
|
|
local button = self.button_table:getButtonById(id)
|
|
if button then
|
|
if enable then
|
|
button:enable()
|
|
else
|
|
button:disable()
|
|
end
|
|
button:refresh()
|
|
end
|
|
end
|
|
self._buttons_scroll_callback = function(low, high)
|
|
if prev_at_top and low > 0 then
|
|
button_update("top", true)
|
|
prev_at_top = false
|
|
elseif not prev_at_top and low <= 0 then
|
|
button_update("top", false)
|
|
prev_at_top = true
|
|
end
|
|
if prev_at_bottom and high < 1 then
|
|
button_update("bottom", true)
|
|
prev_at_bottom = false
|
|
elseif not prev_at_bottom and high >= 1 then
|
|
button_update("bottom", false)
|
|
prev_at_bottom = true
|
|
end
|
|
end
|
|
|
|
-- buttons
|
|
local default_buttons =
|
|
{
|
|
{
|
|
text = _("Find"),
|
|
id = "find",
|
|
callback = function()
|
|
if self._find_next then
|
|
self:findCallback()
|
|
else
|
|
self:findDialog()
|
|
end
|
|
end,
|
|
hold_callback = function()
|
|
if self._find_next then
|
|
self:findDialog()
|
|
else
|
|
if self.default_hold_callback then
|
|
self.default_hold_callback()
|
|
end
|
|
end
|
|
end,
|
|
},
|
|
{
|
|
text = "⇱",
|
|
id = "top",
|
|
callback = function()
|
|
self.scroll_text_w:scrollToTop()
|
|
end,
|
|
hold_callback = self.default_hold_callback,
|
|
allow_hold_when_disabled = true,
|
|
},
|
|
{
|
|
text = "⇲",
|
|
id = "bottom",
|
|
callback = function()
|
|
self.scroll_text_w:scrollToBottom()
|
|
end,
|
|
hold_callback = self.default_hold_callback,
|
|
allow_hold_when_disabled = true,
|
|
},
|
|
{
|
|
text = _("Close"),
|
|
callback = function()
|
|
self:onClose()
|
|
end,
|
|
hold_callback = self.default_hold_callback,
|
|
},
|
|
}
|
|
local buttons = self.buttons_table or {}
|
|
if (self.add_default_buttons and not reinit) or not self.buttons_table then
|
|
table.insert(buttons, default_buttons)
|
|
end
|
|
self.button_table = ButtonTable:new{
|
|
width = self.width - 2*self.button_padding,
|
|
buttons = buttons,
|
|
zero_sep = true,
|
|
show_parent = self,
|
|
}
|
|
|
|
-- NT: add titlebar.left_button (hamburger menu) to FocusManager.
|
|
if Device:hasDPad() and not (Device:hasSymKey() or Device:hasScreenKB()) then
|
|
table.insert(self.button_table.layout, 1, { self.titlebar.left_button })
|
|
-- Refocus on the updated layout
|
|
self.button_table:refocusWidget()
|
|
end
|
|
|
|
local textw_height = self.height - self.titlebar:getHeight() - self.button_table:getSize().h
|
|
|
|
self.scroll_text_w = ScrollTextWidget:new{
|
|
text = self.text,
|
|
face = Font:getFace(text_font_face, self.text_font_size),
|
|
fgcolor = self.fgcolor,
|
|
width = self.width - 2*self.text_padding - 2*self.text_margin,
|
|
height = textw_height - 2*self.text_padding -2*self.text_margin,
|
|
dialog = self,
|
|
alignment = self.alignment,
|
|
justified = self.justified,
|
|
lang = self.lang,
|
|
para_direction_rtl = self.para_direction_rtl,
|
|
auto_para_direction = self.auto_para_direction,
|
|
alignment_strict = self.alignment_strict,
|
|
scroll_callback = self._buttons_scroll_callback,
|
|
}
|
|
self.textw = FrameContainer:new{
|
|
padding = self.text_padding,
|
|
margin = self.text_margin,
|
|
bordersize = 0,
|
|
self.scroll_text_w
|
|
}
|
|
|
|
self.frame = FrameContainer:new{
|
|
radius = Size.radius.window,
|
|
padding = 0,
|
|
margin = 0,
|
|
background = Blitbuffer.COLOR_WHITE,
|
|
VerticalGroup:new{
|
|
self.titlebar,
|
|
CenterContainer:new{
|
|
dimen = Geom:new{
|
|
w = self.width,
|
|
h = self.textw:getSize().h,
|
|
},
|
|
self.textw,
|
|
},
|
|
CenterContainer:new{
|
|
dimen = Geom:new{
|
|
w = self.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 text widget, and may
|
|
-- or may not be processed by it
|
|
"swipe", "hold", "hold_release", "hold_pan",
|
|
-- These do not have direct effect over the text widget,
|
|
-- but may happen while selecting text: we need to check
|
|
-- a few things before forwarding them
|
|
"touch", "pan", "pan_release",
|
|
},
|
|
self.frame,
|
|
}
|
|
self[1] = WidgetContainer:new{
|
|
align = self.align,
|
|
dimen = self.region,
|
|
self.movable,
|
|
}
|
|
end
|
|
|
|
function TextViewer:onCloseWidget()
|
|
UIManager:setDirty(nil, function()
|
|
return "partial", self.frame.dimen
|
|
end)
|
|
end
|
|
|
|
function TextViewer:onShow()
|
|
UIManager:setDirty(self, function()
|
|
return "partial", self.frame.dimen
|
|
end)
|
|
return true
|
|
end
|
|
|
|
function TextViewer:onTapClose(arg, ges_ev)
|
|
if ges_ev.pos:notIntersectWith(self.frame.dimen) then
|
|
self:onClose()
|
|
end
|
|
return true
|
|
end
|
|
|
|
function TextViewer:onMultiSwipe(arg, ges_ev)
|
|
-- For consistency with other fullscreen widgets where swipe south can't be
|
|
-- used to close and where we then allow any multiswipe to close, allow any
|
|
-- multiswipe to close this widget too.
|
|
self:onClose()
|
|
return true
|
|
end
|
|
|
|
function TextViewer:onClose()
|
|
UIManager:close(self)
|
|
if self.close_callback then
|
|
self.close_callback()
|
|
end
|
|
return true
|
|
end
|
|
|
|
function TextViewer:onSwipe(arg, ges)
|
|
if ges.pos:intersectWith(self.textw.dimen) then
|
|
local direction = BD.flipDirectionIfMirroredUILayout(ges.direction)
|
|
if direction == "west" then
|
|
self.scroll_text_w:scrollText(1)
|
|
return true
|
|
elseif direction == "east" then
|
|
self.scroll_text_w:scrollText(-1)
|
|
return true
|
|
else
|
|
-- 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
|
|
end
|
|
-- Let our MovableContainer handle swipe outside of text
|
|
return self.movable:onMovableSwipe(arg, ges)
|
|
end
|
|
|
|
-- The following handlers are similar to the ones in DictQuickLookup:
|
|
-- we just forward to our MoveableContainer the events that our
|
|
-- TextBoxWidget has not handled with text selection.
|
|
function TextViewer:onHoldStartText(_, ges)
|
|
-- Forward Hold events not processed by TextBoxWidget event handler
|
|
-- to our MovableContainer
|
|
return self.movable:onMovableHold(_, ges)
|
|
end
|
|
|
|
function TextViewer: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 TextViewer: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 TextViewer: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.textw.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 TextViewer: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 TextViewer: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
|
|
self.scroll_text_w:scrollText(1)
|
|
elseif ges.relative.y > 0 then
|
|
self.scroll_text_w:scrollText(-1)
|
|
end
|
|
return true
|
|
end
|
|
-- We can forward onMovablePanRelease() does enough checks
|
|
return self.movable:onMovablePanRelease(arg, ges)
|
|
end
|
|
|
|
function TextViewer:findDialog()
|
|
local input_dialog, check_button_case
|
|
input_dialog = InputDialog:new{
|
|
title = _("Enter text to search for"),
|
|
input = self.search_value,
|
|
buttons = {
|
|
{
|
|
{
|
|
text = _("Cancel"),
|
|
id = "close",
|
|
callback = function()
|
|
UIManager:close(input_dialog)
|
|
end,
|
|
},
|
|
{
|
|
text = _("Find first"),
|
|
callback = function()
|
|
self._find_next = false
|
|
self:findCallback(input_dialog)
|
|
end,
|
|
},
|
|
{
|
|
text = _("Find next"),
|
|
is_enter_default = true,
|
|
callback = function()
|
|
self._find_next = true
|
|
self:findCallback(input_dialog)
|
|
end,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
check_button_case = CheckButton:new{
|
|
text = _("Case sensitive"),
|
|
checked = self.case_sensitive,
|
|
parent = input_dialog,
|
|
callback = function()
|
|
self.case_sensitive = check_button_case.checked
|
|
end,
|
|
}
|
|
input_dialog:addWidget(check_button_case)
|
|
|
|
UIManager:show(input_dialog)
|
|
input_dialog:onShowKeyboard(true)
|
|
end
|
|
|
|
function TextViewer:findCallback(input_dialog)
|
|
if input_dialog then
|
|
self.search_value = input_dialog:getInputText()
|
|
if self.search_value == "" then return end
|
|
UIManager:close(input_dialog)
|
|
end
|
|
local start_pos = 1
|
|
if self._find_next then
|
|
local charpos, new_virtual_line_num = self.scroll_text_w:getCharPos()
|
|
if math.abs(new_virtual_line_num - self._old_virtual_line_num) > self.find_centered_lines_count then
|
|
start_pos = self.scroll_text_w:getCharPosAtXY(0, 0) -- first char of the top line
|
|
else
|
|
start_pos = (charpos or 0) + 1 -- previous search result
|
|
end
|
|
end
|
|
local char_pos, search_charlist
|
|
char_pos, self.charlist, search_charlist =
|
|
util.stringSearch(self.charlist or self.text, self.search_value, self.case_sensitive, start_pos)
|
|
local msg
|
|
if char_pos > 0 then
|
|
self:setTextBold(char_pos, #search_charlist)
|
|
self.scroll_text_w:moveCursorToCharPos(char_pos, self.find_centered_lines_count)
|
|
msg = T(_("Found, screen line %1."), self.scroll_text_w:getCharPosLineNum())
|
|
self._find_next = true
|
|
self._old_virtual_line_num = select(2, self.scroll_text_w:getCharPos())
|
|
else
|
|
msg = _("Not found.")
|
|
self._find_next = false
|
|
self._old_virtual_line_num = 1
|
|
end
|
|
UIManager:show(Notification:new{
|
|
text = msg,
|
|
})
|
|
if self._find_next_button ~= self._find_next then
|
|
self._find_next_button = self._find_next
|
|
local button_text = self._find_next and _("Find next") or _("Find")
|
|
local find_button = self.button_table:getButtonById("find")
|
|
find_button:setText(button_text, find_button.width)
|
|
find_button:refresh()
|
|
end
|
|
end
|
|
|
|
function TextViewer:handleTextSelection(text, hold_duration, start_idx, end_idx, to_source_index_func)
|
|
if self.text_selection_callback then
|
|
self.text_selection_callback(text, hold_duration, start_idx, end_idx, to_source_index_func)
|
|
return
|
|
end
|
|
if Device:hasClipboard() then
|
|
Device.input.setClipboardText(text)
|
|
UIManager:show(Notification:new{
|
|
text = start_idx == end_idx and _("Word copied to clipboard.")
|
|
or _("Selection copied to clipboard."),
|
|
})
|
|
end
|
|
end
|
|
|
|
function TextViewer:reinit()
|
|
local text_settings = G_reader_settings:readSetting("textviewer_text_types", {})
|
|
text_settings[self.text_type] = { monospace_font = self.monospace_font, font_size = self.text_font_size, justified = self.justified }
|
|
local low, high = self.scroll_text_w.text_widget:getVisibleHeightRatios() -- try to keep position
|
|
local ratio = low == 0 and 0 or (low + high) / 2 -- if we are at the beginning, keep the first line visible
|
|
self:init(true) -- do not add default buttons once more
|
|
UIManager:setDirty("all", "partial", self.frame.dimen)
|
|
self.scroll_text_w:scrollToRatio(ratio, ratio == 0)
|
|
end
|
|
|
|
function TextViewer:setTextBold(start_pos, len)
|
|
local end_pos = start_pos + len
|
|
local text = self.text
|
|
if self.charlist == nil then
|
|
self.charlist = util.splitToChars(text)
|
|
end
|
|
local charlist = { TextBoxWidget.PTF_HEADER }
|
|
for i, char in ipairs(self.charlist) do
|
|
if i == start_pos then
|
|
table.insert(charlist, TextBoxWidget.PTF_BOLD_START)
|
|
elseif i == end_pos then
|
|
table.insert(charlist, TextBoxWidget.PTF_BOLD_END)
|
|
end
|
|
table.insert(charlist, char)
|
|
end
|
|
self.text = table.concat(charlist)
|
|
self:reinit()
|
|
self.text = text -- restore original text
|
|
end
|
|
|
|
function TextViewer:onShowMenu()
|
|
local dialog
|
|
local buttons = {
|
|
{{
|
|
text_func = function()
|
|
return T(_("Font size: %1"), self.text_font_size)
|
|
end,
|
|
align = "left",
|
|
callback = function()
|
|
UIManager:close(dialog)
|
|
local SpinWidget = require("ui/widget/spinwidget")
|
|
local widget = SpinWidget:new{
|
|
title_text = _("Font size"),
|
|
value = self.text_font_size,
|
|
value_min = 12,
|
|
value_max = 30,
|
|
default_value = self.monospace_font and 16 or 20,
|
|
keep_shown_on_apply = true,
|
|
callback = function(spin)
|
|
self.text_font_size = spin.value
|
|
self:reinit()
|
|
end,
|
|
}
|
|
UIManager:show(widget)
|
|
end,
|
|
}},
|
|
{{
|
|
text = _("Monospace font"),
|
|
checked_func = function()
|
|
return self.monospace_font
|
|
end,
|
|
align = "left",
|
|
callback = function()
|
|
self.monospace_font = not self.monospace_font
|
|
self:reinit()
|
|
end,
|
|
}},
|
|
{{
|
|
text = _("Justify"),
|
|
checked_func = function()
|
|
return self.justified
|
|
end,
|
|
align = "left",
|
|
callback = function()
|
|
self.justified = not self.justified
|
|
self:reinit()
|
|
end,
|
|
}},
|
|
}
|
|
dialog = ButtonDialog:new{
|
|
shrink_unneeded_width = true,
|
|
buttons = buttons,
|
|
anchor = function()
|
|
return self.titlebar.left_button.image.dimen
|
|
end,
|
|
}
|
|
UIManager:show(dialog)
|
|
end
|
|
|
|
-- Register DocumentRegistry auxiliary provider.
|
|
function TextViewer:register(registry)
|
|
registry:addAuxProvider({
|
|
provider_name = _("Text viewer"),
|
|
provider = "textviewer",
|
|
order = 20, -- order in OpenWith dialog
|
|
enabled_func = function()
|
|
return true -- all files
|
|
end,
|
|
callback = TextViewer.openFile,
|
|
disable_file = true,
|
|
disable_type = false,
|
|
})
|
|
end
|
|
|
|
function TextViewer.openFile(file)
|
|
local function _openFile(file_path)
|
|
local file_handle = io.open(file_path, "rb")
|
|
if not file_handle then return end
|
|
local file_content = file_handle:read("*all")
|
|
file_handle:close()
|
|
UIManager:show(TextViewer:new{
|
|
title = file_path,
|
|
title_multilines = true,
|
|
text = file_content,
|
|
text_type = "file_content",
|
|
})
|
|
end
|
|
local attr = lfs.attributes(file)
|
|
if attr then
|
|
if attr.size > 400000 then
|
|
local ConfirmBox = require("ui/widget/confirmbox")
|
|
UIManager:show(ConfirmBox:new{
|
|
text = T(_("This file is %2:\n\n%1\n\nAre you sure you want to open it?\n\nOpening big files may take some time."),
|
|
BD.filepath(file), util.getFriendlySize(attr.size)),
|
|
ok_text = _("Open"),
|
|
ok_callback = function()
|
|
_openFile(file)
|
|
end,
|
|
})
|
|
else
|
|
_openFile(file)
|
|
end
|
|
end
|
|
end
|
|
|
|
return TextViewer
|