Merge pull request #1 from hwhw/new_ui_code

New ui code
pull/2/merge
clenton 11 years ago
commit a9721b17b4

2
.gitignore vendored

@ -14,6 +14,7 @@ git-rev
kpdfview
slider_watcher
*.o
tags
kindlepdfviewer-*.zip
@ -46,3 +47,4 @@ popen-noshell/popen_noshell_examples.c
popen-noshell/popen_noshell_tests.c
popen-noshell/popen_noshell_tests.cpp
kpvcrlib/libcrengine.so

@ -0,0 +1,38 @@
require "settings" -- for dump method
Dbg = {
is_on = false,
ev_log = nil,
}
function Dbg:turnOn()
self.is_on = true
-- create or clear ev log file
os.execute("echo > ev.log")
self.ev_log = io.open("ev.log", "w")
end
function Dbg:logEv(ev)
local log = ev.type.."|"..ev.code.."|"
..ev.value.."|"..ev.time.sec.."|"..ev.time.usec.."\n"
self.ev_log:write(log)
self.ev_log:flush()
end
function DEBUG(...)
LvDEBUG(math.huge, ...)
end
function LvDEBUG(lv, ...)
local line = ""
for i,v in ipairs({...}) do
if type(v) == "table" then
line = line .. " " .. dump(v, lv)
else
line = line .. " " .. tostring(v)
end
end
print("#"..line)
end

@ -130,7 +130,7 @@ function CreDocument:engineInit()
if not engine_initilized then
-- initialize cache
cre.initCache(1024*1024*64)
-- we need to initialize the CRE font list
local fonts = Font:getFontList()
for _k, _v in ipairs(fonts) do

