mirror of
https://github.com/koreader/koreader
synced 2024-10-31 21:20:20 +00:00
0e6b099647
It is especially annoying when TTS is enabled.
291 lines
10 KiB
Lua
291 lines
10 KiB
Lua
require "widget"
|
|
require "font"
|
|
|
|
-- some definitions for developers
|
|
MSG_AUX = 1
|
|
MSG_WARN = 2
|
|
MSG_ERROR = 3
|
|
MSG_CONFIRM = 4
|
|
MSG_BUG = 5
|
|
|
|
InfoMessage = {
|
|
InfoMethod = { --[[
|
|
The items define how to inform user about various types of events, the values should be 0..3:
|
|
the lowest bit is responcible for showing popup windows,
|
|
while the 2nd bit allows using TTS-voice messages.
|
|
The current default values {1,1,1,1,1} correspond to previous kpdfviewer-settings
|
|
when every message is shown in popup window. ]]
|
|
1, -- auxiliary messages = 0 or 2 (nothing or TTS)
|
|
1, -- warnings = 1 or 2 (popup or TTS)
|
|
1, -- errors = 1 or 3 (popup or popup & TTS)
|
|
1, -- confirmations (must not be silent!) = 1 or 3 (popup or popup & TTS)
|
|
1, -- bugs (must not be silent!) = 1 or 3 (popup or popup & TTS)
|
|
},
|
|
-- images for various message types
|
|
Images = {
|
|
"resources/info-aux.png",
|
|
"resources/info-warn.png",
|
|
"resources/info-error.png",
|
|
"resources/info-confirm.png",
|
|
"resources/info-bug.png",
|
|
},
|
|
ImageFile = "resources/info-warn.png",
|
|
-- TTS-related parameters
|
|
TTSspeed = nil,
|
|
SoundVolume = nil,
|
|
|
|
--[[ as Kindle3-volume has nonlinear scale, one need to define the 'VolumeLevels'-values
|
|
TODO: test whether VolumeLevels are different for other sound-equipped Kindle models (K2, KDX)
|
|
by running self.VolumeLevels = self:getVolumeLevels() ]]
|
|
VolumeLevels = { 0, 1, 2, 4, 6, 7, 14, 21, 28, 35, 42, 49, 56, 63, 70, 77}
|
|
}
|
|
|
|
function InfoMessage:show(text,refresh_mode)
|
|
Debug("# InfoMessage ", text, refresh_mode)
|
|
local dialog = CenterContainer:new({
|
|
dimen = { w = G_width, h = G_height },
|
|
FrameContainer:new({
|
|
margin = 2,
|
|
background = 0,
|
|
HorizontalGroup:new({
|
|
align = "center",
|
|
ImageWidget:new({
|
|
file = self.ImageFile
|
|
}),
|
|
Widget:new({
|
|
dimen = { w = 10, h = 0 }
|
|
}),
|
|
TextWidget:new({
|
|
text = text,
|
|
face = Font:getFace("infofont", 30)
|
|
})
|
|
})
|
|
})
|
|
})
|
|
dialog:paintTo(fb.bb, 0, 0)
|
|
dialog:free()
|
|
if refresh_mode ~= nil then
|
|
fb:refresh(refresh_mode)
|
|
end
|
|
end
|
|
|
|
function showInfoMsgWithDelay(text, msec, refresh_mode)
|
|
if not refresh_mode then refresh_mode = 0 end
|
|
Screen:saveCurrentBB()
|
|
|
|
InfoMessage:show(text)
|
|
fb:refresh(refresh_mode)
|
|
-- util.usleep(msec*1000)
|
|
|
|
-- eat the first key release event
|
|
local ev = input.waitForEvent()
|
|
adjustKeyEvents(ev)
|
|
repeat
|
|
ok = pcall( function()
|
|
ev = input.waitForEvent(msec*1000)
|
|
adjustKeyEvents(ev)
|
|
end)
|
|
until not ok or ev.value == EVENT_VALUE_KEY_PRESS
|
|
|
|
Screen:restoreFromSavedBB()
|
|
fb:refresh(refresh_mode)
|
|
end
|
|
|
|
--[[ Unified function to inform user about smth. It is generally intended to replace showInfoMsgWithDelay() & InfoMessage:show().
|
|
Since the former function is used in Lua-code more often than the latter, I kept here the same sequence of parameters as used in
|
|
showInfoMsgWithDelay() + added two not obligatory parameters (see description below).
|
|
|
|
Thus, this trick allows multiple text replaces thoughout the whole Lua-code: 'showInfoMsgWithDelay(' to 'InfoMessage:inform('
|
|
Yet, it would be much better to accompany such replaces by adding the 'message_importance' and, if needed, by
|
|
'alternative_voice_message' that is not so restricted in length as 'text'
|
|
|
|
But replacing InfoMessage:show(...) by InfoMessage:inform(...) MUST be accompanied by adding the 2nd parameter -- either msec=nil or msec=0.
|
|
Adding new parameters -- 'message_importance' & 'alternative_voice_message' -- is also appreciated.
|
|
|
|
Brief description of the function parameters
|
|
-- text : is the text message for visual and (if 'alternative_voice_message' isn't defined) voice notification
|
|
-- msec : parameter to define visual notification method
|
|
msec=0: one calls InfoMessage:show(), otherwise one calls showInfoMsgWithDelay()
|
|
msec>0: with either predefines msec-value or
|
|
msec<0: autocalculated from the text length: one may eventually add user-configurable
|
|
parameter 'reading_speed' that would define the popup exposition for this regime
|
|
-- message_importance : parameter separating various messages on
|
|
1 - not obligatory messages that might be readily avoided
|
|
2 - warnings (important messages)
|
|
3 - errors
|
|
4 - confirmations
|
|
5 - bugs
|
|
-- alternative_voice_message: not obligatory parameter that allows to send longer messages to TTS-engine
|
|
if not defined, the default 'text' will be TTS-voiced
|
|
]]
|
|
|
|
function InfoMessage:inform(text, msec, refresh_mode, message_importance, alternative_voice_message)
|
|
-- temporary test for 'message_importance'; it might be further removed as soon
|
|
-- as every message will be properly marked by 'importance'
|
|
if not message_importance then message_importance = 5 end
|
|
local popup, voice = InfoMessage:getMethodForEvent(message_importance)
|
|
if voice then
|
|
alternative_voice_message = alternative_voice_message or text
|
|
say(alternative_voice_message)
|
|
-- here one may set pause -- it might be useful only if one sound message
|
|
-- is directly followed by another, otherwise it's just wasting of time.
|
|
--[[ if msec and msec ~=0 then
|
|
-- pause = 0.5sec + 40ms/character * string.len() / normalized_voice_speed
|
|
util.usleep(500000 + 40*alternative_voice_message:len*10000/self.TTSspeed)
|
|
end ]]
|
|
end
|
|
if not popup then return end -- to avoid drawing popup window
|
|
self.ImageFile = self.Images[message_importance] -- select proper image for window
|
|
if not msec or msec == 0 then
|
|
InfoMessage:show(text, refresh_mode)
|
|
else
|
|
if msec < 0 then msec = 500 + string.len(text) * 50 end
|
|
showInfoMsgWithDelay(text, msec, refresh_mode)
|
|
end
|
|
end
|
|
|
|
function InfoMessage:getMethodForEvent(event)
|
|
local popup, voice = true, true
|
|
if self.InfoMethod[event] %2 == 0 then popup = false end
|
|
if self.InfoMethod[event] < 2 then voice = false end
|
|
return popup, voice
|
|
end
|
|
|
|
-- GUI-methods for user to choose the way to inform about various events --
|
|
|
|
function InfoMessage:chooseMethodForEvent(event)
|
|
local popup, voice = self:getMethodForEvent(event)
|
|
local items_menu = SelectMenu:new{
|
|
menu_title = "Choose the way how to inform you",
|
|
item_array = {"Avoid any notifications",
|
|
"Show popup window",
|
|
"Use TTS-voice",
|
|
"Popup window and TTS-voice",
|
|
},
|
|
current_entry = (popup and 1 or 0) + (voice and 1 or 0) * 2,
|
|
}
|
|
local item_no = items_menu:choose(0, fb.bb:getHeight())
|
|
if item_no then
|
|
self.InfoMethod[event] = item_no - 1
|
|
-- just to illustrate the way how the selected method works; might be removed
|
|
-- self:inform("Event = "..event..", Method = "..self.InfoMethod[event], -1500, 1, event,
|
|
-- "You have chosen the method number "..self.InfoMethod[event].." for the event item number "..event)
|
|
end
|
|
end
|
|
|
|
function InfoMessage:chooseEventForMethod(event)
|
|
event = event or 0
|
|
local item_no = 0
|
|
local event_list = {
|
|
"Messages (e.g. 'Scanning folder...')",
|
|
"Warnings (e.g. 'Already first jump!')",
|
|
"Errors (e.g. 'Zip contains improper content!')",
|
|
"Confirmations (e.g. 'Press Y to confirm deleting')",
|
|
"Bugs",
|
|
}
|
|
while item_no ~= event and item_no < #event_list do
|
|
item_no = item_no + 1
|
|
end
|
|
local event_menu = SelectMenu:new{
|
|
menu_title = "Select the event type to tune",
|
|
item_array = event_list,
|
|
current_entry = item_no - 1,
|
|
}
|
|
item_no = event_menu:choose(0, G_height)
|
|
return item_no
|
|
end
|
|
|
|
function InfoMessage:chooseNotificatonMethods()
|
|
local event = 1 -- default: auxiliary messages
|
|
while event do
|
|
event = self:chooseEventForMethod(event)
|
|
if event then self:chooseMethodForEvent(event) end
|
|
end
|
|
end
|
|
|
|
---------------- audio-related functions ----------------
|
|
|
|
function InfoMessage:incrTTSspeed(direction) -- either +1 or -1
|
|
-- make sure new TTS-speed is within reasonable range
|
|
self.TTSspeed = math.max(self.TTSspeed + direction * 20, 40) -- min = 40%
|
|
self.TTSspeed = math.min(self.TTSspeed, 200) -- max = 200%
|
|
-- set new value & give an example for more convenient tuning
|
|
os.execute('lipc-set-prop com.lab126.tts TtsISpeed '..self.TTSspeed)
|
|
say("The current voice speed is "..self.TTSspeed.." percent.")
|
|
end
|
|
|
|
function InfoMessage:getTTSspeed()
|
|
if util.isEmulated() == 1 then
|
|
return 0
|
|
end
|
|
local tmp = io.popen('lipc-get-prop com.lab126.tts TtsISpeed', "r")
|
|
local speed = tmp:read("*number")
|
|
tmp:close()
|
|
return speed or 100 -- nominal TTS-speed
|
|
end
|
|
|
|
function InfoMessage:incrSoundVolume(direction) -- either +1 or -1
|
|
-- make sure that new volume is within reasonable range
|
|
self.SoundVolume = math.max(self.SoundVolume + direction, 1)
|
|
self.SoundVolume = math.min(self.SoundVolume, #self.VolumeLevels)
|
|
-- set new value & give an example for more convenient tuning
|
|
os.execute('lipc-set-prop com.lab126.audio Volume '..(self.SoundVolume-1))
|
|
-- that is not exactly the volume percents, but more conventional values,
|
|
-- than abstract units returned by 'lipc-get-prop com.lab126.audio Volume'
|
|
local percents = math.floor(100*self.VolumeLevels[self.SoundVolume]/self.VolumeLevels[#self.VolumeLevels])
|
|
say("The current sound volume is "..percents.." percent.")
|
|
end
|
|
|
|
function InfoMessage:getSoundVolume()
|
|
if util.isEmulated() == 1 then
|
|
return 0
|
|
end
|
|
local tmp = io.popen('lipc-get-prop com.lab126.audio Volume', "r")
|
|
local volume = tmp:read("*number")
|
|
tmp:close()
|
|
local i = 1
|
|
while self.VolumeLevels[i] < volume and i < #self.VolumeLevels do
|
|
i = i + 1
|
|
end
|
|
return i or 16 -- maximum volume
|
|
end
|
|
|
|
--[[ -- to determine self.VolumeLevels in various Kindle-models
|
|
function InfoMessage:getVolumeLevels()
|
|
local levels, v, i = {}, 0, 0
|
|
while i < 16 do -- proper K3-range 0..15 might be different for other models
|
|
os.execute('lipc-set-prop com.lab126.audio Volume '..i)
|
|
v = self:getSoundVolume()
|
|
table.insert(levels, v)
|
|
i = i + 1
|
|
end
|
|
return levels
|
|
end ]]
|
|
|
|
function say(text)
|
|
if util.isEmulated() == 1 then
|
|
os.execute("espeak \""..text.."\"")
|
|
else
|
|
os.execute("say \""..text.."\"")
|
|
end
|
|
end
|
|
|
|
-- The read/write global InfoMessage settings. When properly tested, the
|
|
-- condition 'if FileChooser.filemanager_expert_mode == ...' might be deleted
|
|
|
|
function InfoMessage:initInfoMessageSettings()
|
|
if FileChooser.filemanager_expert_mode == FileChooser.ROOT_MODE then
|
|
InfoMessage.InfoMethod = G_reader_settings:readSetting("info_message_methods") or InfoMessage.InfoMethod
|
|
InfoMessage.TTSspeed = G_reader_settings:readSetting("tts_speed") or InfoMessage:getTTSspeed()
|
|
InfoMessage.SoundVolume = G_reader_settings:readSetting("sound_volume") or InfoMessage:getSoundVolume()
|
|
end
|
|
end
|
|
|
|
function InfoMessage:saveInfoMessageSettings()
|
|
if FileChooser.filemanager_expert_mode == FileChooser.ROOT_MODE then
|
|
G_reader_settings:saveSetting("info_message_methods", InfoMessage.InfoMethod)
|
|
G_reader_settings:saveSetting("sound_volume", InfoMessage.SoundVolume-1)
|
|
G_reader_settings:saveSetting("tts_speed", InfoMessage.TTSspeed)
|
|
end
|
|
end
|