2
0
mirror of https://github.com/koreader/koreader synced 2024-10-31 21:20:20 +00:00
koreader/dialog.lua
Tigran Aivazian 683ec7570c Enable TTS for the emulator.
On the PC in the EMU mode we can run espeak command which has the same interface as say
on Kindle.
2012-10-06 10:10:03 +01:00

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