mirror of
https://github.com/koreader/koreader.git
synced 2025-08-10 00:52:38 +00:00
Merge branch 'master' of github.com:koreader/koreader
This commit is contained in:
@@ -38,6 +38,7 @@ jobs:
|
||||
CCACHE_MAXSIZE: "128M"
|
||||
CLICOLOR_FORCE: "1"
|
||||
EMULATE_READER: "1"
|
||||
KODEBUG: ""
|
||||
MAKEFLAGS: "PARALLEL_JOBS=3 OUTPUT_DIR=build INSTALL_DIR=install"
|
||||
steps:
|
||||
# Checkout / fetch. {{{
|
||||
|
||||
1
.github/workflows/build.yml
vendored
1
.github/workflows/build.yml
vendored
@@ -35,6 +35,7 @@ jobs:
|
||||
# Bump first number to reset all caches.
|
||||
CACHE_KEY: "1-macOS-${{ matrix.image }}-${{ matrix.platform }}-XC${{ matrix.xcode_version }}-DT${{ matrix.deployment_target }}"
|
||||
CLICOLOR_FORCE: '1'
|
||||
KODEBUG: ""
|
||||
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.deployment_target }}
|
||||
MAKEFLAGS: 'OUTPUT_DIR=build INSTALL_DIR=install TARGET=macos'
|
||||
|
||||
|
||||
2
base
2
base
Submodule base updated: e9a00bdf9c...b4e76037fa
@@ -129,7 +129,7 @@ function FileManager:setupLayout()
|
||||
button_padding = Screen:scaleBySize(5),
|
||||
left_icon = "home",
|
||||
left_icon_size_ratio = 1,
|
||||
left_icon_tap_callback = function() self:goHome() end,
|
||||
left_icon_tap_callback = function() self:onHome() end,
|
||||
left_icon_hold_callback = function() self:onShowFolderMenu() end,
|
||||
right_icon = self.selected_files and "check" or "plus",
|
||||
right_icon_size_ratio = 1,
|
||||
@@ -163,7 +163,7 @@ function FileManager:setupLayout()
|
||||
if file_manager.selected_files then -- toggle selection
|
||||
item.dim = not item.dim and true or nil
|
||||
file_manager.selected_files[item.path] = item.dim
|
||||
self:updateItems()
|
||||
self:updateItems(1, true)
|
||||
else
|
||||
file_manager:openFile(item.path)
|
||||
end
|
||||
@@ -212,7 +212,7 @@ function FileManager:setupLayout()
|
||||
if is_file then
|
||||
file_manager.selected_files[file] = true
|
||||
item.dim = true
|
||||
self:updateItems()
|
||||
self:updateItems(1, true)
|
||||
end
|
||||
end,
|
||||
},
|
||||
@@ -649,10 +649,11 @@ function FileManager:tapPlus()
|
||||
end
|
||||
|
||||
else -- no selected files
|
||||
local folder = self.file_chooser.path
|
||||
local function refresh_titlebar_callback()
|
||||
self:updateTitleBarPath()
|
||||
self:updateTitleBarPath(folder)
|
||||
end
|
||||
title = BD.dirpath(filemanagerutil.abbreviate(self.file_chooser.path))
|
||||
title = BD.dirpath(filemanagerutil.abbreviate(folder))
|
||||
buttons = {
|
||||
{
|
||||
{
|
||||
@@ -696,7 +697,7 @@ function FileManager:tapPlus()
|
||||
text = _("Go to HOME folder"),
|
||||
callback = function()
|
||||
UIManager:close(plus_dialog)
|
||||
self:goHome()
|
||||
self:onHome()
|
||||
end
|
||||
},
|
||||
},
|
||||
@@ -706,12 +707,12 @@ function FileManager:tapPlus()
|
||||
callback = function()
|
||||
UIManager:close(plus_dialog)
|
||||
-- any random document
|
||||
self:openRandomFile(self.file_chooser.path, false)
|
||||
self:openRandomFile(folder, false)
|
||||
end,
|
||||
hold_callback = function()
|
||||
UIManager:close(plus_dialog)
|
||||
-- only previously unopened
|
||||
self:openRandomFile(self.file_chooser.path, true)
|
||||
self:openRandomFile(folder, true)
|
||||
end
|
||||
},
|
||||
},
|
||||
@@ -719,7 +720,7 @@ function FileManager:tapPlus()
|
||||
self.folder_shortcuts:genShowFolderShortcutsButton(close_dialog_callback),
|
||||
},
|
||||
{
|
||||
self.folder_shortcuts:genAddRemoveShortcutButton(self.file_chooser.path, close_dialog_callback, refresh_titlebar_callback),
|
||||
self.folder_shortcuts:genAddRemoveShortcutButton(folder, close_dialog_callback, refresh_titlebar_callback),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -727,12 +728,12 @@ function FileManager:tapPlus()
|
||||
table.insert(buttons, 4, { -- after "Paste" or "Import files here" button
|
||||
{
|
||||
text_func = function()
|
||||
return Device:isValidPath(self.file_chooser.path)
|
||||
return Device:isValidPath(folder)
|
||||
and _("Switch to SDCard") or _("Switch to internal storage")
|
||||
end,
|
||||
callback = function()
|
||||
UIManager:close(plus_dialog)
|
||||
if Device:isValidPath(self.file_chooser.path) then
|
||||
if Device:isValidPath(folder) then
|
||||
local ok, sd_path = Device:hasExternalSD()
|
||||
if ok then
|
||||
self.file_chooser:changeToPath(sd_path)
|
||||
@@ -749,10 +750,10 @@ function FileManager:tapPlus()
|
||||
table.insert(buttons, 4, { -- always after "Paste" button
|
||||
{
|
||||
text = _("Import files here"),
|
||||
enabled = Device:isValidPath(self.file_chooser.path),
|
||||
enabled = Device:isValidPath(folder),
|
||||
callback = function()
|
||||
UIManager:close(plus_dialog)
|
||||
Device.importFile(self.file_chooser.path)
|
||||
Device.importFile(folder)
|
||||
end,
|
||||
},
|
||||
})
|
||||
@@ -847,7 +848,10 @@ function FileManager:onRefresh()
|
||||
return true
|
||||
end
|
||||
|
||||
function FileManager:goHome()
|
||||
FileManager.onRefreshContent = FileManager.onRefresh
|
||||
FileManager.onBookMetadataChanged = FileManager.onRefresh
|
||||
|
||||
function FileManager:onHome()
|
||||
if not self.file_chooser:goHome() then
|
||||
self:setHome()
|
||||
end
|
||||
@@ -1307,18 +1311,6 @@ function FileManager:copyRecursive(from, to)
|
||||
return ffiUtil.execute(self.cp_bin, "-r", from, to ) == 0
|
||||
end
|
||||
|
||||
function FileManager:onHome()
|
||||
return self:goHome()
|
||||
end
|
||||
|
||||
function FileManager:onRefreshContent()
|
||||
self:onRefresh()
|
||||
end
|
||||
|
||||
function FileManager:onBookMetadataChanged()
|
||||
self:onRefresh()
|
||||
end
|
||||
|
||||
function FileManager:onShowFolderMenu()
|
||||
local button_dialog
|
||||
local function genButton(button_text, button_path)
|
||||
|
||||
@@ -1388,7 +1388,7 @@ function FileManagerCollection:searchCollections(coll_name)
|
||||
-- Fortunately, this is run in a subprocess, so we won't be affecting the
|
||||
-- main process's crengine state or any document opened in the main
|
||||
-- process (we furthermore prevent this feature when one is opened).
|
||||
-- To avoid creating half-rendered/invalide cache files, it's best to disable
|
||||
-- To avoid creating half-rendered/invalid cache files, it's best to disable
|
||||
-- crengine saving of such cache files.
|
||||
if not self.is_cre_cache_disabled then
|
||||
local cre = require("document/credocument"):engineInit()
|
||||
|
||||
@@ -99,6 +99,7 @@ function ReaderBookmark:addToMainMenu(menu_items)
|
||||
checked_func = function()
|
||||
return self.ui.paging.bookmark_flipping_mode
|
||||
end,
|
||||
check_callback_closes_menu = true,
|
||||
callback = function(touchmenu_instance)
|
||||
self.ui.paging:onToggleBookmarkFlipping()
|
||||
touchmenu_instance:closeMenu()
|
||||
|
||||
@@ -852,6 +852,9 @@ function ReaderDictionary:cleanSelection(text, is_sane)
|
||||
text = text:gsub("\u{2019}", "'") -- Right single quotation mark
|
||||
-- Strip punctuation characters around selection
|
||||
text = util.stripPunctuation(text)
|
||||
-- In some dictionaries, both interpuncts (·) and pipes (|) are used to delimiter syllables.
|
||||
-- Up arrows (↑), are used in some dictionaries to indicate related words.
|
||||
text = text:gsub("[·|↑]", "")
|
||||
-- Strip some common english grammatical construct
|
||||
text = text:gsub("'s$", '') -- english possessive
|
||||
-- Strip some common french grammatical constructs
|
||||
|
||||
@@ -1015,13 +1015,10 @@ function ReaderHighlight:onTapSelectModeIcon()
|
||||
end
|
||||
|
||||
function ReaderHighlight:onTap(_, ges)
|
||||
-- We only actually need to clear if we have something to clear in the first place.
|
||||
-- (We mainly want to avoid CRe's clearSelection,
|
||||
-- which may incur a redraw as it invalidates the cache, c.f., #6854)
|
||||
-- ReaderHighlight:clear can only return true if self.hold_pos was set anyway.
|
||||
local cleared = self.hold_pos and self:clear()
|
||||
-- We only care about potential taps on existing highlights, not on taps that closed a highlight menu.
|
||||
if not cleared and ges and #self.view.highlight.visible_boxes > 0 then
|
||||
if self.hold_pos then -- accidental tap while long-pressing
|
||||
return self:onHoldRelease()
|
||||
end
|
||||
if ges and #self.view.highlight.visible_boxes > 0 then
|
||||
local pos = self.view:screenToPageTransform(ges.pos)
|
||||
local highlights_tapped = {}
|
||||
for _, box in ipairs(self.view.highlight.visible_boxes) do
|
||||
@@ -1487,7 +1484,6 @@ function ReaderHighlight:showHighlightDialog(index)
|
||||
end,
|
||||
}
|
||||
UIManager:show(edit_highlight_dialog)
|
||||
return true
|
||||
end
|
||||
|
||||
function ReaderHighlight:addToHighlightDialog(idx, fn_button)
|
||||
@@ -1526,7 +1522,11 @@ function ReaderHighlight:onShowHighlightMenu(index)
|
||||
anchor = function()
|
||||
return self:_getDialogAnchor(self.highlight_dialog, index)
|
||||
end,
|
||||
tap_close_callback = function() self:handleEvent(Event:new("Tap")) end,
|
||||
tap_close_callback = function()
|
||||
if self.hold_pos then
|
||||
self:clear()
|
||||
end
|
||||
end,
|
||||
}
|
||||
-- NOTE: Disable merging for this update,
|
||||
-- or the buggy Sage kernel may alpha-blend it into the page (with a bogus alpha value, to boot)...
|
||||
@@ -1678,15 +1678,13 @@ function ReaderHighlight:onHold(arg, ges)
|
||||
local image = self.ui.document:getImageFromPosition(self.hold_pos, true, true)
|
||||
if image then
|
||||
logger.dbg("hold on image")
|
||||
self.hold_pos = nil
|
||||
local ImageViewer = require("ui/widget/imageviewer")
|
||||
local imgviewer = ImageViewer:new{
|
||||
UIManager:show(ImageViewer:new{
|
||||
image = image,
|
||||
-- title_text = _("Document embedded image"),
|
||||
-- No title, more room for image
|
||||
with_title_bar = false,
|
||||
with_title_bar = false, -- more room for image
|
||||
fullscreen = true,
|
||||
}
|
||||
UIManager:show(imgviewer)
|
||||
})
|
||||
self:onStopHighlightIndicator()
|
||||
return true
|
||||
end
|
||||
|
||||
@@ -1122,6 +1122,7 @@ See Style tweaks → Miscellaneous → Alternative ToC hints.]])
|
||||
checked_func = function()
|
||||
return self.ui.document:isTocAlternativeToc()
|
||||
end,
|
||||
check_callback_closes_menu = true,
|
||||
callback = function(touchmenu_instance)
|
||||
if self.ui.document:isTocAlternativeToc() then
|
||||
UIManager:show(ConfirmBox:new{
|
||||
|
||||
@@ -953,6 +953,7 @@ local KindleOasis = Kindle:extend{
|
||||
hasKeys = yes,
|
||||
hasGSensor = yes,
|
||||
display_dpi = 300,
|
||||
hasAuxBattery = yes,
|
||||
--[[
|
||||
-- NOTE: Points to event3 on Wi-Fi devices, event4 on 3G devices...
|
||||
-- 3G devices apparently have an extra SX9500 Proximity/Capacitive controller for mysterious purposes...
|
||||
@@ -1351,9 +1352,11 @@ function KindleOasis:init()
|
||||
self.powerd = require("device/kindle/powerd"):new{
|
||||
device = self,
|
||||
fl_intensity_file = "/sys/class/backlight/max77696-bl/brightness",
|
||||
-- NOTE: Points to the embedded battery. The one in the cover is codenamed "soda".
|
||||
-- NOTE: Points to the embedded battery. The one in the cover is codenamed "soda", see aux_batt_capacity_file below.
|
||||
batt_capacity_file = "/sys/devices/system/wario_battery/wario_battery0/battery_capacity",
|
||||
is_charging_file = "/sys/devices/system/wario_charger/wario_charger0/charging",
|
||||
aux_batt_capacity_file = "/sys/devices/platform/soda/power_supply/soda_fg/capacity",
|
||||
aux_batt_status_file = "/sys/devices/platform/soda/power_supply/soda_fg/status",
|
||||
hall_file = "/sys/devices/system/wario_hall/wario_hall0/hall_enable",
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,33 @@ function KindlePowerD:init()
|
||||
self.fl_max = self.fl_max + 1
|
||||
end
|
||||
|
||||
if self.device:hasAuxBattery() then
|
||||
self.getAuxCapacityHW = function(this)
|
||||
return this:unchecked_read_int_file(self.aux_batt_capacity_file)
|
||||
end
|
||||
|
||||
self.isAuxBatteryConnectedHW = function(this)
|
||||
local status = this:read_str_file(self.aux_batt_status_file)
|
||||
if status == nil then
|
||||
-- File could not be read, assume not connected
|
||||
return false
|
||||
end
|
||||
-- File was read, assume aux battery is connected
|
||||
return true
|
||||
end
|
||||
|
||||
self.isAuxChargingHW = function(this)
|
||||
-- "Discharging" when discharging
|
||||
-- "Full" when full
|
||||
-- "Charging" when charging via DCP
|
||||
return this:read_str_file(this.aux_batt_status_file) ~= "Discharging"
|
||||
end
|
||||
|
||||
self.isAuxChargedHW = function(this)
|
||||
return this:read_str_file(this.aux_batt_status_file) == "Full"
|
||||
end
|
||||
end
|
||||
|
||||
self:initWakeupMgr()
|
||||
end
|
||||
|
||||
|
||||
@@ -995,11 +995,10 @@ function Dispatcher:addSubMenu(caller, menu, location, settings)
|
||||
menu.ignored_by_menu_search = true -- all those would be duplicated
|
||||
table.insert(menu, {
|
||||
text = _("Nothing"),
|
||||
keep_menu_open = true,
|
||||
no_refresh_on_check = true,
|
||||
checked_func = function()
|
||||
return location[settings] ~= nil and Dispatcher:_itemsCount(location[settings]) == 0
|
||||
end,
|
||||
check_callback_updates_menu = true,
|
||||
callback = function(touchmenu_instance)
|
||||
local function do_remove()
|
||||
local actions = location[settings]
|
||||
|
||||
@@ -487,7 +487,7 @@ function Document:renderPage(pageno, rect, zoom, rotation, gamma, white_threshol
|
||||
-- Make the context match the rotation,
|
||||
-- by pointing at the rotated origin via coordinates offsets.
|
||||
-- NOTE: We rotate our *Screen* bb on rotation (SetRotationMode), not the document,
|
||||
-- so we hardly ever exercize this codepath...
|
||||
-- so we hardly ever exercise this codepath...
|
||||
-- AFAICT, the only thing that *ever* (attempted to) rotate the document was ReaderRotation's key bindings (RotationUpdate).
|
||||
--- @note: It was broken as all hell (it had likely never worked outside of its original implementation in KPV), and has been removed in #12658
|
||||
if rotation == 90 then
|
||||
|
||||
@@ -299,7 +299,7 @@ function KoptInterface:reflowPage(doc, pageno, bbox, background)
|
||||
kc:setPreCache()
|
||||
self.bg_thread = true
|
||||
end
|
||||
-- Caculate zoom.
|
||||
-- Calculate zoom.
|
||||
kc.zoom = (1.5 * kc.zoom * kc.quality * kc.dev_width) / bbox.x1
|
||||
-- Generate pixmap.
|
||||
local page = doc._document:openPage(pageno)
|
||||
@@ -1435,7 +1435,7 @@ end
|
||||
local function get_pattern_list(pattern, case_insensitive)
|
||||
-- pattern list of single words
|
||||
local plist = {}
|
||||
-- (as in util.splitToWords(), but only splitting on spaces, keeping punctuations)
|
||||
-- (as in util.splitToWords(), but only splitting on spaces, keeping punctuation marks)
|
||||
for word in util.gsplit(pattern, "%s+") do
|
||||
if util.hasCJKChar(word) then
|
||||
for char in util.gsplit(word, "[\192-\255][\128-\191]+", true) do
|
||||
|
||||
@@ -14,6 +14,7 @@ local function genMenuItem(text, mode)
|
||||
return Screen:getRotationMode() == mode
|
||||
end,
|
||||
radio = true,
|
||||
check_callback_closes_menu = true,
|
||||
callback = function(touchmenu_instance)
|
||||
UIManager:broadcastEvent(Event:new("SetRotationMode", mode))
|
||||
touchmenu_instance:closeMenu()
|
||||
|
||||
@@ -618,7 +618,7 @@ function BookMapRow:paintTo(bb, x, y)
|
||||
alt_bb = glyph.bb:rotatedCopy(indicator.rotation)
|
||||
end
|
||||
-- Glyph's bb fit the blackbox of the glyph, so there's no cropping
|
||||
-- or complicated positionning to do
|
||||
-- or complicated positioning to do
|
||||
-- By default, just center the glyph at x
|
||||
local d_x_pct = indicator.shift_x_pct or 0.5
|
||||
local d_x = math.floor(glyph.bb:getWidth() * d_x_pct)
|
||||
@@ -759,7 +759,7 @@ function BookMapWidget:init()
|
||||
}
|
||||
end
|
||||
|
||||
-- No real need for any explicite edge and inter-row padding:
|
||||
-- No real need for any explicit edge and inter-row padding:
|
||||
-- we use the scrollbar width on both sides for balance (we may put a start
|
||||
-- page number on the left space), and each BookMapRow will have itself some
|
||||
-- blank space at bottom below page slots (where we may put hanging markers
|
||||
|
||||
@@ -264,6 +264,8 @@ function HtmlBoxWidget:setContent(body, css, default_font_size, is_xhtml, no_css
|
||||
self.document:layoutDocument(self.dimen.w, self.dimen.h, default_font_size)
|
||||
|
||||
self.page_count = self.document:getPages()
|
||||
self.page_boxes = nil
|
||||
self:clearHighlight()
|
||||
end
|
||||
|
||||
function HtmlBoxWidget:_render()
|
||||
|
||||
@@ -643,7 +643,7 @@ function InputDialog:toggleKeyboard(force_toggle)
|
||||
-- Remember the *current* visibility, as the following close will reset it
|
||||
local visible = self:isKeyboardVisible()
|
||||
|
||||
-- When we forcibly close the keyboard, remember its current visiblity state, so that we can properly restore it later.
|
||||
-- When we forcibly close the keyboard, remember its current visibility state, so that we can properly restore it later.
|
||||
-- (This is used by some buttons in fullscreen mode, where we might want to keep the original keyboard hidden when popping up a new one for another InputDialog).
|
||||
if force_toggle == false then
|
||||
-- NOTE: visible will be nil between our own init and a show of the keyboard, which is precisely what happens when we *hide* the keyboard.
|
||||
|
||||
@@ -50,6 +50,8 @@ local TouchMenuItem = InputContainer:extend{
|
||||
dimen = nil,
|
||||
face = Font:getFace("smallinfofont"),
|
||||
show_parent = nil,
|
||||
check_callback_updates_menu = nil, -- set to true for item with checkmark if its callback updates menu
|
||||
check_callback_closes_menu = nil, -- set to true for item with checkmark if its callback closes menu
|
||||
}
|
||||
|
||||
function TouchMenuItem:init()
|
||||
@@ -206,8 +208,7 @@ function TouchMenuItem:onTapSelect(arg, ges)
|
||||
-- Unhighlight
|
||||
--
|
||||
self.item_frame.invert = false
|
||||
-- NOTE: If the menu is going to be closed, we can safely drop that.
|
||||
if self.item.keep_menu_open then
|
||||
if self.item.keep_menu_open or self.item.check_callback_updates_menu then
|
||||
UIManager:widgetInvert(self.item_frame, highlight_dimen.x, highlight_dimen.y, highlight_dimen.w)
|
||||
UIManager:setDirty(nil, "ui", highlight_dimen)
|
||||
end
|
||||
@@ -470,7 +471,6 @@ local TouchMenu = FocusManager:extend{
|
||||
fface = Font:getFace("ffont"),
|
||||
width = nil,
|
||||
height = nil,
|
||||
page = 1,
|
||||
max_per_page_default = 10,
|
||||
-- for UIManager:setDirty
|
||||
show_parent = nil,
|
||||
@@ -480,6 +480,7 @@ local TouchMenu = FocusManager:extend{
|
||||
}
|
||||
|
||||
function TouchMenu:init()
|
||||
self.screen_size = Screen:getSize()
|
||||
-- We won't include self.bordersize in our width calculations, so that
|
||||
-- borders are pushed off-(screen-)width and so not visible.
|
||||
-- We'll then be similar to bottom menu ConfigDialog (where this
|
||||
@@ -499,8 +500,8 @@ function TouchMenu:init()
|
||||
ges = "tap",
|
||||
range = Geom:new{
|
||||
x = 0, y = 0,
|
||||
w = Screen:getWidth(),
|
||||
h = Screen:getHeight(),
|
||||
w = self.screen_size.w,
|
||||
h = self.screen_size.h,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -623,7 +624,7 @@ function TouchMenu:init()
|
||||
-- This CenterContainer will make the left and right borders drawn
|
||||
-- off-screen
|
||||
self[1] = CenterContainer:new{
|
||||
dimen = Screen:getSize(),
|
||||
dimen = self.screen_size,
|
||||
ignore = "height",
|
||||
self.menu_frame
|
||||
}
|
||||
@@ -642,51 +643,31 @@ function TouchMenu:init()
|
||||
HorizontalSpan:new{width = Size.span.horizontal_default},
|
||||
}
|
||||
self.footer_top_margin = VerticalSpan:new{width = Size.span.vertical_default}
|
||||
|
||||
local menu_height = self.height and math.min(self.height, self.screen_size.h) or self.screen_size.h
|
||||
local items_height = menu_height - self.bar:getSize().h - self.footer_top_margin:getSize().h - self.footer:getSize().h
|
||||
self.max_per_page = math.floor(items_height / (self.item_height + self.split_line:getSize().h))
|
||||
|
||||
self.bar:switchToTab(self.last_index or 1)
|
||||
end
|
||||
|
||||
function TouchMenu:onCloseWidget()
|
||||
-- NOTE: We don't pass a region in order to ensure a full-screen flash to avoid ghosting,
|
||||
-- but we only need to do that if we actually have a FM or RD below us.
|
||||
-- Don't do anything when we're switching between the two, or if we don't actually have a live instance of 'em...
|
||||
local FileManager = require("apps/filemanager/filemanager")
|
||||
local ReaderUI = require("apps/reader/readerui")
|
||||
if (FileManager.instance and not FileManager.instance.tearing_down)
|
||||
or (ReaderUI.instance and not ReaderUI.instance.tearing_down) then
|
||||
UIManager:setDirty(nil, "flashui")
|
||||
end
|
||||
end
|
||||
|
||||
function TouchMenu:_recalculatePageLayout()
|
||||
local content_height -- content == item_list + footer
|
||||
|
||||
local bar_height = self.bar:getSize().h
|
||||
local footer_height = self.footer:getSize().h
|
||||
if self.height then
|
||||
content_height = self.height - bar_height
|
||||
else
|
||||
content_height = #self.item_table * self.item_height + footer_height
|
||||
-- split line height
|
||||
content_height = content_height + (#self.item_table - 1)
|
||||
content_height = content_height + self.footer_top_margin:getSize().h
|
||||
end
|
||||
if content_height + bar_height > Screen:getHeight() then
|
||||
content_height = Screen:getHeight() - bar_height
|
||||
end
|
||||
|
||||
local item_list_content_height = content_height - footer_height
|
||||
self.perpage = math.floor(item_list_content_height / self.item_height)
|
||||
local max_per_page = self.item_table.max_per_page or self.max_per_page_default
|
||||
if self.perpage > max_per_page then
|
||||
self.perpage = max_per_page
|
||||
end
|
||||
|
||||
function TouchMenu:updateItems(target_page, target_item_id)
|
||||
if #self.item_table == 0 then return end
|
||||
self.perpage = math.min(self.max_per_page, self.item_table.max_per_page or self.max_per_page_default)
|
||||
self.page_num = math.ceil(#self.item_table / self.perpage)
|
||||
end
|
||||
if target_item_id ~= nil then -- show menu page with target item
|
||||
for i, v in ipairs(self.item_table) do
|
||||
if v.menu_item_id == target_item_id then
|
||||
target_page = math.floor( (i - 1) / self.perpage ) + 1
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
self.page = target_page or self.page
|
||||
if self.page > self.page_num then
|
||||
self.page = self.page_num
|
||||
end
|
||||
|
||||
function TouchMenu:updateItems()
|
||||
local old_dimen = self.dimen and self.dimen:copy()
|
||||
self:_recalculatePageLayout()
|
||||
self.item_group:clear()
|
||||
self.layout = {}
|
||||
table.insert(self.item_group, self.bar)
|
||||
@@ -707,14 +688,12 @@ function TouchMenu:updateItems()
|
||||
h = self.item_height,
|
||||
},
|
||||
show_parent = self.show_parent,
|
||||
item_visible_index = c,
|
||||
}
|
||||
table.insert(self.item_group, item_tmp)
|
||||
if item_tmp:isEnabled() then
|
||||
table.insert(self.layout, {[self.cur_tab] = item_tmp}) -- for the focusmanager
|
||||
end
|
||||
if item.separator and c ~= self.perpage and i ~= #self.item_table then
|
||||
-- insert split line
|
||||
table.insert(self.item_group, self.split_line)
|
||||
end
|
||||
else
|
||||
@@ -742,7 +721,6 @@ function TouchMenu:updateItems()
|
||||
local batt_lvl = powerd:getCapacity()
|
||||
local batt_symbol = powerd:getBatterySymbol(powerd:isCharged(), powerd:isCharging(), batt_lvl)
|
||||
time_info_txt = BD.wrap(time_info_txt) .. " " .. BD.wrap("⌁") .. BD.wrap(batt_symbol) .. BD.wrap(batt_lvl .. "%")
|
||||
|
||||
if Device:hasAuxBattery() and powerd:isAuxBatteryConnected() then
|
||||
local aux_batt_lvl = powerd:getAuxCapacity()
|
||||
local aux_batt_symbol = powerd:getBatterySymbol(powerd:isAuxCharged(), powerd:isAuxCharging(), aux_batt_lvl)
|
||||
@@ -752,6 +730,7 @@ function TouchMenu:updateItems()
|
||||
self.time_info:setText(time_info_txt)
|
||||
|
||||
-- recalculate dimen based on new layout
|
||||
local old_dimen = self.dimen:copy()
|
||||
self.dimen.w = self.width
|
||||
self.dimen.h = self.item_group:getSize().h + self.bordersize*2 + self.padding -- (no padding at top)
|
||||
self:moveFocusTo(self.cur_tab, 1, FocusManager.NOT_FOCUS) -- reset the position of the focusmanager
|
||||
@@ -790,13 +769,11 @@ function TouchMenu:switchMenuTab(tab_num)
|
||||
-- It's like getting a new menu every time we switch tab!
|
||||
-- Also, switching to the _same_ tab resets the stack and takes us back to
|
||||
-- the top of the menu tree
|
||||
self.page = 1
|
||||
-- clear item table stack
|
||||
self.item_table_stack = {}
|
||||
self.parent_id = nil
|
||||
self.cur_tab = tab_num
|
||||
self.item_table = self.tab_item_table[tab_num]
|
||||
self:updateItems()
|
||||
self:updateItems(1)
|
||||
end
|
||||
|
||||
function TouchMenu:backToUpperMenu(no_close)
|
||||
@@ -807,68 +784,40 @@ function TouchMenu:backToUpperMenu(no_close)
|
||||
if self.item_table.needs_refresh and self.item_table.refresh_func then
|
||||
self.item_table = self.item_table.refresh_func()
|
||||
end
|
||||
self.page = 1
|
||||
if self.parent_id then
|
||||
self:_recalculatePageLayout() -- we need an accurate self.perpage
|
||||
for i = 1, #self.item_table do
|
||||
if self.item_table[i].menu_item_id == self.parent_id then
|
||||
self.page = math.floor( (i - 1) / self.perpage ) + 1
|
||||
break
|
||||
end
|
||||
end
|
||||
self.parent_id = nil
|
||||
end
|
||||
self:updateItems()
|
||||
self:updateItems(1, self.parent_id)
|
||||
self.parent_id = nil
|
||||
elseif not no_close then
|
||||
self:closeMenu()
|
||||
end
|
||||
end
|
||||
|
||||
function TouchMenu:closeMenu()
|
||||
self.close_callback()
|
||||
function TouchMenu:onBack()
|
||||
self:backToUpperMenu()
|
||||
end
|
||||
|
||||
function TouchMenu:onNextPage()
|
||||
if self.page < self.page_num then
|
||||
self.page = self.page + 1
|
||||
elseif self.page == self.page_num then
|
||||
self.page = 1
|
||||
end
|
||||
self:updateItems()
|
||||
return true
|
||||
return self:onGotoPage(self.page + 1)
|
||||
end
|
||||
|
||||
function TouchMenu:onPrevPage()
|
||||
if self.page > 1 then
|
||||
self.page = self.page - 1
|
||||
elseif self.page == 1 then
|
||||
self.page = self.page_num
|
||||
end
|
||||
self:updateItems()
|
||||
return true
|
||||
return self:onGotoPage(self.page - 1)
|
||||
end
|
||||
|
||||
function TouchMenu:onFirstPage()
|
||||
self.page = 1
|
||||
self:updateItems()
|
||||
return true
|
||||
return self:onGotoPage(1)
|
||||
end
|
||||
|
||||
function TouchMenu:onLastPage()
|
||||
self.page = self.page_num
|
||||
self:updateItems()
|
||||
return true
|
||||
return self:onGotoPage(self.page_num)
|
||||
end
|
||||
|
||||
function TouchMenu:onGotoPage(nb)
|
||||
if nb > self.page_num then
|
||||
self.page = self.page_num
|
||||
if nb > self.page_num then -- cycle by swipes only
|
||||
nb = 1
|
||||
elseif nb < 1 then
|
||||
self.page = 1
|
||||
else
|
||||
self.page = nb
|
||||
nb = self.page_num
|
||||
end
|
||||
self:updateItems()
|
||||
self:updateItems(nb)
|
||||
return true
|
||||
end
|
||||
|
||||
@@ -924,7 +873,7 @@ function TouchMenu:onMenuSelect(item, tap_on_checkmark)
|
||||
-- must set keep_menu_open=true if that is wished)
|
||||
callback(self)
|
||||
if refresh then
|
||||
if not item.no_refresh_on_check then
|
||||
if not (item.check_callback_updates_menu or item.check_callback_closes_menu) then
|
||||
self:updateItems()
|
||||
end
|
||||
elseif not item.keep_menu_open then
|
||||
@@ -936,18 +885,7 @@ function TouchMenu:onMenuSelect(item, tap_on_checkmark)
|
||||
item.menu_item_id = item.menu_item_id or tostring(item) -- unique id
|
||||
self.parent_id = item.menu_item_id
|
||||
self.item_table = sub_item_table
|
||||
self.page = 1
|
||||
if self.item_table.open_on_menu_item_id_func then
|
||||
self:_recalculatePageLayout() -- we need an accurate self.perpage
|
||||
local open_id = self.item_table.open_on_menu_item_id_func()
|
||||
for i = 1, #self.item_table do
|
||||
if self.item_table[i].menu_item_id == open_id then
|
||||
self.page = math.floor( (i - 1) / self.perpage ) + 1
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
self:updateItems()
|
||||
self:updateItems(1, self.item_table.open_on_menu_item_id_func and self.item_table.open_on_menu_item_id_func())
|
||||
end
|
||||
end
|
||||
return true
|
||||
@@ -999,6 +937,10 @@ function TouchMenu:onMenuHold(item, text_truncated)
|
||||
return true
|
||||
end
|
||||
|
||||
function TouchMenu:closeMenu()
|
||||
self.close_callback()
|
||||
end
|
||||
|
||||
function TouchMenu:onTapCloseAllMenus(arg, ges_ev)
|
||||
if ges_ev.pos:notIntersectWith(self.dimen) then
|
||||
self:closeMenu()
|
||||
@@ -1009,8 +951,16 @@ function TouchMenu:onClose()
|
||||
self:closeMenu()
|
||||
end
|
||||
|
||||
function TouchMenu:onBack()
|
||||
self:backToUpperMenu()
|
||||
function TouchMenu:onCloseWidget()
|
||||
-- NOTE: We don't pass a region in order to ensure a full-screen flash to avoid ghosting,
|
||||
-- but we only need to do that if we actually have a FM or RD below us.
|
||||
-- Don't do anything when we're switching between the two, or if we don't actually have a live instance of 'em...
|
||||
local FileManager = require("apps/filemanager/filemanager")
|
||||
local ReaderUI = require("apps/reader/readerui")
|
||||
if (FileManager.instance and not FileManager.instance.tearing_down)
|
||||
or (ReaderUI.instance and not ReaderUI.instance.tearing_down) then
|
||||
UIManager:setDirty(nil, "flashui")
|
||||
end
|
||||
end
|
||||
|
||||
-- Menu search feature
|
||||
@@ -1018,7 +968,7 @@ function TouchMenu:search(search_for)
|
||||
local found_menu_items = {}
|
||||
|
||||
local MAX_MENU_DEPTH = 10 -- our menu max depth is currently 6
|
||||
local function recurse(item_table, path, text, icon, depth, is_disabled)
|
||||
local function recurse(item_table, path, text, icon, depth)
|
||||
if item_table.ignored_by_menu_search then
|
||||
return
|
||||
end
|
||||
@@ -1030,8 +980,7 @@ function TouchMenu:search(search_for)
|
||||
if type(v) == "table" and not v.ignored_by_menu_search then
|
||||
local entry_text = v.text_func and v.text_func() or v.text
|
||||
local entry_displayed_text = entry_text
|
||||
is_disabled = is_disabled or v.enabled == false or (v.enabled_func and v.enabled_func() == false)
|
||||
if is_disabled then
|
||||
if v.enabled == false or (v.enabled_func and v.enabled_func() == false) then
|
||||
entry_displayed_text = "\u{2592}\u{200A}" .. entry_displayed_text -- Medium Shade (▒) + Hair Space
|
||||
end
|
||||
local indent = "\u{2192}\u{200A}" -- Rightwards Arrow (→) + Hair Space
|
||||
@@ -1048,7 +997,7 @@ function TouchMenu:search(search_for)
|
||||
sub_item_table = v.sub_item_table_func()
|
||||
end
|
||||
if sub_item_table and not sub_item_table.ignored_by_menu_search then
|
||||
recurse(sub_item_table, walk_path, walk_text, icon, depth, is_disabled)
|
||||
recurse(sub_item_table, walk_path, walk_text, icon, depth)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1170,10 +1119,9 @@ function TouchMenu:openMenu(path, with_animation)
|
||||
end
|
||||
end
|
||||
elseif step == STEPS.MENU_ITEM_HIGHLIGHT then
|
||||
local item_visible_index = (item_nb - 1) % self.perpage + 1
|
||||
local item_widget
|
||||
for i, w in ipairs(self.item_group) do
|
||||
if w.item_visible_index == item_visible_index then
|
||||
for _, w in ipairs(self.item_group) do
|
||||
if w.item and w.item.idx == item_nb then
|
||||
item_widget = w
|
||||
break
|
||||
end
|
||||
@@ -1311,8 +1259,8 @@ function TouchMenu:onShowMenuSearch()
|
||||
title = _("Search results"),
|
||||
subtitle = T(_("Query: %1"), search_string),
|
||||
item_table = get_current_search_results(),
|
||||
width = math.floor(Screen:getWidth() * 0.9),
|
||||
height = math.floor(Screen:getHeight() * 0.9),
|
||||
width = math.floor(self.screen_size.w * 0.9),
|
||||
height = math.floor(self.screen_size.h * 0.9),
|
||||
single_line = true,
|
||||
items_per_page = 10,
|
||||
items_font_size = Menu.getItemFontSize(10),
|
||||
@@ -1329,7 +1277,7 @@ function TouchMenu:onShowMenuSearch()
|
||||
|
||||
-- build container
|
||||
self.results_menu_container = CenterContainer:new{
|
||||
dimen = Screen:getSize(),
|
||||
dimen = self.screen_size,
|
||||
results_menu,
|
||||
}
|
||||
|
||||
|
||||
@@ -948,14 +948,17 @@ end
|
||||
|
||||
--- Replaces characters that are invalid filenames.
|
||||
--
|
||||
-- Replaces the characters <code>\/:*?"<>|</code> with an <code>_</code>.
|
||||
-- Replaces the characters <code>\/:*?"<>|</code> with an <code>_</code>
|
||||
-- and removes trailing dots and spaces, in line with <https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions>.
|
||||
-- These characters are problematic on Windows filesystems. On Linux only
|
||||
-- <code>/</code> poses a problem.
|
||||
---- @string str filename
|
||||
---- @treturn string sanitized filename
|
||||
local function replaceAllInvalidChars(str)
|
||||
if str then
|
||||
return str:gsub('[\\/:*?"<>|]', '_')
|
||||
str = str:gsub('[\\/:*?"<>|]', '_')
|
||||
str = str:gsub("[.%s]+$", "")
|
||||
return str
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
18
kodev
18
kodev
@@ -475,12 +475,18 @@ ANDROID TARGET:
|
||||
if [[ -n "${VALUE}" ]]; then
|
||||
declare -a "wrap=(${VALUE})"
|
||||
else
|
||||
# Try to use friendly defaults for GDB:
|
||||
# - DDD is a slightly less nice GUI
|
||||
# - cgdb is a nice curses-based GDB front
|
||||
# - GDB standard CLI has a fallback
|
||||
if ! wrap=("$(command -v ddd cgdb gdb | head -n1)"); then
|
||||
die 1 "Couldn't find GDB."
|
||||
if is_system macOS; then
|
||||
if ! wrap=("$(command -v lldb | head -n1)"); then
|
||||
die 1 "Couldn't find LLDB."
|
||||
fi
|
||||
else
|
||||
# Try to use friendly defaults for GDB:
|
||||
# - DDD is a slightly less nice GUI
|
||||
# - cgdb is a nice curses-based GDB front
|
||||
# - GDB standard CLI has a fallback
|
||||
if ! wrap=("$(command -v ddd cgdb gdb | head -n1)"); then
|
||||
die 1 "Couldn't find GDB."
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
if [[ ${#wrap[@]} -eq 1 ]]; then
|
||||
|
||||
@@ -170,8 +170,8 @@ function SSH:addToMainMenu(menu_items)
|
||||
sub_item_table = {
|
||||
{
|
||||
text = _("SSH server"),
|
||||
keep_menu_open = true,
|
||||
checked_func = function() return self:isRunning() end,
|
||||
check_callback_updates_menu = true,
|
||||
callback = function(touchmenu_instance)
|
||||
self:onToggleSSHServer()
|
||||
-- sleeping might not be needed, but it gives the feeling
|
||||
|
||||
@@ -131,6 +131,7 @@ function AutoTurn:addToMainMenu(menu_items)
|
||||
return self:_enabled() and T(_("Autoturn: %1"), time_string) or _("Autoturn")
|
||||
end,
|
||||
checked_func = function() return self:_enabled() end,
|
||||
check_callback_updates_menu = true,
|
||||
callback = function(menu)
|
||||
local DateTimeWidget = require("ui/widget/datetimewidget")
|
||||
local autoturn_seconds = G_reader_settings:readSetting("autoturn_timeout_seconds", 30)
|
||||
|
||||
@@ -608,6 +608,7 @@ function AutoWarmth:getSubMenuItems()
|
||||
return not self.easy_mode
|
||||
end,
|
||||
help_text = _("In the expert mode, different types of twilight can be used in addition to civil twilight."),
|
||||
check_callback_updates_menu = true,
|
||||
callback = function(touchmenu_instance)
|
||||
self.easy_mode = not self.easy_mode
|
||||
G_reader_settings:saveSetting("autowarmth_easy_mode", self.easy_mode)
|
||||
@@ -662,6 +663,7 @@ function AutoWarmth:getFlOffDuringDayMenu()
|
||||
return _("Frontlight off during day")
|
||||
end
|
||||
end,
|
||||
check_callback_updates_menu = true,
|
||||
callback = function(touchmenu_instance)
|
||||
if self.easy_mode then
|
||||
self.fl_off_during_day = not self.fl_off_during_day
|
||||
@@ -889,6 +891,7 @@ function AutoWarmth:getScheduleMenu()
|
||||
checked_func = function()
|
||||
return self.scheduler_times[num] ~= nil
|
||||
end,
|
||||
check_callback_updates_menu = true,
|
||||
callback = function(touchmenu_instance)
|
||||
local hh = 12
|
||||
local mm = 0
|
||||
@@ -1112,6 +1115,7 @@ function AutoWarmth:getWarmthMenu()
|
||||
})
|
||||
end
|
||||
end,
|
||||
check_callback_updates_menu = true,
|
||||
callback = function(touchmenu_instance)
|
||||
if Device:hasNaturalLight() then
|
||||
if self.control_warmth and self.control_nightmode then
|
||||
|
||||
@@ -67,7 +67,7 @@ function Calibre:onDispatcherRegisterActions()
|
||||
Dispatcher:registerAction("calibre_browse_authors", { category="none", event="CalibreBrowseBy", arg="authors", title=_("Browse all calibre authors"), general=true,})
|
||||
Dispatcher:registerAction("calibre_browse_titles", { category="none", event="CalibreBrowseBy", arg="title", title=_("Browse all calibre titles"), general=true, separator=true,})
|
||||
Dispatcher:registerAction("calibre_start_connection", { category="none", event="StartWirelessConnection", title=_("Calibre wireless connect"), general=true,})
|
||||
Dispatcher:registerAction("calibre_close_connection", { category="none", event="CloseWirelessConnection", title=_("Calibre wireless disconnect"), general=true,})
|
||||
Dispatcher:registerAction("calibre_close_connection", { category="none", event="CloseWirelessConnection", title=_("Calibre wireless disconnect"), general=true, separator=true,})
|
||||
end
|
||||
|
||||
function Calibre:init()
|
||||
@@ -303,6 +303,7 @@ function Calibre:getWirelessMenuTable()
|
||||
checked_func = function()
|
||||
return G_reader_settings:has("calibre_wireless_url")
|
||||
end,
|
||||
check_callback_updates_menu = true,
|
||||
callback = function(touchmenu_instance)
|
||||
local MultiInputDialog = require("ui/widget/multiinputdialog")
|
||||
local url_dialog
|
||||
|
||||
@@ -502,6 +502,7 @@ function CoverImage:menuEntryCache()
|
||||
checked_func = function()
|
||||
return self.cover_image_cache_maxfiles >= 0
|
||||
end,
|
||||
check_callback_updates_menu = true,
|
||||
callback = function(touchmenu_instance)
|
||||
self:sizeSpinner(touchmenu_instance, "cover_image_cache_maxfiles", _("Number of covers"), -1, 100, 36, self.cleanCache)
|
||||
end,
|
||||
@@ -522,6 +523,7 @@ function CoverImage:menuEntryCache()
|
||||
checked_func = function()
|
||||
return self.cover_image_cache_maxsize >= 0
|
||||
end,
|
||||
check_callback_updates_menu = true,
|
||||
callback = function(touchmenu_instance)
|
||||
self:sizeSpinner(touchmenu_instance, "cover_image_cache_maxsize", _("Cache size"), -1, 100, 5, self.cleanCache, C_("Data storage size", "MB"))
|
||||
end,
|
||||
@@ -574,6 +576,7 @@ function CoverImage:menuEntrySetPath(key, title, help, info, default, folder_onl
|
||||
checked_func = function()
|
||||
return isFileOk(self[key]) or (isPathAllowed(self[key]) and folder_only)
|
||||
end,
|
||||
check_callback_updates_menu = true,
|
||||
callback = function(touchmenu_instance)
|
||||
UIManager:show(ConfirmBox:new{
|
||||
text = info,
|
||||
|
||||
@@ -295,11 +295,10 @@ function Gestures:genMenu(ges)
|
||||
if gestures_list[ges] ~= nil then
|
||||
table.insert(sub_items, {
|
||||
text = T(_("%1 (default)"), Dispatcher:menuTextFunc(self.defaults[ges])),
|
||||
keep_menu_open = true,
|
||||
no_refresh_on_check = true,
|
||||
checked_func = function()
|
||||
return util.tableEquals(self.gestures[ges], self.defaults[ges])
|
||||
end,
|
||||
check_callback_updates_menu = true,
|
||||
callback = function(touchmenu_instance)
|
||||
local function do_remove()
|
||||
self.gestures[ges] = util.tableDeepCopy(self.defaults[ges])
|
||||
@@ -313,11 +312,10 @@ function Gestures:genMenu(ges)
|
||||
end
|
||||
table.insert(sub_items, {
|
||||
text = _("Pass through"),
|
||||
keep_menu_open = true,
|
||||
no_refresh_on_check = true,
|
||||
checked_func = function()
|
||||
return self.gestures[ges] == nil
|
||||
end,
|
||||
check_callback_updates_menu = true,
|
||||
callback = function(touchmenu_instance)
|
||||
local function do_remove()
|
||||
self.gestures[ges] = nil
|
||||
|
||||
@@ -192,11 +192,10 @@ function HotKeys:genMenu(hotkey)
|
||||
local default_text = default_action and Dispatcher:menuTextFunc(default_action) or _("No action")
|
||||
table.insert(sub_items, {
|
||||
text = T(_("%1 (default)"), default_text),
|
||||
keep_menu_open = true,
|
||||
no_refresh_on_check = true,
|
||||
checked_func = function()
|
||||
return util.tableEquals(self.hotkeys[hotkey], self.defaults[hotkey])
|
||||
end,
|
||||
check_callback_updates_menu = true,
|
||||
callback = function(touchmenu_instance)
|
||||
local function do_remove()
|
||||
self.hotkeys[hotkey] = util.tableDeepCopy(self.defaults[hotkey])
|
||||
@@ -209,12 +208,10 @@ function HotKeys:genMenu(hotkey)
|
||||
end
|
||||
table.insert(sub_items, {
|
||||
text = _("No action"),
|
||||
keep_menu_open = true,
|
||||
no_refresh_on_check = true,
|
||||
separator = true,
|
||||
checked_func = function()
|
||||
return self.hotkeys[hotkey] == nil or next(self.hotkeys[hotkey]) == nil
|
||||
end,
|
||||
check_callback_updates_menu = true,
|
||||
callback = function(touchmenu_instance)
|
||||
local function do_remove()
|
||||
self.hotkeys[hotkey] = nil
|
||||
@@ -223,6 +220,7 @@ function HotKeys:genMenu(hotkey)
|
||||
end
|
||||
Dispatcher.removeActions(self.hotkeys[hotkey], do_remove)
|
||||
end,
|
||||
separator = true,
|
||||
})
|
||||
Dispatcher:addSubMenu(self, sub_items, self.hotkeys, hotkey)
|
||||
-- Since we are already handling potential conflicts via overrideConflictingKeyEvents(), both "No action" and "Nothing",
|
||||
|
||||
@@ -6,6 +6,7 @@ local InfoMessage = require("ui/widget/infomessage")
|
||||
local Math = require("optmath")
|
||||
local MultiInputDialog = require("ui/widget/multiinputdialog")
|
||||
local NetworkMgr = require("ui/network/manager")
|
||||
local Notification = require("ui/widget/notification")
|
||||
local UIManager = require("ui/uimanager")
|
||||
local WidgetContainer = require("ui/widget/container/widgetcontainer")
|
||||
local logger = require("logger")
|
||||
@@ -164,6 +165,10 @@ local function validateUser(user, pass)
|
||||
end
|
||||
|
||||
function KOSync:onDispatcherRegisterActions()
|
||||
Dispatcher:registerAction("kosync_set_autosync",
|
||||
{ category="string", event="KOSyncToggleAutoSync", title=_("Set auto progress sync"), reader=true,
|
||||
args={true, false}, toggle={_("on"), _("off")},})
|
||||
Dispatcher:registerAction("kosync_toggle_autosync", { category="none", event="KOSyncToggleAutoSync", title=_("Toggle auto progress sync"), reader=true,})
|
||||
Dispatcher:registerAction("kosync_push_progress", { category="none", event="KOSyncPushProgress", title=_("Push progress from this device"), reader=true,})
|
||||
Dispatcher:registerAction("kosync_pull_progress", { category="none", event="KOSyncPullProgress", title=_("Pull progress from other devices"), reader=true, separator=true,})
|
||||
end
|
||||
@@ -225,23 +230,7 @@ function KOSync:addToMainMenu(menu_items)
|
||||
checked_func = function() return self.settings.auto_sync end,
|
||||
help_text = _([[This may lead to nagging about toggling WiFi on document close and suspend/resume, depending on the device's connectivity.]]),
|
||||
callback = function()
|
||||
-- Actively recommend switching the before wifi action to "turn_on" instead of prompt, as prompt will just not be practical (or even plain usable) here.
|
||||
if Device:hasSeamlessWifiToggle() and G_reader_settings:readSetting("wifi_enable_action") ~= "turn_on" and not self.settings.auto_sync then
|
||||
UIManager:show(InfoMessage:new{ text = _("You will have to switch the 'Action when Wi-Fi is off' Network setting to 'turn on' to be able to enable this feature!") })
|
||||
return
|
||||
end
|
||||
|
||||
self.settings.auto_sync = not self.settings.auto_sync
|
||||
self:registerEvents()
|
||||
if self.settings.auto_sync then
|
||||
-- Since we will update the progress when closing the document,
|
||||
-- pull the current progress now so as not to silently overwrite it.
|
||||
self:getProgress(true, true)
|
||||
else
|
||||
-- Since we won't update the progress when closing the document,
|
||||
-- push the current progress now so as not to lose it.
|
||||
self:updateProgress(true, true)
|
||||
end
|
||||
self:onKOSyncToggleAutoSync(nil, true)
|
||||
end,
|
||||
},
|
||||
{
|
||||
@@ -919,6 +908,37 @@ function KOSync:onKOSyncPullProgress()
|
||||
self:getProgress(true, true)
|
||||
end
|
||||
|
||||
function KOSync:onKOSyncToggleAutoSync(toggle, from_menu)
|
||||
if toggle == self.settings.auto_sync then
|
||||
return true
|
||||
end
|
||||
-- Actively recommend switching the before wifi action to "turn_on" instead of prompt,
|
||||
-- as prompt will just not be practical (or even plain usable) here.
|
||||
if not self.settings.auto_sync
|
||||
and Device:hasSeamlessWifiToggle()
|
||||
and G_reader_settings:readSetting("wifi_enable_action") ~= "turn_on" then
|
||||
UIManager:show(InfoMessage:new{ text = _("You will have to switch the 'Action when Wi-Fi is off' Network setting to 'turn on' to be able to enable this feature!") })
|
||||
return true
|
||||
end
|
||||
self.settings.auto_sync = not self.settings.auto_sync
|
||||
self:registerEvents()
|
||||
|
||||
if self.settings.auto_sync then
|
||||
-- Since we will update the progress when closing the document,
|
||||
-- pull the current progress now so as not to silently overwrite it.
|
||||
self:getProgress(true, true)
|
||||
elseif from_menu then
|
||||
-- Since we won't update the progress when closing the document,
|
||||
-- push the current progress now so as not to lose it.
|
||||
self:updateProgress(true, true)
|
||||
end
|
||||
|
||||
if not from_menu then
|
||||
Notification:notify(self.settings.auto_sync and _("Auto progress sync: on") or _("Auto progress sync: off"))
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function KOSync:registerEvents()
|
||||
if self.settings.auto_sync then
|
||||
self.onCloseDocument = self._onCloseDocument
|
||||
|
||||
@@ -45,12 +45,14 @@ local OPDS = WidgetContainer:extend{
|
||||
}
|
||||
|
||||
function OPDS:init()
|
||||
self.settings = LuaSettings:open(self.opds_settings_file)
|
||||
if next(self.settings.data) == nil then
|
||||
self.opds_settings = LuaSettings:open(self.opds_settings_file)
|
||||
if next(self.opds_settings.data) == nil then
|
||||
self.updated = true -- first run, force flush
|
||||
end
|
||||
self.servers = self.settings:readSetting("servers", self.default_servers)
|
||||
self.downloads = self.settings:readSetting("downloads", {})
|
||||
self.servers = self.opds_settings:readSetting("servers", self.default_servers)
|
||||
self.downloads = self.opds_settings:readSetting("downloads", {})
|
||||
self.settings = self.opds_settings:readSetting("settings", {})
|
||||
self.pending_syncs = self.opds_settings:readSetting("pending_syncs", {})
|
||||
self:onDispatcherRegisterActions()
|
||||
self.ui.menu:registerToMainMenu(self)
|
||||
end
|
||||
@@ -76,6 +78,8 @@ function OPDS:onShowOPDSCatalog()
|
||||
self.opds_browser = OPDSBrowser:new{
|
||||
servers = self.servers,
|
||||
downloads = self.downloads,
|
||||
settings = self.settings,
|
||||
pending_syncs = self.pending_syncs,
|
||||
title = _("OPDS catalog"),
|
||||
is_popout = false,
|
||||
is_borderless = true,
|
||||
@@ -121,7 +125,7 @@ end
|
||||
|
||||
function OPDS:onFlushSettings()
|
||||
if self.updated then
|
||||
self.settings:flush()
|
||||
self.opds_settings:flush()
|
||||
self.updated = nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,6 +3,7 @@ local ButtonDialog = require("ui/widget/buttondialog")
|
||||
local Cache = require("cache")
|
||||
local CheckButton = require("ui/widget/checkbutton")
|
||||
local ConfirmBox = require("ui/widget/confirmbox")
|
||||
local Device = require("device")
|
||||
local DocumentRegistry = require("document/documentregistry")
|
||||
local InfoMessage = require("ui/widget/infomessage")
|
||||
local InputDialog = require("ui/widget/inputdialog")
|
||||
@@ -12,6 +13,9 @@ local NetworkMgr = require("ui/network/manager")
|
||||
local Notification = require("ui/widget/notification")
|
||||
local OPDSParser = require("opdsparser")
|
||||
local OPDSPSE = require("opdspse")
|
||||
local SpinWidget = require("ui/widget/spinwidget")
|
||||
local TextViewer = require("ui/widget/textviewer")
|
||||
local Trapper = require("ui/trapper")
|
||||
local UIManager = require("ui/uimanager")
|
||||
local http = require("socket.http")
|
||||
local lfs = require("libs/libkoreader-lfs")
|
||||
@@ -59,22 +63,96 @@ local OPDSBrowser = Menu:extend{
|
||||
function OPDSBrowser:init()
|
||||
self.item_table = self:genItemTableFromRoot()
|
||||
self.catalog_title = nil
|
||||
self.title_bar_left_icon = "plus"
|
||||
self.title_bar_left_icon = "appbar.menu"
|
||||
self.onLeftButtonTap = function()
|
||||
self:addEditCatalog()
|
||||
self:showOPDSMenu()
|
||||
end
|
||||
Menu.init(self) -- call parent's init()
|
||||
end
|
||||
|
||||
function OPDSBrowser:showOPDSMenu()
|
||||
local dialog
|
||||
dialog = ButtonDialog:new{
|
||||
buttons = {
|
||||
{{
|
||||
text = _("Add catalog"),
|
||||
callback = function()
|
||||
UIManager:close(dialog)
|
||||
self:addEditCatalog()
|
||||
end,
|
||||
align = "left",
|
||||
}},
|
||||
{},
|
||||
{{
|
||||
text = _("Sync all catalogs"),
|
||||
callback = function()
|
||||
UIManager:close(dialog)
|
||||
NetworkMgr:runWhenConnected(function()
|
||||
self.sync_force = false
|
||||
self:checkSyncDownload()
|
||||
end)
|
||||
end,
|
||||
align = "left",
|
||||
}},
|
||||
{{
|
||||
text = _("Force sync all catalogs"),
|
||||
callback = function()
|
||||
UIManager:close(dialog)
|
||||
NetworkMgr:runWhenConnected(function()
|
||||
self.sync_force = true
|
||||
self:checkSyncDownload()
|
||||
end)
|
||||
end,
|
||||
align = "left",
|
||||
}},
|
||||
{{
|
||||
text = _("Set max number of files to sync"),
|
||||
callback = function()
|
||||
self:setMaxSyncDownload()
|
||||
end,
|
||||
align = "left",
|
||||
}},
|
||||
{{
|
||||
text = _("Set sync folder"),
|
||||
callback = function()
|
||||
self:setSyncDir()
|
||||
end,
|
||||
align = "left",
|
||||
}},
|
||||
{{
|
||||
text = _("Set file types to sync"),
|
||||
callback = function()
|
||||
self:setSyncFiletypes()
|
||||
end,
|
||||
align = "left",
|
||||
}},
|
||||
},
|
||||
shrink_unneeded_width = true,
|
||||
anchor = function()
|
||||
return self.title_bar.left_button.image.dimen
|
||||
end,
|
||||
}
|
||||
UIManager:show(dialog)
|
||||
end
|
||||
|
||||
|
||||
local function buildRootEntry(server)
|
||||
local icons = ""
|
||||
if server.username then
|
||||
icons = "\u{f2c0}"
|
||||
end
|
||||
if server.sync then
|
||||
icons = "\u{f46a} " .. icons
|
||||
end
|
||||
return {
|
||||
text = server.title,
|
||||
mandatory = server.username and "\u{f2c0}",
|
||||
mandatory = icons,
|
||||
url = server.url,
|
||||
username = server.username,
|
||||
password = server.password,
|
||||
raw_names = server.raw_names, -- use server raw filenames for download
|
||||
searchable = server.url and server.url:match("%%s") and true or false,
|
||||
sync = server.sync,
|
||||
}
|
||||
end
|
||||
|
||||
@@ -120,7 +198,7 @@ function OPDSBrowser:addEditCatalog(item)
|
||||
title = _("Add OPDS catalog")
|
||||
end
|
||||
|
||||
local dialog, check_button_raw_names
|
||||
local dialog, check_button_raw_names, check_button_sync_catalog
|
||||
dialog = MultiInputDialog:new{
|
||||
title = title,
|
||||
fields = fields,
|
||||
@@ -138,6 +216,7 @@ function OPDSBrowser:addEditCatalog(item)
|
||||
callback = function()
|
||||
local new_fields = dialog:getFields()
|
||||
new_fields[5] = check_button_raw_names.checked or nil
|
||||
new_fields[6] = check_button_sync_catalog.checked or nil
|
||||
self:editCatalogFromInput(new_fields, item)
|
||||
UIManager:close(dialog)
|
||||
end,
|
||||
@@ -150,7 +229,13 @@ function OPDSBrowser:addEditCatalog(item)
|
||||
checked = item and item.raw_names,
|
||||
parent = dialog,
|
||||
}
|
||||
check_button_sync_catalog = CheckButton:new{
|
||||
text = _("Sync catalog"),
|
||||
checked = item and item.sync,
|
||||
parent = dialog,
|
||||
}
|
||||
dialog:addWidget(check_button_raw_names)
|
||||
dialog:addWidget(check_button_sync_catalog)
|
||||
UIManager:show(dialog)
|
||||
dialog:onShowKeyboard()
|
||||
end
|
||||
@@ -198,6 +283,7 @@ function OPDSBrowser:editCatalogFromInput(fields, item, no_refresh)
|
||||
username = fields[3] ~= "" and fields[3] or nil,
|
||||
password = fields[4] ~= "" and fields[4] or nil,
|
||||
raw_names = fields[5],
|
||||
sync = fields[6],
|
||||
}
|
||||
local new_item = buildRootEntry(new_server)
|
||||
local new_idx, itemnumber
|
||||
@@ -208,7 +294,7 @@ function OPDSBrowser:editCatalogFromInput(fields, item, no_refresh)
|
||||
new_idx = #self.servers + 2
|
||||
itemnumber = new_idx
|
||||
end
|
||||
self.servers[new_idx - 1] = new_server
|
||||
self.servers[new_idx - 1] = new_server -- first item is "Downloads"
|
||||
self.item_table[new_idx] = new_item
|
||||
if not no_refresh then
|
||||
self:switchItemTable(nil, self.item_table, itemnumber)
|
||||
@@ -366,25 +452,27 @@ function OPDSBrowser:genItemTableFromCatalog(catalog, item_url)
|
||||
hrefs[link.rel] = build_href(link.href)
|
||||
end
|
||||
end
|
||||
-- OpenSearch
|
||||
if link.type:find(self.search_type) then
|
||||
if link.href then
|
||||
table.insert(item_table, { -- the first item in each subcatalog
|
||||
text = "\u{f002} " .. _("Search"), -- append SEARCH icon
|
||||
url = build_href(self:getSearchTemplate(build_href(link.href))),
|
||||
searchable = true,
|
||||
})
|
||||
has_opensearch = true
|
||||
if not self.sync then
|
||||
-- OpenSearch
|
||||
if link.type:find(self.search_type) then
|
||||
if link.href then
|
||||
table.insert(item_table, { -- the first item in each subcatalog
|
||||
text = "\u{f002} " .. _("Search"), -- append SEARCH icon
|
||||
url = build_href(self:getSearchTemplate(build_href(link.href))),
|
||||
searchable = true,
|
||||
})
|
||||
has_opensearch = true
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Calibre search (also matches the actual template for OpenSearch!)
|
||||
if link.type:find(self.search_template_type) and link.rel and link.rel:find("search") then
|
||||
if link.href and not has_opensearch then
|
||||
table.insert(item_table, {
|
||||
text = "\u{f002} " .. _("Search"),
|
||||
url = build_href(link.href:gsub("{searchTerms}", "%%s")),
|
||||
searchable = true,
|
||||
})
|
||||
-- Calibre search (also matches the actual template for OpenSearch!)
|
||||
if link.type:find(self.search_template_type) and link.rel and link.rel:find("search") then
|
||||
if link.href and not has_opensearch then
|
||||
table.insert(item_table, {
|
||||
text = "\u{f002} " .. _("Search"),
|
||||
url = build_href(link.href:gsub("{searchTerms}", "%%s")),
|
||||
searchable = true,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -460,13 +548,16 @@ function OPDSBrowser:genItemTableFromCatalog(catalog, item_url)
|
||||
-- Check for the presence of the pdf suffix and add it
|
||||
-- if it's missing.
|
||||
local href = link.href
|
||||
if util.getFileNameSuffix(href) ~= "pdf" then
|
||||
href = href .. ".pdf"
|
||||
-- Calibre web OPDS download links end with "/<filetype>/"
|
||||
if not util.stringEndsWith(href, "/pdf/") then
|
||||
if util.getFileNameSuffix(href) ~= "pdf" then
|
||||
href = href .. ".pdf"
|
||||
end
|
||||
table.insert(item.acquisitions, {
|
||||
type = link.title,
|
||||
href = build_href(href),
|
||||
})
|
||||
end
|
||||
table.insert(item.acquisitions, {
|
||||
type = link.title,
|
||||
href = build_href(href),
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -514,6 +605,7 @@ function OPDSBrowser:updateCatalog(item_url, paths_updated)
|
||||
})
|
||||
end
|
||||
self:switchItemTable(self.catalog_title, menu_table)
|
||||
self:setTitleBarLeftIcon("plus")
|
||||
self.onLeftButtonTap = function()
|
||||
self:addSubCatalog(item_url)
|
||||
end
|
||||
@@ -577,14 +669,7 @@ end
|
||||
-- Shows dialog to download / stream a book
|
||||
function OPDSBrowser:showDownloads(item)
|
||||
local acquisitions = item.acquisitions
|
||||
local filename = item.title
|
||||
if item.author then
|
||||
filename = item.author .. " - " .. filename
|
||||
end
|
||||
local filename_orig = filename
|
||||
if self.root_catalog_raw_names then
|
||||
filename = nil
|
||||
end
|
||||
local filename, filename_orig = self:getFileName(item)
|
||||
|
||||
local function createTitle(path, file) -- title for ButtonDialog
|
||||
return T(_("Download folder:\n%1\n\nDownload filename:\n%2\n\nDownload file type:"),
|
||||
@@ -635,14 +720,7 @@ function OPDSBrowser:showDownloads(item)
|
||||
enabled = false,
|
||||
})
|
||||
else
|
||||
local filetype = util.getFileNameSuffix(acquisition.href)
|
||||
logger.dbg("Filetype for download is", filetype)
|
||||
if not DocumentRegistry:hasProvider("dummy." .. filetype) then
|
||||
filetype = nil
|
||||
end
|
||||
if not filetype and DocumentRegistry:hasProvider(nil, acquisition.type) then
|
||||
filetype = DocumentRegistry:mimeToExt(acquisition.type)
|
||||
end
|
||||
local filetype = self.getFiletype(acquisition)
|
||||
if filetype then -- supported file type
|
||||
local text = url.unescape(acquisition.title or string.upper(filetype))
|
||||
table.insert(download_buttons, {
|
||||
@@ -663,7 +741,7 @@ function OPDSBrowser:showDownloads(item)
|
||||
password = self.root_catalog_password,
|
||||
})
|
||||
self._manager.updated = true
|
||||
Notification:notify(_("Book added to download list"))
|
||||
Notification:notify(_("Book added to download list"), Notification.SOURCE_OTHER)
|
||||
end,
|
||||
})
|
||||
end
|
||||
@@ -700,7 +778,7 @@ function OPDSBrowser:showDownloads(item)
|
||||
G_reader_settings:saveSetting("download_dir", path)
|
||||
self.download_dialog:setTitle(createTitle(path, filename))
|
||||
end,
|
||||
}:chooseDir(self.getCurrentDownloadDir())
|
||||
}:chooseDir(self:getCurrentDownloadDir())
|
||||
end,
|
||||
},
|
||||
{
|
||||
@@ -729,7 +807,7 @@ function OPDSBrowser:showDownloads(item)
|
||||
filename = filename_orig
|
||||
end
|
||||
UIManager:close(dialog)
|
||||
self.download_dialog:setTitle(createTitle(self.getCurrentDownloadDir(), filename))
|
||||
self.download_dialog:setTitle(createTitle(self:getCurrentDownloadDir(), filename))
|
||||
end,
|
||||
},
|
||||
}
|
||||
@@ -753,7 +831,6 @@ function OPDSBrowser:showDownloads(item)
|
||||
text = _("Book information"),
|
||||
enabled = type(item.content) == "string",
|
||||
callback = function()
|
||||
local TextViewer = require("ui/widget/textviewer")
|
||||
UIManager:show(TextViewer:new{
|
||||
title = item.text,
|
||||
title_multilines = true,
|
||||
@@ -765,19 +842,35 @@ function OPDSBrowser:showDownloads(item)
|
||||
})
|
||||
|
||||
self.download_dialog = ButtonDialog:new{
|
||||
title = createTitle(self.getCurrentDownloadDir(), filename),
|
||||
title = createTitle(self:getCurrentDownloadDir(), filename),
|
||||
buttons = buttons,
|
||||
}
|
||||
UIManager:show(self.download_dialog)
|
||||
end
|
||||
|
||||
-- Helper function to get the filetype from an acquisitions table
|
||||
function OPDSBrowser.getFiletype(link)
|
||||
local filetype = util.getFileNameSuffix(link.href)
|
||||
if not DocumentRegistry:hasProvider("dummy." .. filetype) then
|
||||
filetype = nil
|
||||
end
|
||||
if not filetype and DocumentRegistry:hasProvider(nil, link.type) then
|
||||
filetype = DocumentRegistry:mimeToExt(link.type)
|
||||
end
|
||||
return filetype
|
||||
end
|
||||
|
||||
-- Returns user selected or last opened folder
|
||||
function OPDSBrowser.getCurrentDownloadDir()
|
||||
return G_reader_settings:readSetting("download_dir") or G_reader_settings:readSetting("lastdir")
|
||||
function OPDSBrowser:getCurrentDownloadDir()
|
||||
if self.sync then
|
||||
return self.settings.sync_dir
|
||||
else
|
||||
return G_reader_settings:readSetting("download_dir") or G_reader_settings:readSetting("lastdir")
|
||||
end
|
||||
end
|
||||
|
||||
function OPDSBrowser:getLocalDownloadPath(filename, filetype, remote_url)
|
||||
local download_dir = OPDSBrowser.getCurrentDownloadDir()
|
||||
local download_dir = self:getCurrentDownloadDir()
|
||||
filename = filename and filename .. "." .. filetype:lower() or self:getServerFileName(remote_url)
|
||||
filename = util.getSafeFilename(filename, download_dir)
|
||||
filename = (download_dir ~= "/" and download_dir or "") .. '/' .. filename
|
||||
@@ -895,6 +988,29 @@ function OPDSBrowser:onMenuHold(item)
|
||||
title = item.text,
|
||||
title_align = "center",
|
||||
buttons = {
|
||||
{
|
||||
{
|
||||
text = _("Force sync"),
|
||||
callback = function()
|
||||
UIManager:close(dialog)
|
||||
NetworkMgr:runWhenConnected(function()
|
||||
self.sync_force = true
|
||||
self:checkSyncDownload(item.idx)
|
||||
end)
|
||||
end,
|
||||
},
|
||||
{
|
||||
text = _("Sync"),
|
||||
callback = function()
|
||||
UIManager:close(dialog)
|
||||
NetworkMgr:runWhenConnected(function()
|
||||
self.sync_force = false
|
||||
self:checkSyncDownload(item.idx)
|
||||
end)
|
||||
end,
|
||||
},
|
||||
},
|
||||
{},
|
||||
{
|
||||
{
|
||||
text = _("Delete"),
|
||||
@@ -974,6 +1090,8 @@ function OPDSBrowser:showDownloadList()
|
||||
title_bar_fm_style = true,
|
||||
onMenuSelect = self.showDownloadListItemDialog,
|
||||
_manager = self,
|
||||
title_bar_left_icon = "appbar.menu",
|
||||
onLeftButtonTap = self.showDownloadListMenu
|
||||
}
|
||||
self.download_list.close_callback = function()
|
||||
UIManager:close(self.download_list)
|
||||
@@ -988,6 +1106,35 @@ function OPDSBrowser:showDownloadList()
|
||||
UIManager:show(self.download_list)
|
||||
end
|
||||
|
||||
function OPDSBrowser:showDownloadListMenu()
|
||||
local dialog
|
||||
dialog = ButtonDialog:new{
|
||||
buttons = {
|
||||
{{
|
||||
text = _("Download all"),
|
||||
callback = function()
|
||||
UIManager:close(dialog)
|
||||
self._manager:confirmDownloadDownloadList()
|
||||
end,
|
||||
align = "left",
|
||||
}},
|
||||
{{
|
||||
text = _("Remove all"),
|
||||
callback = function()
|
||||
UIManager:close(dialog)
|
||||
self._manager:confirmClearDownloadList()
|
||||
end,
|
||||
align = "left",
|
||||
}},
|
||||
},
|
||||
shrink_unneeded_width = true,
|
||||
anchor = function()
|
||||
return self.title_bar.left_button.image.dimen
|
||||
end,
|
||||
}
|
||||
UIManager:show(dialog)
|
||||
end
|
||||
|
||||
function OPDSBrowser:updateDownloadListItemTable(item_table)
|
||||
if item_table == nil then
|
||||
item_table = {}
|
||||
@@ -1002,6 +1149,35 @@ function OPDSBrowser:updateDownloadListItemTable(item_table)
|
||||
self.download_list:switchItemTable(title, item_table)
|
||||
end
|
||||
|
||||
function OPDSBrowser:confirmDownloadDownloadList()
|
||||
UIManager:show(ConfirmBox:new{
|
||||
text = _("Download all books?\nExisting files will be overwritten."),
|
||||
ok_text = _("Download"),
|
||||
ok_callback = function()
|
||||
NetworkMgr:runWhenConnected(function()
|
||||
Trapper:wrap(function()
|
||||
self:downloadDownloadList()
|
||||
end)
|
||||
end)
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
function OPDSBrowser:confirmClearDownloadList()
|
||||
UIManager:show(ConfirmBox:new{
|
||||
text = _("Remove all downloads?"),
|
||||
ok_text = _("Remove"),
|
||||
ok_callback = function()
|
||||
for i in ipairs(self.downloads) do
|
||||
self.downloads[i] = nil
|
||||
end
|
||||
self.download_list_updated = true
|
||||
self._manager.updated = true
|
||||
self.download_list:close_callback()
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
function OPDSBrowser:showDownloadListItemDialog(item)
|
||||
local dl_item = self._manager.downloads[item.idx]
|
||||
local textviewer
|
||||
@@ -1040,36 +1216,14 @@ function OPDSBrowser:showDownloadListItemDialog(item)
|
||||
text = _("Remove all"),
|
||||
callback = function()
|
||||
textviewer:onClose()
|
||||
UIManager:show(ConfirmBox:new{
|
||||
text = _("Remove all downloads?"),
|
||||
ok_text = _("Remove"),
|
||||
ok_callback = function()
|
||||
for i in ipairs(self._manager.downloads) do
|
||||
self._manager.downloads[i] = nil
|
||||
end
|
||||
self._manager.download_list_updated = true
|
||||
self._manager._manager.updated = true
|
||||
self:close_callback()
|
||||
end,
|
||||
})
|
||||
self._manager:confirmClearDownloadList()
|
||||
end,
|
||||
},
|
||||
{
|
||||
text = _("Download all"),
|
||||
callback = function()
|
||||
textviewer:onClose()
|
||||
UIManager:show(ConfirmBox:new{
|
||||
text = _("Download all books?\nExisting files will be overwritten."),
|
||||
ok_text = _("Download"),
|
||||
ok_callback = function()
|
||||
NetworkMgr:runWhenConnected(function()
|
||||
local Trapper = require("ui/trapper")
|
||||
Trapper:wrap(function()
|
||||
self._manager:downloadDownloadList()
|
||||
end)
|
||||
end)
|
||||
end,
|
||||
})
|
||||
self._manager:confirmDownloadDownloadList()
|
||||
end,
|
||||
},
|
||||
},
|
||||
@@ -1084,7 +1238,6 @@ function OPDSBrowser:showDownloadListItemDialog(item)
|
||||
TextBoxWidget.PTF_BOLD_START, _("Description"), TextBoxWidget.PTF_BOLD_END, "\n",
|
||||
dl_item.info,
|
||||
})
|
||||
local TextViewer = require("ui/widget/textviewer")
|
||||
textviewer = TextViewer:new{
|
||||
title = dl_item.catalog,
|
||||
text = text,
|
||||
@@ -1095,11 +1248,11 @@ function OPDSBrowser:showDownloadListItemDialog(item)
|
||||
return true
|
||||
end
|
||||
|
||||
-- Download whole download list
|
||||
function OPDSBrowser:downloadDownloadList()
|
||||
local info = InfoMessage:new{ text = _("Downloading… (tap to cancel)") }
|
||||
UIManager:show(info)
|
||||
UIManager:forceRePaint()
|
||||
local Trapper = require("ui/trapper")
|
||||
local completed, downloaded = Trapper:dismissableRunInSubprocess(function()
|
||||
local dl = {}
|
||||
for _, item in ipairs(self.downloads) do
|
||||
@@ -1137,4 +1290,383 @@ function OPDSBrowser:downloadDownloadList()
|
||||
end
|
||||
end
|
||||
|
||||
function OPDSBrowser:setMaxSyncDownload()
|
||||
local current_max_dl = self.settings.sync_max_dl or 50
|
||||
local spin = SpinWidget:new{
|
||||
title_text = "Set maximum sync size",
|
||||
info_text = "Set the max number of books to download at a time",
|
||||
value = current_max_dl,
|
||||
value_min = 0,
|
||||
value_max = 1000,
|
||||
value_step = 10,
|
||||
value_hold_step = 50,
|
||||
default_value = 50,
|
||||
wrap = true,
|
||||
ok_text = "Save",
|
||||
callback = function(spin)
|
||||
self.settings.sync_max_dl = spin.value
|
||||
self._manager.updated = true
|
||||
end,
|
||||
}
|
||||
UIManager:show(spin)
|
||||
end
|
||||
|
||||
function OPDSBrowser:setSyncDir()
|
||||
local force_chooser_dir
|
||||
if Device:isAndroid() then
|
||||
force_chooser_dir = Device.home_dir
|
||||
end
|
||||
|
||||
require("ui/downloadmgr"):new{
|
||||
onConfirm = function(inbox)
|
||||
logger.info("set opds sync folder", inbox)
|
||||
self.settings.sync_dir = inbox
|
||||
self._manager.updated = true
|
||||
end,
|
||||
}:chooseDir(force_chooser_dir)
|
||||
end
|
||||
|
||||
-- Set string for desired filetypes
|
||||
function OPDSBrowser:setSyncFiletypes(filetype_list)
|
||||
local input = self.settings.filetypes
|
||||
local dialog
|
||||
dialog = InputDialog:new{
|
||||
title = _("File types to sync"),
|
||||
description = _("A comma separated list of desired filetypes"),
|
||||
input_hint = _("epub, mobi"),
|
||||
input = input,
|
||||
buttons = {
|
||||
{
|
||||
{
|
||||
text = _("Cancel"),
|
||||
id = "close",
|
||||
callback = function()
|
||||
UIManager:close(dialog)
|
||||
end,
|
||||
},
|
||||
{
|
||||
text = _("Save"),
|
||||
is_enter_default = true,
|
||||
callback = function()
|
||||
local str = dialog:getInputText()
|
||||
self.settings.filetypes = str ~= "" and str or nil
|
||||
self._manager.updated = true
|
||||
UIManager:close(dialog)
|
||||
end,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
UIManager:show(dialog)
|
||||
dialog:onShowKeyboard()
|
||||
end
|
||||
|
||||
-- Helper function to get filename and set nil if using raw names
|
||||
function OPDSBrowser:getFileName(item)
|
||||
local filename = item.title
|
||||
if item.author then
|
||||
filename = item.author .. " - " .. filename
|
||||
end
|
||||
local filename_orig = filename
|
||||
if self.root_catalog_raw_names then
|
||||
filename = nil
|
||||
end
|
||||
return filename, filename_orig
|
||||
end
|
||||
|
||||
function OPDSBrowser:updateFieldInCatalog(item, name, value)
|
||||
item[name] = value
|
||||
self._manager.updated = true
|
||||
end
|
||||
|
||||
function OPDSBrowser:checkSyncDownload(idx)
|
||||
if self.settings.sync_dir then
|
||||
self.sync = true
|
||||
local info = InfoMessage:new{
|
||||
text = _("Synchronizing lists…"),
|
||||
}
|
||||
UIManager:show(info)
|
||||
UIManager:forceRePaint()
|
||||
if idx then
|
||||
self:fillPendingSyncs(self.servers[idx-1]) -- First item is "Downloads"
|
||||
else
|
||||
for _, item in ipairs(self.servers) do
|
||||
if item.sync then
|
||||
self:fillPendingSyncs(item)
|
||||
end
|
||||
end
|
||||
end
|
||||
UIManager:close(info)
|
||||
if #self.pending_syncs > 0 then
|
||||
Trapper:wrap(function()
|
||||
self:downloadPendingSyncs()
|
||||
end)
|
||||
else
|
||||
UIManager:show(InfoMessage:new{
|
||||
text = _("Up to date!"),
|
||||
})
|
||||
end
|
||||
self.sync = false
|
||||
else
|
||||
UIManager:show(InfoMessage:new{
|
||||
text = _("Please choose a folder for sync downloads first"),
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
-- Add entries to self.pending_syncs
|
||||
function OPDSBrowser:fillPendingSyncs(server)
|
||||
self.root_catalog_password = server.password
|
||||
self.root_catalog_raw_names = server.raw_names
|
||||
self.root_catalog_username = server.username
|
||||
self.root_catalog_title = server.title
|
||||
self.sync_server = server
|
||||
self.sync_server_list = self.sync_server_list or {}
|
||||
self.sync_max_dl = self.settings.sync_max_dl or 50
|
||||
|
||||
local file_list
|
||||
local file_str = self.settings.filetypes
|
||||
local new_last_download = nil
|
||||
local dl_count = 1
|
||||
if file_str then
|
||||
file_list = {}
|
||||
for filetype in util.gsplit(file_str, ",") do
|
||||
file_list[util.trim(filetype)] = true
|
||||
end
|
||||
end
|
||||
local sync_list = self:getSyncDownloadList()
|
||||
if sync_list then
|
||||
for i, entry in ipairs(sync_list) do
|
||||
-- for project gutenberg
|
||||
local sub_table = {}
|
||||
local item
|
||||
if entry.url then
|
||||
sub_table = self:getSyncDownloadList(entry.url)
|
||||
end
|
||||
if #sub_table > 0 then
|
||||
-- The first element seems to be most compatible. Second element has most options
|
||||
item = sub_table[2]
|
||||
else
|
||||
item = entry
|
||||
end
|
||||
for j, link in ipairs(item.acquisitions) do
|
||||
-- Only save first link in case of several file types
|
||||
if i == 1 and j == 1 then
|
||||
new_last_download = link.href
|
||||
end
|
||||
local filetype = self.getFiletype(link)
|
||||
if filetype then
|
||||
if not file_str or file_list and file_list[filetype] then
|
||||
local filename = self:getFileName(entry)
|
||||
local download_path = self:getLocalDownloadPath(filename, filetype, link.href)
|
||||
if dl_count <= self.sync_max_dl then -- Append only max_dl entries... may still have sync backlog
|
||||
table.insert(self.pending_syncs, {
|
||||
file = download_path,
|
||||
url = link.href,
|
||||
username = self.root_catalog_username,
|
||||
password = self.root_catalog_password,
|
||||
catalog = server.url,
|
||||
})
|
||||
dl_count = dl_count + 1
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
self.sync_server_list[server.url] = true
|
||||
if new_last_download then
|
||||
logger.dbg("Updating opds last download for server", server.title, "to", new_last_download)
|
||||
self:updateFieldInCatalog(server, "last_download", new_last_download)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- Get list of books to download bigger than sync_max_dl
|
||||
function OPDSBrowser:getSyncDownloadList(url_arg)
|
||||
local sync_table = {}
|
||||
local fetch_url = url_arg or self.sync_server.url
|
||||
local sub_table
|
||||
local up_to_date = false
|
||||
while #sync_table < self.sync_max_dl and not up_to_date do
|
||||
sub_table = self:genItemTableFromURL(fetch_url)
|
||||
-- timeout
|
||||
if #sub_table == 0 then
|
||||
return sync_table
|
||||
end
|
||||
local count = 1
|
||||
local acquisitions_empty = false
|
||||
-- For project gutenberg
|
||||
while #sub_table[count].acquisitions == 0 do
|
||||
if util.stringEndsWith(sub_table[count].url, ".opds") then
|
||||
acquisitions_empty = true
|
||||
break
|
||||
end
|
||||
if count == #sub_table then
|
||||
return sync_table
|
||||
end
|
||||
count = count + 1
|
||||
end
|
||||
-- First entry in table is the newest
|
||||
-- If already downloaded, return
|
||||
local first_href
|
||||
if acquisitions_empty then
|
||||
first_href = sub_table[count].url
|
||||
else
|
||||
first_href = sub_table[1].acquisitions[1].href
|
||||
end
|
||||
if first_href == self.sync_server.last_download and not self.sync_force then
|
||||
return nil
|
||||
end
|
||||
local href
|
||||
for i, entry in ipairs(sub_table) do
|
||||
if acquisitions_empty then
|
||||
if i >= count then
|
||||
href = entry.url
|
||||
else
|
||||
href = nil
|
||||
end
|
||||
else
|
||||
href = entry.acquisitions[1].href
|
||||
end
|
||||
if href then
|
||||
if href == self.sync_server.last_download and not self.sync_force then
|
||||
up_to_date = true
|
||||
break
|
||||
else
|
||||
table.insert(sync_table, entry)
|
||||
end
|
||||
end
|
||||
end
|
||||
if not sub_table.hrefs.next then
|
||||
break
|
||||
end
|
||||
fetch_url = sub_table.hrefs.next
|
||||
end
|
||||
return sync_table
|
||||
end
|
||||
|
||||
-- Download pending syncs list
|
||||
function OPDSBrowser:downloadPendingSyncs()
|
||||
local dl_list = self.pending_syncs
|
||||
local function dismissable_download()
|
||||
local info = InfoMessage:new{ text = _("Downloading… (tap to cancel)") }
|
||||
UIManager:show(info)
|
||||
UIManager:forceRePaint()
|
||||
local completed, downloaded, duplicate_list = Trapper:dismissableRunInSubprocess(function()
|
||||
local dl = {}
|
||||
local dupe_list = {}
|
||||
for _, item in ipairs(dl_list) do
|
||||
if self.sync_server_list[item.catalog] then
|
||||
if lfs.attributes(item.file) and not self.sync_force then
|
||||
table.insert(dupe_list, item)
|
||||
else
|
||||
if self:downloadFile(item.file, item.url, item.username, item.password) then
|
||||
dl[item.file] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return dl, dupe_list
|
||||
end, info)
|
||||
|
||||
if completed then
|
||||
UIManager:close(info)
|
||||
end
|
||||
local dl_count = 0
|
||||
local dl_size = #dl_list
|
||||
for i = dl_size, 1, -1 do
|
||||
local item = dl_list[i]
|
||||
if downloaded and downloaded[item.file] then
|
||||
dl_count = dl_count + 1
|
||||
table.remove(dl_list, i)
|
||||
else -- if subprocess has been interrupted, check for the downloaded file
|
||||
local attr = lfs.attributes(item.file)
|
||||
if attr then
|
||||
if attr.size > 0 then
|
||||
table.remove(dl_list, i)
|
||||
if attr.modification > os.time() - 300 then -- Only count files touched in the last 5 mins
|
||||
dl_count = dl_count + 1
|
||||
end
|
||||
else -- incomplete download
|
||||
os.remove(item.file)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
local duplicate_count = duplicate_list and #duplicate_list or 0
|
||||
dl_count = dl_count - duplicate_count
|
||||
-- Make downloaded count timeout if there's a duplicate file prompt
|
||||
local timeout = nil
|
||||
if duplicate_count > 0 then
|
||||
timeout = 3
|
||||
end
|
||||
if dl_count > 0 then
|
||||
UIManager:show(InfoMessage:new{ text = T(N_("1 book downloaded", "%1 books downloaded", dl_count), dl_count), timeout = timeout,})
|
||||
end
|
||||
self._manager.updated = true
|
||||
return duplicate_list
|
||||
end
|
||||
|
||||
local duplicate_list = dismissable_download()
|
||||
|
||||
if duplicate_list and #duplicate_list > 0 then
|
||||
local textviewer
|
||||
local duplicate_files = { _("These files are already on the device:") }
|
||||
for _, entry in ipairs(duplicate_list) do
|
||||
table.insert(duplicate_files, entry.file)
|
||||
end
|
||||
local text = table.concat(duplicate_files, "\n")
|
||||
textviewer = TextViewer:new{
|
||||
title = _("Duplicate files"),
|
||||
text = text,
|
||||
buttons_table = {
|
||||
{
|
||||
{
|
||||
text = _("Do nothing"),
|
||||
callback = function()
|
||||
textviewer:onClose()
|
||||
end
|
||||
},
|
||||
{
|
||||
text = _("Overwrite"),
|
||||
callback = function()
|
||||
self.sync_force = true
|
||||
textviewer:onClose()
|
||||
for _, entry in ipairs(duplicate_list) do
|
||||
table.insert(dl_list, entry)
|
||||
end
|
||||
Trapper:wrap(function()
|
||||
dismissable_download()
|
||||
end)
|
||||
end
|
||||
},
|
||||
{
|
||||
text = _("Download copies"),
|
||||
callback = function()
|
||||
self.sync_force = true
|
||||
textviewer:onClose()
|
||||
local copy_download_dir, original_dir, copies_dir, copy_download_path
|
||||
copies_dir = "copies"
|
||||
original_dir = util.splitFilePathName(duplicate_list[1].file)
|
||||
copy_download_dir = original_dir .. copies_dir .. "/"
|
||||
util.makePath(copy_download_dir)
|
||||
for _, entry in ipairs(duplicate_list) do
|
||||
local _, file_name = util.splitFilePathName(entry.file)
|
||||
copy_download_path = copy_download_dir .. file_name
|
||||
entry.file = copy_download_path
|
||||
table.insert(dl_list, entry)
|
||||
end
|
||||
Trapper:wrap(function()
|
||||
dismissable_download()
|
||||
end)
|
||||
end
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
UIManager:show(textviewer)
|
||||
end
|
||||
end
|
||||
return OPDSBrowser
|
||||
|
||||
@@ -629,10 +629,10 @@ function Profiles:genAutoExecPathChangedMenuItem(text, event, profile_name, sepa
|
||||
local value = util.tableGetValue(self.autoexec, event, profile_name, condition)
|
||||
return value and txt .. ": " .. value or txt
|
||||
end,
|
||||
no_refresh_on_check = true,
|
||||
checked_func = function()
|
||||
return util.tableGetValue(self.autoexec, event, profile_name, condition)
|
||||
end,
|
||||
check_callback_updates_menu = true,
|
||||
callback = function(touchmenu_instance)
|
||||
local dialog
|
||||
local buttons = {{
|
||||
@@ -760,10 +760,10 @@ function Profiles:genAutoExecDocConditionalMenuItem(text, event, profile_name, s
|
||||
local txt = util.tableGetValue(self.autoexec, event, profile_name, condition, prop)
|
||||
return txt and title .. " " .. txt or title:sub(1, -2)
|
||||
end,
|
||||
no_refresh_on_check = true,
|
||||
checked_func = function()
|
||||
return util.tableGetValue(self.autoexec, event, profile_name, condition, prop) and true
|
||||
end,
|
||||
check_callback_updates_menu = true,
|
||||
callback = function(touchmenu_instance)
|
||||
local dialog
|
||||
local buttons = self.document == nil and {} or {{
|
||||
@@ -830,10 +830,10 @@ function Profiles:genAutoExecDocConditionalMenuItem(text, event, profile_name, s
|
||||
enabled_func = function()
|
||||
return not util.tableGetValue(self.autoexec, event_always, profile_name)
|
||||
end,
|
||||
no_refresh_on_check = true,
|
||||
checked_func = function()
|
||||
return util.tableGetValue(self.autoexec, event, profile_name, conditions[3][2]) and true
|
||||
end,
|
||||
check_callback_updates_menu = true,
|
||||
callback = function(touchmenu_instance)
|
||||
local condition = conditions[3][2]
|
||||
local dialog
|
||||
@@ -895,10 +895,10 @@ function Profiles:genAutoExecDocConditionalMenuItem(text, event, profile_name, s
|
||||
enabled_func = function()
|
||||
return not util.tableGetValue(self.autoexec, event_always, profile_name)
|
||||
end,
|
||||
no_refresh_on_check = true,
|
||||
checked_func = function()
|
||||
return util.tableGetValue(self.autoexec, event, profile_name, conditions[4][2]) and true
|
||||
end,
|
||||
check_callback_updates_menu = true,
|
||||
callback = function(touchmenu_instance)
|
||||
local condition = conditions[4][2]
|
||||
local collections = util.tableGetValue(self.autoexec, event, profile_name, condition)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
local CheckButton = require("ui/widget/checkbutton")
|
||||
local ConfirmBox = require("ui/widget/confirmbox")
|
||||
local DateTimeWidget = require("ui/widget/datetimewidget")
|
||||
local Dispatcher = require("dispatcher")
|
||||
local Event = require("ui/event")
|
||||
local InfoMessage = require("ui/widget/infomessage")
|
||||
local UIManager = require("ui/uimanager")
|
||||
@@ -17,6 +18,15 @@ local ReadTimer = WidgetContainer:extend{
|
||||
last_interval_time = 0,
|
||||
}
|
||||
|
||||
function ReadTimer:onDispatcherRegisterActions()
|
||||
Dispatcher:registerAction("show_alarm",
|
||||
{category="none", event="ShowAlarm", title=_("Set reader alarm"), general=true})
|
||||
Dispatcher:registerAction("show_timer",
|
||||
{category="none", event="ShowTimer", title=_("Set reader timer"), general=true})
|
||||
Dispatcher:registerAction("stop_timer",
|
||||
{category="none", event="StopTimer", title=_("Stop reader timer"), general=true, separator=true})
|
||||
end
|
||||
|
||||
function ReadTimer:init()
|
||||
self.timer_symbol = "\u{23F2}" -- ⏲ timer symbol
|
||||
self.timer_letter = "T"
|
||||
@@ -91,6 +101,7 @@ function ReadTimer:init()
|
||||
end
|
||||
|
||||
self.ui.menu:registerToMainMenu(self)
|
||||
self:onDispatcherRegisterActions()
|
||||
end
|
||||
|
||||
function ReadTimer:update_status_bars(seconds)
|
||||
@@ -248,85 +259,14 @@ function ReadTimer:addToMainMenu(menu_items)
|
||||
text = _("Set time"),
|
||||
keep_menu_open = true,
|
||||
callback = function(touchmenu_instance)
|
||||
local now_t = os.date("*t")
|
||||
local curr_hour = now_t.hour
|
||||
local curr_min = now_t.min
|
||||
local time_widget = DateTimeWidget:new{
|
||||
hour = curr_hour,
|
||||
min = curr_min,
|
||||
ok_text = _("Set alarm"),
|
||||
title_text = _("New alarm"),
|
||||
info_text = _("Enter a time in hours and minutes."),
|
||||
callback = function(alarm_time)
|
||||
self.last_interval_time = 0
|
||||
self:unschedule()
|
||||
local then_t = now_t
|
||||
then_t.hour = alarm_time.hour
|
||||
then_t.min = alarm_time.min
|
||||
then_t.sec = 0
|
||||
local seconds = os.difftime(os.time(then_t), os.time())
|
||||
if seconds <= 0 then
|
||||
then_t.day = then_t.day + 1
|
||||
seconds = os.difftime(os.time(then_t), os.time())
|
||||
end
|
||||
self:rescheduleIn(seconds)
|
||||
local user_duration_format = G_reader_settings:readSetting("duration_format")
|
||||
UIManager:show(InfoMessage:new{
|
||||
-- @translators %1:%2 is a clock time (HH:MM), %3 is a duration
|
||||
text = T(_("Timer set for %1:%2.\n\nThat's %3 from now."),
|
||||
string.format("%02d", alarm_time.hour), string.format("%02d", alarm_time.min),
|
||||
datetime.secondsToClockDuration(user_duration_format, seconds, false)),
|
||||
timeout = 5,
|
||||
})
|
||||
if touchmenu_instance then touchmenu_instance:updateItems() end
|
||||
end
|
||||
}
|
||||
self:addCheckboxes(time_widget)
|
||||
UIManager:show(time_widget)
|
||||
self:onShowAlarm(touchmenu_instance)
|
||||
end,
|
||||
},
|
||||
{
|
||||
text = _("Set interval"),
|
||||
keep_menu_open = true,
|
||||
callback = function(touchmenu_instance)
|
||||
local remain_time = {}
|
||||
local remain_hours, remain_minutes = self:remainingTime()
|
||||
if not remain_hours and not remain_minutes then
|
||||
remain_time = G_reader_settings:readSetting("reader_timer_remain_time")
|
||||
if remain_time then
|
||||
remain_hours = remain_time[1]
|
||||
remain_minutes = remain_time[2]
|
||||
end
|
||||
end
|
||||
local time_widget = DateTimeWidget:new{
|
||||
hour = remain_hours or 0,
|
||||
min = remain_minutes or 0,
|
||||
hour_max = 17,
|
||||
ok_text = _("Set timer"),
|
||||
title_text = _("Set reader timer"),
|
||||
info_text = _("Enter a time in hours and minutes."),
|
||||
callback = function(timer_time)
|
||||
self:unschedule()
|
||||
local seconds = timer_time.hour * 3600 + timer_time.min * 60
|
||||
if seconds > 0 then
|
||||
self.last_interval_time = seconds
|
||||
self:rescheduleIn(seconds)
|
||||
local user_duration_format = G_reader_settings:readSetting("duration_format")
|
||||
UIManager:show(InfoMessage:new{
|
||||
-- @translators This is a duration
|
||||
text = T(_("Timer will expire in %1."),
|
||||
datetime.secondsToClockDuration(user_duration_format, seconds, true)),
|
||||
timeout = 5,
|
||||
})
|
||||
remain_time = {timer_time.hour, timer_time.min}
|
||||
G_reader_settings:saveSetting("reader_timer_remain_time", remain_time)
|
||||
if touchmenu_instance then touchmenu_instance:updateItems() end
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
self:addCheckboxes(time_widget)
|
||||
UIManager:show(time_widget)
|
||||
self:onShowTimer(touchmenu_instance)
|
||||
end,
|
||||
},
|
||||
{
|
||||
@@ -336,9 +276,7 @@ function ReadTimer:addToMainMenu(menu_items)
|
||||
return self:scheduled()
|
||||
end,
|
||||
callback = function(touchmenu_instance)
|
||||
self.last_interval_time = 0
|
||||
self:unschedule()
|
||||
touchmenu_instance:updateItems()
|
||||
self:onStopTimer(touchmenu_instance)
|
||||
end,
|
||||
},
|
||||
},
|
||||
@@ -365,4 +303,108 @@ function ReadTimer:onResume()
|
||||
end
|
||||
end
|
||||
|
||||
function ReadTimer:onShowAlarm(touchmenu_instance)
|
||||
local now_t = os.date("*t")
|
||||
local curr_hour = now_t.hour
|
||||
local curr_min = now_t.min
|
||||
local time_widget = DateTimeWidget:new{
|
||||
hour = curr_hour,
|
||||
min = curr_min,
|
||||
ok_text = _("Set alarm"),
|
||||
title_text = _("New alarm"),
|
||||
info_text = _("Enter a time in hours and minutes."),
|
||||
callback = function(alarm_time)
|
||||
self:setAlarm(alarm_time, now_t, touchmenu_instance)
|
||||
end
|
||||
}
|
||||
self:addCheckboxes(time_widget)
|
||||
UIManager:show(time_widget)
|
||||
return true
|
||||
end
|
||||
|
||||
function ReadTimer:setAlarm(alarm_time, then_t, touchmenu_instance)
|
||||
then_t.hour = alarm_time.hour
|
||||
then_t.min = alarm_time.min
|
||||
then_t.sec = 0
|
||||
local seconds = os.difftime(os.time(then_t), os.time())
|
||||
if seconds <= 0 then
|
||||
then_t.day = then_t.day + 1
|
||||
seconds = os.difftime(os.time(then_t), os.time())
|
||||
end
|
||||
self.last_interval_time = 0
|
||||
self:unschedule()
|
||||
self:rescheduleIn(seconds)
|
||||
|
||||
local user_duration_format = G_reader_settings:readSetting("duration_format")
|
||||
UIManager:show(InfoMessage:new{
|
||||
-- @translators %1:%2 is a clock time (HH:MM), %3 is a duration
|
||||
text = T(_("Timer set for %1:%2.\n\nThat's %3 from now."),
|
||||
string.format("%02d", alarm_time.hour), string.format("%02d", alarm_time.min),
|
||||
datetime.secondsToClockDuration(user_duration_format, seconds, false)),
|
||||
timeout = 5,
|
||||
})
|
||||
if touchmenu_instance then touchmenu_instance:updateItems() end
|
||||
end
|
||||
|
||||
function ReadTimer:onShowTimer(touchmenu_instance)
|
||||
local remain_hours, remain_minutes = self:remainingTime()
|
||||
if not remain_hours and not remain_minutes then
|
||||
local remain_time = G_reader_settings:readSetting("reader_timer_remain_time")
|
||||
if remain_time then
|
||||
remain_hours = remain_time[1]
|
||||
remain_minutes = remain_time[2]
|
||||
end
|
||||
end
|
||||
local time_widget = DateTimeWidget:new{
|
||||
hour = remain_hours or 0,
|
||||
min = remain_minutes or 0,
|
||||
hour_max = 17,
|
||||
ok_text = _("Set timer"),
|
||||
title_text = _("Set reader timer"),
|
||||
info_text = _("Enter a time in hours and minutes."),
|
||||
callback = function(timer_time)
|
||||
self:setInterval(timer_time, touchmenu_instance)
|
||||
end
|
||||
}
|
||||
|
||||
self:addCheckboxes(time_widget)
|
||||
UIManager:show(time_widget)
|
||||
return true
|
||||
end
|
||||
|
||||
function ReadTimer:setInterval(timer_time, touchmenu_instance)
|
||||
local seconds = timer_time.hour * 3600 + timer_time.min * 60
|
||||
if seconds > 0 then
|
||||
self:unschedule()
|
||||
self.last_interval_time = seconds
|
||||
self:rescheduleIn(seconds)
|
||||
|
||||
local user_duration_format = G_reader_settings:readSetting("duration_format")
|
||||
UIManager:show(InfoMessage:new{
|
||||
-- @translators This is a duration
|
||||
text = T(_("Timer will expire in %1."),
|
||||
datetime.secondsToClockDuration(user_duration_format, seconds, true)),
|
||||
timeout = 5,
|
||||
})
|
||||
if touchmenu_instance then touchmenu_instance:updateItems() end
|
||||
|
||||
-- Save settings
|
||||
local remain_time = {timer_time.hour, timer_time.min}
|
||||
G_reader_settings:saveSetting("reader_timer_remain_time", remain_time)
|
||||
end
|
||||
end
|
||||
|
||||
function ReadTimer:onStopTimer(touchmenu_instance)
|
||||
if self:scheduled() then
|
||||
self.last_interval_time = 0
|
||||
self:unschedule()
|
||||
if touchmenu_instance then
|
||||
touchmenu_instance:updateItems()
|
||||
else
|
||||
UIManager:show(InfoMessage:new{text=_("Timer stopped")})
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
return ReadTimer
|
||||
|
||||
@@ -477,6 +477,9 @@ function Terminal:generateInputDialog()
|
||||
end,
|
||||
},
|
||||
}},
|
||||
del_word_callback = function()
|
||||
self:transmit("\023") -- Ctrl+U
|
||||
end,
|
||||
enter_callback = function()
|
||||
self:transmit("\r")
|
||||
end,
|
||||
|
||||
@@ -779,6 +779,12 @@ function TermInputText:delChar()
|
||||
InputText.delChar(self)
|
||||
end
|
||||
|
||||
function TermInputText:delWord(left_to_cursor)
|
||||
if self.parent and self.parent.del_word_callback then
|
||||
self.parent.del_word_callback(left_to_cursor)
|
||||
end
|
||||
end
|
||||
|
||||
function TermInputText:delToStartOfLine()
|
||||
return
|
||||
end
|
||||
|
||||
@@ -88,6 +88,7 @@ function Wallabag:init()
|
||||
|
||||
-- These settings do have defaults
|
||||
self.filter_tag = self.wb_settings.data.wallabag.filter_tag or ""
|
||||
self.filter_starred = self.wb_settings.data.wallabag.filter_starred or false
|
||||
self.ignore_tags = self.wb_settings.data.wallabag.ignore_tags or ""
|
||||
self.auto_tags = self.wb_settings.data.wallabag.auto_tags or ""
|
||||
self.archive_finished = self.wb_settings.data.wallabag.archive_finished or true
|
||||
@@ -260,6 +261,17 @@ function Wallabag:addToMainMenu(menu_items)
|
||||
)
|
||||
end,
|
||||
},
|
||||
{
|
||||
text = _("Only download starred articles"),
|
||||
keep_menu_open = true,
|
||||
checked_func = function()
|
||||
return self.filter_starred or false
|
||||
end,
|
||||
callback = function()
|
||||
self.filter_starred = not self.filter_starred
|
||||
self:saveSettings()
|
||||
end,
|
||||
},
|
||||
{
|
||||
text = _("Prefer original non-HTML document"),
|
||||
keep_menu_open = true,
|
||||
@@ -616,6 +628,10 @@ function Wallabag:getArticleList()
|
||||
filtering = "&tags=" .. self.filter_tag
|
||||
end
|
||||
|
||||
if self.filter_starred then
|
||||
filtering = filtering .. "&starred=1"
|
||||
end
|
||||
|
||||
local article_list = {}
|
||||
local page = 1
|
||||
|
||||
@@ -1593,6 +1609,7 @@ function Wallabag:saveSettings()
|
||||
password = self.password,
|
||||
directory = self.directory,
|
||||
filter_tag = self.filter_tag,
|
||||
filter_starred = self.filter_starred,
|
||||
ignore_tags = self.ignore_tags,
|
||||
auto_tags = self.auto_tags,
|
||||
archive_finished = self.archive_finished,
|
||||
|
||||
Reference in New Issue
Block a user