2
0
mirror of https://github.com/koreader/koreader synced 2024-10-31 21:20:20 +00:00
koreader/selectmenu.lua
Tigran Aivazian ea92f8c1fc Don't use kerning for monospaced fonts.
If you happened to have a word like "HISTORY" in the book's TOC you will know
how ugly it looks when kerning is enabled. Since we use monospaced font
(by default) for selectmenu, it makes sense to disable kerning for the
actual items (but leave it on for shortcuts and "..." etc).
2012-10-06 20:46:19 +01:00

411 lines
12 KiB
Lua

require "rendertext"
require "keys"
require "graphics"
require "font"
require "commands"
SelectMenu = {
fsize = 22, -- font for displaying item names
tfsize = 25, -- font for page title
ffsize = 16,-- font for paging display
title_H = 40, -- title height
spacing = 36, -- spacing between lines
foot_H = 27, -- foot height
margin_H = 10, -- horisontal margin
current_entry = 0,
menu_title = "No Title",
no_item_msg = "No items found.",
item_array = {},
items = 0,
item_shortcuts = {
"Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P",
"A", "S", "D", "F", "G", "H", "J", "K", "L", "/",
"Z", "X", "C", "V", "B", "N", "M", ".", "Sym", "Ent",
},
last_shortcut = 0,
-- state buffer
page = 1,
current = 1,
oldcurrent = 0,
selected_item = nil,
commands = nil,
expandable = false, -- if true handle Right/Left FW selector keys
deletable = false, -- if true handle Del key as a request to delete item
-- note that currently expandable and deletable are mutually exclusive
-- NuPogodi, 30.08.12: define font to render menu items
own_glyph = 0, -- render menu items with default "cfont"
-- own_glyph = 1 => own glyphs for items like "Droid/DroidSans.ttf"
-- own_glyph = 2 => own glyphs for Font.fontmap._index like "ffont", "tfont", etc.
}
function SelectMenu:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
o.items = #o.item_array
o.page = 1
o.current = 1
o.oldcurrent = 0
o.selected_item = nil
-- increase spacing for DXG so we don't have more than 30 shortcuts
if fb.bb:getHeight() == 1200 then
o.spacing = 37
end
o:addAllCommands()
return o
end
function SelectMenu:getItemIndexByShortCut(c, perpage)
if c == nil then return end -- unused key
for _k,_v in ipairs(self.item_shortcuts) do
if _v == c and _k <= self.last_shortcut then
return (perpage * (self.page - 1) + _k)
end
end
end
function SelectMenu:addAllCommands()
self.commands = Commands:new{}
self.commands:add(KEY_FW_UP, nil, "joypad up",
"previous item",
function(sm)
if sm.current == 1 then
if sm.page > 1 then
sm.current = sm.perpage
sm.page = sm.page - 1
sm.pagedirty = true
end
else
sm.current = sm.current - 1
sm.markerdirty = true
end
end
)
self.commands:add(KEY_FW_DOWN, nil, "joypad down",
"next item",
function(sm)
if sm.current == sm.perpage then
if sm.page < (sm.items / sm.perpage) then
sm.current = 1
sm.page = sm.page + 1
sm.pagedirty = true
end
else
if sm.page ~= math.floor(sm.items / sm.perpage) + 1
or sm.current + (sm.page - 1) * sm.perpage < sm.items then
sm.current = sm.current + 1
sm.markerdirty = true
end
end
end
)
self.commands:add({KEY_PGFWD, KEY_LPGFWD}, nil, ">",
"next page",
function(sm)
if sm.page < (sm.items / sm.perpage) then
if sm.current + sm.page * sm.perpage > sm.items then
sm.current = sm.items - sm.page * sm.perpage
end
sm.page = sm.page + 1
sm.pagedirty = true
else
sm.current = sm.items - (sm.page - 1) * sm.perpage
sm.markerdirty = true
end
end
)
self.commands:add({KEY_PGBCK, KEY_LPGBCK}, nil, "<",
"previous page",
function(sm)
if sm.page > 1 then
sm.page = sm.page - 1
sm.pagedirty = true
else
sm.current = 1
sm.markerdirty = true
end
end
)
self.commands:add(KEY_FW_PRESS, nil, "joypad center",
"select item",
function(sm)
if sm.items == 0 then
return "break"
else
self.selected_item = (sm.perpage * (sm.page - 1) + sm.current)
end
end
)
if self.deletable then
self.commands:add(KEY_DEL, nil, "Del",
"delete item",
function(sm)
self.selected_item = (sm.perpage * (sm.page - 1) + sm.current)
return "delete"
end
)
end
if self.expandable then
self.commands:add(KEY_FW_RIGHT, nil, "joypad right",
"expand item",
function(sm)
self.selected_item = (sm.perpage * (sm.page - 1) + sm.current)
return "expand"
end
)
self.commands:add(KEY_FW_LEFT, nil, "joypad left",
"collapse item",
function(sm)
self.selected_item = (sm.perpage * (sm.page - 1) + sm.current)
return "collapse"
end
)
self.commands:add(KEY_FW_RIGHT, MOD_SHIFT, "joypad right",
"expand all subitems",
function(sm)
self.selected_item = (sm.perpage * (sm.page - 1) + sm.current)
return "expand all"
end
)
end
local KEY_Q_to_P = {}
for i = KEY_Q, KEY_P do
table.insert(KEY_Q_to_P, Keydef:new(i, nil, ""))
end
self.commands:addGroup("Q to P", KEY_Q_to_P,
"select item with Q to P key as shortcut",
function(sm, keydef)
sm.selected_item = sm:getItemIndexByShortCut(
sm.item_shortcuts[ keydef.keycode - KEY_Q + 1 ], sm.perpage)
end
)
local KEY_A_to_L = {}
for i = KEY_A, KEY_L do
table.insert(KEY_A_to_L, Keydef:new(i, nil, ""))
end
self.commands:addGroup("A to L", KEY_A_to_L,
"select item with A to L key as shortcut",
function(sm, keydef)
sm.selected_item = sm:getItemIndexByShortCut(
sm.item_shortcuts[ keydef.keycode - KEY_A + 11 ], sm.perpage)
end
)
local KEY_Z_to_M = {}
for i = KEY_Z, KEY_M do
table.insert(KEY_Z_to_M, Keydef:new(i, nil, ""))
end
self.commands:addGroup("Z to M", KEY_Z_to_M,
"select item with Z to M key as shortcut",
function(sm, keydef)
sm.selected_item = sm:getItemIndexByShortCut(
sm.item_shortcuts[ keydef.keycode - KEY_Z + 21 ], sm.perpage)
end
)
self.commands:add(KEY_SLASH, nil, "/",
"select item with / key as shortcut",
function(sm)
sm.selected_item = sm:getItemIndexByShortCut("/", sm.perpage)
end
)
self.commands:add(KEY_DOT, nil, ".",
"select item with dot key as shortcut",
function(sm)
sm.selected_item = sm:getItemIndexByShortCut(".", sm.perpage)
end
)
self.commands:add(KEY_SYM, nil, "Sym",
"select item with Sym key as shortcut",
function(sm)
sm.selected_item = sm:getItemIndexByShortCut("Sym", sm.perpage)
end
)
self.commands:add(KEY_ENTER, nil, "Enter",
"select item with Enter key as shortcut",
function(sm)
sm.selected_item = sm:getItemIndexByShortCut("Ent", sm.perpage)
end
)
self.commands:add(KEY_H,MOD_ALT,"H",
"show help page",
function(sm)
HelpPage:show(0, G_height, sm.commands)
sm.pagedirty = true
end)
self.commands:add({KEY_BACK,KEY_HOME}, nil, "Back, Home",
"exit menu",
function(sm)
return "break"
end
)
end
function SelectMenu:clearCommands()
self.commands = Commands:new{}
self.commands:add({KEY_BACK,KEY_HOME}, nil, "Back, Home",
"exit menu",
function(sm)
return "break"
end)
end
------------------------------------------------
-- return the index of selected item
------------------------------------------------
function SelectMenu:choose(ypos, height)
self.perpage = math.floor(height / self.spacing) - 2
self.pagedirty = true
self.markerdirty = false
self.last_shortcut = 0
self.current_entry = math.min(self.current_entry,self.items)
-- now calculating the page & cursor
self.page = math.floor(self.current_entry / self.perpage) + 1
self.page = math.max(1, self.page)
self.current = self.current_entry - (self.page - 1) * self.perpage + 1
self.current = math.max(1, self.current)
local own_face
while true do
local cface = Font:getFace("cfont", 22)
local tface = Font:getFace("tfont", 25)
local fface = Font:getFace("ffont", 16)
local sface = Font:getFace("scfont", 22)
local lx = self.margin_H + 40
local fw = fb.bb:getWidth() - lx - self.margin_H
if self.pagedirty then
fb.bb:paintRect(0, ypos, fb.bb:getWidth(), height, 0)
self.markerdirty = true
-- draw menu title
DrawTitle(self.menu_title,self.margin_H,0,self.title_H,3,tface)
-- draw items
fb.bb:paintRect(0, ypos + self.title_H + self.margin_H, fb.bb:getWidth(), height - self.title_H, 0)
if self.items == 0 then
y = ypos + self.title_H + (self.spacing * 2)
renderUtf8Text(fb.bb, self.margin_H + 20, y, cface,
"Oops... Bad news for you:", true)
y = y + self.spacing
renderUtf8Text(fb.bb, self.margin_H + 20, y, cface,
self.no_item_msg, true)
self.markerdirty = false
self:clearCommands()
else
local c
for c = 1, self.perpage do
local i = (self.page - 1) * self.perpage + c
if i <= self.items then
y = ypos + self.title_H + (self.spacing * c) + 4
-- paint shortcut indications
if c <= 10 or c > 20 then
blitbuffer.paintBorder(fb.bb, self.margin_H, y-22, 29, 29, 2, 15)
else
fb.bb:paintRect(self.margin_H, y-22, 29, 29, 3)
end
if self.item_shortcuts[c] ~= nil and
string.len(self.item_shortcuts[c]) == 3 then
-- debug "Del", "Sym and "Ent"
renderUtf8Text(fb.bb, self.margin_H + 3, y, fface,
self.item_shortcuts[c], true)
else
renderUtf8Text(fb.bb, self.margin_H + 8, y, sface,
self.item_shortcuts[c], true)
end
self.last_shortcut = c
-- NuPogodi, 30.08.12: improved method to use own fontface for each menu item
if self.own_glyph == 1 then -- Font.fontmap[_index], like "Droid/DroidSans.ttf"
own_face = Font:getFace(self.item_array[i], 22)
elseif self.own_glyph == 2 then -- Font.fontmap._index, like "[cfont] description"
own_face = Font:getFace(string.sub(string.match(self.item_array[i],"%b[]"), 2, -2), 22)
else
own_face = cface
end
-- rendering menu items
if sizeUtf8Text(lx,fb.bb:getWidth(),own_face,self.item_array[i],false).x < (fw - 10) then
renderUtf8Text(fb.bb,lx,y,own_face,self.item_array[i],false)
else
local gapx = sizeUtf8Text(0,fb.bb:getWidth(),own_face,"...", true).x
gapx = lx + renderUtf8TextWidth(fb.bb,lx,y,own_face,self.item_array[i],false,fw-gapx-15).x
renderUtf8Text(fb.bb,gapx,y,own_face,"...",true)
end
-- end of changes (NuPogodi)
end -- if i <= self.items
end -- for c=1, self.perpage
end -- if self.items == 0
local footer = "Page "..self.page.." of "..(math.ceil(self.items / self.perpage)).." - Press Alt-H for help"
renderUtf8Text(fb.bb, self.margin_H, height-7, fface, footer, true)
end
if self.markerdirty then
if not self.pagedirty then
if self.oldcurrent > 0 then
y = ypos + self.title_H + (self.spacing * self.oldcurrent) + 12
fb.bb:paintRect( lx, y, fw, 3, 0)
fb:refresh(1, lx, y, fw, 3)
end
end
-- draw new marker line
y = ypos + self.title_H + (self.spacing * self.current) + 12
fb.bb:paintRect(lx, y, fw, 3, 15)
if not self.pagedirty then
fb:refresh(1, lx, y, fw, 3)
end
self.oldcurrent = self.current
self.markerdirty = false
end
if self.pagedirty then
fb:refresh(0, 0, ypos, fb.bb:getWidth(), height)
self.pagedirty = false
end
local ev = input.saveWaitForEvent()
ev.code = adjustKeyEvents(ev)
if ev.type == EV_KEY and ev.value ~= EVENT_VALUE_KEY_RELEASE then
keydef = Keydef:new(ev.code, getKeyModifier())
Debug("key pressed: "..tostring(keydef))
command = self.commands:getByKeydef(keydef)
if command ~= nil then
Debug("command to execute: "..tostring(command))
ret_code = command.func(self, keydef)
else
Debug("command not found: "..tostring(command))
end
if ret_code == "break" then
break
end
if self.selected_item ~= nil then
if self.expandable then
if ret_code == "expand" then
return nil, self.selected_item
elseif ret_code == "collapse" then
return nil, -self.selected_item
elseif ret_code == "expand all" then
return nil, self.selected_item, "all"
end
elseif self.deletable and ret_code == "delete" then
return nil, self.selected_item
end
Debug("# selected "..self.selected_item)
return self.selected_item, self.item_array[self.selected_item]
end
end -- EOF if
end -- EOF while
return nil
end