Merge branch 'master' of github.com:koreader/koreader
Some checks failed
macos / macOS 13 x86-64 🔨15.2 🎯10.15 (push) Has been cancelled
macos / macOS 14 ARM64 🔨15.4 🎯11.0 (push) Has been cancelled

This commit is contained in:
kerivin
2025-07-23 23:16:21 +03:00
36 changed files with 984 additions and 369 deletions

View File

@@ -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. {{{

View File

@@ -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

Submodule base updated: e9a00bdf9c...b4e76037fa

View File

@@ -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)

View File

@@ -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()

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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{

View File

@@ -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",
}

View File

@@ -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

View File

@@ -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]

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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()

View File

@@ -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.

View File

@@ -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,
}

View File

@@ -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
View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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,

View File

@@ -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

View File

@@ -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",

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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,

View File

@@ -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

View File

@@ -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,