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 kpdfview
slider_watcher slider_watcher
*.o *.o
tags
kindlepdfviewer-*.zip kindlepdfviewer-*.zip
@ -46,3 +47,4 @@ popen-noshell/popen_noshell_examples.c
popen-noshell/popen_noshell_tests.c popen-noshell/popen_noshell_tests.c
popen-noshell/popen_noshell_tests.cpp 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 if not engine_initilized then
-- initialize cache -- initialize cache
cre.initCache(1024*1024*64) cre.initCache(1024*1024*64)
-- we need to initialize the CRE font list -- we need to initialize the CRE font list
local fonts = Font:getFontList() local fonts = Font:getFontList()
for _k, _v in ipairs(fonts) do for _k, _v in ipairs(fonts) do

@ -80,6 +80,14 @@ function DjvuDocument:renderPage(pageno, rect, zoom, rotation, gamma, render_mod
end end
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) function DjvuDocument:drawPage(target, x, y, rect, pageno, zoom, rotation, gamma, render_mode)
if self.configurable.text_wrap == 1 then if self.configurable.text_wrap == 1 then
self.koptinterface:drawPage(self, target, x, y, rect, pageno, zoom, rotation, render_mode) 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 -- get reflow context
function KoptInterface:getKOPTContext(doc, pageno, bbox) 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 kc = KOPTContext.new()
local screen_size = Screen:getSize() local screen_size = Screen:getSize()
kc:setTrim(doc.configurable.trim_page) kc:setTrim(doc.configurable.trim_page)
@ -222,6 +250,7 @@ function KoptInterface:getKOPTContext(doc, pageno, bbox)
end end
function KoptInterface:setTrimPage(doc, pageno) function KoptInterface:setTrimPage(doc, pageno)
if doc.configurable.trim_page == 0 then return end
local page_dimens = doc:getNativePageDimensions(pageno) local page_dimens = doc:getNativePageDimensions(pageno)
--DEBUG("original page dimens", page_dimens) --DEBUG("original page dimens", page_dimens)
local orig_bbox = doc:getUsedBBox(pageno) 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 return doc.file.."|"..pageno.."|"..doc.configurable:hash("|").."|"..bbox_hash.."|"..screen_size_hash
end 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 -- calculates page dimensions
function KoptInterface:getPageDimensions(doc, pageno, zoom, rotation) function KoptInterface:getPageDimensions(doc, pageno, zoom, rotation)
self:setTrimPage(doc, pageno) self:setTrimPage(doc, pageno)
@ -251,7 +296,12 @@ function KoptInterface:getPageDimensions(doc, pageno, zoom, rotation)
local kc = self:getKOPTContext(doc, pageno, bbox) local kc = self:getKOPTContext(doc, pageno, bbox)
local page = doc._document:openPage(pageno) local page = doc._document:openPage(pageno)
-- reflow page -- reflow page
--local secs, usecs = util.gettime()
page:reflow(kc, 0) 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() page:close()
local fullwidth, fullheight = kc:getPageDim() local fullwidth, fullheight = kc:getPageDim()
DEBUG("page::reflowPage:", "fullwidth:", fullwidth, "fullheight:", fullheight) DEBUG("page::reflowPage:", "fullwidth:", fullwidth, "fullheight:", fullheight)
@ -261,11 +311,37 @@ function KoptInterface:getPageDimensions(doc, pageno, zoom, rotation)
return page_size return page_size
end end
--DEBUG("Found cached koptcontex on page", pageno, cached) --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 } local page_size = Geom:new{ w = fullwidth, h = fullheight }
return page_size return page_size
end 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) function KoptInterface:renderPage(doc, pageno, rect, zoom, rotation, render_mode)
self:setTrimPage(doc, pageno) self:setTrimPage(doc, pageno)
doc.render_mode = render_mode doc.render_mode = render_mode
@ -291,30 +367,33 @@ function KoptInterface:renderPage(doc, pageno, rect, zoom, rotation, render_mode
end end
local cached = Cache:check(hash) 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 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() page:close()
--DEBUG("cached hash", hash) Cache:insert(hash, CacheItem:new{ kctx = kc })
if not Cache:check(hash) then
Cache:insert(hash, tile)
end
return tile
end end
DEBUG("Error: cannot render page before reflowing.")
end end
function KoptInterface:drawPage(doc, target, x, y, rect, pageno, zoom, rotation, render_mode) 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
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) function PdfDocument:drawPage(target, x, y, rect, pageno, zoom, rotation, gamma, render_mode)
if self.configurable.text_wrap == 1 then if self.configurable.text_wrap == 1 then
self.koptinterface:drawPage(self, target, x, y, rect, pageno, zoom, rotation, render_mode) 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" return "./history/["..basename:gsub("/","#").."] "..filename..".lua"
end 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) function DocSettings:open(docfile)
local conf_path = nil local conf_path = nil
if docfile == ".reader" then if docfile == ".reader" then
@ -49,26 +64,22 @@ function DocSettings:delSetting(key)
self.data[key] = nil self.data[key] = nil
end end
function dump(data) function dump(data, max_lv)
local out = {} local out = {}
DocSettings:_serialize(data, out, 0) DocSettings:_serialize(data, out, 0, max_lv)
return table.concat(out) return table.concat(out)
end end
function DEBUG(...) -- simple serialization function, won't do uservalues, functions, loops
local line = "" function DocSettings:_serialize(what, outt, indent, max_lv)
for i,v in ipairs({...}) do if not max_lv then
if type(v) == "table" then max_lv = math.huge
line = line .. " " .. dump(v) end
else
line = line .. " " .. tostring(v) if indent > max_lv then
end return
end end
print("#"..line)
end
-- simple serialization function, won't do uservalues, functions, loops
function DocSettings:_serialize(what, outt, indent)
if type(what) == "table" then if type(what) == "table" then
local didrun = false local didrun = false
table.insert(outt, "{") table.insert(outt, "{")
@ -79,9 +90,9 @@ function DocSettings:_serialize(what, outt, indent)
table.insert(outt, "\n") table.insert(outt, "\n")
table.insert(outt, string.rep("\t", indent+1)) table.insert(outt, string.rep("\t", indent+1))
table.insert(outt, "[") table.insert(outt, "[")
self:_serialize(k, outt, indent+1) self:_serialize(k, outt, indent+1, max_lv)
table.insert(outt, "] = ") table.insert(outt, "] = ")
self:_serialize(v, outt, indent+1) self:_serialize(v, outt, indent+1, max_lv)
didrun = true didrun = true
end end
if didrun then if didrun then

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

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