@ -80,6 +80,14 @@ function DjvuDocument:renderPage(pageno, rect, zoom, rotation, gamma, render_mod
end
end
function DjvuDocument:hintPage(pageno, zoom, rotation, gamma, render_mode)
if self.configurable.text_wrap == 1 then
self.koptinterface:hintPage(self, pageno, zoom, rotation, gamma, render_mode)
else
Document.hintPage(self, pageno, zoom, rotation, gamma, render_mode)
end
end
function DjvuDocument:drawPage(target, x, y, rect, pageno, zoom, rotation, gamma, render_mode)
if self.configurable.text_wrap == 1 then
self.koptinterface:drawPage(self, target, x, y, rect, pageno, zoom, rotation, render_mode)

@ -195,10 +195,38 @@ KoptOptions = {
},
}
KoptInterface = {}
KoptInterface = {
bg_context = {
contex = nil,
pageno = nil,
hash = nil,
cached = false,
},
}
function KoptInterface:waitForContext(kc)
-- if koptcontext is being processed in background thread
-- the isPreCache will return 1.
while kc and kc:isPreCache() == 1 do
DEBUG("waiting for background rendering")
util.usleep(100000)
end
end
function KoptInterface:consumeBgContext(doc)
-- clear up background context
self:waitForContext(self.bg_context.context)
if self.bg_context.context and not self.bg_context.cached then
self:makeCache(doc, self.bg_context.pageno, self.bg_context.hash)
self.bg_context.cached = true
end
end
-- get reflow context
function KoptInterface:getKOPTContext(doc, pageno, bbox)
-- since libk2pdfopt only has one bitmap buffer that holds reflowed page
-- we should consume background production before allocating new context.
self:consumeBgContext(doc)
local kc = KOPTContext.new()
local screen_size = Screen:getSize()
kc:setTrim(doc.configurable.trim_page)
@ -222,6 +250,7 @@ function KoptInterface:getKOPTContext(doc, pageno, bbox)
end
function KoptInterface:setTrimPage(doc, pageno)
if doc.configurable.trim_page == 0 then return end
local page_dimens = doc:getNativePageDimensions(pageno)
--DEBUG("original page dimens", page_dimens)
local orig_bbox = doc:getUsedBBox(pageno)
@ -240,6 +269,22 @@ function KoptInterface:getContextHash(doc, pageno, bbox)
return doc.file.."|"..pageno.."|"..doc.configurable:hash("|").."|"..bbox_hash.."|"..screen_size_hash
end
function KoptInterface:logReflowDuration(pageno, dur)
local file = io.open("reflowlog.txt", "a+")
if file then
if file:seek("end") == 0 then -- write the header only once
file:write("PAGE\tDUR\n")
end
file:write(string.format("%s\t%s\n", pageno, dur))
file:close()
end
end
function KoptInterface:getReflowedDim(kc)
self:waitForContext(kc)
return kc:getPageDim()
end
-- calculates page dimensions
function KoptInterface:getPageDimensions(doc, pageno, zoom, rotation)
self:setTrimPage(doc, pageno)
@ -251,7 +296,12 @@ function KoptInterface:getPageDimensions(doc, pageno, zoom, rotation)
local kc = self:getKOPTContext(doc, pageno, bbox)
local page = doc._document:openPage(pageno)
-- reflow page
--local secs, usecs = util.gettime()
page:reflow(kc, 0)
--local nsecs, nusecs = util.gettime()
--local dur = nsecs - secs + (nusecs - usecs) / 1000000
--DEBUG("Reflow duration:", dur)
--self:logReflowDuration(pageno, dur)
page:close()
local fullwidth, fullheight = kc:getPageDim()
DEBUG("page::reflowPage:", "fullwidth:", fullwidth, "fullheight:", fullheight)
@ -261,11 +311,37 @@ function KoptInterface:getPageDimensions(doc, pageno, zoom, rotation)
return page_size
end
--DEBUG("Found cached koptcontex on page", pageno, cached)
local fullwidth, fullheight = cached.kctx:getPageDim()
local fullwidth, fullheight = self:getReflowedDim(cached.kctx)
local page_size = Geom:new{ w = fullwidth, h = fullheight }
return page_size
end
function KoptInterface:makeCache(doc, pageno, context_hash)
-- draw to blitbuffer
local kc_hash = "kctx|"..context_hash
local tile_hash = "renderpg|"..context_hash
local page = doc._document:openPage(pageno)
local cached = Cache:check(kc_hash)
if cached then
local fullwidth, fullheight = self:getReflowedDim(cached.kctx)
-- prepare cache item with contained blitbuffer
local tile = CacheItem:new{
size = fullwidth * fullheight / 2 + 64, -- estimation
excerpt = Geom:new{ w = fullwidth, h = fullheight },
pageno = pageno,
bb = Blitbuffer.new(fullwidth, fullheight)
}
page:rfdraw(cached.kctx, tile.bb)
page:close()
--DEBUG("cached hash", hash)
if not Cache:check(tile_hash) then
Cache:insert(tile_hash, tile)
end
return tile
end
DEBUG("Error: cannot render page before reflowing.")
end
function KoptInterface:renderPage(doc, pageno, rect, zoom, rotation, render_mode)
self:setTrimPage(doc, pageno)
doc.render_mode = render_mode
@ -291,30 +367,33 @@ function KoptInterface:renderPage(doc, pageno, rect, zoom, rotation, render_mode
end
local cached = Cache:check(hash)
if cached then return cached end
-- prepare cache item with contained blitbuffer
local tile = CacheItem:new{
size = size.w * size.h / 2 + 64, -- estimation
excerpt = size,
pageno = pageno,
bb = Blitbuffer.new(size.w, size.h)
}
-- draw to blitbuffer
local kc_hash = "kctx|"..context_hash
local page = doc._document:openPage(pageno)
local cached = Cache:check(kc_hash)
if cached then
page:rfdraw(cached.kctx, tile.bb)
return cached
else
return self:makeCache(doc, pageno, context_hash)
end
end
function KoptInterface:hintPage(doc, pageno, zoom, rotation, gamma, render_mode)
self:setTrimPage(doc, pageno)
local bbox = doc:getPageBBox(pageno)
local context_hash = self:getContextHash(doc, pageno, bbox)
local hash = "kctx|"..context_hash
local cached = Cache:check(hash)
if not cached then
local kc = self:getKOPTContext(doc, pageno, bbox)
local page = doc._document:openPage(pageno)
kc:setPreCache()
self.bg_context.context = kc
self.bg_context.pageno = pageno
self.bg_context.hash = context_hash
self.bg_context.cached = false
DEBUG("hinting page", pageno, "in background")
-- will return immediately
page:reflow(kc, 0)
page:close()
--DEBUG("cached hash", hash)
if not Cache:check(hash) then
Cache:insert(hash, tile)
end
return tile
Cache:insert(hash, CacheItem:new{ kctx = kc })
end
DEBUG("Error: cannot render page before reflowing.")
end
function KoptInterface:drawPage(doc, target, x, y, rect, pageno, zoom, rotation, render_mode)

@ -82,6 +82,14 @@ function PdfDocument:renderPage(pageno, rect, zoom, rotation, gamma, render_mode
end
end
function PdfDocument:hintPage(pageno, zoom, rotation, gamma, render_mode)
if self.configurable.text_wrap == 1 then
self.koptinterface:hintPage(self, pageno, zoom, rotation, gamma, render_mode)
else
Document.hintPage(self, pageno, zoom, rotation, gamma, render_mode)
end
end
function PdfDocument:drawPage(target, x, y, rect, pageno, zoom, rotation, gamma, render_mode)
if self.configurable.text_wrap == 1 then
self.koptinterface:drawPage(self, target, x, y, rect, pageno, zoom, rotation, render_mode)

@ -15,6 +15,21 @@ function DocSettings:getHistoryPath(fullpath)
return "./history/["..basename:gsub("/","#").."] "..filename..".lua"
end
function DocSettings:getPathFromHistory(hist_name)
-- 1. select everything included in brackets
local s = string.match(hist_name,"%b[]")
-- 2. crop the bracket-sign from both sides
-- 3. and finally replace decorative signs '#' to dir-char '/'
return string.gsub(string.sub(s,2,-3),"#","/")
end
function DocSettings:getNameFromHistory(hist_name)
-- at first, search for path length
local s = string.len(string.match(hist_name,"%b[]"))
-- and return the rest of string without 4 last characters (".lua")
return string.sub(hist_name, s+2, -5)
end
function DocSettings:open(docfile)
local conf_path = nil
if docfile == ".reader" then
@ -49,26 +64,22 @@ function DocSettings:delSetting(key)
self.data[key] = nil
end
function dump(data)
function dump(data, max_lv)
local out = {}
DocSettings:_serialize(data, out, 0)
DocSettings:_serialize(data, out, 0, max_lv)
return table.concat(out)
end
function DEBUG(...)
local line = ""
for i,v in ipairs({...}) do
if type(v) == "table" then
line = line .. " " .. dump(v)
else
line = line .. " " .. tostring(v)
end
-- simple serialization function, won't do uservalues, functions, loops
function DocSettings:_serialize(what, outt, indent, max_lv)
if not max_lv then
max_lv = math.huge
end
if indent > max_lv then
return
end
print("#"..line)
end
-- simple serialization function, won't do uservalues, functions, loops
function DocSettings:_serialize(what, outt, indent)
if type(what) == "table" then
local didrun = false
table.insert(outt, "{")
@ -79,9 +90,9 @@ function DocSettings:_serialize(what, outt, indent)
table.insert(outt, "\n")
table.insert(outt, string.rep("\t", indent+1))
table.insert(outt, "[")
self:_serialize(k, outt, indent+1)
self:_serialize(k, outt, indent+1, max_lv)
table.insert(outt, "] = ")
self:_serialize(v, outt, indent+1)
self:_serialize(v, outt, indent+1, max_lv)
didrun = true
end
if didrun then

@ -43,8 +43,8 @@ function Font:getFace(font, size)
-- default to content font
font = self.cfont
end
local size = math.floor(size*Screen:getDPI()/167)
local size = math.floor(scaleByDPI(size))
local face = self.faces[font..size]
-- build face if not found

@ -78,7 +78,7 @@ GestureDetector = {
DOUBLE_TAP_DISTANCE = 50,
TWO_FINGER_TAP_REGION = 20,
PAN_THRESHOLD = 50,
-- states are stored in separated slots
states = {},
track_ids = {},
@ -272,7 +272,7 @@ function GestureDetector:tapState(tev)
y = tev.y,
timev = tev.timev,
}
if self.last_taps[slot] ~= nil and
self:isDoubleTap(self.last_taps[slot], cur_tap) then
-- it is a double tap
@ -282,10 +282,10 @@ function GestureDetector:tapState(tev)
DEBUG("double tap detected in slot", slot)
return ges_ev
end
-- set current tap to last tap
self.last_taps[slot] = cur_tap
DEBUG("set up tap timer")
-- deadline should be calculated by adding current tap time and the interval
local deadline = cur_tap.timev + TimeVal:new{
@ -305,7 +305,7 @@ function GestureDetector:tapState(tev)
-- we are already at the end of touch event
-- so reset the state
self:clearState(slot)
else
else
-- last tev in this slot is cleared by last two finger tap
self:clearState(slot)
return {
@ -368,7 +368,19 @@ function GestureDetector:panState(tev)
time = tev.timev,
}
end
DEBUG("pan release detected in slot", slot)
local release_pos = Geom:new{
x = self.last_tevs[slot].x,
y = self.last_tevs[slot].y,
w = 0, h = 0,
}
local pan_release = {
ges = "pan_release",
pos = release_pos,
time = tev.timev,
}
self:clearState(slot)
return pan_release
else
if self.states[slot] ~= self.panState then
self.states[slot] = self.panState
@ -399,7 +411,7 @@ end
function GestureDetector:holdState(tev, hold)
DEBUG("in hold state...")
local slot = tev.slot
local slot = tev.slot
-- when we switch to hold state, we pass additional param "hold"
if tev.id ~= -1 and hold and self.last_tevs[slot].x and self.last_tevs[slot].y then
self.states[slot] = self.holdState
@ -443,7 +455,7 @@ function GestureDetector:adjustGesCoordinate(ges)
if ges.pos then
ges.pos.x, ges.pos.y = (Screen.width - ges.pos.y), (ges.pos.x)
end
if ges.ges == "swipe" then
if ges.ges == "swipe" or ges.ges == "pan" then
if ges.direction == "down" then
ges.direction = "left"
elseif ges.direction == "up" then

@ -2,7 +2,6 @@ require "ui/event"
require "ui/device"
require "ui/time"
require "ui/gesturedetector"
require "settings"
-- constants from <linux/input.h>
EV_SYN = 0
@ -112,7 +111,7 @@ function Key:match(sequence)
return false
end
end
return true
end
@ -121,6 +120,7 @@ an interface to get input events
]]
Input = {
event_map = {},
modifiers = {},
rotation_map = {
[0] = {},
[1] = { Up = "Right", Right = "Down", Down = "Left", Left = "Up" },
@ -287,6 +287,8 @@ function Input:init()
elseif dev_mod == "KindleTouch" then
input.open("/dev/input/event2") -- Home button
input.open("/dev/input/event3") -- touchscreen
-- KT does have one key!
self.event_map[102] = "Home"
-- update event hook
function Input:eventAdjustHook(ev)
if ev.type == EV_ABS then
@ -334,7 +336,7 @@ end
function Input:setTimeout(cb, tv_out)
local item = {
callback = cb,
callback = cb,
deadline = tv_out,
}
for k,v in ipairs(self.timer_callbacks) do
@ -443,7 +445,7 @@ function Input:handleTouchEv(ev)
local touch_ges = GestureDetector:feedEvent(self.MTSlots)
self.MTSlots = {}
if touch_ges then
return Event:new("Gesture",
return Event:new("Gesture",
GestureDetector:adjustGesCoordinate(touch_ges)
)
end
@ -451,7 +453,7 @@ function Input:handleTouchEv(ev)
elseif ev.type == EV_ABS then
if #self.MTSlots == 0 then
table.insert(self.MTSlots, self:getMtSlot(self.cur_slot))
end
end
if ev.code == ABS_MT_SLOT then
if self.cur_slot ~= ev.value then
table.insert(self.MTSlots, self:getMtSlot(ev.value))
@ -471,7 +473,7 @@ function Input:waitEvent(timeout_us, timeout_s)
-- wrapper for input.waitForEvents that will retry for some cases
local ok, ev
local wait_deadline = TimeVal:now() + TimeVal:new{
sec = timeout_s,
sec = timeout_s,
usec = timeout_us
}
while true do
@ -490,7 +492,7 @@ function Input:waitEvent(timeout_us, timeout_s)
-- Do we really need to clear all setTimeout after
-- decided a gesture? FIXME
Input.timer_callbacks = {}
return Event:new("Gesture",
return Event:new("Gesture",
GestureDetector:adjustGesCoordinate(touch_ges)
)
end -- EOF if touch_ges
@ -520,13 +522,10 @@ function Input:waitEvent(timeout_us, timeout_s)
end
if ok and ev then
ev = self:eventAdjustHook(ev)
if G_debug_mode then
local log = ev.type.."|"..ev.code.."|"
..ev.value.."|"..ev.time.sec.."|"..ev.time.usec.."\n"
G_ev_log:write(log)
G_ev_log:flush()
if Dbg.is_on and ev then
Dbg:logEv(ev)
end
ev = self:eventAdjustHook(ev)
if ev.type == EV_KEY then
return self:handleKeyBoardEv(ev)
elseif ev.type == EV_ABS or ev.type == EV_SYN then

@ -1,4 +1,4 @@
require "ui/notification"
require "ui/widget/notification"
ReaderBookmark = InputContainer:new{
bm_menu_title = "Bookmarks",
@ -89,7 +89,7 @@ function ReaderBookmark:onShowBookmark()
local bm_menu = Menu:new{
title = "Bookmarks",
item_table = self.bookmarks,
width = Screen:getWidth()-20,
width = Screen:getWidth()-20,
height = Screen:getHeight(),
}
-- buid up menu widget method as closure
@ -111,7 +111,7 @@ function ReaderBookmark:onShowBookmark()
dimen = Screen:getSize(),
bm_menu,
}
bm_menu.close_callback = function()
bm_menu.close_callback = function()
UIManager:close(menu_container)
end
@ -119,9 +119,9 @@ function ReaderBookmark:onShowBookmark()
return true
end
function ReaderBookmark:addToMainMenu(item_table)
function ReaderBookmark:addToMainMenu(tab_item_table)
-- insert table to main reader menu
table.insert(item_table, {
table.insert(tab_item_table.navi, {
text = self.bm_menu_title,
callback = function()
self:onShowBookmark()
@ -154,7 +154,7 @@ function ReaderBookmark:addBookmark(pn_or_xp)
return self:isBookmarkInSequence(a, b)
end)
return true
end
end
function ReaderBookmark:isBookmarkInSequence(a, b)
return a.page < b.page

@ -1,4 +1,4 @@
require "ui/config"
require "ui/widget/config"
Configurable = {}
@ -47,7 +47,7 @@ end
ReaderConfig = InputContainer:new{
dimen = Geom:new{
x = 0,
x = 0,
y = 7*Screen:getHeight()/8,
w = Screen:getWidth(),
h = Screen:getHeight()/8,
@ -82,8 +82,11 @@ function ReaderConfig:onShowConfigMenu()
ui = self.ui,
configurable = self.configurable,
config_options = self.options,
close_callback = function()
self.ui:handleEvent(Event:new("RestoreHinting"))
end,
}
self.ui:handleEvent(Event:new("DisableHinting"))
UIManager:show(self.config_dialog)
return true

@ -1,12 +1,13 @@
require "ui/widget"
require "ui/bbox"
require "ui/widget/group"
require "ui/widget/bbox"
require "ui/widget/button"
PageCropDialog = VerticalGroup:new{
ok_text = "OK",
cancel_text = "Cancel",
ok_callback = function() end,
cancel_callback = function() end,
button_width = math.floor(70*Screen:getDPI()/167),
button_width = math.floor(scaleByDPI(70)),
}
function PageCropDialog:init()
@ -63,6 +64,8 @@ function ReaderCropping:onPageCrop(mode)
-- backup original page scroll
self.orig_page_scroll = self.view.page_scroll
self.view.page_scroll = false
-- backup and disable original hinting state
self.ui:handleEvent(Event:new("DisableHinting"))
-- backup original reflow mode as cropping use non-reflow mode
self.orig_reflow_mode = self.document.configurable.text_wrap
if self.orig_reflow_mode == 1 then
@ -110,6 +113,8 @@ function ReaderCropping:onCancelPageCrop()
end
function ReaderCropping:exitPageCrop(confirmed)
-- restore hinting state
self.ui:handleEvent(Event:new("RestoreHinting"))
-- restore page scroll
self.view.page_scroll = self.orig_page_scroll
-- restore view bgcolor

@ -1,3 +1,4 @@
require "ui/widget/image"
ReaderDogear = RightContainer:new{}
@ -13,4 +14,4 @@ end
function ReaderDogear:onSetDogearVisibility(visible)
self.view.dogear_visible = visible
return true
end
end

@ -13,11 +13,11 @@ function ReaderFont:init()
-- add shortcut for keyboard
self.key_events = {
ShowFontMenu = { {"F"}, doc = "show font menu" },
IncreaseSize = {
{ "Shift", Input.group.PgFwd },
doc = "increase font size",
IncreaseSize = {
{ "Shift", Input.group.PgFwd },
doc = "increase font size",
event = "ChangeSize", args = "increase" },
DecreaseSize = {
DecreaseSize = {
{ "Shift", Input.group.PgBack },
doc = "decrease font size",
event = "ChangeSize", args = "decrease" },
@ -52,33 +52,33 @@ end
function ReaderFont:onReadSettings(config)
self.font_face = config:readSetting("font_face")
if not self.font_face then
if not self.font_face then
self.font_face = self.ui.document.default_font
end
self.ui.document:setFontFace(self.font_face)
self.header_font_face = config:readSetting("header_font_face")
if not self.header_font_face then
if not self.header_font_face then
self.header_font_face = self.ui.document.header_font
end
self.ui.document:setHeaderFont(self.header_font_face)
self.font_size = config:readSetting("font_size")
if not self.font_size then
if not self.font_size then
--@TODO change this! 12.01 2013 (houqp)
self.font_size = 29
end
self.ui.document:setFontSize(self.font_size)
self.line_space_percent = config:readSetting("line_space_percent")
if not self.line_space_percent then
if not self.line_space_percent then
self.line_space_percent = 100
else
self.ui.document:setInterlineSpacePercent(self.line_space_percent)
end
self.gamma_index = config:readSetting("gamma_index")
if not self.gamma_index then
if not self.gamma_index then
self.gamma_index = 15
end
self.ui.document:setGammaIndex(self.gamma_index)
@ -104,7 +104,7 @@ function ReaderFont:onShowFontMenu()
main_menu,
dimen = Screen:getSize(),
}
main_menu.close_callback = function ()
main_menu.close_callback = function ()
UIManager:close(menu_container)
end
-- show menu
@ -211,9 +211,9 @@ function ReaderFont:setFont(face)
end
end
function ReaderFont:addToMainMenu(item_table)
function ReaderFont:addToMainMenu(tab_item_table)
-- insert table to main reader menu
table.insert(item_table, {
table.insert(tab_item_table.typeset, {
text = self.font_menu_title,
sub_item_table = self.face_table,
})

@ -1,3 +1,4 @@
require "ui/widget/progress"
ReaderFooter = InputContainer:new{
pageno = nil,

@ -0,0 +1,19 @@
ReaderHinting = EventListener:new{
hinting_states = {}
}
function ReaderHinting:onSetHinting(hinting)
self.view.hinting = hinting
end
function ReaderHinting:onDisableHinting()
table.insert(self.hinting_states, self.view.hinting)
self.view.hinting = false
return true
end
function ReaderHinting:onRestoreHinting()
self.view.hinting = table.remove(self.hinting_states)
return true
end

@ -1,11 +1,24 @@
require "ui/widget/menu"
require "ui/widget/touchmenu"
ReaderMenu = InputContainer:new{
_name = "ReaderMenu",
item_table = {},
tab_item_table = nil,
registered_widgets = {},
}
function ReaderMenu:init()
self.item_table = {}
self.tab_item_table = {
main = {
icon = "resources/icons/appbar.pokeball.png",
},
navi = {
icon = "resources/icons/appbar.page.corner.bookmark.png",
},
typeset = {
icon = "resources/icons/appbar.page.text.png",
},
}
self.registered_widgets = {}
if Device:hasKeyboard() then
@ -32,34 +45,14 @@ function ReaderMenu:initGesListener()
end
function ReaderMenu:setUpdateItemTable()
table.insert(self.item_table, {
text = "Screen rotate",
sub_item_table = {
{
text = "landscape",
callback = function()
self.ui:handleEvent(
Event:new("SetScreenMode", "landscape"))
end
},
{
text = "portrait",
callback = function()
self.ui:handleEvent(
Event:new("SetScreenMode", "portrait"))
end
},
}
})
for _, widget in pairs(self.registered_widgets) do
widget:addToMainMenu(self.item_table)
widget:addToMainMenu(self.tab_item_table)
end
table.insert(self.item_table, {
table.insert(self.tab_item_table.main, {
text = "Return to file manager",
callback = function()
self.ui:handleEvent(Event:new("RestoreScreenMode",
self.ui:handleEvent(Event:new("RestoreScreenMode",
G_reader_settings:readSetting("screen_mode") or "portrait"))
UIManager:close(self.menu_container)
self.ui:onClose()
@ -68,27 +61,48 @@ function ReaderMenu:setUpdateItemTable()
end
function ReaderMenu:onShowMenu()
if #self.item_table == 0 then
if #self.tab_item_table.main == 0 then
self:setUpdateItemTable()
end
local main_menu = Menu:new{
title = "Document menu",
item_table = self.item_table,
width = Screen:getWidth() - 100,
}
local menu_container = CenterContainer:new{
name = "haha",
ignore = "height",
dimen = Screen:getSize(),
main_menu,
}
main_menu.close_callback = function ()
local main_menu = nil
if Device:isTouchDevice() then
main_menu = TouchMenu:new{
name = "wocao",
tab_item_table = {
self.tab_item_table.navi,
self.tab_item_table.typeset,
self.tab_item_table.main,
},
show_parent = menu_container,
}
else
main_menu = Menu:new{
title = "Document menu",
item_table = {},
width = Screen:getWidth() - 100,
}
for _,item_table in pairs(self.tab_item_table) do
for k,v in ipairs(item_table) do
table.insert(main_menu.item_table, v)
end
end
end
main_menu.close_callback = function ()
UIManager:close(menu_container)
end
menu_container[1] = main_menu
-- maintain a reference to menu_container
self.menu_container = menu_container
UIManager:show(menu_container)
return true

@ -6,38 +6,38 @@ ReaderPaging = InputContainer:new{
visible_area = nil,
page_area = nil,
show_overlap_enable = true,
overlap = 20 * Screen:getDPI()/167,
overlap = scaleByDPI(20),
}
function ReaderPaging:init()
if Device:hasKeyboard() then
self.key_events = {
GotoNextPage = {
GotoNextPage = {
{Input.group.PgFwd}, doc = "go to next page",
event = "GotoPageRel", args = 1 },
GotoPrevPage = {
GotoPrevPage = {
{Input.group.PgBack}, doc = "go to previous page",
event = "GotoPageRel", args = -1 },
GotoFirst = {
GotoFirst = {
{"1"}, doc = "go to start", event = "GotoPercent", args = 0},
Goto11 = {
Goto11 = {
{"2"}, doc = "go to 11%", event = "GotoPercent", args = 11},
Goto22 = {
Goto22 = {
{"3"}, doc = "go to 22%", event = "GotoPercent", args = 22},
Goto33 = {
Goto33 = {
{"4"}, doc = "go to 33%", event = "GotoPercent", args = 33},
Goto44 = {
Goto44 = {
{"5"}, doc = "go to 44%", event = "GotoPercent", args = 44},
Goto55 = {
Goto55 = {
{"6"}, doc = "go to 55%", event = "GotoPercent", args = 55},
Goto66 = {
Goto66 = {
{"7"}, doc = "go to 66%", event = "GotoPercent", args = 66},
Goto77 = {
Goto77 = {
{"8"}, doc = "go to 77%", event = "GotoPercent", args = 77},
Goto88 = {
Goto88 = {
{"9"}, doc = "go to 88%", event = "GotoPercent", args = 88},
GotoLast = {
GotoLast = {
{"0"}, doc = "go to end", event = "GotoPercent", args = 100},
}
end
@ -62,7 +62,7 @@ function ReaderPaging:initGesListener()
GestureRange:new{
ges = "tap",
range = Geom:new{
x = 0,
x = 0,
y = Screen:getHeight()/4,
w = Screen:getWidth()/4,
h = 5*Screen:getHeight()/8,
@ -100,6 +100,16 @@ function ReaderPaging:initGesListener()
rate = 4.0,
}
},
PanRelease = {
GestureRange:new{
ges = "pan_release",
range = Geom:new{
x = 0, y = 0,
w = Screen:getWidth(),
h = Screen:getHeight(),
},
}
},
}
end
@ -128,45 +138,69 @@ end
function ReaderPaging:onToggleFlipping()
self.view.flipping_visible = not self.view.flipping_visible
self.flipping_page = self.view.flipping_visible and self.current_page or nil
self.flipping_mode = self.view.flipping_visible
self.flipping_page = self.current_page
if self.flipping_mode then
self:updateOriginalPage(self.current_page)
end
self.ui:handleEvent(Event:new("SetHinting", not self.flipping_mode))
UIManager:setDirty(self.view.dialog, "partial")
end
function ReaderPaging:updateOriginalPage(page)
self.original_page = page
end
function ReaderPaging:updateFlippingPage(page)
self.flipping_page = page
end
function ReaderPaging:flipping(flipping_page, flipping_ges)
local read = flipping_page - 1
local unread = self.number_of_pages - flipping_page
local whole = self.number_of_pages
local rel_proportion = flipping_ges.distance / Screen:getWidth()
local abs_proportion = flipping_ges.distance / Screen:getHeight()
if flipping_ges.direction == "right" then
self:gotoPage(flipping_page - math.floor(read*rel_proportion))
elseif flipping_ges.direction == "left" then
self:gotoPage(flipping_page + math.floor(unread*rel_proportion))
elseif flipping_ges.direction == "down" then
self:gotoPage(flipping_page - math.floor(whole*abs_proportion))
elseif flipping_ges.direction == "up" then
self:gotoPage(flipping_page + math.floor(whole*abs_proportion))
end
UIManager:setDirty(self.view.dialog, "partial")
end
function ReaderPaging:onSwipe(arg, ges)
if self.flipping_page == nil then
if ges.direction == "left" or ges.direction == "up" then
self:onPagingRel(1)
elseif ges.direction == "right" or ges.direction == "down" then
self:onPagingRel(-1)
end
elseif self.flipping_page then
self:gotoPage(self.flipping_page)
if self.flipping_mode then
self:flipping(self.flipping_page, ges)
self:updateFlippingPage(self.current_page)
elseif self.original_page then
self:gotoPage(self.original_page)
self:updateOriginalPage(nil)
elseif ges.direction == "left" or ges.direction == "up" then
self:onPagingRel(1)
elseif ges.direction == "right" or ges.direction == "down" then
self:onPagingRel(-1)
end
return true
end
function ReaderPaging:onPan(arg, ges)
if self.flipping_page then
local read = self.flipping_page - 1
local unread = self.number_of_pages - self.flipping_page
local whole = self.number_of_pages
local rel_proportion = ges.distance / Screen:getWidth()
local abs_proportion = ges.distance / Screen:getHeight()
if ges.direction == "right" then
self:gotoPage(self.flipping_page - math.floor(read*rel_proportion))
elseif ges.direction == "left" then
self:gotoPage(self.flipping_page + math.floor(unread*rel_proportion))
elseif ges.direction == "down" then
self:gotoPage(self.flipping_page - math.floor(whole*abs_proportion))
elseif ges.direction == "up" then
self:gotoPage(self.flipping_page + math.floor(whole*abs_proportion))
end
UIManager:setDirty(self.view.dialog, "partial")
if self.flipping_mode then
self:flipping(self.flipping_page, ges)
end
return true
end
function ReaderPaging:onPanRelease(arg, ges)
if self.flipping_mode then
self:updateFlippingPage(self.current_page)
end
end
function ReaderPaging:onZoomModeUpdate(new_mode)
-- we need to remember zoom mode to handle page turn event
self.zoom_mode = new_mode
@ -288,7 +322,11 @@ function ReaderPaging:updateLastPageState(state, blank_area, offset)
local visible_area = Geom:new{x = 0, y = 0}
visible_area.w, visible_area.h = blank_area.w, blank_area.h
visible_area.x, visible_area.y = state.visible_area.x, state.visible_area.y
visible_area = visible_area:shrinkInside(state.page_area, offset.x, offset.y)
if state.page == self.number_of_pages then
visible_area:offsetWithin(state.page_area, offset.x, offset.y)
else
visible_area = visible_area:shrinkInside(state.page_area, offset.x, offset.y)
end
-- shrink blank area by the height of visible area
blank_area.h = blank_area.h - visible_area.h
state.visible_area = visible_area
@ -300,7 +338,11 @@ function ReaderPaging:updateFirstPageState(state, blank_area, offset)
visible_area.w, visible_area.h = blank_area.w, blank_area.h
visible_area.x = state.page_area.x
visible_area.y = state.visible_area.y + state.visible_area.h - visible_area.h
visible_area = visible_area:shrinkInside(state.page_area, offset.x, offset.y)
if state.page == 1 then
visible_area:offsetWithin(state.page_area, offset.x, offset.y)
else
visible_area = visible_area:shrinkInside(state.page_area, offset.x, offset.y)
end
-- shrink blank area by the height of visible area
blank_area.h = blank_area.h - visible_area.h
state.visible_area = visible_area
@ -317,6 +359,12 @@ function ReaderPaging:onScrollPageRel(diff)
x = 0,
y = last_page_state.visible_area.h - self.overlap
}
-- Scroll down offset should always be greater than 0
-- otherwise if offset is less than 0 the height of blank area will be
-- larger than 0 even if page area is much larger than visible area,
-- which will trigger the drawing of next page leaving part of current
-- page undrawn. This should also be true for scroll up offset.
if offset.y < 0 then offset.y = 0 end
local state = self:updateLastPageState(last_page_state, blank_area, offset)
--DEBUG("updated state", state)
self.view.page_states = {}
@ -327,7 +375,8 @@ function ReaderPaging:onScrollPageRel(diff)
while blank_area.h > 0 do
blank_area.h = blank_area.h - self.view.page_gap.height
if blank_area.h > 0 then
self:gotoPage(state.page + 1, "scrolling")
if self.current_page == self.number_of_pages then break end
self:gotoPage(self.current_page + 1, "scrolling")
local state = self:getNextPageState(blank_area, Geom:new{})
--DEBUG("new state", state)
table.insert(self.view.page_states, state)
@ -340,6 +389,8 @@ function ReaderPaging:onScrollPageRel(diff)
x = 0,
y = -first_page_state.visible_area.h + self.overlap
}
-- scroll up offset should always be less than 0
if offset.y > 0 then offset.y = 0 end
local state = self:updateFirstPageState(first_page_state, blank_area, offset)
--DEBUG("updated state", state)
self.view.page_states = {}
@ -350,7 +401,8 @@ function ReaderPaging:onScrollPageRel(diff)
while blank_area.h > 0 do
blank_area.h = blank_area.h - self.view.page_gap.height
if blank_area.h > 0 then
self:gotoPage(state.page - 1, "scrolling")
if self.current_page == 1 then break end
self:gotoPage(self.current_page - 1, "scrolling")
local state = self:getPrevPageState(blank_area, Geom:new{})
--DEBUG("new state", state)
table.insert(self.view.page_states, 1, state)

@ -3,14 +3,14 @@ ReaderScreenshot = InputContainer:new{}
function ReaderScreenshot:init()
local diagonal = math.sqrt(
math.pow(Screen:getWidth(), 2) +
math.pow(Screen:getWidth(), 2) +
math.pow(Screen:getHeight(), 2)
)
self.ges_events = {
Screenshot = {
GestureRange:new{
ges = "two_finger_tap",
scale = {diagonal - 80*Screen:getDPI()/167, diagonal},
scale = {diagonal - scaleByDPI(80), diagonal},
rate = 1.0,
}
},

@ -81,7 +81,7 @@ function ReaderToc:onShowToc()
title = "Table of Contents",
item_table = self.toc,
ui = self.ui,
width = Screen:getWidth()-20,
width = Screen:getWidth()-20,
height = Screen:getHeight(),
}
function toc_menu:onMenuChoice(item)
@ -92,7 +92,7 @@ function ReaderToc:onShowToc()
dimen = Screen:getSize(),
toc_menu,
}
toc_menu.close_callback = function()
toc_menu.close_callback = function()
UIManager:close(menu_container)
end
@ -100,9 +100,9 @@ function ReaderToc:onShowToc()
return true
end
function ReaderToc:addToMainMenu(item_table)
function ReaderToc:addToMainMenu(tab_item_table)
-- insert table to main reader menu
table.insert(item_table, {
table.insert(tab_item_table.navi, {
text = self.toc_menu_title,
callback = function()
self:onShowToc()

@ -10,7 +10,7 @@ end
function ReaderTypeset:onReadSettings(config)
self.css = config:readSetting("css")
if self.css and self.css ~= "" then
if self.css and self.css ~= "" then
self.ui.document:setStyleSheet(self.css)
else
self.ui.document:setStyleSheet("")
@ -56,7 +56,7 @@ function ReaderTypeset:genStyleSheetMenu()
if lfs.attributes("./data/"..f, "mode") == "file" and string.match(f, "%.css$") then
table.insert(file_list, {
text = f,
callback = function()
callback = function()
self:setStyleSheet("./data/"..f)
end
})
@ -98,9 +98,9 @@ function ReaderTypeset:toggleEmbeddedStyleSheet()
self.ui:handleEvent(Event:new("UpdatePos"))
end
function ReaderTypeset:addToMainMenu(item_table)
function ReaderTypeset:addToMainMenu(tab_item_table)
-- insert table to main reader menu
table.insert(item_table, {
table.insert(tab_item_table.typeset, {
text = self.css_menu_title,
sub_item_table = self:genStyleSheetMenu(),
})

@ -5,7 +5,7 @@ require "ui/reader/readerdogear"
ReaderView = OverlapGroup:new{
_name = "ReaderView",
document = nil,
-- single page state
state = {
page = 0,
@ -23,14 +23,15 @@ ReaderView = OverlapGroup:new{
page_states = {},
scroll_mode = "vertical",
page_gap = {
width = 8 * Screen:getDPI()/167,
height = 8 * Screen:getDPI()/167,
width = scaleByDPI(8),
height = scaleByDPI(8),
color = 8,
},
-- DjVu page rendering mode (used in djvu.c:drawPage())
render_mode = 0, -- default to COLOR
-- Crengine view mode
view_mode = "page", -- default to page mode
hinting = true,
-- visible area within current viewing page
visible_area = Geom:new{x = 0, y = 0},
@ -38,7 +39,7 @@ ReaderView = OverlapGroup:new{
page_area = Geom:new{},
-- dimen for area to dim
dim_area = Geom:new{w = 0, h = 0},
-- has footer
-- has footer
footer_visible = false,
-- has dogear
dogear_visible = false,
@ -72,7 +73,7 @@ function ReaderView:paintTo(bb, x, y)
else
self:drawPageSurround(bb, x, y)
end
-- draw page content
if self.ui.document.info.has_pages then
if self.page_scroll then
@ -87,16 +88,16 @@ function ReaderView:paintTo(bb, x, y)
self:drawScrollView(bb, x, y)
end
end
-- dim last read area
if self.document.view_mode ~= "page"
if self.document.view_mode ~= "page"
and self.dim_area.w ~= 0 and self.dim_area.h ~= 0 then
bb:dimRect(
self.dim_area.x, self.dim_area.y,
self.dim_area.w, self.dim_area.h
)
end
-- paint dogear
if self.dogear_visible then
self.dogear:paintTo(bb, x, y)
@ -118,12 +119,12 @@ end
function ReaderView:drawPageSurround(bb, x, y)
if self.dimen.h > self.visible_area.h then
bb:paintRect(x, y, self.dimen.w, self.state.offset.y, self.outer_page_color)
bb:paintRect(x, y + self.dimen.h - self.state.offset.y - 1,
bb:paintRect(x, y + self.dimen.h - self.state.offset.y - 1,
self.dimen.w, self.state.offset.y + 1, self.outer_page_color)
end
if self.dimen.w > self.visible_area.w then
bb:paintRect(x, y, self.state.offset.x, self.dimen.h, self.outer_page_color)
bb:paintRect(x + self.dimen.w - self.state.offset.x - 1, y,
bb:paintRect(x + self.dimen.w - self.state.offset.x - 1, y,
self.state.offset.x + 1, self.dimen.h, self.outer_page_color)
end
end
@ -142,13 +143,15 @@ function ReaderView:drawScrollPages(bb, x, y)
state.gamma,
self.render_mode)
pos.y = pos.y + state.visible_area.h
-- draw page gap if not the last part
-- draw page gap if not the last part
if page ~= #self.page_states then
self:drawPageGap(bb, pos.x, pos.y)
pos.y = pos.y + self.page_gap.height
end
end
UIManager:scheduleIn(0, function() self.ui:handleEvent(Event:new("HintPage")) end)
UIManager:scheduleIn(0, function()
self.ui:handleEvent(Event:new("HintPage", self.hinting))
end)
end
function ReaderView:drawPageGap(bb, x, y)
@ -170,7 +173,9 @@ function ReaderView:drawSinglePage(bb, x, y)
self.state.rotation,
self.state.gamma,
self.render_mode)
UIManager:scheduleIn(0, function() self.ui:handleEvent(Event:new("HintPage")) end)
UIManager:scheduleIn(0, function()
self.ui:handleEvent(Event:new("HintPage", self.hinting))
end)
end
function ReaderView:drawPageView(bb, x, y)
@ -294,7 +299,7 @@ end
function ReaderView:onSetFullScreen(full_screen)
self.footer_visible = not full_screen
self:onSetDimensions(Screen:getSize())
self.ui:handleEvent(Event:new("SetDimensions", Screen:getSize()))
end
function ReaderView:onToggleScrollMode(page_scroll)

@ -11,29 +11,29 @@ function ReaderZooming:init()
if Device:hasKeyboard() then
self.key_events = {
ZoomIn = {
{ "Shift", Input.group.PgFwd },
{ "Shift", Input.group.PgFwd },
doc = "zoom in",
event = "Zoom", args = "in"
event = "Zoom", args = "in"
},
ZoomOut = {
{ "Shift", Input.group.PgBack },
{ "Shift", Input.group.PgBack },
doc = "zoom out",
event = "Zoom", args = "out"
event = "Zoom", args = "out"
},
ZoomToFitPage = {
{ "A" },
{ "A" },
doc = "zoom to fit page",
event = "SetZoomMode", args = "page"
event = "SetZoomMode", args = "page"
},
ZoomToFitContent = {
{ "Shift", "A" },
{ "Shift", "A" },
doc = "zoom to fit content",
event = "SetZoomMode", args = "content"
event = "SetZoomMode", args = "content"
},
ZoomToFitPageWidth = {
{ "S" },
{ "S" },
doc = "zoom to fit page width",
event = "SetZoomMode", args = "pagewidth"
event = "SetZoomMode", args = "pagewidth"
},
ZoomToFitContentWidth = {
{ "Shift", "S" },
@ -41,9 +41,9 @@ function ReaderZooming:init()
event = "SetZoomMode", args = "contentwidth"
},
ZoomToFitPageHeight = {
{ "D" },
{ "D" },
doc = "zoom to fit page height",
event = "SetZoomMode", args = "pageheight"
event = "SetZoomMode", args = "pageheight"
},
ZoomToFitContentHeight = {
{ "Shift", "D" },
@ -57,7 +57,7 @@ end
function ReaderZooming:onReadSettings(config)
-- @TODO config file from old code base uses globalzoom_mode
-- instead of zoom_mode, we need to handle this imcompatibility
-- instead of zoom_mode, we need to handle this imcompatibility
-- 04.12 2012 (houqp)
local zoom_mode = config:readSetting("zoom_mode")
if not zoom_mode then
@ -110,12 +110,13 @@ function ReaderZooming:onPageUpdate(new_page_no)
end
function ReaderZooming:onHintPage()
if not self.view.hinting then return true end
if self.current_page < self.ui.document.info.number_of_pages then
self.ui.document:hintPage(
self.view.state.page + 1,
self.view.state.page + 1,
self:getZoom(self.view.state.page + 1),
self.view.state.rotation,
self.view.state.gamma,
self.view.state.rotation,
self.view.state.gamma,
self.view.render_mode)
end
return true
@ -125,7 +126,7 @@ function ReaderZooming:getZoom(pageno)
-- check if we're in bbox mode and work on bbox if that's the case
local zoom = nil
local page_size = {}
if self.zoom_mode == "content"
if self.zoom_mode == "content"
or self.zoom_mode == "contentwidth"
or self.zoom_mode == "contentheight" then
local ubbox_dimen = self.ui.document:getUsedBBoxDimensions(pageno, 1)
@ -178,9 +179,9 @@ function ReaderZooming:genSetZoomModeCallBack(mode)
end
end
function ReaderZooming:addToMainMenu(item_table)
function ReaderZooming:addToMainMenu(tab_item_table)
if self.ui.document.info.has_pages then
table.insert(item_table, {
table.insert(tab_item_table.typeset, {
text = "Switch zoom mode",
sub_item_table = {
{

@ -1,4 +1,3 @@
require "ui/ui"
require "ui/reader/readerview"
require "ui/reader/readerzooming"
require "ui/reader/readerpanning"
@ -15,6 +14,7 @@ require "ui/reader/readercropping"
require "ui/reader/readerkopt"
require "ui/reader/readercopt"
require "ui/reader/readerscreenshot"
require "ui/reader/readerhinting"
--[[
This is an abstraction for a reader interface
@ -132,6 +132,14 @@ function ReaderUI:init()
document = self.document,
}
table.insert(self, cropper)
-- hinting controller
local hinter = ReaderHinting:new{
dialog = self.dialog,
view = self[1],
ui = self,
document = self.document,
}
table.insert(self, hinter)
else
-- make sure we load document first before calling any callback
table.insert(self.postInitCallback, function()

@ -18,7 +18,7 @@
--[[
Codes for rotation modes:
1 for no rotation,
1 for no rotation,
2 for landscape with bottom on the right side of screen, etc.
2
@ -26,8 +26,8 @@ Codes for rotation modes:
| +----------+ |
| | | |
| | Freedom! | |
| | | |
| | | |
| | | |
| | | |
3 | | | | 1
| | | |
| | | |
@ -99,6 +99,15 @@ function Screen:getDPI()
return Device:getModel() == "KindlePaperWhite" and 212 or 167
end
function Screen:scaleByDPI(px)
return (px * self:getDPI()/167)
end
-- make a shortcut to Screen:scaleByDPI
function scaleByDPI(px)
return Screen:scaleByDPI(px)
end
function Screen:getPitch()
return self.fb:getPitch()
end

@ -1,9 +1,8 @@
require "ui/geometry"
require "ui/device"
require "ui/inputevent"
require "ui/widget"
require "ui/screen"
require "settings" -- for DEBUG(), TODO: put DEBUG() somewhere else
require "debug"
-- initialize output module, this must be initialized before Input
Screen:init()
@ -25,7 +24,7 @@ UIManager = {
-- trigger a full refresh when counter reaches FULL_REFRESH_COUNT
FULL_REFRESH_COUNT = 6,
refresh_count = 0,
_running = true,
_window_stack = {},
_execution_stack = {},
@ -156,7 +155,7 @@ function UIManager:run()
while self._running do
local now = { util.gettime() }
local wait_until = self:checkTasks()
--DEBUG("---------------------------------------------------")
--DEBUG("exec stack", self._execution_stack)
--DEBUG("window stack", self._window_stack)
@ -181,19 +180,19 @@ function UIManager:run()
end
if self._dirty[widget.widget] == "full" then
force_full_refresh = true
end
end
-- and remove from list after painting
self._dirty[widget.widget] = nil
-- trigger repaint
dirty = true
end
end
if self.full_refresh then
dirty = true
force_full_refresh = true
end
self.repaint_all = false
self.full_refresh = false
@ -214,9 +213,9 @@ function UIManager:run()
-- reset refresh_type
self.refresh_type = 1
end
self:checkTasks()
-- wait for next event
-- note that we will skip that if in the meantime we have tasks that are ready to run
local input_event = nil

@ -1,748 +0,0 @@
require "ui/screen"
require "ui/rendertext"
require "ui/graphics"
require "ui/image"
require "ui/event"
require "ui/inputevent"
require "ui/gesturedetector"
require "ui/font"
--[[
The EventListener is an interface that handles events
EventListeners have a rudimentary event handler/dispatcher that
will call a method "onEventName" for an event with name
"EventName"
]]
EventListener = {}
function EventListener:new(o)
local o = o or {}
setmetatable(o, self)
self.__index = self
if o.init then o:init() end
return o
end
function EventListener:handleEvent(event)
if self[event.handler] then
return self[event.handler](self, unpack(event.args))
end
end
--[[
This is a generic Widget interface
widgets can be queried about their size and can be paint.
that's it for now. Probably we need something more elaborate
later.
if the table that was given to us as parameter has an "init"
method, it will be called. use this to set _instance_ variables
rather than class variables.
]]
Widget = EventListener:new()
function Widget:new(o)
local o = o or {}
setmetatable(o, self)
self.__index = self
-- Both o._init and o.init are called on object create. But o._init is used
-- for base widget initialization (basic component used to build other
-- widgets). While o.init is for higher level widgets, for example Menu
-- Widget
if o._init then o:_init() end
if o.init then o:init() end
return o
end
function Widget:getSize()
return self.dimen
end
function Widget:paintTo(bb, x, y)
end
--[[
WidgetContainer is a container for another Widget
]]
WidgetContainer = Widget:new()
function WidgetContainer:getSize()
if self.dimen then
-- fixed size
return self.dimen
elseif self[1] then
-- return size of first child widget
return self[1]:getSize()
else
return Geom:new{ w = 0, h = 0 }
end
end
--[[
delete all child widgets
]]--
function WidgetContainer:clear()
while table.remove(self) do end
end
function WidgetContainer:paintTo(bb, x, y)
-- default to pass request to first child widget
if self[1] then
return self[1]:paintTo(bb, x, y)
end
end
function WidgetContainer:propagateEvent(event)
-- propagate to children
for _, widget in ipairs(self) do
if widget:handleEvent(event) then
-- stop propagating when an event handler returns true
return true
end
end
return false
end
--[[
Containers will pass events to children or react on them themselves
]]--
function WidgetContainer:handleEvent(event)
if not self:propagateEvent(event) then
-- call our own standard event handler
return Widget.handleEvent(self, event)
else
return true
end
end
function WidgetContainer:free()
for _, widget in ipairs(self) do
if widget.free then widget:free() end
end
end
--[[
BottomContainer contains its content (1 widget) at the bottom of its own dimensions
]]
BottomContainer = WidgetContainer:new()
function BottomContainer:paintTo(bb, x, y)
local contentSize = self[1]:getSize()
if contentSize.w > self.dimen.w or contentSize.h > self.dimen.h then
-- throw error? paint to scrap buffer and blit partially?
-- for now, we ignore this
end
self[1]:paintTo(bb,
x + (self.dimen.w - contentSize.w)/2,
y + (self.dimen.h - contentSize.h))
end
--[[
CenterContainer centers its content (1 widget) within its own dimensions
]]
CenterContainer = WidgetContainer:new()
function CenterContainer:paintTo(bb, x, y)
local contentSize = self[1]:getSize()
if contentSize.w > self.dimen.w or contentSize.h > self.dimen.h then
-- throw error? paint to scrap buffer and blit partially?
-- for now, we ignore this
end
local x_pos = x
local y_pos = y
if self.ignore ~= "height" then
y_pos = y + (self.dimen.h - contentSize.h)/2
end
if self.ignore ~= "width" then
x_pos = x + (self.dimen.w - contentSize.w)/2
end
self[1]:paintTo(bb, x_pos, y_pos)
end
--[[
LeftContainer aligns its content (1 widget) at the left of its own dimensions
]]
LeftContainer = WidgetContainer:new()
function LeftContainer:paintTo(bb, x, y)
local contentSize = self[1]:getSize()
if contentSize.w > self.dimen.w or contentSize.h > self.dimen.h then
-- throw error? paint to scrap buffer and blit partially?
-- for now, we ignore this
end
self[1]:paintTo(bb, x , y + (self.dimen.h - contentSize.h)/2)
end
--[[
RightContainer aligns its content (1 widget) at the right of its own dimensions
]]
RightContainer = WidgetContainer:new()
function RightContainer:paintTo(bb, x, y)
local contentSize = self[1]:getSize()
if contentSize.w > self.dimen.w or contentSize.h > self.dimen.h then
-- throw error? paint to scrap buffer and blit partially?
-- for now, we ignore this
end
self[1]:paintTo(bb,
x + (self.dimen.w - contentSize.w),
y + (self.dimen.h - contentSize.h)/2)
end
--[[
A FrameContainer is some graphics content (1 widget) that is surrounded by a frame
]]
FrameContainer = WidgetContainer:new{
background = nil,
color = 15,
margin = 0,
radius = 0,
bordersize = 2,
padding = 5,
}
function FrameContainer:getSize()
local content_size =self[1]:getSize()
return Geom:new{
w = content_size.w + ( self.margin + self.bordersize + self.padding ) * 2,
h = content_size.h + ( self.margin + self.bordersize + self.padding ) * 2
}
end
function FrameContainer:paintTo(bb, x, y)
local my_size = self:getSize()
if self.background then
bb:paintRoundedRect(x, y, my_size.w, my_size.h, self.background, self.radius)
end
if self.bordersize > 0 then
bb:paintBorder(x + self.margin, y + self.margin,
my_size.w - self.margin * 2, my_size.h - self.margin * 2,
self.bordersize, self.color, self.radius)
end
if self[1] then
self[1]:paintTo(bb,
x + self.margin + self.bordersize + self.padding,
y + self.margin + self.bordersize + self.padding)
end
end
--[[
A TextWidget puts a string on a single line
]]
TextWidget = Widget:new{
text = nil,
face = nil,
color = 15,
_bb = nil,
_length = 0,
_height = 0,
_maxlength = 1200,
}
--function TextWidget:_render()
--local h = self.face.size * 1.3
--self._bb = Blitbuffer.new(self._maxlength, h)
--self._length = renderUtf8Text(self._bb, 0, h*0.8, self.face, self.text, self.color)
--end
function TextWidget:getSize()
--if not self._bb then
--self:_render()
--end
--return { w = self._length, h = self._bb:getHeight() }
local tsize = sizeUtf8Text(0, Screen:getWidth(), self.face, self.text, true)
if not tsize then
return Geom:new{}
end
self._length = tsize.x
self._height = self.face.size * 1.5
return Geom:new{
w = self._length,
h = self._height,
}
end
function TextWidget:paintTo(bb, x, y)
--if not self._bb then
--self:_render()
--end
--bb:blitFrom(self._bb, x, y, 0, 0, self._length, self._bb:getHeight())
--@TODO Don't use kerning for monospaced fonts. (houqp)
renderUtf8Text(bb, x, y+self._height*0.7, self.face, self.text, true)
end
function TextWidget:free()
if self._bb then
self._bb:free()
self._bb = nil
end
end
--[[
A TextWidget that handles long text wrapping
]]
TextBoxWidget = Widget:new{
text = nil,
face = nil,
color = 15,
width = 400, -- in pixels
line_height = 0.3, -- in em
v_list = nil,
_bb = nil,
_length = 0,
}
function TextBoxWidget:_wrapGreedyAlg(h_list)
local cur_line_width = 0
local space_w = sizeUtf8Text(0, Screen:getWidth(), self.face, " ", true).x
local cur_line = {}
local v_list = {}
for k,w in ipairs(h_list) do
cur_line_width = cur_line_width + w.width
if cur_line_width <= self.width then
cur_line_width = cur_line_width + space_w
table.insert(cur_line, w)
else
-- wrap to next line
table.insert(v_list, cur_line)
cur_line = {}
cur_line_width = w.width + space_w
table.insert(cur_line, w)
end
end
-- handle last line
table.insert(v_list, cur_line)
return v_list
end
function TextBoxWidget:_getVerticalList(alg)
-- build horizontal list
h_list = {}
for w in self.text:gmatch("%S+") do
word_box = {}
word_box.word = w
word_box.width = sizeUtf8Text(0, Screen:getWidth(), self.face, w, true).x
table.insert(h_list, word_box)
end
-- @TODO check alg here 25.04 2012 (houqp)
-- @TODO replace greedy algorithm with K&P algorithm 25.04 2012 (houqp)
return self:_wrapGreedyAlg(h_list)
end
function TextBoxWidget:_render()
self.v_list = self:_getVerticalList()
local v_list = self.v_list
local font_height = self.face.size
local line_height_px = self.line_height * font_height
local space_w = sizeUtf8Text(0, Screen:getWidth(), self.face, " ", true).x
local h = (font_height + line_height_px) * #v_list - line_height_px
self._bb = Blitbuffer.new(self.width, h)
local y = font_height
local pen_x = 0
for _,l in ipairs(v_list) do
pen_x = 0
for _,w in ipairs(l) do
--@TODO Don't use kerning for monospaced fonts. (houqp)
-- refert to cb25029dddc42693cc7aaefbe47e9bd3b7e1a750 in master tree
renderUtf8Text(self._bb, pen_x, y*0.8, self.face, w.word, true)
pen_x = pen_x + w.width + space_w
end
y = y + line_height_px + font_height
end
-- if text is shorter than one line, shrink to text's width
if #v_list == 1 then
self.width = pen_x
end
end
function TextBoxWidget:getSize()
if not self._bb then
self:_render()
end
return { w = self.width, h = self._bb:getHeight() }
end
function TextBoxWidget:paintTo(bb, x, y)
if not self._bb then
self:_render()
end
bb:blitFrom(self._bb, x, y, 0, 0, self.width, self._bb:getHeight())
end
function TextBoxWidget:free()
if self._bb then
self._bb:free()
self._bb = nil
end
end
--[[
ImageWidget shows an image from a file
]]
ImageWidget = Widget:new{
invert = nil,
file = nil,
_bb = nil
}
function ImageWidget:_render()
local itype = string.lower(string.match(self.file, ".+%.([^.]+)") or "")
if itype == "jpeg" or itype == "jpg" then
self._bb = Image.fromJPEG(self.file)
elseif itype == "png" then
self._bb = Image.fromPNG(self.file)
end
end
function ImageWidget:getSize()
if not self._bb then
self:_render()
end
return Geom:new{ w = self._bb:getWidth(), h = self._bb:getHeight() }
end
function ImageWidget:paintTo(bb, x, y)
local size = self:getSize()
bb:blitFrom(self._bb, x, y, 0, 0, size.w, size.h)
if self.invert then
bb:invertRect(x, y, size.w, size.h)
end
end
function ImageWidget:free()
if self._bb then
self._bb:free()
self._bb = nil
end
end
--[[
ProgressWidget shows a progress bar
]]
ProgressWidget = Widget:new{
width = nil,
height = nil,
margin_h = 3,
margin_v = 1,
radius = 2,
bordersize = 1,
bordercolor = 15,
bgcolor = 0,
rectcolor = 10,
percentage = nil,
}
function ProgressWidget:getSize()
return { w = self.width, h = self.height }
end
function ProgressWidget:paintTo(bb, x, y)
local my_size = self:getSize()
bb:paintRoundedRect(x, y, my_size.w, my_size.h, self.bgcolor, self.radius)
bb:paintBorder(x, y, my_size.w, my_size.h, self.bordersize, self.bordercolor, self.radius)
bb:paintRect(x+self.margin_h, y+self.margin_v+self.bordersize,
(my_size.w-2*self.margin_h)*self.percentage, (my_size.h-2*(self.margin_v+self.bordersize)), self.rectcolor)
end
function ProgressWidget:setPercentage(percentage)
self.percentage = percentage
end
--[[
A Layout widget that puts objects besides each others
]]
HorizontalGroup = WidgetContainer:new{
align = "center",
_size = nil,
}
function HorizontalGroup:getSize()
if not self._size then
self._size = { w = 0, h = 0 }
self._offsets = { }
for i, widget in ipairs(self) do
local w_size = widget:getSize()
self._offsets[i] = {
x = self._size.w,
y = w_size.h
}
self._size.w = self._size.w + w_size.w
if w_size.h > self._size.h then
self._size.h = w_size.h
end
end
end
return self._size
end
function HorizontalGroup:paintTo(bb, x, y)
local size = self:getSize()
for i, widget in ipairs(self) do
if self.align == "center" then
widget:paintTo(bb, x + self._offsets[i].x, y + (size.h - self._offsets[i].y) / 2)
elseif self.align == "top" then
widget:paintTo(bb, x + self._offsets[i].x, y)
elseif self.align == "bottom" then
widget:paintTo(bb, x + self._offsets[i].x, y + size.h - self._offsets[i].y)
end
end
end
function HorizontalGroup:clear()
self:free()
WidgetContainer.clear(self)
end
function HorizontalGroup:resetLayout()
self._size = nil
self._offsets = {}
end
function HorizontalGroup:free()
self:resetLayout()
WidgetContainer.free(self)
end
--[[
Dummy Widget that reserves horizontal space
]]
HorizontalSpan = Widget:new{
width = 0,
}
function HorizontalSpan:getSize()
return {w = self.width, h = 0}
end
--[[
A Layout widget that puts objects under each other
]]
VerticalGroup = WidgetContainer:new{
align = "center",
_size = nil,
_offsets = {}
}
function VerticalGroup:getSize()
if not self._size then
self._size = { w = 0, h = 0 }
self._offsets = { }
for i, widget in ipairs(self) do
local w_size = widget:getSize()
self._offsets[i] = {
x = w_size.w,
y = self._size.h,
}
self._size.h = self._size.h + w_size.h
if w_size.w > self._size.w then
self._size.w = w_size.w
end
end
end
return self._size
end
function VerticalGroup:paintTo(bb, x, y)
local size = self:getSize()
for i, widget in ipairs(self) do
if self.align == "center" then
widget:paintTo(bb, x + (size.w - self._offsets[i].x) / 2, y + self._offsets[i].y)
elseif self.align == "left" then
widget:paintTo(bb, x, y + self._offsets[i].y)
elseif self.align == "right" then
widget:paintTo(bb, x + size.w - self._offsets[i].x, y + self._offsets[i].y)
end
end
end
function VerticalGroup:clear()
self:free()
WidgetContainer.clear(self)
end
function VerticalGroup:resetLayout()
self._size = nil
self._offsets = {}
end
function VerticalGroup:free()
self:resetLayout()
WidgetContainer.free(self)
end
--[[
A Layout widget that puts objects above each other
]]
OverlapGroup = WidgetContainer:new{
_size = nil,
}
function OverlapGroup:getSize()
if not self._size then
self._size = {w = 0, h = 0}
self._offsets = { x = math.huge, y = math.huge }
for i, widget in ipairs(self) do
local w_size = widget:getSize()
if self._size.h < w_size.h then
self._size.h = w_size.h
end
if self._size.w < w_size.w then
self._size.w = w_size.w
end
end
end
if self.dimen.w then
self._size.w = self.dimen.w
end
if self.dimen.h then
self._size.h = self.dimen.h
end
return self._size
end
function OverlapGroup:paintTo(bb, x, y)
local size = self:getSize()
for i, wget in ipairs(self) do
local wget_size = wget:getSize()
if wget.align == "right" then
wget:paintTo(bb, x+size.w-wget_size.w, y)
elseif wget.align == "center" then
wget:paintTo(bb, x+(size.w-wget_size.w)/2, y)
else
-- default to left
wget:paintTo(bb, x, y)
end
end
end
--[[
Dummy Widget that reserves vertical space
]]
VerticalSpan = Widget:new{
width = 0,
}
function VerticalSpan:getSize()
return {w = 0, h = self.width}
end
--[[
an UnderlineContainer is a WidgetContainer that is able to paint
a line under its child node
]]
UnderlineContainer = WidgetContainer:new{
linesize = 2,
padding = 1,
color = 0,
}
function UnderlineContainer:getSize()
if self.dimen then
return { w = self.dimen.w, h = self.dimen.h }
else
local contentSize = self[1]:getSize()
return {
w = contentSize.w,
h = contentSize.h + self.linesize + self.padding
}
end
end
function UnderlineContainer:paintTo(bb, x, y)
local content_size = self:getSize()
self[1]:paintTo(bb, x, y)
bb:paintRect(x, y + content_size.h - self.linesize,
content_size.w, self.linesize, self.color)
end
--[[
an InputContainer is an WidgetContainer that handles input events
an example for a key_event is this:
PanBy20 = { { "Shift", Input.group.Cursor }, seqtext = "Shift+Cursor", doc = "pan by 20px", event = "Pan", args = 20, is_inactive = true },
PanNormal = { { Input.group.Cursor }, seqtext = "Cursor", doc = "pan by 10 px", event = "Pan", args = 10 },
Quit = { {"Home"} },
it is suggested to reference configurable sequences from another table
and store that table as configuration setting
]]
InputContainer = WidgetContainer:new{}
function InputContainer:_init()
-- we need to do deep copy here
local new_key_events = {}
if self.key_events then
for k,v in pairs(self.key_events) do
new_key_events[k] = v
end
end
self.key_events = new_key_events
local new_ges_events = {}
if self.ges_events then
for k,v in pairs(self.ges_events) do
new_ges_events[k] = v
end
end
self.ges_events = new_ges_events
if not self.dimen then
self.dimen = Geom:new{}
end
end
function InputContainer:paintTo(bb, x, y)
self.dimen.x = x
self.dimen.y = y
if self[1] then
return self[1]:paintTo(bb, x, y)
end
end
-- the following handler handles keypresses and checks
-- if they lead to a command.
-- if this is the case, we retransmit another event within
-- ourselves
function InputContainer:onKeyPress(key)
for name, seq in pairs(self.key_events) do
if not seq.is_inactive then
for _, oneseq in ipairs(seq) do
if key:match(oneseq) then
local eventname = seq.event or name
return self:handleEvent(Event:new(eventname, seq.args, key))
end
end
end
end
end
function InputContainer:onGesture(ev)
for name, gsseq in pairs(self.ges_events) do
for _, gs_range in ipairs(gsseq) do
--DEBUG("gs_range", gs_range)
if gs_range:match(ev) then
local eventname = gsseq.event or name
return self:handleEvent(Event:new(eventname, gsseq.args, ev))
end
end
end
end

@ -0,0 +1,68 @@
require "ui/screen"
require "ui/rendertext"
require "ui/graphics"
require "ui/image"
require "ui/event"
require "ui/inputevent"
require "ui/gesturedetector"
require "ui/font"
--[[
The EventListener is an interface that handles events
EventListeners have a rudimentary event handler/dispatcher that
will call a method "onEventName" for an event with name
"EventName"
--]]
EventListener = {}
function EventListener:new(o)
local o = o or {}
setmetatable(o, self)
self.__index = self
if o.init then o:init() end
return o
end
function EventListener:handleEvent(event)
if self[event.handler] then
return self[event.handler](self, unpack(event.args))
end
end
--[[
This is a generic Widget interface
widgets can be queried about their size and can be paint.
that's it for now. Probably we need something more elaborate
later.
if the table that was given to us as parameter has an "init"
method, it will be called. use this to set _instance_ variables
rather than class variables.
--]]
Widget = EventListener:new()
function Widget:new(o)
local o = o or {}
setmetatable(o, self)
self.__index = self
-- Both o._init and o.init are called on object create. But o._init is used
-- for base widget initialization (basic component used to build other
-- widgets). While o.init is for higher level widgets, for example Menu
-- Widget
if o._init then o:_init() end
if o.init then o:init() end
return o
end
function Widget:getSize()
return self.dimen
end
function Widget:paintTo(bb, x, y)
end

@ -1,4 +1,5 @@
require "math"
require "ui/widget/container"
--[[
BBoxWidget shows a bbox for page cropping

@ -1,8 +1,8 @@
require "ui/widget"
require "ui/widget/container"
--[[
a button widget
]]
--]]
Button = InputContainer:new{
text = nil, -- mandatory
preselect = false,
@ -23,6 +23,9 @@ function Button:init()
face = Font:getFace(self.text_font_face, self.text_font_size)
}
local text_size = text_widget:getSize()
if self.width == nil then
self.width = text_size.w
end
-- set FrameContainer content
self[1] = FrameContainer:new{
margin = self.margin,

@ -1,26 +1,5 @@
require "ui/widget"
require "ui/focusmanager"
require "ui/infomessage"
require "ui/font"
require "ui/toggleswitch"
FixedTextWidget = TextWidget:new{}
function FixedTextWidget:getSize()
local tsize = sizeUtf8Text(0, Screen:getWidth(), self.face, self.text, true)
if not tsize then
return Geom:new{}
end
self._length = tsize.x
self._height = self.face.size
return Geom:new{
w = self._length,
h = self._height,
}
end
function FixedTextWidget:paintTo(bb, x, y)
renderUtf8Text(bb, x, y+self._height, self.face, self.text, true)
end
require "ui/widget/container"
require "ui/widget/toggleswitch"
MenuBarItem = InputContainer:new{}
function MenuBarItem:init()
@ -180,7 +159,9 @@ function ConfigOption:init()
local default_option_height = 50
local default_option_padding = 15
local vertical_group = VerticalGroup:new{}
table.insert(vertical_group, VerticalSpan:new{ width = default_option_padding * Screen:getDPI()/167 })
table.insert(vertical_group, VerticalSpan:new{
width = scaleByDPI(default_option_padding),
})
for c = 1, #self.options do
if self.options[c].show ~= false then
local name_align = self.options[c].name_align_right and self.options[c].name_align_right or 0.33
@ -189,9 +170,9 @@ function ConfigOption:init()
local name_font_size = self.options[c].name_font_size and self.options[c].name_font_size or default_name_font_size
local item_font_face = self.options[c].item_font_face and self.options[c].item_font_face or "cfont"
local item_font_size = self.options[c].item_font_size and self.options[c].item_font_size or default_item_font_size
local option_height = (self.options[c].height and self.options[c].height or default_option_height) * Screen:getDPI()/167
local items_spacing = HorizontalSpan:new{
width = (self.options[c].spacing and self.options[c].spacing or default_items_spacing) * Screen:getDPI()/167
local option_height = scaleByDPI(self.options[c].height and self.options[c].height or default_option_height)
local items_spacing = HorizontalSpan:new{
width = scaleByDPI(self.options[c].spacing and self.options[c].spacing or default_items_spacing)
}
local horizontal_group = HorizontalGroup:new{}
if self.options[c].name_text then
@ -205,7 +186,7 @@ function ConfigOption:init()
table.insert(option_name_container, option_name)
table.insert(horizontal_group, option_name_container)
end
if self.options[c].widget == "ProgressWidget" then
local widget_container = CenterContainer:new{
dimen = Geom:new{w = Screen:getWidth()*self.options[c].widget_align_center, h = option_height}
@ -218,7 +199,7 @@ function ConfigOption:init()
table.insert(widget_container, widget)
table.insert(horizontal_group, widget_container)
end
local option_items_container = CenterContainer:new{
dimen = Geom:new{w = Screen:getWidth()*item_align, h = option_height}
}
@ -261,7 +242,7 @@ function ConfigOption:init()
end
end
end
if self.options[c].item_text then
for d = 1, #self.options[c].item_text do
local option_item = nil
@ -298,7 +279,7 @@ function ConfigOption:init()
end
end
end
if self.options[c].item_icons then
for d = 1, #self.options[c].item_icons do
local option_item = OptionIconItem:new{
@ -322,7 +303,7 @@ function ConfigOption:init()
end
end
end
if self.options[c].toggle then
local switch = ToggleSwitch:new{
name = self.options[c].name,
@ -338,7 +319,7 @@ function ConfigOption:init()
switch:setPosition(position)
table.insert(option_items_group, switch)
end
table.insert(option_items_container, option_items_group)
table.insert(horizontal_group, option_items_container)
table.insert(vertical_group, horizontal_group)
@ -352,7 +333,7 @@ end
ConfigPanel = FrameContainer:new{ background = 0, bordersize = 0, }
function ConfigPanel:init()
local config_options = self.config_dialog.config_options
local default_option = config_options.default_options and config_options.default_options
local default_option = config_options.default_options and config_options.default_options
or config_options[1].options
local panel = ConfigOption:new{
options = self.index and config_options[self.index].options or default_option,
@ -375,26 +356,26 @@ function MenuBar:init()
local icon_dimen = menu_icon:getSize()
icons_width = icons_width + icon_dimen.w
icons_height = icon_dimen.h > icons_height and icon_dimen.h or icons_height
menu_items[c] = MenuBarItem:new{
menu_icon,
index = c,
config = self.config_dialog,
}
end
local spacing = HorizontalSpan:new{
width = (Screen:getWidth() - icons_width) / (#menu_items+1)
}
local menu_bar = HorizontalGroup:new{}
for c = 1, #menu_items do
table.insert(menu_bar, spacing)
table.insert(menu_bar, menu_items[c])
end
table.insert(menu_bar, spacing)
self.dimen = Geom:new{ w = Screen:getWidth(), h = icons_height}
table.insert(self, menu_bar)
end
@ -415,7 +396,7 @@ Widget that displays config menubar and config panel
+----------------+
| Menu Bar |
+----------------+
--]]
ConfigDialog = InputContainer:new{
@ -429,7 +410,7 @@ function ConfigDialog:init()
self.config_panel = ConfigPanel:new{
config_dialog = self,
}
self.config_menubar = MenuBar:new{
self.config_menubar = MenuBar:new{
config_dialog = self,
}
self:makeDialog()
@ -454,7 +435,7 @@ function ConfigDialog:init()
self.key_events.FocusRight = nil
end
self.key_events.Select = { {"Press"}, doc = "select current menu item"}
UIManager:setDirty(self, "partial")
end
@ -470,9 +451,9 @@ function ConfigDialog:makeDialog()
self.config_panel,
self.config_menubar,
}
local dialog_size = dialog:getSize()
self[1] = BottomContainer:new{
dimen = Screen:getSize(),
FrameContainer:new{
@ -481,13 +462,13 @@ function ConfigDialog:makeDialog()
dialog,
}
}
self.dialog_dimen = Geom:new{
x = (Screen:getWidth() - dialog_size.w)/2,
y = Screen:getHeight() - dialog_size.h,
w = dialog_size.w,
h = dialog_size.h,
}
}
end
function ConfigDialog:onShowConfigPanel(index)

@ -1,6 +1,6 @@
require "ui/widget"
require "ui/focusmanager"
require "ui/button"
require "ui/widget/container"
require "ui/widget/focusmanager"
require "ui/widget/button"
--[[
Widget that shows a message and OK/Cancel buttons
@ -25,14 +25,22 @@ function ConfirmBox:init()
local ok_button = Button:new{
text = self.ok_text,
callback = function()
self.ok_callback()
UIManager:close(self)
end,
}
local cancel_button = Button:new{
text = self.cancel_text,
preselect = true
preselect = true,
callback = function()
self.cancel_callback()
UIManager:close(self)
end,
}
self.layout = { { ok_button, cancel_button } }
self.selected.x = 2 -- Cancel is default
self.selected.x = 2 -- Cancel is default
self[1] = CenterContainer:new{
dimen = Screen:getSize(),

@ -0,0 +1,317 @@
require "ui/widget/base"
--[[
WidgetContainer is a container for another Widget
--]]
WidgetContainer = Widget:new()
function WidgetContainer:getSize()
if self.dimen then
-- fixed size
return self.dimen
elseif self[1] then
-- return size of first child widget
return self[1]:getSize()
else
return Geom:new{ w = 0, h = 0 }
end
end
--[[
delete all child widgets
--]]
function WidgetContainer:clear()
while table.remove(self) do end
end
function WidgetContainer:paintTo(bb, x, y)
-- default to pass request to first child widget
if self[1] then
return self[1]:paintTo(bb, x, y)
end
end
function WidgetContainer:propagateEvent(event)
-- propagate to children
for _, widget in ipairs(self) do
if widget:handleEvent(event) then
-- stop propagating when an event handler returns true
return true
end
end
return false
end
--[[
Containers will pass events to children or react on them themselves
--]]
function WidgetContainer:handleEvent(event)
if not self:propagateEvent(event) then
-- call our own standard event handler
return Widget.handleEvent(self, event)
else
return true
end
end
function WidgetContainer:free()
for _, widget in ipairs(self) do
if widget.free then widget:free() end
end
end
--[[
BottomContainer contains its content (1 widget) at the bottom of its own
dimensions
--]]
BottomContainer = WidgetContainer:new()
function BottomContainer:paintTo(bb, x, y)
local contentSize = self[1]:getSize()
if contentSize.w > self.dimen.w or contentSize.h > self.dimen.h then
-- throw error? paint to scrap buffer and blit partially?
-- for now, we ignore this
end
self[1]:paintTo(bb,
x + (self.dimen.w - contentSize.w)/2,
y + (self.dimen.h - contentSize.h))
end
--[[
CenterContainer centers its content (1 widget) within its own dimensions
--]]
CenterContainer = WidgetContainer:new()
function CenterContainer:paintTo(bb, x, y)
local contentSize = self[1]:getSize()
if contentSize.w > self.dimen.w or contentSize.h > self.dimen.h then
-- throw error? paint to scrap buffer and blit partially?
-- for now, we ignore this
end
local x_pos = x
local y_pos = y
if self.ignore ~= "height" then
y_pos = y + (self.dimen.h - contentSize.h)/2
end
if self.ignore ~= "width" then
x_pos = x + (self.dimen.w - contentSize.w)/2
end
self[1]:paintTo(bb, x_pos, y_pos)
end
--[[
LeftContainer aligns its content (1 widget) at the left of its own dimensions
--]]
LeftContainer = WidgetContainer:new()
function LeftContainer:paintTo(bb, x, y)
local contentSize = self[1]:getSize()
if contentSize.w > self.dimen.w or contentSize.h > self.dimen.h then
-- throw error? paint to scrap buffer and blit partially?
-- for now, we ignore this
end
self[1]:paintTo(bb, x , y + (self.dimen.h - contentSize.h)/2)
end
--[[
RightContainer aligns its content (1 widget) at the right of its own dimensions
--]]
RightContainer = WidgetContainer:new()
function RightContainer:paintTo(bb, x, y)
local contentSize = self[1]:getSize()
if contentSize.w > self.dimen.w or contentSize.h > self.dimen.h then
-- throw error? paint to scrap buffer and blit partially?
-- for now, we ignore this
end
self[1]:paintTo(bb,
x + (self.dimen.w - contentSize.w),
y + (self.dimen.h - contentSize.h)/2)
end
--[[
A FrameContainer is some graphics content (1 widget) that is surrounded by a
frame
--]]
FrameContainer = WidgetContainer:new{
background = nil,
color = 15,
margin = 0,
radius = 0,
bordersize = 2,
padding = 5,
width = nil,
height = nil,
invert = false,
}
function FrameContainer:getSize()
local content_size = self[1]:getSize()
return Geom:new{
w = content_size.w + ( self.margin + self.bordersize + self.padding ) * 2,
h = content_size.h + ( self.margin + self.bordersize + self.padding ) * 2
}
end
function FrameContainer:paintTo(bb, x, y)
local my_size = self:getSize()
local container_width = self.width or my_size.w
local container_height = self.height or my_size.h
--@TODO get rid of margin here? 13.03 2013 (houqp)
if self.background then
bb:paintRoundedRect(x, y, container_width, container_height,
self.background, self.radius)
end
if self.bordersize > 0 then
bb:paintBorder(x + self.margin, y + self.margin,
container_width - self.margin * 2,
container_height - self.margin * 2,
self.bordersize, self.color, self.radius)
end
if self[1] then
self[1]:paintTo(bb,
x + self.margin + self.bordersize + self.padding,
y + self.margin + self.bordersize + self.padding)
end
if self.invert then
bb:invertRect(x, y, container_width, container_height)
end
end
--[[
an UnderlineContainer is a WidgetContainer that is able to paint
a line under its child node
--]]
UnderlineContainer = WidgetContainer:new{
linesize = 2,
padding = 1,
color = 0,
vertical_align = "top",
}
function UnderlineContainer:getSize()
if self.dimen then
return { w = self.dimen.w, h = self.dimen.h }
else
return self:getContentSize()
end
end
function UnderlineContainer:getContentSize()
local contentSize = self[1]:getSize()
return {
w = contentSize.w,
h = contentSize.h + self.linesize + self.padding
}
end
function UnderlineContainer:paintTo(bb, x, y)
local container_size = self:getSize()
local content_size = self:getContentSize()
local p_y = y
if self.vertical_align == "center" then
p_y = (container_size.h - content_size.h) / 2 + y
elseif self.vertical_align == "bottom" then
p_y = (container_size.h - content_size.h) + y
end
self[1]:paintTo(bb, x, p_y)
bb:paintRect(x, y + container_size.h - self.linesize,
container_size.w, self.linesize, self.color)
end
--[[
an InputContainer is an WidgetContainer that handles input events
an example for a key_event is this:
PanBy20 = {
{ "Shift", Input.group.Cursor },
seqtext = "Shift+Cursor",
doc = "pan by 20px",
event = "Pan", args = 20, is_inactive = true,
},
PanNormal = {
{ Input.group.Cursor },
seqtext = "Cursor",
doc = "pan by 10 px", event = "Pan", args = 10,
},
Quit = { {"Home"} },
it is suggested to reference configurable sequences from another table
and store that table as configuration setting
--]]
InputContainer = WidgetContainer:new{
vertical_align = "top",
}
function InputContainer:_init()
-- we need to do deep copy here
local new_key_events = {}
if self.key_events then
for k,v in pairs(self.key_events) do
new_key_events[k] = v
end
end
self.key_events = new_key_events
local new_ges_events = {}
if self.ges_events then
for k,v in pairs(self.ges_events) do
new_ges_events[k] = v
end
end
self.ges_events = new_ges_events
if not self.dimen then
self.dimen = Geom:new{}
end
end
function InputContainer:paintTo(bb, x, y)
self.dimen.x = x
self.dimen.y = y
if self[1] then
if self.vertical_align == "center" then
local content_size = self[1]:getSize()
self[1]:paintTo(bb, x, y + (self.dimen.h - content_size.h)/2)
else
self[1]:paintTo(bb, x, y)
end
end
end
--[[
the following handler handles keypresses and checks if they lead to a command.
if this is the case, we retransmit another event within ourselves
--]]
function InputContainer:onKeyPress(key)
for name, seq in pairs(self.key_events) do
if not seq.is_inactive then
for _, oneseq in ipairs(seq) do
if key:match(oneseq) then
local eventname = seq.event or name
return self:handleEvent(Event:new(eventname, seq.args, key))
end
end
end
end
end
function InputContainer:onGesture(ev)
for name, gsseq in pairs(self.ges_events) do
for _, gs_range in ipairs(gsseq) do
--DEBUG("gs_range", gs_range)
if gs_range:match(ev) then
local eventname = gsseq.event or name
return self:handleEvent(Event:new(eventname, gsseq.args, ev))
end
end
end
end

@ -1,4 +1,4 @@
require "ui/menu"
require "ui/widget/menu"
FileChooser = Menu:new{
height = Screen:getHeight(),

@ -0,0 +1,168 @@
require "ui/widget/container"
--[[
A Layout widget that puts objects besides each others
--]]
HorizontalGroup = WidgetContainer:new{
align = "center",
_size = nil,
}
function HorizontalGroup:getSize()
if not self._size then
self._size = { w = 0, h = 0 }
self._offsets = { }
for i, widget in ipairs(self) do
local w_size = widget:getSize()
self._offsets[i] = {
x = self._size.w,
y = w_size.h
}
self._size.w = self._size.w + w_size.w
if w_size.h > self._size.h then
self._size.h = w_size.h
end
end
end
return self._size
end
function HorizontalGroup:paintTo(bb, x, y)
local size = self:getSize()
for i, widget in ipairs(self) do
if self.align == "center" then
widget:paintTo(bb,
x + self._offsets[i].x,
y + (size.h - self._offsets[i].y) / 2)
elseif self.align == "top" then
widget:paintTo(bb, x + self._offsets[i].x, y)
elseif self.align == "bottom" then
widget:paintTo(bb, x + self._offsets[i].x, y + size.h - self._offsets[i].y)
end
end
end
function HorizontalGroup:clear()
self:free()
WidgetContainer.clear(self)
end
function HorizontalGroup:resetLayout()
self._size = nil
self._offsets = {}
end
function HorizontalGroup:free()
self:resetLayout()
WidgetContainer.free(self)
end
--[[
A Layout widget that puts objects under each other
--]]
VerticalGroup = WidgetContainer:new{
align = "center",
_size = nil,
_offsets = {}
}
function VerticalGroup:getSize()
if not self._size then
self._size = { w = 0, h = 0 }
self._offsets = { }
for i, widget in ipairs(self) do
local w_size = widget:getSize()
self._offsets[i] = {
x = w_size.w,
y = self._size.h,
}
self._size.h = self._size.h + w_size.h
if w_size.w > self._size.w then
self._size.w = w_size.w
end
end
end
return self._size
end
function VerticalGroup:paintTo(bb, x, y)
local size = self:getSize()
for i, widget in ipairs(self) do
if self.align == "center" then
widget:paintTo(bb, x + (size.w - self._offsets[i].x) / 2, y + self._offsets[i].y)
elseif self.align == "left" then
widget:paintTo(bb, x, y + self._offsets[i].y)
elseif self.align == "right" then
widget:paintTo(bb, x + size.w - self._offsets[i].x, y + self._offsets[i].y)
end
end
end
function VerticalGroup:clear()
self:free()
WidgetContainer.clear(self)
end
function VerticalGroup:resetLayout()
self._size = nil
self._offsets = {}
end
function VerticalGroup:free()
self:resetLayout()
WidgetContainer.free(self)
end
--[[
A Layout widget that puts objects above each other
--]]
OverlapGroup = WidgetContainer:new{
_size = nil,
}
function OverlapGroup:getSize()
if not self._size then
self._size = {w = 0, h = 0}
self._offsets = { x = math.huge, y = math.huge }
for i, widget in ipairs(self) do
local w_size = widget:getSize()
if self._size.h < w_size.h then
self._size.h = w_size.h
end
if self._size.w < w_size.w then
self._size.w = w_size.w
end
end
end
if self.dimen.w then
self._size.w = self.dimen.w
end
if self.dimen.h then
self._size.h = self.dimen.h
end
return self._size
end
function OverlapGroup:paintTo(bb, x, y)
local size = self:getSize()
for i, wget in ipairs(self) do
local wget_size = wget:getSize()
if wget.align == "right" then
wget:paintTo(bb, x+size.w-wget_size.w, y)
elseif wget.align == "center" then
wget:paintTo(bb, x+(size.w-wget_size.w)/2, y)
else
-- default to left
wget:paintTo(bb, x, y)
end
end
end

@ -0,0 +1,55 @@
require "ui/widget/container"
require "ui/widget/image"
--[[
Button with a big icon image! Designed for touch device
--]]
IconButton = InputContainer:new{
icon_file = "resources/info-confirm.png",
dimen = nil,
-- show_parent is used for UIManager:setDirty, so we can trigger repaint
show_parent = nil,
callback = function() end,
}
function IconButton:init()
self.image = ImageWidget:new{
file = self.icon_file
}
self.show_parent = self.show_parent or self
self.dimen = self.image:getSize()
self:initGesListener()
self[1] = self.image
end
function IconButton:initGesListener()
self.ges_events = {
TapClickButton = {
GestureRange:new{
ges = "tap",
range = self.dimen,
}
},
}
end
function IconButton:onTapClickButton()
self.image.invert = true
UIManager:setDirty(self.show_parent, "partial")
-- make sure button reacts before doing callback
UIManager:scheduleIn(0.1, function()
self.callback()
self.image.invert = false
UIManager:setDirty(self.show_parent, "partial")
end)
return true
end
function IconButton:onSetDimensions(new_dimen)
self.dimen = new_dimen
end

@ -0,0 +1,45 @@
require "ui/widget/base"
require "ui/image"
--[[
ImageWidget shows an image from a file
--]]
ImageWidget = Widget:new{
invert = nil,
file = nil,
_bb = nil
}
function ImageWidget:_render()
local itype = string.lower(string.match(self.file, ".+%.([^.]+)") or "")
if itype == "jpeg" or itype == "jpg" then
self._bb = Image.fromJPEG(self.file)
elseif itype == "png" then
self._bb = Image.fromPNG(self.file)
end
end
function ImageWidget:getSize()
if not self._bb then
self:_render()
end
return Geom:new{ w = self._bb:getWidth(), h = self._bb:getHeight() }
end
function ImageWidget:paintTo(bb, x, y)
local size = self:getSize()
bb:blitFrom(self._bb, x, y, 0, 0, size.w, size.h)
if self.invert then
bb:invertRect(x, y, size.w, size.h)
end
end
function ImageWidget:free()
if self._bb then
self._bb:free()
self._bb = nil
end
end

@ -1,5 +1,4 @@
require "ui/ui"
require "ui/widget"
require "ui/widget/container"
--[[
Widget that displays an informational message

@ -0,0 +1,31 @@
require "ui/widget/base"
LineWidget = Widget:new{
style = "solid",
background = 15,
dimen = nil,
--@TODO replay dirty hack here 13.03 2013 (houqp)
empty_segments = nil,
}
function LineWidget:paintTo(bb, x, y)
if self.style == "dashed" then
for i = 0, self.dimen.w - 20, 20 do
bb:paintRect(x + i, y,
16, self.dimen.h, self.background)
end
else
if self.empty_segments then
bb:paintRect(x, y,
self.empty_segments[1].s,
self.dimen.h,
self.background)
bb:paintRect(x + self.empty_segments[1].e, y,
self.dimen.w - x - self.empty_segments[1].e,
self.dimen.h,
self.background)
else
bb:paintRect(x, y, self.dimen.w, self.dimen.h, self.background)
end
end
end

@ -1,11 +1,14 @@
require "ui/widget"
require "ui/focusmanager"
require "ui/infomessage"
require "ui/widget/container"
require "ui/widget/focusmanager"
require "ui/widget/infomessage"
require "ui/widget/text"
require "ui/widget/group"
require "ui/widget/span"
require "ui/font"
--[[
Widget that displays a shortcut icon for menu item
]]
--]]
ItemShortCutIcon = WidgetContainer:new{
dimen = Geom:new{ w = 22, h = 22 },
key = nil,
@ -100,7 +103,7 @@ function MenuItem:init()
local shortcut_icon_dimen = Geom:new()
if self.shortcut then
shortcut_icon_dimen.w = math.floor(self.dimen.h*4/5)
shortcut_icon_dimen.h = shortcut_icon_dimen.w
shortcut_icon_dimen.h = shortcut_icon_dimen.w
end
self.detail = self.text
@ -129,7 +132,7 @@ function MenuItem:init()
if Device:isTouchDevice() then
else
self.active_key_events.ShowItemDetail = {
{"Right"}, doc = "show item detail"
{"Right"}, doc = "show item detail"
}
end
indicator = " >>"
@ -346,7 +349,7 @@ function Menu:init()
if self.is_enable_shortcut then
self.key_events.SelectByShortCut = { {self.item_shortcuts} }
end
self.key_events.Select = {
self.key_events.Select = {
{"Press"}, doc = "select current menu item"
}
end
@ -368,7 +371,7 @@ function Menu:updateItems(select_number)
for c = 1, self.perpage do
-- calculate index in item_table
local i = (self.page - 1) * self.perpage + c
local i = (self.page - 1) * self.perpage + c
if i <= #self.item_table then
local item_shortcut = nil
local shortcut_style = "square"
@ -398,7 +401,7 @@ function Menu:updateItems(select_number)
table.insert(self.layout, {item_tmp})
else
-- item not enough to fill the whole page, break out of loop
table.insert(self.item_group,
table.insert(self.item_group,
VerticalSpan:new{
width = (self.item_dimen.h * (self.perpage - c + 1))
})
@ -438,7 +441,7 @@ function Menu:onSelectByShortCut(_, keyevent)
if self.item_table[(self.page-1)*self.perpage + k] then
self:onMenuSelect(self.item_table[(self.page-1)*self.perpage + k])
end
break
break
end
end
return true
@ -468,7 +471,7 @@ override this function to process the item selected in a different manner
]]--
function Menu:onMenuSelect(item)
if item.sub_item_table == nil then
self.close_callback()
self.close_callback()
self:onMenuChoice(item)
else
-- save menu title for later resume

@ -1,5 +1,4 @@
require "ui/ui"
require "ui/widget"
require "ui/widget/container"
--[[
Widget that displays a tiny notification on top of screen

@ -0,0 +1,39 @@
require "ui/widget/base"
--[[
ProgressWidget shows a progress bar
--]]
ProgressWidget = Widget:new{
width = nil,
height = nil,
margin_h = 3,
margin_v = 1,
radius = 2,
bordersize = 1,
bordercolor = 15,
bgcolor = 0,
rectcolor = 10,
percentage = nil,
}
function ProgressWidget:getSize()
return { w = self.width, h = self.height }
end
function ProgressWidget:paintTo(bb, x, y)
local my_size = self:getSize()
bb:paintRoundedRect(x, y, my_size.w, my_size.h, self.bgcolor, self.radius)
bb:paintBorder(x, y, my_size.w, my_size.h,
self.bordersize, self.bordercolor, self.radius)
bb:paintRect(x+self.margin_h, y+self.margin_v+self.bordersize,
(my_size.w-2*self.margin_h)*self.percentage,
(my_size.h-2*(self.margin_v+self.bordersize)), self.rectcolor)
end
function ProgressWidget:setPercentage(percentage)
self.percentage = percentage
end

@ -0,0 +1,27 @@
require "ui/widget/base"
--[[
Dummy Widget that reserves horizontal space
--]]
HorizontalSpan = Widget:new{
width = 0,
}
function HorizontalSpan:getSize()
return {w = self.width, h = 0}
end
--[[
Dummy Widget that reserves vertical space
--]]
VerticalSpan = Widget:new{
width = 0,
}
function VerticalSpan:getSize()
return {w = 0, h = self.width}
end

@ -0,0 +1,180 @@
require "ui/widget/base"
require "ui/rendertext"
--[[
A TextWidget puts a string on a single line
--]]
TextWidget = Widget:new{
text = nil,
face = nil,
color = 15,
_bb = nil,
_length = 0,
_height = 0,
_maxlength = 1200,
}
--function TextWidget:_render()
--local h = self.face.size * 1.3
--self._bb = Blitbuffer.new(self._maxlength, h)
--self._length = renderUtf8Text(self._bb, 0, h*0.8, self.face, self.text, self.color)
--end
function TextWidget:getSize()
--if not self._bb then
--self:_render()
--end
--return { w = self._length, h = self._bb:getHeight() }
local tsize = sizeUtf8Text(0, Screen:getWidth(), self.face, self.text, true)
if not tsize then
return Geom:new{}
end
self._length = tsize.x
self._height = self.face.size * 1.5
return Geom:new{
w = self._length,
h = self._height,
}
end
function TextWidget:paintTo(bb, x, y)
--if not self._bb then
--self:_render()
--end
--bb:blitFrom(self._bb, x, y, 0, 0, self._length, self._bb:getHeight())
--@TODO Don't use kerning for monospaced fonts. (houqp)
renderUtf8Text(bb, x, y+self._height*0.7, self.face, self.text, true)
end
function TextWidget:free()
if self._bb then
self._bb:free()
self._bb = nil
end
end
--[[
A TextWidget that handles long text wrapping
--]]
TextBoxWidget = Widget:new{
text = nil,
face = nil,
color = 15,
width = 400, -- in pixels
line_height = 0.3, -- in em
v_list = nil,
_bb = nil,
_length = 0,
}
function TextBoxWidget:_wrapGreedyAlg(h_list)
local cur_line_width = 0
local space_w = sizeUtf8Text(0, Screen:getWidth(), self.face, " ", true).x
local cur_line = {}
local v_list = {}
for k,w in ipairs(h_list) do
cur_line_width = cur_line_width + w.width
if cur_line_width <= self.width then
cur_line_width = cur_line_width + space_w
table.insert(cur_line, w)
else
-- wrap to next line
table.insert(v_list, cur_line)
cur_line = {}
cur_line_width = w.width + space_w
table.insert(cur_line, w)
end
end
-- handle last line
table.insert(v_list, cur_line)
return v_list
end
function TextBoxWidget:_getVerticalList(alg)
-- build horizontal list
h_list = {}
for w in self.text:gmatch("%S+") do
word_box = {}
word_box.word = w
word_box.width = sizeUtf8Text(0, Screen:getWidth(), self.face, w, true).x
table.insert(h_list, word_box)
end
-- @TODO check alg here 25.04 2012 (houqp)
-- @TODO replace greedy algorithm with K&P algorithm 25.04 2012 (houqp)
return self:_wrapGreedyAlg(h_list)
end
function TextBoxWidget:_render()
self.v_list = self:_getVerticalList()
local v_list = self.v_list
local font_height = self.face.size
local line_height_px = self.line_height * font_height
local space_w = sizeUtf8Text(0, Screen:getWidth(), self.face, " ", true).x
local h = (font_height + line_height_px) * #v_list - line_height_px
self._bb = Blitbuffer.new(self.width, h)
local y = font_height
local pen_x = 0
for _,l in ipairs(v_list) do
pen_x = 0
for _,w in ipairs(l) do
--@TODO Don't use kerning for monospaced fonts. (houqp)
-- refert to cb25029dddc42693cc7aaefbe47e9bd3b7e1a750 in master tree
renderUtf8Text(self._bb, pen_x, y*0.8, self.face, w.word, true)
pen_x = pen_x + w.width + space_w
end
y = y + line_height_px + font_height
end
-- if text is shorter than one line, shrink to text's width
if #v_list == 1 then
self.width = pen_x
end
end
function TextBoxWidget:getSize()
if not self._bb then
self:_render()
end
return { w = self.width, h = self._bb:getHeight() }
end
function TextBoxWidget:paintTo(bb, x, y)
if not self._bb then
self:_render()
end
bb:blitFrom(self._bb, x, y, 0, 0, self.width, self._bb:getHeight())
end
function TextBoxWidget:free()
if self._bb then
self._bb:free()
self._bb = nil
end
end
--[[
FixedTextWidget
--]]
FixedTextWidget = TextWidget:new{}
function FixedTextWidget:getSize()
local tsize = sizeUtf8Text(0, Screen:getWidth(), self.face, self.text, true)
if not tsize then
return Geom:new{}
end
self._length = tsize.x
self._height = self.face.size
return Geom:new{
w = self._length,
h = self._height,
}
end
function FixedTextWidget:paintTo(bb, x, y)
renderUtf8Text(bb, x, y+self._height, self.face, self.text, true)
end

@ -0,0 +1,389 @@
require "ui/widget/container"
require "ui/widget/group"
require "ui/widget/line"
require "ui/widget/iconbutton"
--[[
TouchMenuItem widget
--]]
TouchMenuItem = InputContainer:new{
menu = nil,
vertical_align = "center",
item = nil,
dimen = nil,
face = Font:getFace("cfont", 22),
show_parent = nil,
}
function TouchMenuItem:init()
self.ges_events = {
TapSelect = {
GestureRange:new{
ges = "tap",
range = self.dimen,
},
doc = "Select Menu Item",
},
}
self.item_frame = FrameContainer:new{
width = self.dimen.w,
bordersize = 0,
color = 15,
HorizontalGroup:new {
align = "center",
HorizontalSpan:new{ width = 10 },
TextWidget:new{
text = self.item.text,
face = self.face,
},
},
}
self[1] = self.item_frame
end
function TouchMenuItem:onTapSelect(arg, ges)
self.item_frame.invert = true
UIManager:setDirty(self.show_parent, "partial")
UIManager:scheduleIn(0.5, function()
self.item_frame.invert = false
UIManager:setDirty(self.show_parent, "partial")
end)
self.menu:onMenuSelect(self.item)
return true
end
--[[
TouchMenuBar widget
--]]
TouchMenuBar = InputContainer:new{
height = scaleByDPI(70),
width = Screen:getWidth(),
icons = {},
-- touch menu that holds the bar, used for trigger repaint on icons
show_parent = nil,
menu = nil,
}
function TouchMenuBar:init()
self.show_parent = self.show_parent or self
self.dimen = Geom:new{
w = self.width,
h = self.height,
}
self.bar_icon_group = HorizontalGroup:new{}
local icon_sep = LineWidget:new{
dimen = Geom:new{
w = scaleByDPI(2),
h = self.height,
}
}
local icon_span = HorizontalSpan:new{ width = scaleByDPI(20) }
-- build up image widget for menu icon bar
self.icon_widgets = {}
-- the start_seg for first icon_widget should be 0
-- we asign negative here to offset it in the loop
start_seg = -icon_sep:getSize().w
end_seg = start_seg
for k, v in ipairs(self.icons) do
local ib = IconButton:new{
show_parent = self.show_parent,
icon_file = v,
callback = nil,
}
table.insert(self.icon_widgets, HorizontalGroup:new{
icon_span,
ib,
icon_span,
})
-- we have to use local variable here for closure callback
local _start_seg = end_seg + icon_sep:getSize().w
local _end_seg = _start_seg + self.icon_widgets[k]:getSize().w
if k == 1 then
self.bar_sep = LineWidget:new{
dimen = Geom:new{
w = self.width,
h = scaleByDPI(2),
},
empty_segments = {
{
s = _start_seg, e = _end_seg
}
},
}
end
ib.callback = function()
self.bar_sep.empty_segments = {
{
s = _start_seg, e = _end_seg
}
}
self.menu:switchMenuTab(k)
end
table.insert(self.bar_icon_group, self.icon_widgets[k])
table.insert(self.bar_icon_group, icon_sep)
start_seg = _start_seg
end_seg = _end_seg
end
self[1] = FrameContainer:new{
bordersize = 0,
padding = 0,
VerticalGroup:new{
align = "left",
-- bar icons
self.bar_icon_group,
-- separate line
self.bar_sep
},
}
end
--[[
TouchMenu widget
--]]
TouchMenu = InputContainer:new{
tab_item_table = {},
-- for returnning in multi-level menus
item_table_stack = nil,
item_table = nil,
item_height = scaleByDPI(50),
bordersize = scaleByDPI(2),
padding = scaleByDPI(5),
width = Screen:getWidth(),
height = nil,
page = 1,
max_per_page = 10,
-- for UIManager:setDirty
show_parent = nil,
cur_tab = -1,
close_callback = nil,
}
function TouchMenu:init()
self.show_parent = self.show_parent or self
if not self.close_callback then
self.close_callback = function()
UIManager:close(self.show_parent)
end
end
self.ges_events.TapCloseAllMenus = {
GestureRange:new{
ges = "tap",
range = Geom:new{
x = 0, y = 0,
w = Screen:getWidth(),
h = Screen:getHeight(),
}
}
}
self.ges_events.Swipe = {
GestureRange:new{
ges = "swipe",
range = self.dimen,
}
}
local icons = {}
for _,v in ipairs(self.tab_item_table) do
table.insert(icons, v.icon)
end
self.bar = TouchMenuBar:new{
width = self.width - self.padding * 2 - self.bordersize * 2,
icons = icons,
show_parent = self.show_parent,
menu = self,
}
self.item_group = VerticalGroup:new{
align = "left",
}
self.footer_page = TextWidget:new{
face = Font:getFace("ffont", 20),
text = "",
}
self.footer = HorizontalGroup:new{
IconButton:new{
invert = true,
icon_file = "resources/icons/appbar.chevron.up.png",
show_parent = self.show_parent,
callback = function()
self:backToUpperMenu()
end,
},
self.footer_page,
}
self[1] = FrameContainer:new{
padding = self.padding,
bordersize = self.bordersize,
background = 0,
-- menubar and footer will be inserted in
-- item_group in updateItems
self.item_group,
}
self:switchMenuTab(1)
self:updateItems()
end
function TouchMenu:_recalculateDimen()
self.dimen.w = self.width
-- if height not given, dynamically calculate it
if not self.height then
self.dimen.h = (#self.item_table + 2) * self.item_height
+ self.bar:getSize().h
else
self.dimen.h = self.height
end
if self.dimen.h > Screen:getHeight() then
self.dimen.h = Screen:getHeight()
end
self.perpage = math.floor(self.dimen.h / self.item_height) - 2
if self.perpage > self.max_per_page then
self.perpage = self.max_per_page
end
self.page_num = math.ceil(#self.item_table / self.perpage)
end
function TouchMenu:updateItems()
self:_recalculateDimen()
self.item_group:clear()
table.insert(self.item_group, self.bar)
local item_width = self.dimen.w - self.padding*2 - self.bordersize*2
for c = 1, self.perpage do
-- calculate index in item_table
local i = (self.page - 1) * self.perpage + c
if i <= #self.item_table then
local item_tmp = TouchMenuItem:new{
item = self.item_table[i],
menu = self,
dimen = Geom:new{
w = item_width,
h = self.item_height,
},
show_parent = self.show_parent,
}
table.insert(self.item_group, item_tmp)
-- insert split line
if c ~= self.perpage then
table.insert(self.item_group, HorizontalGroup:new{
-- pad with spacing
HorizontalSpan:new{width = scaleByDPI(10)},
LineWidget:new{
style = "dashed",
dimen = Geom:new{
w = item_width - 20,
h = 1,
}
}
})
end
else
-- item not enough to fill the whole page, break out of loop
--table.insert(self.item_group,
--VerticalSpan:new{
--width = self.item_height
--})
--break
end -- if i <= self.items
end -- for c=1, self.perpage
table.insert(self.item_group, VerticalSpan:new{width = scaleByDPI(2)})
table.insert(self.item_group, self.footer)
self.footer_page.text = "Page "..self.page.."/"..self.page_num
-- FIXME: this is a dirty hack to clear previous menus
-- refert to issue #664
UIManager.repaint_all = true
end
function TouchMenu:switchMenuTab(tab_num)
if self.cur_tab ~= tab_num then
-- it's like getting a new menu everytime we switch tab!
self.page = 1
-- clear item table stack
self.item_table_stack = {}
self.cur_tab = tab_num
self.item_table = self.tab_item_table[tab_num]
self:updateItems()
end
end
function TouchMenu:backToUpperMenu()
if #self.item_table_stack ~= 0 then
self.item_table = table.remove(self.item_table_stack)
self:updateItems()
end
end
function TouchMenu:closeMenu()
self.close_callback()
end
function TouchMenu:onNextPage()
if self.page < self.page_num then
self.page = self.page + 1
self:updateItems()
end
return true
end
function TouchMenu:onPrevPage()
if self.page > 1 then
self.page = self.page - 1
self:updateItems()
end
return true
end
function TouchMenu:onSwipe(arg, ges_ev)
if ges_ev.direction == "left" then
self:onNextPage()
elseif ges_ev.direction == "right" then
self:onPrevPage()
end
end
function TouchMenu:onMenuSelect(item)
if item.sub_item_table == nil then
if item.callback then
-- put stuff in scheduler so we can See
-- the effect of inverted menu item
UIManager:scheduleIn(0.1, function()
self:closeMenu()
item.callback()
end)
end
else
table.insert(self.item_table_stack, self.item_table)
self.item_table = item.sub_item_table
self:updateItems()
end
return true
end
function TouchMenu:onTapCloseAllMenus(arg, ges_ev)
if ges_ev.pos:notIntersectWith(self.dimen) then
self:closeMenu()
return true
end
end

@ -20,6 +20,9 @@ if test "$1" == "--framework_stop"; then
/etc/init.d/framework stop
fi
# dismiss chrome bar
lipc-set-prop com.lab126.pillow disableEnablePillow disable
# stop cvm
#killall -stop cvm
@ -33,3 +36,7 @@ fi
# always try to continue cvm
killall -cont cvm || /etc/init.d/framework start
# display chrome bar
lipc-set-prop com.lab126.pillow disableEnablePillow enable

@ -1,17 +1,20 @@
#!./kpdfview
package.path = "./frontend/?.lua"
require "ui/ui"
require "ui/uimanager"
require "ui/widget/filechooser"
require "ui/widget/infomessage"
require "ui/readerui"
require "ui/filechooser"
require "ui/infomessage"
require "ui/button"
require "document/document"
require "settings"
require "dbg"
HomeMenu = InputContainer:new{
item_table = {},
key_events = {
TapShowMenu = { {"Home"}, doc = "Show Home Menu"},
},
ges_events = {
TapShowMenu = {
GestureRange:new{
@ -26,19 +29,57 @@ HomeMenu = InputContainer:new{
},
}
function exitReader()
G_reader_settings:close()
input.closeAll()
if util.isEmulated() ==0 then
if Device:isKindle3() or (Device:getModel() == "KindleDXG") then
-- send double menu key press events to trigger screen refresh
os.execute("echo 'send 139' > /proc/keypad;echo 'send 139' > /proc/keypad")
end
end
end
function HomeMenu:setUpdateItemTable()
function readHistDir(order_arg, re)
local pipe_out = io.popen("ls "..order_arg.." -1 ./history")
for f in pipe_out:lines() do
table.insert(re, {
dir = DocSettings:getPathFromHistory(f),
name = DocSettings:getNameFromHistory(f),
})
end
end
local hist_sub_item_table = {}
local last_files = {}
readHistDir("-c", last_files)
for _,v in pairs(last_files) do
table.insert(hist_sub_item_table, {
text = v.name,
callback = function()
showReader(v.dir .. "/" .. v.name)
end
})
end
table.insert(self.item_table, {
text = "Last documents",
sub_item_table = hist_sub_item_table,
})
table.insert(self.item_table, {
text = "Exit",
callback = function()
os.exit(0)
exitReader()
end
})
end
function HomeMenu:onTapShowMenu()
if #self.item_table == 0 then
self:setUpdateItemTable()
end
self.item_table = {}
self:setUpdateItemTable()
local home_menu = Menu:new{
title = "Home menu",
@ -51,7 +92,7 @@ function HomeMenu:onTapShowMenu()
dimen = Screen:getSize(),
home_menu,
}
home_menu.close_callback = function ()
home_menu.close_callback = function ()
UIManager:close(menu_container)
end
@ -68,6 +109,7 @@ function showReader(file, pass)
return
end
G_reader_settings:saveSetting("lastfile", file)
local reader = ReaderUI:new{
dialog = readerwindow,
dimen = Screen:getSize(),
@ -92,7 +134,7 @@ function showHomePage(path)
end
return true
end,
file_filter = function(filename)
file_filter = function(filename)
if DocumentRegistry:getProvider(filename) then
return true
end
@ -148,11 +190,8 @@ end
local argidx = 1
if ARGV[1] == "-d" then
argidx = argidx + 1
G_debug_mode = true
os.execute("echo > ev.log")
-- create ev log file
G_ev_log = io.open("ev.log", "w")
Dbg:turnOn()
argidx = argidx + 1
else
DEBUG = function() end
end
@ -175,7 +214,7 @@ local last_file = G_reader_settings:readSetting("lastfile")
--87712cf0e43fed624f8a9f610be42b1fe174b9fe
if ARGV[argidx] then
if ARGV[argidx] and ARGV[argidx] ~= "" then
if lfs.attributes(ARGV[argidx], "mode") == "directory" then
showHomePage(ARGV[argidx])
elseif lfs.attributes(ARGV[argidx], "mode") == "file" then
@ -189,11 +228,4 @@ else
return showusage()
end
input.closeAll()
if util.isEmulated()==0 then
if Device:isKindle3() or (Device:getModel() == "KindleDXG") then
-- send double menu key press events to trigger screen refresh
os.execute("echo 'send 139' > /proc/keypad;echo 'send 139' > /proc/keypad")
end
end
exitReader()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 422 B

After

Width:  |  Height:  |  Size: 750 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 776 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 649 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" width="76" height="76" viewBox="0 0 76.00 76.00" enable-background="new 0 0 76.00 76.00" xml:space="preserve">
<path fill="#000000" fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" d="M 35.8724,37.6042L 39.0391,40.7708L 50.5182,51.8542L 40.2266,51.8542L 25.1849,37.6041L 40.2266,23.3542L 50.5182,23.3542L 39.0391,34.4375L 35.8724,37.6042 Z "/>
</svg>

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" width="76" height="76" viewBox="0 0 76.00 76.00" enable-background="new 0 0 76.00 76.00" xml:space="preserve">
<path fill="#000000" fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" d="M 37.8516,35.625L 34.6849,38.7917L 23.6016,50.2708L 23.6016,39.9792L 37.8516,24.9375L 52.1016,39.9792L 52.1016,50.2708L 41.0182,38.7917L 37.8516,35.625 Z "/>
</svg>

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" width="76" height="76" viewBox="0 0 76.00 76.00" enable-background="new 0 0 76.00 76.00" xml:space="preserve">
<path fill="#000000" fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" d="M 28,19L 8.60116,19C 7.57586,20.5832 6.67496,22.2545 5.91232,24L 28,24L 28,56L 38,45.9167L 48,56L 48,24L 52,24L 52,70.0877C 53.7455,69.3251 55.4168,68.4242 57,67.3988L 57,19L 48,19L 48,16L 28,16L 28,19 Z M 24,30L 3.91849,30C 3.5383,31.626 3.27147,33.2956 3.12678,35L 24,35L 24,30 Z M 24,40L 3.05619,40C 3.15208,41.702 3.36958,43.3715 3.70014,45L 24,45L 24,40 Z M 24,55L 24,50L 5.11131,50C 5.74451,51.735 6.51108,53.4058 7.39861,55L 24,55 Z M 46,60L 10.7772,60C 12.256,61.8276 13.9151,63.5033 15.7274,65L 46,65L 46,60 Z "/>
</svg>

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" width="76" height="76" viewBox="0 0 76.00 76.00" enable-background="new 0 0 76.00 76.00" xml:space="preserve">
<path fill="#000000" fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" d="M 20,17L 44.25,17L 56,28.75L 56,59L 20,59L 20,17 Z M 24,21L 24,55L 52,55L 52,33L 40,33L 40,21L 24,21 Z M 44,22.25L 44,29L 50.75,29L 44,22.25 Z M 26,23L 38,23L 38,28L 26,28L 26,23 Z M 26,30L 38,30L 38,33L 26,33L 26,30 Z M 26,35L 50,35L 50,38L 26,38L 26,35 Z M 26,40L 50,40L 50,43L 26,43L 26,40 Z M 26,45L 50,45L 50,48L 26,48L 26,45 Z M 26,50L 50,50L 50,53L 26,53L 26,50 Z "/>
</svg>

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" width="76" height="76" viewBox="0 0 76.00 76.00" enable-background="new 0 0 76.00 76.00" xml:space="preserve">
<path fill="#000000" fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" d="M 38,19C 48.4934,19 57,27.5066 57,38C 57,48.4934 48.4934,57 38,57C 27.5066,57 19,48.4934 19,38C 19,27.5066 27.5066,19 38,19 Z M 38,22.9583C 30.2275,22.9583 23.8319,28.8536 23.0407,36.4167L 30.2417,36.4167C 30.9752,32.8031 34.17,30.0833 38,30.0833C 41.83,30.0833 45.0248,32.8031 45.7583,36.4167L 52.9593,36.4167C 52.1681,28.8536 45.7725,22.9583 38,22.9583 Z M 23.0407,39.5833C 23.8319,47.1464 30.2275,53.0417 38,53.0417C 45.7725,53.0417 52.1681,47.1464 52.9593,39.5833L 45.7583,39.5834C 45.0248,43.1969 41.83,45.9167 38,45.9167C 34.17,45.9167 30.9752,43.1969 30.2417,39.5834L 23.0407,39.5833 Z M 38,33.25C 35.3766,33.25 33.25,35.3767 33.25,38C 33.25,40.6234 35.3766,42.75 38,42.75C 40.6233,42.75 42.75,40.6234 42.75,38C 42.75,35.3767 40.6233,33.25 38,33.25 Z M 38,35.625C 39.3117,35.625 40.375,36.6883 40.375,38C 40.375,39.3117 39.3117,40.375 38,40.375C 36.6883,40.375 35.625,39.3117 35.625,38C 35.625,36.6883 36.6883,35.625 38,35.625 Z "/>
</svg>

@ -1,13 +1,18 @@
print(package.path)
package.path = "./frontend/?.lua"
require "ui/widget"
require "ui/ui"
require "ui/readerui"
require "ui/menu"
require "ui/infomessage"
require "ui/confirmbox"
require "ui/uimanager"
require "ui/widget/menu"
require "ui/widget/infomessage"
require "ui/widget/confirmbox"
require "ui/widget/touchmenu"
require "document/document"
require "ui/readerui"
require "dbg"
-----------------------------------------------------
-- widget that paints the grid on the background
-----------------------------------------------------
TestGrid = Widget:new{}
function TestGrid:paintTo(bb)
@ -25,7 +30,9 @@ function TestGrid:paintTo(bb)
end
end
-----------------------------------------------------
-- we create a widget that paints a background:
-----------------------------------------------------
Background = InputContainer:new{
is_always_active = true, -- receive events when other dialogs are active
key_events = {
@ -37,7 +44,13 @@ Background = InputContainer:new{
FrameContainer:new{
background = 3,
bordersize = 0,
dimen = Screen:getSize()
dimen = Screen:getSize(),
Widget:new{
dimen = {
w = Screen:getWidth(),
h = Screen:getHeight(),
}
},
}
}
@ -64,7 +77,9 @@ end
-----------------------------------------------------
-- example widget: a clock
-----------------------------------------------------
Clock = FrameContainer:new{
background = 0,
bordersize = 1,
@ -96,6 +111,9 @@ function Clock:getTextWidget()
}
end
-----------------------------------------------------
-- a confirmbox box widget
-----------------------------------------------------
Quiz = ConfirmBox:new{
text = "Tell me the truth, isn't it COOL?!",
width = 300,
@ -108,6 +126,9 @@ Quiz = ConfirmBox:new{
end,
}
-----------------------------------------------------
-- a menu widget
-----------------------------------------------------
menu_items = {
{text = "item1"},
{text = "item2"},
@ -136,6 +157,9 @@ M = Menu:new{
}
-----------------------------------------------------
-- a reader view widget
-----------------------------------------------------
readerwindow = CenterContainer:new{
dimen = Screen:getSize(),
FrameContainer:new{
@ -151,12 +175,86 @@ reader = ReaderUI:new{
}
readerwindow[1][1] = reader
touch_menu = TouchMenu:new{
title = "Document menu",
tab_item_table = {
{
icon = "resources/icons/appbar.pokeball.png",
{
text = "item1",
callback = function()
end,
},
{
text = "item2",
callback = function()
end,
},
{
text = "item3",
callback = function()
end,
},
{
text = "item4",
callback = function()
end,
},
{
text = "item5",
callback = function()
end,
},
{
text = "item6",
callback = function()
end,
},
{
text = "item7",
callback = function()
end,
},
{
text = "item8",
callback = function()
end,
},
{
text = "item9",
callback = function()
end,
},
},
{
icon = "resources/icons/appbar.page.corner.bookmark.png",
{
text = "item10",
callback = function()
end,
},
{
text = "item11",
callback = function()
end,
},
}
},
}
-----------------------------------------------------------------------
-- you may want to uncomment following show calls to see the changes
-----------------------------------------------------------------------
UIManager:show(Background:new())
UIManager:show(TestGrid)
UIManager:show(Clock:new())
UIManager:show(M)
UIManager:show(Quiz)
UIManager:show(readerwindow)
--UIManager:show(Clock:new())
--UIManager:show(M)
--UIManager:show(Quiz)
--UIManager:show(readerwindow)
UIManager:show(touch_menu)
UIManager:run()

Loading…
Cancel
Save