@ -2,7 +2,6 @@ require "ui/event"
require "ui/device" require "ui/device"
require "ui/time" require "ui/time"
require "ui/gesturedetector" require "ui/gesturedetector"
require "settings"
-- constants from <linux/input.h> -- constants from <linux/input.h>
EV_SYN = 0 EV_SYN = 0
@ -112,7 +111,7 @@ function Key:match(sequence)
return false return false
end end
end end
return true return true
end end
@ -121,6 +120,7 @@ an interface to get input events
]] ]]
Input = { Input = {
event_map = {}, event_map = {},
modifiers = {},
rotation_map = { rotation_map = {
[0] = {}, [0] = {},
[1] = { Up = "Right", Right = "Down", Down = "Left", Left = "Up" }, [1] = { Up = "Right", Right = "Down", Down = "Left", Left = "Up" },
@ -287,6 +287,8 @@ function Input:init()
elseif dev_mod == "KindleTouch" then elseif dev_mod == "KindleTouch" then
input.open("/dev/input/event2") -- Home button input.open("/dev/input/event2") -- Home button
input.open("/dev/input/event3") -- touchscreen input.open("/dev/input/event3") -- touchscreen
-- KT does have one key!
self.event_map[102] = "Home"
-- update event hook -- update event hook
function Input:eventAdjustHook(ev) function Input:eventAdjustHook(ev)
if ev.type == EV_ABS then if ev.type == EV_ABS then
@ -334,7 +336,7 @@ end
function Input:setTimeout(cb, tv_out) function Input:setTimeout(cb, tv_out)
local item = { local item = {
callback = cb, callback = cb,
deadline = tv_out, deadline = tv_out,
} }
for k,v in ipairs(self.timer_callbacks) do for k,v in ipairs(self.timer_callbacks) do
@ -443,7 +445,7 @@ function Input:handleTouchEv(ev)
local touch_ges = GestureDetector:feedEvent(self.MTSlots) local touch_ges = GestureDetector:feedEvent(self.MTSlots)
self.MTSlots = {} self.MTSlots = {}
if touch_ges then if touch_ges then
return Event:new("Gesture", return Event:new("Gesture",
GestureDetector:adjustGesCoordinate(touch_ges) GestureDetector:adjustGesCoordinate(touch_ges)
) )
end end
@ -451,7 +453,7 @@ function Input:handleTouchEv(ev)
elseif ev.type == EV_ABS then elseif ev.type == EV_ABS then
if #self.MTSlots == 0 then if #self.MTSlots == 0 then
table.insert(self.MTSlots, self:getMtSlot(self.cur_slot)) table.insert(self.MTSlots, self:getMtSlot(self.cur_slot))
end end
if ev.code == ABS_MT_SLOT then if ev.code == ABS_MT_SLOT then
if self.cur_slot ~= ev.value then if self.cur_slot ~= ev.value then
table.insert(self.MTSlots, self:getMtSlot(ev.value)) 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 -- wrapper for input.waitForEvents that will retry for some cases
local ok, ev local ok, ev
local wait_deadline = TimeVal:now() + TimeVal:new{ local wait_deadline = TimeVal:now() + TimeVal:new{
sec = timeout_s, sec = timeout_s,
usec = timeout_us usec = timeout_us
} }
while true do while true do
@ -490,7 +492,7 @@ function Input:waitEvent(timeout_us, timeout_s)
-- Do we really need to clear all setTimeout after -- Do we really need to clear all setTimeout after
-- decided a gesture? FIXME -- decided a gesture? FIXME
Input.timer_callbacks = {} Input.timer_callbacks = {}
return Event:new("Gesture", return Event:new("Gesture",
GestureDetector:adjustGesCoordinate(touch_ges) GestureDetector:adjustGesCoordinate(touch_ges)
) )
end -- EOF if touch_ges end -- EOF if touch_ges
@ -520,13 +522,10 @@ function Input:waitEvent(timeout_us, timeout_s)
end end
if ok and ev then if ok and ev then
ev = self:eventAdjustHook(ev) if Dbg.is_on and ev then
if G_debug_mode then Dbg:logEv(ev)
local log = ev.type.."|"..ev.code.."|"
..ev.value.."|"..ev.time.sec.."|"..ev.time.usec.."\n"
G_ev_log:write(log)
G_ev_log:flush()
end end
ev = self:eventAdjustHook(ev)
if ev.type == EV_KEY then if ev.type == EV_KEY then
return self:handleKeyBoardEv(ev) return self:handleKeyBoardEv(ev)
elseif ev.type == EV_ABS or ev.type == EV_SYN then 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{ ReaderBookmark = InputContainer:new{
bm_menu_title = "Bookmarks", bm_menu_title = "Bookmarks",
@ -89,7 +89,7 @@ function ReaderBookmark:onShowBookmark()
local bm_menu = Menu:new{ local bm_menu = Menu:new{
title = "Bookmarks", title = "Bookmarks",
item_table = self.bookmarks, item_table = self.bookmarks,
width = Screen:getWidth()-20, width = Screen:getWidth()-20,
height = Screen:getHeight(), height = Screen:getHeight(),
} }
-- buid up menu widget method as closure -- buid up menu widget method as closure
@ -111,7 +111,7 @@ function ReaderBookmark:onShowBookmark()
dimen = Screen:getSize(), dimen = Screen:getSize(),
bm_menu, bm_menu,
} }
bm_menu.close_callback = function() bm_menu.close_callback = function()
UIManager:close(menu_container) UIManager:close(menu_container)
end end
@ -119,9 +119,9 @@ function ReaderBookmark:onShowBookmark()
return true return true
end end
function ReaderBookmark:addToMainMenu(item_table) function ReaderBookmark:addToMainMenu(tab_item_table)
-- insert table to main reader menu -- insert table to main reader menu
table.insert(item_table, { table.insert(tab_item_table.navi, {
text = self.bm_menu_title, text = self.bm_menu_title,
callback = function() callback = function()
self:onShowBookmark() self:onShowBookmark()
@ -154,7 +154,7 @@ function ReaderBookmark:addBookmark(pn_or_xp)
return self:isBookmarkInSequence(a, b) return self:isBookmarkInSequence(a, b)
end) end)
return true return true
end end
function ReaderBookmark:isBookmarkInSequence(a, b) function ReaderBookmark:isBookmarkInSequence(a, b)
return a.page < b.page return a.page < b.page

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

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

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

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

@ -1,3 +1,4 @@
require "ui/widget/progress"
ReaderFooter = InputContainer:new{ ReaderFooter = InputContainer:new{
pageno = nil, 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{ ReaderMenu = InputContainer:new{
_name = "ReaderMenu", _name = "ReaderMenu",
item_table = {}, tab_item_table = nil,
registered_widgets = {}, registered_widgets = {},
} }
function ReaderMenu:init() 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 = {} self.registered_widgets = {}
if Device:hasKeyboard() then if Device:hasKeyboard() then
@ -32,34 +45,14 @@ function ReaderMenu:initGesListener()
end end
function ReaderMenu:setUpdateItemTable() 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 for _, widget in pairs(self.registered_widgets) do
widget:addToMainMenu(self.item_table) widget:addToMainMenu(self.tab_item_table)
end end
table.insert(self.item_table, { table.insert(self.tab_item_table.main, {
text = "Return to file manager", text = "Return to file manager",
callback = function() callback = function()
self.ui:handleEvent(Event:new("RestoreScreenMode", self.ui:handleEvent(Event:new("RestoreScreenMode",
G_reader_settings:readSetting("screen_mode") or "portrait")) G_reader_settings:readSetting("screen_mode") or "portrait"))
UIManager:close(self.menu_container) UIManager:close(self.menu_container)
self.ui:onClose() self.ui:onClose()
@ -68,27 +61,48 @@ function ReaderMenu:setUpdateItemTable()
end end
function ReaderMenu:onShowMenu() function ReaderMenu:onShowMenu()
if #self.item_table == 0 then if #self.tab_item_table.main == 0 then
self:setUpdateItemTable() self:setUpdateItemTable()
end end
local main_menu = Menu:new{
title = "Document menu",
item_table = self.item_table,
width = Screen:getWidth() - 100,
}
local menu_container = CenterContainer:new{ local menu_container = CenterContainer:new{
name = "haha",
ignore = "height", ignore = "height",
dimen = Screen:getSize(), 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) UIManager:close(menu_container)
end end
menu_container[1] = main_menu
-- maintain a reference to menu_container -- maintain a reference to menu_container
self.menu_container = menu_container self.menu_container = menu_container
UIManager:show(menu_container) UIManager:show(menu_container)
return true return true

@ -6,38 +6,38 @@ ReaderPaging = InputContainer:new{
visible_area = nil, visible_area = nil,
page_area = nil, page_area = nil,
show_overlap_enable = true, show_overlap_enable = true,
overlap = 20 * Screen:getDPI()/167, overlap = scaleByDPI(20),
} }
function ReaderPaging:init() function ReaderPaging:init()
if Device:hasKeyboard() then if Device:hasKeyboard() then
self.key_events = { self.key_events = {
GotoNextPage = { GotoNextPage = {
{Input.group.PgFwd}, doc = "go to next page", {Input.group.PgFwd}, doc = "go to next page",
event = "GotoPageRel", args = 1 }, event = "GotoPageRel", args = 1 },
GotoPrevPage = { GotoPrevPage = {
{Input.group.PgBack}, doc = "go to previous page", {Input.group.PgBack}, doc = "go to previous page",
event = "GotoPageRel", args = -1 }, event = "GotoPageRel", args = -1 },
GotoFirst = { GotoFirst = {
{"1"}, doc = "go to start", event = "GotoPercent", args = 0}, {"1"}, doc = "go to start", event = "GotoPercent", args = 0},
Goto11 = { Goto11 = {
{"2"}, doc = "go to 11%", event = "GotoPercent", args = 11}, {"2"}, doc = "go to 11%", event = "GotoPercent", args = 11},
Goto22 = { Goto22 = {
{"3"}, doc = "go to 22%", event = "GotoPercent", args = 22}, {"3"}, doc = "go to 22%", event = "GotoPercent", args = 22},
Goto33 = { Goto33 = {
{"4"}, doc = "go to 33%", event = "GotoPercent", args = 33}, {"4"}, doc = "go to 33%", event = "GotoPercent", args = 33},
Goto44 = { Goto44 = {
{"5"}, doc = "go to 44%", event = "GotoPercent", args = 44}, {"5"}, doc = "go to 44%", event = "GotoPercent", args = 44},
Goto55 = { Goto55 = {
{"6"}, doc = "go to 55%", event = "GotoPercent", args = 55}, {"6"}, doc = "go to 55%", event = "GotoPercent", args = 55},
Goto66 = { Goto66 = {
{"7"}, doc = "go to 66%", event = "GotoPercent", args = 66}, {"7"}, doc = "go to 66%", event = "GotoPercent", args = 66},
Goto77 = { Goto77 = {
{"8"}, doc = "go to 77%", event = "GotoPercent", args = 77}, {"8"}, doc = "go to 77%", event = "GotoPercent", args = 77},
Goto88 = { Goto88 = {
{"9"}, doc = "go to 88%", event = "GotoPercent", args = 88}, {"9"}, doc = "go to 88%", event = "GotoPercent", args = 88},
GotoLast = { GotoLast = {
{"0"}, doc = "go to end", event = "GotoPercent", args = 100}, {"0"}, doc = "go to end", event = "GotoPercent", args = 100},
} }
end end
@ -62,7 +62,7 @@ function ReaderPaging:initGesListener()
GestureRange:new{ GestureRange:new{
ges = "tap", ges = "tap",
range = Geom:new{ range = Geom:new{
x = 0, x = 0,
y = Screen:getHeight()/4, y = Screen:getHeight()/4,
w = Screen:getWidth()/4, w = Screen:getWidth()/4,
h = 5*Screen:getHeight()/8, h = 5*Screen:getHeight()/8,
@ -100,6 +100,16 @@ function ReaderPaging:initGesListener()
rate = 4.0, rate = 4.0,
} }
}, },
PanRelease = {
GestureRange:new{
ges = "pan_release",
range = Geom:new{
x = 0, y = 0,
w = Screen:getWidth(),
h = Screen:getHeight(),
},
}
},
} }
end end
@ -128,45 +138,69 @@ end
function ReaderPaging:onToggleFlipping() function ReaderPaging:onToggleFlipping()
self.view.flipping_visible = not self.view.flipping_visible 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") UIManager:setDirty(self.view.dialog, "partial")
end end
function ReaderPaging:onSwipe(arg, ges) function ReaderPaging:onSwipe(arg, ges)
if self.flipping_page == nil then if self.flipping_mode then
if ges.direction == "left" or ges.direction == "up" then self:flipping(self.flipping_page, ges)
self:onPagingRel(1) self:updateFlippingPage(self.current_page)
elseif ges.direction == "right" or ges.direction == "down" then elseif self.original_page then
self:onPagingRel(-1) self:gotoPage(self.original_page)
end self:updateOriginalPage(nil)
elseif self.flipping_page then elseif ges.direction == "left" or ges.direction == "up" then
self:gotoPage(self.flipping_page) self:onPagingRel(1)
elseif ges.direction == "right" or ges.direction == "down" then
self:onPagingRel(-1)
end end
return true return true
end end
function ReaderPaging:onPan(arg, ges) function ReaderPaging:onPan(arg, ges)
if self.flipping_page then if self.flipping_mode then
local read = self.flipping_page - 1 self:flipping(self.flipping_page, ges)
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")
end end
return true return true
end end
function ReaderPaging:onPanRelease(arg, ges)
if self.flipping_mode then
self:updateFlippingPage(self.current_page)
end
end
function ReaderPaging:onZoomModeUpdate(new_mode) function ReaderPaging:onZoomModeUpdate(new_mode)
-- we need to remember zoom mode to handle page turn event -- we need to remember zoom mode to handle page turn event
self.zoom_mode = new_mode 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} local visible_area = Geom:new{x = 0, y = 0}
visible_area.w, visible_area.h = blank_area.w, blank_area.h 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.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 -- shrink blank area by the height of visible area
blank_area.h = blank_area.h - visible_area.h blank_area.h = blank_area.h - visible_area.h
state.visible_area = visible_area 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.w, visible_area.h = blank_area.w, blank_area.h
visible_area.x = state.page_area.x visible_area.x = state.page_area.x
visible_area.y = state.visible_area.y + state.visible_area.h - visible_area.h 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 -- shrink blank area by the height of visible area
blank_area.h = blank_area.h - visible_area.h blank_area.h = blank_area.h - visible_area.h
state.visible_area = visible_area state.visible_area = visible_area
@ -317,6 +359,12 @@ function ReaderPaging:onScrollPageRel(diff)
x = 0, x = 0,
y = last_page_state.visible_area.h - self.overlap 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) local state = self:updateLastPageState(last_page_state, blank_area, offset)
--DEBUG("updated state", state) --DEBUG("updated state", state)
self.view.page_states = {} self.view.page_states = {}
@ -327,7 +375,8 @@ function ReaderPaging:onScrollPageRel(diff)
while blank_area.h > 0 do while blank_area.h > 0 do
blank_area.h = blank_area.h - self.view.page_gap.height blank_area.h = blank_area.h - self.view.page_gap.height
if blank_area.h > 0 then 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{}) local state = self:getNextPageState(blank_area, Geom:new{})
--DEBUG("new state", state) --DEBUG("new state", state)
table.insert(self.view.page_states, state) table.insert(self.view.page_states, state)
@ -340,6 +389,8 @@ function ReaderPaging:onScrollPageRel(diff)
x = 0, x = 0,
y = -first_page_state.visible_area.h + self.overlap 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) local state = self:updateFirstPageState(first_page_state, blank_area, offset)
--DEBUG("updated state", state) --DEBUG("updated state", state)
self.view.page_states = {} self.view.page_states = {}
@ -350,7 +401,8 @@ function ReaderPaging:onScrollPageRel(diff)
while blank_area.h > 0 do while blank_area.h > 0 do
blank_area.h = blank_area.h - self.view.page_gap.height blank_area.h = blank_area.h - self.view.page_gap.height
if blank_area.h > 0 then 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{}) local state = self:getPrevPageState(blank_area, Geom:new{})
--DEBUG("new state", state) --DEBUG("new state", state)
table.insert(self.view.page_states, 1, state) table.insert(self.view.page_states, 1, state)

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

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

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

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

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

@ -1,4 +1,3 @@
require "ui/ui"
require "ui/reader/readerview" require "ui/reader/readerview"
require "ui/reader/readerzooming" require "ui/reader/readerzooming"
require "ui/reader/readerpanning" require "ui/reader/readerpanning"
@ -15,6 +14,7 @@ require "ui/reader/readercropping"
require "ui/reader/readerkopt" require "ui/reader/readerkopt"
require "ui/reader/readercopt" require "ui/reader/readercopt"
require "ui/reader/readerscreenshot" require "ui/reader/readerscreenshot"
require "ui/reader/readerhinting"
--[[ --[[
This is an abstraction for a reader interface This is an abstraction for a reader interface
@ -132,6 +132,14 @@ function ReaderUI:init()
document = self.document, document = self.document,
} }
table.insert(self, cropper) 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 else
-- make sure we load document first before calling any callback -- make sure we load document first before calling any callback
table.insert(self.postInitCallback, function() table.insert(self.postInitCallback, function()

@ -18,7 +18,7 @@
--[[ --[[
Codes for rotation modes: 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 for landscape with bottom on the right side of screen, etc.
2 2
@ -26,8 +26,8 @@ Codes for rotation modes:
| +----------+ | | +----------+ |
| | | | | | | |
| | Freedom! | | | | Freedom! | |
| | | | | | | |
| | | | | | | |
3 | | | | 1 3 | | | | 1
| | | | | | | |
| | | | | | | |
@ -99,6 +99,15 @@ function Screen:getDPI()
return Device:getModel() == "KindlePaperWhite" and 212 or 167 return Device:getModel() == "KindlePaperWhite" and 212 or 167
end 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() function Screen:getPitch()
return self.fb:getPitch() return self.fb:getPitch()
end end

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

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

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

@ -1,6 +1,6 @@
require "ui/widget" require "ui/widget/container"
require "ui/focusmanager" require "ui/widget/focusmanager"
require "ui/button" require "ui/widget/button"
--[[ --[[
Widget that shows a message and OK/Cancel buttons Widget that shows a message and OK/Cancel buttons
@ -25,14 +25,22 @@ function ConfirmBox:init()
local ok_button = Button:new{ local ok_button = Button:new{
text = self.ok_text, text = self.ok_text,
callback = function()
self.ok_callback()
UIManager:close(self)
end,
} }
local cancel_button = Button:new{ local cancel_button = Button:new{
text = self.cancel_text, text = self.cancel_text,
preselect = true preselect = true,
callback = function()
self.cancel_callback()
UIManager:close(self)
end,
} }
self.layout = { { ok_button, cancel_button } } self.layout = { { ok_button, cancel_button } }
self.selected.x = 2 -- Cancel is default self.selected.x = 2 -- Cancel is default
self[1] = CenterContainer:new{ self[1] = CenterContainer:new{
dimen = Screen:getSize(), 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{ FileChooser = Menu:new{
height = Screen:getHeight(), 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/container"
require "ui/widget"
--[[ --[[
Widget that displays an informational message 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/widget/container"
require "ui/focusmanager" require "ui/widget/focusmanager"
require "ui/infomessage" require "ui/widget/infomessage"
require "ui/widget/text"
require "ui/widget/group"
require "ui/widget/span"
require "ui/font" require "ui/font"
--[[ --[[
Widget that displays a shortcut icon for menu item Widget that displays a shortcut icon for menu item
]] --]]
ItemShortCutIcon = WidgetContainer:new{ ItemShortCutIcon = WidgetContainer:new{
dimen = Geom:new{ w = 22, h = 22 }, dimen = Geom:new{ w = 22, h = 22 },
key = nil, key = nil,
@ -100,7 +103,7 @@ function MenuItem:init()
local shortcut_icon_dimen = Geom:new() local shortcut_icon_dimen = Geom:new()
if self.shortcut then if self.shortcut then
shortcut_icon_dimen.w = math.floor(self.dimen.h*4/5) 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 end
self.detail = self.text self.detail = self.text
@ -129,7 +132,7 @@ function MenuItem:init()
if Device:isTouchDevice() then if Device:isTouchDevice() then
else else
self.active_key_events.ShowItemDetail = { self.active_key_events.ShowItemDetail = {
{"Right"}, doc = "show item detail" {"Right"}, doc = "show item detail"
} }
end end
indicator = " >>" indicator = " >>"
@ -346,7 +349,7 @@ function Menu:init()
if self.is_enable_shortcut then if self.is_enable_shortcut then
self.key_events.SelectByShortCut = { {self.item_shortcuts} } self.key_events.SelectByShortCut = { {self.item_shortcuts} }
end end
self.key_events.Select = { self.key_events.Select = {
{"Press"}, doc = "select current menu item" {"Press"}, doc = "select current menu item"
} }
end end
@ -368,7 +371,7 @@ function Menu:updateItems(select_number)
for c = 1, self.perpage do for c = 1, self.perpage do
-- calculate index in item_table -- 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 if i <= #self.item_table then
local item_shortcut = nil local item_shortcut = nil
local shortcut_style = "square" local shortcut_style = "square"
@ -398,7 +401,7 @@ function Menu:updateItems(select_number)
table.insert(self.layout, {item_tmp}) table.insert(self.layout, {item_tmp})
else else
-- item not enough to fill the whole page, break out of loop -- item not enough to fill the whole page, break out of loop
table.insert(self.item_group, table.insert(self.item_group,
VerticalSpan:new{ VerticalSpan:new{
width = (self.item_dimen.h * (self.perpage - c + 1)) 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 if self.item_table[(self.page-1)*self.perpage + k] then
self:onMenuSelect(self.item_table[(self.page-1)*self.perpage + k]) self:onMenuSelect(self.item_table[(self.page-1)*self.perpage + k])
end end
break break
end end
end end
return true return true
@ -468,7 +471,7 @@ override this function to process the item selected in a different manner
]]-- ]]--
function Menu:onMenuSelect(item) function Menu:onMenuSelect(item)
if item.sub_item_table == nil then if item.sub_item_table == nil then
self.close_callback() self.close_callback()
self:onMenuChoice(item) self:onMenuChoice(item)
else else
-- save menu title for later resume -- save menu title for later resume

@ -1,5 +1,4 @@
require "ui/ui" require "ui/widget/container"
require "ui/widget"
--[[ --[[
Widget that displays a tiny notification on top of screen 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 /etc/init.d/framework stop
fi fi
# dismiss chrome bar
lipc-set-prop com.lab126.pillow disableEnablePillow disable
# stop cvm # stop cvm
#killall -stop cvm #killall -stop cvm
@ -33,3 +36,7 @@ fi
# always try to continue cvm # always try to continue cvm
killall -cont cvm || /etc/init.d/framework start killall -cont cvm || /etc/init.d/framework start
# display chrome bar
lipc-set-prop com.lab126.pillow disableEnablePillow enable

@ -1,17 +1,20 @@
#!./kpdfview #!./kpdfview
package.path = "./frontend/?.lua" package.path = "./frontend/?.lua"
require "ui/ui" require "ui/uimanager"
require "ui/widget/filechooser"
require "ui/widget/infomessage"
require "ui/readerui" require "ui/readerui"
require "ui/filechooser"
require "ui/infomessage"
require "ui/button"
require "document/document" require "document/document"
require "settings"
require "dbg"
HomeMenu = InputContainer:new{ HomeMenu = InputContainer:new{
item_table = {}, item_table = {},
key_events = {
TapShowMenu = { {"Home"}, doc = "Show Home Menu"},
},
ges_events = { ges_events = {
TapShowMenu = { TapShowMenu = {
GestureRange:new{ 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 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, { table.insert(self.item_table, {
text = "Exit", text = "Exit",
callback = function() callback = function()
os.exit(0) exitReader()
end end
}) })
end end
function HomeMenu:onTapShowMenu() function HomeMenu:onTapShowMenu()
if #self.item_table == 0 then self.item_table = {}
self:setUpdateItemTable() self:setUpdateItemTable()
end
local home_menu = Menu:new{ local home_menu = Menu:new{
title = "Home menu", title = "Home menu",
@ -51,7 +92,7 @@ function HomeMenu:onTapShowMenu()
dimen = Screen:getSize(), dimen = Screen:getSize(),
home_menu, home_menu,
} }
home_menu.close_callback = function () home_menu.close_callback = function ()
UIManager:close(menu_container) UIManager:close(menu_container)
end end
@ -68,6 +109,7 @@ function showReader(file, pass)
return return
end end
G_reader_settings:saveSetting("lastfile", file)
local reader = ReaderUI:new{ local reader = ReaderUI:new{
dialog = readerwindow, dialog = readerwindow,
dimen = Screen:getSize(), dimen = Screen:getSize(),
@ -92,7 +134,7 @@ function showHomePage(path)
end end
return true return true
end, end,
file_filter = function(filename) file_filter = function(filename)
if DocumentRegistry:getProvider(filename) then if DocumentRegistry:getProvider(filename) then
return true return true
end end
@ -148,11 +190,8 @@ end
local argidx = 1 local argidx = 1
if ARGV[1] == "-d" then if ARGV[1] == "-d" then
argidx = argidx + 1 Dbg:turnOn()
G_debug_mode = true argidx = argidx + 1
os.execute("echo > ev.log")
-- create ev log file
G_ev_log = io.open("ev.log", "w")
else else
DEBUG = function() end DEBUG = function() end
end end
@ -175,7 +214,7 @@ local last_file = G_reader_settings:readSetting("lastfile")
--87712cf0e43fed624f8a9f610be42b1fe174b9fe --87712cf0e43fed624f8a9f610be42b1fe174b9fe
if ARGV[argidx] then if ARGV[argidx] and ARGV[argidx] ~= "" then
if lfs.attributes(ARGV[argidx], "mode") == "directory" then if lfs.attributes(ARGV[argidx], "mode") == "directory" then
showHomePage(ARGV[argidx]) showHomePage(ARGV[argidx])
elseif lfs.attributes(ARGV[argidx], "mode") == "file" then elseif lfs.attributes(ARGV[argidx], "mode") == "file" then
@ -189,11 +228,4 @@ else
return showusage() return showusage()
end end
input.closeAll() exitReader()
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

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) print(package.path)
package.path = "./frontend/?.lua" package.path = "./frontend/?.lua"
require "ui/widget" require "ui/uimanager"
require "ui/ui" require "ui/widget/menu"
require "ui/readerui" require "ui/widget/infomessage"
require "ui/menu" require "ui/widget/confirmbox"
require "ui/infomessage" require "ui/widget/touchmenu"
require "ui/confirmbox"
require "document/document" require "document/document"
require "ui/readerui"
require "dbg"
-----------------------------------------------------
-- widget that paints the grid on the background
-----------------------------------------------------
TestGrid = Widget:new{} TestGrid = Widget:new{}
function TestGrid:paintTo(bb) function TestGrid:paintTo(bb)
@ -25,7 +30,9 @@ function TestGrid:paintTo(bb)
end end
end end
-----------------------------------------------------
-- we create a widget that paints a background: -- we create a widget that paints a background:
-----------------------------------------------------
Background = InputContainer:new{ Background = InputContainer:new{
is_always_active = true, -- receive events when other dialogs are active is_always_active = true, -- receive events when other dialogs are active
key_events = { key_events = {
@ -37,7 +44,13 @@ Background = InputContainer:new{
FrameContainer:new{ FrameContainer:new{
background = 3, background = 3,
bordersize = 0, 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 -- example widget: a clock
-----------------------------------------------------
Clock = FrameContainer:new{ Clock = FrameContainer:new{
background = 0, background = 0,
bordersize = 1, bordersize = 1,
@ -96,6 +111,9 @@ function Clock:getTextWidget()
} }
end end
-----------------------------------------------------
-- a confirmbox box widget
-----------------------------------------------------
Quiz = ConfirmBox:new{ Quiz = ConfirmBox:new{
text = "Tell me the truth, isn't it COOL?!", text = "Tell me the truth, isn't it COOL?!",
width = 300, width = 300,
@ -108,6 +126,9 @@ Quiz = ConfirmBox:new{
end, end,
} }
-----------------------------------------------------
-- a menu widget
-----------------------------------------------------
menu_items = { menu_items = {
{text = "item1"}, {text = "item1"},
{text = "item2"}, {text = "item2"},
@ -136,6 +157,9 @@ M = Menu:new{
} }
-----------------------------------------------------
-- a reader view widget
-----------------------------------------------------
readerwindow = CenterContainer:new{ readerwindow = CenterContainer:new{
dimen = Screen:getSize(), dimen = Screen:getSize(),
FrameContainer:new{ FrameContainer:new{
@ -151,12 +175,86 @@ reader = ReaderUI:new{
} }
readerwindow[1][1] = reader 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(Background:new())
UIManager:show(TestGrid) UIManager:show(TestGrid)
UIManager:show(Clock:new()) --UIManager:show(Clock:new())
UIManager:show(M) --UIManager:show(M)
UIManager:show(Quiz) --UIManager:show(Quiz)
UIManager:show(readerwindow) --UIManager:show(readerwindow)
UIManager:show(touch_menu)
UIManager:run() UIManager:run()

Loading…
Cancel
Save