diff --git a/Makefile b/Makefile index 106b1e517..5f9de89ee 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ MUPDFLIBDIR=$(MUPDFDIR)/$(MUPDFTARGET) SQLITE3DIR=sqlite-amalgamation-3070900 LSQLITE3DIR=lsqlite3_svn08 -FREETYPEDIR=$(MUPDFDIR)/thirdparty/freetype-2.4.4 +FREETYPEDIR=$(MUPDFDIR)/thirdparty/freetype-2.4.8 LFSDIR=luafilesystem # set this to your ARM cross compiler: diff --git a/filechooser.lua b/filechooser.lua index ae5104c82..d7007f9ee 100644 --- a/filechooser.lua +++ b/filechooser.lua @@ -2,16 +2,26 @@ require "rendertext" require "keys" require "graphics" require "fontchooser" +require "filesearcher" +require "inputbox" +require "selectmenu" FileChooser = { -- Class vars: - - -- font for displaying file/dir names - face = freetype.newBuiltinFace("sans", 25), - fhash = "s25", + -- font for displaying toc item names + fsize = 25, + face = nil, + fhash = nil, + --face = freetype.newBuiltinFace("sans", 25), + --fhash = "s25", + -- font for paging display - sface = freetype.newBuiltinFace("sans", 16), - sfhash = "s16", + ffsize = 16, + fface = nil, + ffhash = nil, + --sface = freetype.newBuiltinFace("sans", 16), + --sfhash = "s16", + -- spacing between lines spacing = 40, @@ -35,6 +45,7 @@ function FileChooser:readdir() table.insert(self.files, f) end end + --@TODO make sure .. is sortted to the first item 16.02 2012 table.sort(self.dirs) table.sort(self.files) end @@ -51,32 +62,15 @@ function FileChooser:setPath(newPath) return true end -function FileChooser:rotationMode() - --[[ - return code for four kinds of rotation mode: - - 0 for no rotation, - 1 for landscape with bottom on the right side of screen, etc. - - 2 - --------- - | | - | | - | | - 3 | | 1 - | | - | | - | | - --------- - 0 - --]] - if KEY_FW_DOWN == 116 then - return 0 +function FileChooser:updateFont() + if self.fhash ~= FontChooser.cfont..self.fsize then + self.face = freetype.newBuiltinFace(FontChooser.cfont, self.fsize) + self.fhash = FontChooser.cfont..self.fsize + end + if self.ffhash ~= FontChooser.ffont..self.ffsize then + self.fface = freetype.newBuiltinFace(FontChooser.ffont, self.ffsize) + self.ffhash = FontChooser.ffont..self.ffsize end - orie_fd = assert(io.open("/sys/module/eink_fb_hal_broads/parameters/bs_orientation", "r")) - updown_fd = assert(io.open("/sys/module/eink_fb_hal_broads/parameters/bs_upside_down", "r")) - mode = orie_fd:read() + (updown_fd:read() * 2) - return mode end function FileChooser:choose(ypos, height) @@ -114,6 +108,7 @@ function FileChooser:choose(ypos, height) end while true do + self:updateFont() if pagedirty then fb.bb:paintRect(0, ypos, fb.bb:getWidth(), height, 0) local c @@ -127,7 +122,7 @@ function FileChooser:choose(ypos, height) renderUtf8Text(fb.bb, 50, ypos + self.spacing*c, self.face, self.fhash, self.files[i-#self.dirs], true) end end - renderUtf8Text(fb.bb, 39, ypos + self.spacing * perpage + 32, self.sface, self.sfhash, + renderUtf8Text(fb.bb, 39, ypos + self.spacing * perpage + 32, self.fface, self.ffhash, "Page "..self.page.." of "..(math.floor(self.items / perpage)+1), true) markerdirty = true end @@ -151,36 +146,35 @@ function FileChooser:choose(ypos, height) end local ev = input.waitForEvent() if ev.type == EV_KEY and ev.value == EVENT_VALUE_KEY_PRESS then + --print("key code:"..ev.code) + ev.code = adjustFWKey(ev.code) if ev.code == KEY_FW_UP then - if self:rotationMode() == 0 then - prevItem() - elseif self:rotationMode() == 2 then - nextItem() - end + prevItem() elseif ev.code == KEY_FW_DOWN then - if self:rotationMode() == 0 then - nextItem() - elseif self:rotationMode() == 2 then - prevItem() - end - elseif ev.code == KEY_FW_LEFT then - if self:rotationMode() == 1 then - prevItem() - elseif self:rotationMode() == 3 then - nextItem() - end - elseif ev.code == KEY_FW_RIGHT then - if self:rotationMode() == 1 then - nextItem() - elseif self:rotationMode() == 3 then - prevItem() - end - elseif ev.code == KEY_F then + nextItem() + elseif ev.code == KEY_F then -- invoke fontchooser menu FontChooser:init() - newfont = FontChooser:choose(0, height) - if newfont ~= nil then - self.face = freetype.newBuiltinFace(newfont, 25) - clearglyphcache() + fonts_menu = SelectMenu:new{ + menu_title = "Fonts Menu", + item_array = FontChooser.fonts, + } + FontChooser.cfont = FontChooser.fonts[fonts_menu:choose(0, height)] + pagedirty = true + elseif ev.code == KEY_S then -- invoke search input + keywords = InputBox:input(height-100, 100, "Search:") + if keywords then -- display search result according to keywords + --[[ + ---------------------------------------------------------------- + || uncomment following line and set the correct path if you want + || to test search feature in EMU mode + ---------------------------------------------------------------- + --]] + --FileSearcher:init("/home/dave/documents/kindle/backup/documents") + FileSearcher:init() + file = FileSearcher:choose(ypos, height, keywords) + if file then + return file + end end pagedirty = true elseif ev.code == KEY_PGFWD then diff --git a/filesearcher.lua b/filesearcher.lua new file mode 100644 index 000000000..1c6199631 --- /dev/null +++ b/filesearcher.lua @@ -0,0 +1,266 @@ +require "rendertext" +require "keys" +require "graphics" +require "fontchooser" + +FileSearcher = { + -- font for displaying toc item names + fsize = 25, + face = nil, + fhash = nil, + -- font for page title + tfsize = 30, + tface = nil, + tfhash = nil, + -- font for paging display + ffsize = 16, + fface = nil, + ffhash = nil, + + -- title height + title_H = 45, + -- spacing between lines + spacing = 40, + -- foot height + foot_H = 27, + + -- state buffer + dirs = {}, + files = {}, + result = {}, + items = 0, + page = 0, + current = 1, + oldcurrent = 1, +} + +function FileSearcher:readdir() + self.dirs = {self.path} + self.files = {} + while #self.dirs ~= 0 do + new_dirs = {} + -- handle each dir + for __, d in pairs(self.dirs) do + -- handle files in d + for f in lfs.dir(d) do + if lfs.attributes(self.path.."/"..f, "mode") == "directory" + and f ~= "." and f~= ".." and not string.match(f, "^%.[^.]") then + table.insert(new_dirs, d.."/"..f) + elseif string.match(f, ".+%.[pP][dD][fF]$") then + file_entry = {dir=d, name=f,} + table.insert(self.files, file_entry) + --print("file:"..d.."/"..f) + end + end + end + self.dirs = new_dirs + end +end + +function FileSearcher:setPath(newPath) + self.path = newPath + self:readdir() + self.items = #self.files + --@TODO check none found 19.02 2012 + if self.items == 0 then + return nil + end + self.page = 1 + self.current = 1 + return true +end + +function FileSearcher:updateFont() + if self.fhash ~= FontChooser.cfont..self.fsize then + self.face = freetype.newBuiltinFace(FontChooser.cfont, self.fsize) + self.fhash = FontChooser.cfont..self.fsize + end + + if self.tfhash ~= FontChooser.tfont..self.tfsize then + self.tface = freetype.newBuiltinFace(FontChooser.tfont, self.tfsize) + self.tfhash = FontChooser.tfont..self.tfsize + end + + if self.ffhash ~= FontChooser.ffont..self.ffsize then + self.fface = freetype.newBuiltinFace(FontChooser.ffont, self.ffsize) + self.ffhash = FontChooser.ffont..self.ffsize + end +end + +function FileSearcher:setSearchResult(keywords) + self.result = {} + if keywords == " " then -- one space to show all files + self.result = self.files + else + for __,f in pairs(self.files) do + if string.find(string.lower(f.name), keywords) then + table.insert(self.result, f) + end + end + end + self.items = #self.result + self.page = 1 + self.current = 1 +end + +function FileSearcher:init(search_path) + if search_path then + self:setPath(search_path) + else + self:setPath("/mnt/us/documents") + end +end + + +function FileSearcher:choose(ypos, height, keywords) + local perpage = math.floor(height / self.spacing) - 2 + local pagedirty = true + local markerdirty = false + self:updateFont() + + local prevItem = function () + if self.current == 1 then + if self.page > 1 then + self.current = perpage + self.page = self.page - 1 + pagedirty = true + end + else + self.current = self.current - 1 + markerdirty = true + end + end + + local nextItem = function () + if self.current == perpage then + if self.page < (self.items / perpage) then + self.current = 1 + self.page = self.page + 1 + pagedirty = true + end + else + if self.page ~= math.floor(self.items / perpage) + 1 + or self.current + (self.page-1)*perpage < self.items then + self.current = self.current + 1 + markerdirty = true + end + end + end + + -- if given keywords, set new result according to keywords. + -- Otherwise, display the previous search result. + if keywords then + self:setSearchResult(keywords) + end + + while true do + if pagedirty then + markerdirty = true + fb.bb:paintRect(0, ypos, fb.bb:getWidth(), height, 0) + + -- draw menu title + renderUtf8Text(fb.bb, 30, ypos + self.title_H, self.tface, self.tfhash, + "Search Result for: "..keywords, true) + + -- draw results + local c + if self.items == 0 then -- nothing found + y = ypos + self.title_H + self.spacing * 2 + renderUtf8Text(fb.bb, 20, y, self.face, self.fhash, + "Sorry, no match found.", true) + renderUtf8Text(fb.bb, 20, y + self.spacing, self.face, self.fhash, + "Please try a different keyword.", true) + markerdirty = false + else -- found something, draw it + for c = 1, perpage do + local i = (self.page - 1) * perpage + c + if i <= self.items then + y = ypos + self.title_H + (self.spacing * c) + renderUtf8Text(fb.bb, 50, y, self.face, self.fhash, + self.result[i].name, true) + end + end + end + + -- draw footer + y = ypos + self.title_H + (self.spacing * perpage) + self.foot_H + x = (fb.bb:getWidth() / 2) - 50 + all_page = (math.floor(self.items / perpage)+1) + renderUtf8Text(fb.bb, x, y, self.fface, self.ffhash, + "Page "..self.page.." of "..all_page, true) + end + + if markerdirty then + if not pagedirty then + if self.oldcurrent > 0 then + y = ypos + self.title_H + (self.spacing * self.oldcurrent) + 10 + fb.bb:paintRect(30, y, fb.bb:getWidth() - 60, 3, 0) + fb:refresh(1, 30, y, fb.bb:getWidth() - 60, 3) + end + end + -- draw new marker line + y = ypos + self.title_H + (self.spacing * self.current) + 10 + fb.bb:paintRect(30, y, fb.bb:getWidth() - 60, 3, 15) + if not pagedirty then + fb:refresh(1, 30, y, fb.bb:getWidth() - 60, 3) + end + self.oldcurrent = self.current + markerdirty = false + end + + if pagedirty then + fb:refresh(0, 0, ypos, fb.bb:getWidth(), height) + pagedirty = false + end + + local ev = input.waitForEvent() + if ev.type == EV_KEY and ev.value == EVENT_VALUE_KEY_PRESS then + ev.code = adjustFWKey(ev.code) + if ev.code == KEY_FW_UP then + prevItem() + elseif ev.code == KEY_FW_DOWN then + nextItem() + elseif ev.code == KEY_PGFWD then + if self.page < (self.items / perpage) then + if self.current + self.page*perpage > self.items then + self.current = self.items - self.page*perpage + end + self.page = self.page + 1 + pagedirty = true + else + self.current = self.items - (self.page-1)*perpage + markerdirty = true + end + elseif ev.code == KEY_PGBCK then + if self.page > 1 then + self.page = self.page - 1 + pagedirty = true + else + self.current = 1 + markerdirty = true + end + elseif ev.code == KEY_S then + old_keywords = keywords + keywords = InputBox:input(height-100, 100, "Search:", old_keywords) + if keywords then + self:setSearchResult(keywords) + else + keywords = old_keywords + end + pagedirty = true + elseif ev.code == KEY_ENTER or ev.code == KEY_FW_PRESS then + file_entry = self.result[perpage*(self.page-1)+self.current] + file_path = file_entry.dir .. "/" .. file_entry.name + + if PDFReader:open(file_path,"") then -- TODO: query for password + PDFReader:goto(tonumber(PDFReader.settings:readsetting("last_page") or 1)) + PDFReader:inputloop() + end + + pagedirty = true + elseif ev.code == KEY_BACK then + return nil + end + end + end +end diff --git a/fontchooser.lua b/fontchooser.lua index 920a32ec8..96e192319 100644 --- a/fontchooser.lua +++ b/fontchooser.lua @@ -1,153 +1,20 @@ -require "rendertext" -require "keys" -require "graphics" FontChooser = { - -- font for displaying file/dir names - face = freetype.newBuiltinFace("sans", 25), - fhash = "s25", - -- font for page title - tface = freetype.newBuiltinFace("Helvetica-BoldOblique", 32), - tfhash = "hbo32", - -- font for paging display - sface = freetype.newBuiltinFace("sans", 16), - sfhash = "s16", - -- title height - title_H = 45, - -- spacing between lines - spacing = 40, - -- foot height - foot_H = 27, + -- font name for menu contents + cfont = "sans", + -- font name for title + tfont = "Helvetica-BoldOblique", + -- font name for footer + ffont = "sans", -- state buffer fonts = {"sans", "cjk", "mono", "Courier", "Courier-Bold", "Courier-Oblique", "Courier-BoldOblique", "Helvetica", "Helvetica-Oblique", "Helvetica-BoldOblique", "Times-Roman", "Times-Bold", "Times-Italic", "Times-BoldItalic",}, - items = 14, - page = 1, - current = 2, - oldcurrent = 1, } function FontChooser:init() - self.items = #self.fonts - table.sort(self.fonts) + clearglyphcache() end - -function FontChooser:choose(ypos, height) - local perpage = math.floor(height / self.spacing) - 2 - local pagedirty = true - local markerdirty = false - - local prevItem = function () - if self.current == 1 then - if self.page > 1 then - self.current = perpage - self.page = self.page - 1 - pagedirty = true - end - else - self.current = self.current - 1 - markerdirty = true - end - end - - local nextItem = function () - if self.current == perpage then - if self.page < (self.items / perpage) then - self.current = 1 - self.page = self.page + 1 - pagedirty = true - end - else - if self.page ~= math.floor(self.items / perpage) + 1 - or self.current + (self.page-1)*perpage < self.items then - self.current = self.current + 1 - markerdirty = true - end - end - end - - - while true do - if pagedirty then - fb.bb:paintRect(0, ypos, fb.bb:getWidth(), height, 0) - - -- draw menu title - renderUtf8Text(fb.bb, 30, ypos + self.title_H, self.tface, self.tfhash, - "[ Fonts Menu ]", true) - - local c - for c = 1, perpage do - local i = (self.page - 1) * perpage + c - if i <= self.items then - y = ypos + self.title_H + (self.spacing * c) - renderUtf8Text(fb.bb, 50, y, self.face, self.fhash, self.fonts[i], true) - end - end - y = ypos + self.title_H + (self.spacing * perpage) + self.foot_H - x = (fb.bb:getWidth() / 2) - 50 - renderUtf8Text(fb.bb, x, y, self.sface, self.sfhash, - "Page "..self.page.." of "..(math.floor(self.items / perpage)+1), true) - markerdirty = true - end - - if markerdirty then - if not pagedirty then - if self.oldcurrent > 0 then - y = ypos + self.title_H + (self.spacing * self.oldcurrent) + 10 - fb.bb:paintRect(30, y, fb.bb:getWidth() - 60, 3, 0) - fb:refresh(1, 30, y, fb.bb:getWidth() - 60, 3) - end - end - -- draw new marker line - y = ypos + self.title_H + (self.spacing * self.current) + 10 - fb.bb:paintRect(30, y, fb.bb:getWidth() - 60, 3, 15) - if not pagedirty then - fb:refresh(1, 30, y, fb.bb:getWidth() - 60, 3) - end - self.oldcurrent = self.current - markerdirty = false - end - - if pagedirty then - fb:refresh(0, 0, ypos, fb.bb:getWidth(), height) - pagedirty = false - end - - local ev = input.waitForEvent() - if ev.type == EV_KEY and ev.value == EVENT_VALUE_KEY_PRESS then - if ev.code == KEY_FW_UP then - prevItem() - elseif ev.code == KEY_FW_DOWN then - nextItem() - elseif ev.code == KEY_PGFWD then - if self.page < (self.items / perpage) then - if self.current + self.page*perpage > self.items then - self.current = self.items - self.page*perpage - end - self.page = self.page + 1 - pagedirty = true - else - self.current = self.items - (self.page-1)*perpage - markerdirty = true - end - elseif ev.code == KEY_PGBCK then - if self.page > 1 then - self.page = self.page - 1 - pagedirty = true - else - self.current = 1 - markerdirty = true - end - elseif ev.code == KEY_ENTER or ev.code == KEY_FW_PRESS then - local newface = self.fonts[perpage*(self.page-1)+self.current] - return newface - elseif ev.code == KEY_BACK then - return nil - end - end - end -end diff --git a/inputbox.lua b/inputbox.lua new file mode 100644 index 000000000..3408567c7 --- /dev/null +++ b/inputbox.lua @@ -0,0 +1,209 @@ +require "rendertext" +require "keys" +require "graphics" + +InputBox = { + -- Class vars: + input_start_x = 145, + input_start_y = nil, + input_cur_x = nil, -- points to the start of next input pos + + input_bg = 0, + + input_string = "", + + shiftmode = false, + altmode = false, + + -- font for displaying input content + face = freetype.newBuiltinFace("mono", 25), + fhash = "m25", + fheight = 25, + fwidth = 16, +} + +function InputBox:setDefaultInput(text) + self.input_string = "" + self:addString(text) + --renderUtf8Text(fb.bb, self.input_start_x, self.input_start_y, + --self.face, self.fhash, text, true) + --self.input_cur_x = self.input_start_x + (string.len(text) * self.fwidth) + --self.input_string = text +end + +function InputBox:addString(str) + for i = 1, #str do + self:addChar(str:sub(i,i)) + end +end + +function InputBox:addChar(char) + renderUtf8Text(fb.bb, self.input_cur_x, self.input_start_y, self.face, self.fhash, + char, true) + fb:refresh(1, self.input_cur_x, self.input_start_y-19, self.fwidth, self.fheight) + self.input_cur_x = self.input_cur_x + self.fwidth + self.input_string = self.input_string .. char +end + +function InputBox:delChar() + if self.input_start_x == self.input_cur_x then + return + end + self.input_cur_x = self.input_cur_x - self.fwidth + --fill last character with blank rectangle + fb.bb:paintRect(self.input_cur_x, self.input_start_y-19, + self.fwidth, self.fheight, self.input_bg) + fb:refresh(1, self.input_cur_x, self.input_start_y-19, self.fwidth, self.fheight) + self.input_string = self.input_string:sub(0,-2) +end + +function InputBox:drawBox(ypos, w, h, title) + -- draw input border + fb.bb:paintRect(20, ypos, w, h, 5) + -- draw input slot + fb.bb:paintRect(140, ypos + 10, w - 130, h - 20, self.input_bg) + -- draw input title + renderUtf8Text(fb.bb, 35, self.input_start_y, self.face, self.fhash, + title, true) +end + + +--[[ + || d_text default to nil (used to set default text in input slot) +--]] +function InputBox:input(ypos, height, title, d_text) + local pagedirty = true + -- do some initilization + self.input_start_y = ypos + 35 + self.input_cur_x = self.input_start_x + + if d_text then -- if specified default text, draw it + w = fb.bb:getWidth() - 40 + h = height - 45 + self:drawBox(ypos, w, h, title) + self:setDefaultInput(d_text) + fb:refresh(1, 20, ypos, w, h) + pagedirty = false + else -- otherwise, leave the draw task to the main loop + self.input_string = "" + end + + while true do + if pagedirty then + w = fb.bb:getWidth() - 40 + h = height - 45 + self:drawBox(ypos, w, h, title) + fb:refresh(1, 20, ypos, w, h) + pagedirty = false + end + + local ev = input.waitForEvent() + if ev.type == EV_KEY and ev.value == EVENT_VALUE_KEY_PRESS then + ev.code = adjustFWKey(ev.code) + --local secs, usecs = util.gettime() + if ev.code == KEY_SHIFT then + self.shiftmode = true + elseif ev.code == KEY_ALT then + self.altmode = true + elseif ev.code == KEY_FW_UP then + elseif ev.code == KEY_FW_DOWN then + elseif ev.code == KEY_A then + self:addChar("a") + elseif ev.code == KEY_B then + self:addChar("b") + elseif ev.code == KEY_C then + self:addChar("c") + elseif ev.code == KEY_D then + self:addChar("d") + elseif ev.code == KEY_E then + self:addChar("e") + elseif ev.code == KEY_F then + self:addChar("f") + elseif ev.code == KEY_G then + self:addChar("g") + elseif ev.code == KEY_H then + self:addChar("h") + elseif ev.code == KEY_I then + self:addChar("i") + elseif ev.code == KEY_J then + self:addChar("j") + elseif ev.code == KEY_K then + self:addChar("k") + elseif ev.code == KEY_L then + self:addChar("l") + elseif ev.code == KEY_M then + self:addChar("m") + elseif ev.code == KEY_N then + self:addChar("n") + elseif ev.code == KEY_O then + self:addChar("o") + elseif ev.code == KEY_P then + self:addChar("p") + elseif ev.code == KEY_Q then + self:addChar("q") + elseif ev.code == KEY_R then + self:addChar("r") + elseif ev.code == KEY_S then + self:addChar("s") + elseif ev.code == KEY_T then + self:addChar("t") + elseif ev.code == KEY_U then + self:addChar("u") + elseif ev.code == KEY_V then + self:addChar("v") + elseif ev.code == KEY_W then + self:addChar("w") + elseif ev.code == KEY_X then + self:addChar("x") + elseif ev.code == KEY_Y then + self:addChar("y") + elseif ev.code == KEY_Z then + self:addChar("z") + elseif ev.code == KEY_1 then + self:addChar("1") + elseif ev.code == KEY_2 then + self:addChar("2") + elseif ev.code == KEY_3 then + self:addChar("3") + elseif ev.code == KEY_4 then + self:addChar("4") + elseif ev.code == KEY_5 then + self:addChar("5") + elseif ev.code == KEY_6 then + self:addChar("6") + elseif ev.code == KEY_7 then + self:addChar("7") + elseif ev.code == KEY_8 then + self:addChar("8") + elseif ev.code == KEY_9 then + self:addChar("9") + elseif ev.code == KEY_0 then + self:addChar("0") + elseif ev.code == KEY_SPACE then + self:addChar(" ") + elseif ev.code == KEY_PGFWD then + elseif ev.code == KEY_PGBCK then + elseif ev.code == KEY_ENTER or ev.code == KEY_FW_PRESS then + if self.input_string == "" then + return nil + else + return self.input_string + end + elseif ev.code == KEY_DEL then + self:delChar() + elseif ev.code == KEY_BACK then + return nil + end + + --local nsecs, nusecs = util.gettime() + --local dur = (nsecs - secs) * 1000000 + nusecs - usecs + --print("E: T="..ev.type.." V="..ev.value.." C="..ev.code.." DUR="..dur) + elseif ev.type == EV_KEY and ev.value == EVENT_VALUE_KEY_RELEASE + and ev.code == KEY_SHIFT then + self.shiftmode = false + elseif ev.type == EV_KEY and ev.value == EVENT_VALUE_KEY_RELEASE + and ev.code == KEY_ALT then + self.altmode = false + end + end +end diff --git a/keys.lua b/keys.lua index b757dd5b0..7e3f5aec7 100644 --- a/keys.lua +++ b/keys.lua @@ -109,25 +109,120 @@ function set_emu_keycodes() KEY_PGFWD = 117 KEY_PGBCK = 112 KEY_BACK = 22 -- backspace + KEY_DEL = 119 -- Delete KEY_MENU = 67 -- F1 KEY_FW_UP = 111 KEY_FW_DOWN = 116 KEY_FW_LEFT = 113 KEY_FW_RIGHT = 114 KEY_FW_PRESS = 36 -- enter for now + KEY_SPACE = 65 KEY_ENTER = 36 + KEY_Q = 24 + KEY_W = 25 + KEY_E = 26 + KEY_R = 27 + KEY_T = 28 + KEY_Y = 29 + KEY_U = 30 + KEY_I = 31 + KEY_O = 32 + KEY_P = 33 + KEY_A = 38 KEY_S = 39 KEY_D = 40 KEY_F = 41 - + KEY_G = 42 + KEY_H = 43 KEY_J = 44 KEY_K = 45 + KEY_L = 46 + + KEY_Z = 52 + KEY_X = 53 + KEY_C = 54 + KEY_V = 55 + KEY_B = 56 + KEY_N = 57 + KEY_M = 58 KEY_SHIFT = 50 -- left shift KEY_ALT = 64 -- left alt KEY_VPLUS = 95 -- F11 KEY_VMINUS = 96 -- F12 end + +function getRotationMode() + --[[ + return code for four kinds of rotation mode: + + 0 for no rotation, + 1 for landscape with bottom on the right side of screen, etc. + + 2 + ----------- + | ------- | + | | | | + | | | | + | | | | + 3 | | | | 1 + | | | | + | | | | + | ------- | + | | + ----------- + 0 + --]] + if KEY_FW_DOWN == 116 then -- in EMU mode always return 0 + return 0 + end + orie_fd = assert(io.open("/sys/module/eink_fb_hal_broads/parameters/bs_orientation", "r")) + updown_fd = assert(io.open("/sys/module/eink_fb_hal_broads/parameters/bs_upside_down", "r")) + mode = orie_fd:read() + (updown_fd:read() * 2) + return mode +end + +function adjustFWKey(code) + if getRotationMode() == 0 then + return code + elseif getRotationMode() == 1 then + if code == KEY_FW_UP then + return KEY_FW_RIGHT + elseif code == KEY_FW_RIGHT then + return KEY_FW_DOWN + elseif code == KEY_FW_DOWN then + return KEY_FW_LEFT + elseif code == KEY_FW_LEFT then + return KEY_FW_UP + else + return code + end + elseif getRotationMode() == 2 then + if code == KEY_FW_UP then + return KEY_FW_DOWN + elseif code == KEY_FW_RIGHT then + return KEY_FW_LEFT + elseif code == KEY_FW_DOWN then + return KEY_FW_UP + elseif code == KEY_FW_LEFT then + return KEY_FW_RIGHT + else + return code + end + elseif getRotationMode() == 3 then + if code == KEY_FW_UP then + return KEY_FW_LEFT + elseif code == KEY_FW_RIGHT then + return KEY_FW_UP + elseif code == KEY_FW_DOWN then + return KEY_FW_RIGHT + elseif code == KEY_FW_LEFT then + return KEY_FW_DOWN + else + return code + end + end +end diff --git a/pdf.c b/pdf.c index 5841659c8..15c6d3f8a 100644 --- a/pdf.c +++ b/pdf.c @@ -87,6 +87,56 @@ static int getNumberOfPages(lua_State *L) { return 1; } +/* + * helper function for getTableOfContent() + */ +static int walkTableOfContent(lua_State *L, fz_outline* ol, int *count, int depth) { + depth++; + while(ol) { + lua_pushnumber(L, *count); + + /* set subtable */ + lua_newtable(L); + lua_pushstring(L, "page"); + lua_pushnumber(L, ol->dest.ld.gotor.page + 1); + lua_settable(L, -3); + lua_pushstring(L, "depth"); + lua_pushnumber(L, depth); + lua_settable(L, -3); + lua_pushstring(L, "title"); + lua_pushstring(L, ol->title); + lua_settable(L, -3); + + lua_settable(L, -3); + + (*count)++; + if (ol->down) { + walkTableOfContent(L, ol->down, count, depth); + } + ol = ol->next; + } + return 0; +} + +/* + * Return a table like this: + * { + * {page=12, depth=1, title="chapter1"}, + * {page=54, depth=1, title="chapter2"}, + * } + */ +static int getTableOfContent(lua_State *L) { + fz_outline *ol; + int count = 1; + + PdfDocument *doc = (PdfDocument*) luaL_checkudata(L, 1, "pdfdocument"); + ol = pdf_load_outline(doc->xref); + + lua_newtable(L); + walkTableOfContent(L, ol, &count, 0); + return 1; +} + static int newDrawContext(lua_State *L) { int rotate = luaL_optint(L, 1, 0); double zoom = luaL_optnumber(L, 2, (double) 1.0); @@ -309,6 +359,7 @@ static const struct luaL_reg pdf_func[] = { static const struct luaL_reg pdfdocument_meth[] = { {"openPage", openPage}, {"getPages", getNumberOfPages}, + {"getTOC", getTableOfContent}, {"close", closeDocument}, {"__gc", closeDocument}, {NULL, NULL} diff --git a/pdfreader.lua b/pdfreader.lua index c01d0473c..bc8b0b37d 100644 --- a/pdfreader.lua +++ b/pdfreader.lua @@ -1,5 +1,7 @@ require "keys" require "settings" +--require "tocmenu" +require "selectmenu" PDFReader = { -- "constants": @@ -58,6 +60,7 @@ PDFReader = { -- tile cache state: cache_current_memsize = 0, cache = {}, + jump_stack = {}, } -- guarantee that we have enough memory in cache @@ -234,6 +237,35 @@ function PDFReader:goto(no) if no < 1 or no > self.doc:getPages() then return end + + -- for jump_stack + if self.pageno and math.abs(self.pageno - no) > 1 then + local jump_item = nil + -- add current page to jump_stack if no in + for _t,_v in ipairs(self.jump_stack) do + if _v.page == self.pageno then + jump_item = _v + table.remove(self.jump_stack, _t) + elseif _v.page == no then + -- the page we jumped to should not be show in stack + table.remove(self.jump_stack, _t) + end + end + -- create a new one if not found + if not jump_item then + jump_item = { + page = self.pageno, + datetime = os.date("%Y-%m-%d %H:%M:%S"), + } + end + -- insert at the start + table.insert(self.jump_stack, 1, jump_item) + if #self.jump_stack > 10 then + -- remove the last element to keep the size less than 10 + table.remove(self.jump_stack) + end + end + self.pageno = no self:show(no) if no < self.doc:getPages() then @@ -275,6 +307,48 @@ function PDFReader:setrotate(rotate) self:goto(self.pageno) end +function PDFReader:showTOC() + toc = self.doc:getTOC() + local menu_items = {} + -- build menu items + for _k,_v in ipairs(toc) do + table.insert(menu_items, + (" "):rep(_v.depth-1).._v.title) + end + toc_menu = SelectMenu:new{ + menu_title = "Table of Contents", + item_array = menu_items, + no_item_msg = "This document does not have a Table of Contents.", + } + item_no = toc_menu:choose(0, fb.bb:getHeight()) + if item_no then + self:goto(toc[item_no].page) + else + self:goto(self.pageno) + end +end + +function PDFReader:showJumpStack() + local menu_items = {} + for _k,_v in ipairs(self.jump_stack) do + table.insert(menu_items, + _v.datetime.." -> Page ".._v.page) + end + jump_menu = SelectMenu:new{ + menu_title = "Jump Keeper (current page: "..self.pageno..")", + item_array = menu_items, + no_item_msg = "No jump history.", + } + item_no = jump_menu:choose(0, fb.bb:getHeight()) + if item_no then + local jump_item = self.jump_stack[item_no] + self:goto(jump_item.page) + else + self:goto(self.pageno) + end +end + + -- wait for input and handle it function PDFReader:inputloop() while 1 do @@ -302,16 +376,24 @@ function PDFReader:inputloop() self:goto(self.pageno - 1) end elseif ev.code == KEY_BACK then - self:clearcache() - if self.doc ~= nil then - self.doc:close() + if self.altmode then + -- in altmode, back to last jump + if #self.jump_stack ~= 0 then + self:goto(self.jump_stack[1].page) + end + else + -- not shiftmode, exit pdfreader + self:clearcache() + if self.doc ~= nil then + self.doc:close() + end + if self.settings ~= nil then + self.settings:savesetting("last_page", self.pageno) + self.settings:savesetting("gamma", self.globalgamma) + self.settings:close() + end + return end - if self.settings ~= nil then - self.settings:savesetting("last_page", self.pageno) - self.settings:savesetting("gamma", self.globalgamma) - self.settings:close() - end - return elseif ev.code == KEY_VPLUS then self:modify_gamma( 1.25 ) elseif ev.code == KEY_VMINUS then @@ -334,7 +416,10 @@ function PDFReader:inputloop() else self:setglobalzoommode(self.ZOOM_FIT_TO_PAGE_HEIGHT) end - + elseif ev.code == KEY_T then + self:showTOC() + elseif ev.code == KEY_B then + self:showJumpStack() elseif ev.code == KEY_J then self:setrotate( self.globalrotate + 10 ) elseif ev.code == KEY_K then @@ -401,6 +486,7 @@ function PDFReader:inputloop() local dur = (nsecs - secs) * 1000000 + nusecs - usecs print("E: T="..ev.type.." V="..ev.value.." C="..ev.code.." DUR="..dur) elseif ev.type == EV_KEY and ev.value == EVENT_VALUE_KEY_RELEASE and ev.code == KEY_SHIFT then + print "shift haha" self.shiftmode = false elseif ev.type == EV_KEY and ev.value == EVENT_VALUE_KEY_RELEASE and ev.code == KEY_ALT then self.altmode = false diff --git a/reader.lua b/reader.lua index 1a5d99174..1cff406ed 100755 --- a/reader.lua +++ b/reader.lua @@ -20,6 +20,7 @@ require "alt_getopt" require "pdfreader" require "filechooser" +require "settings" -- option parsing: longopts = { @@ -82,6 +83,14 @@ end fb = einkfb.open("/dev/fb0") width, height = fb:getSize() +-- set up reader's setting: font +reader_settings = DocSettings:open(".reader") +r_cfont = reader_settings:readsetting("cfont") +if r_cfont ~=nil then + FontChooser.cfont = r_cfont +end + + if lfs.attributes(ARGV[optind], "mode") == "directory" then local running = true FileChooser:setPath(ARGV[optind]) @@ -101,3 +110,10 @@ else PDFReader:goto(tonumber(optarg["g"]) or tonumber(PDFReader.settings:readsetting("last_page") or 1)) PDFReader:inputloop() end + +-- save reader settings +reader_settings:savesetting("cfont", FontChooser.cfont) +reader_settings:close() + +input.closeAll() +os.execute('test -e /proc/keypad && echo "send '..KEY_HOME..'" > /proc/keypad ') diff --git a/selectmenu.lua b/selectmenu.lua new file mode 100644 index 000000000..8c6219f60 --- /dev/null +++ b/selectmenu.lua @@ -0,0 +1,206 @@ +require "rendertext" +require "keys" +require "graphics" +require "fontchooser" + +SelectMenu = { + -- font for displaying item names + fsize = 22, + face = nil, + fhash = nil, + -- font for page title + tfsize = 25, + tface = nil, + tfhash = nil, + -- font for paging display + ffsize = 16, + fface = nil, + ffhash = nil, + + -- title height + title_H = 40, + -- spacing between lines + spacing = 36, + -- foot height + foot_H = 27, + + menu_title = "None Titled", + no_item_msg = "No items found.", + item_array = {}, + items = 14, + + -- state buffer + page = 1, + current = 1, + oldcurrent = 0, +} + +function SelectMenu:new(o) + o = o or {} + setmetatable(o, self) + self.__index = self + o.items = #o.item_array + o.page = 1 + o.current = 1 + o.oldcurrent = 0 + return o +end + +function SelectMenu:updateFont() + if self.fhash ~= FontChooser.cfont..self.fsize then + self.face = freetype.newBuiltinFace(FontChooser.cfont, self.fsize) + self.fhash = FontChooser.cfont..self.fsize + end + + if self.tfhash ~= FontChooser.tfont..self.tfsize then + self.tface = freetype.newBuiltinFace(FontChooser.tfont, self.tfsize) + self.tfhash = FontChooser.tfont..self.tfsize + end + + if self.ffhash ~= FontChooser.ffont..self.ffsize then + self.fface = freetype.newBuiltinFace(FontChooser.ffont, self.ffsize) + self.ffhash = FontChooser.ffont..self.ffsize + end +end + +--[ +-- return the index of selected item +--] +function SelectMenu:choose(ypos, height) + local perpage = math.floor(height / self.spacing) - 2 + local pagedirty = true + local markerdirty = false + self:updateFont() + + local prevItem = function () + if self.current == 1 then + if self.page > 1 then + self.current = perpage + self.page = self.page - 1 + pagedirty = true + end + else + self.current = self.current - 1 + markerdirty = true + end + end + + local nextItem = function () + if self.current == perpage then + if self.page < (self.items / perpage) then + self.current = 1 + self.page = self.page + 1 + pagedirty = true + end + else + if self.page ~= math.floor(self.items / perpage) + 1 + or self.current + (self.page-1)*perpage < self.items then + self.current = self.current + 1 + markerdirty = true + end + end + end + + + while true do + if pagedirty then + markerdirty = true + -- draw menu title + fb.bb:paintRect(0, ypos, fb.bb:getWidth(), self.title_H + 10, 0) + fb.bb:paintRect(30, ypos + 10, fb.bb:getWidth() - 60, self.title_H, 5) + --x = fb.bb:getWidth() - 260 -- move text to the right + x = 40 + y = ypos + self.title_H + renderUtf8Text(fb.bb, x, y, self.tface, self.tfhash, + self.menu_title, true) + + -- draw items + fb.bb:paintRect(0, ypos + self.title_H + 10, fb.bb:getWidth(), height - self.title_H, 0) + if self.items == 0 then + y = ypos + self.title_H + (self.spacing * 2) + renderUtf8Text(fb.bb, 30, y, self.face, self.fhash, + "Oops... Bad news for you:", true) + y = y + self.spacing + renderUtf8Text(fb.bb, 30, y, self.face, self.fhash, + self.no_item_msg, true) + markerdirty = false + else + local c + for c = 1, perpage do + local i = (self.page - 1) * perpage + c + if i <= self.items then + y = ypos + self.title_H + (self.spacing * c) + renderUtf8Text(fb.bb, 30, y, self.face, self.fhash, + self.item_array[i], true) + end + end + end + + -- draw footer + y = ypos + self.title_H + (self.spacing * perpage) + self.foot_H + 5 + x = (fb.bb:getWidth() / 2) - 50 + renderUtf8Text(fb.bb, x, y, self.fface, self.ffhash, + "Page "..self.page.." of "..(math.floor(self.items / perpage)+1), true) + end + + if markerdirty then + if not pagedirty then + if self.oldcurrent > 0 then + y = ypos + self.title_H + (self.spacing * self.oldcurrent) + 8 + fb.bb:paintRect(30, y, fb.bb:getWidth() - 60, 3, 0) + fb:refresh(1, 30, y, fb.bb:getWidth() - 60, 3) + end + end + -- draw new marker line + y = ypos + self.title_H + (self.spacing * self.current) + 8 + fb.bb:paintRect(30, y, fb.bb:getWidth() - 60, 3, 15) + if not pagedirty then + fb:refresh(1, 30, y, fb.bb:getWidth() - 60, 3) + end + self.oldcurrent = self.current + markerdirty = false + end + + if pagedirty then + fb:refresh(0, 0, ypos, fb.bb:getWidth(), height) + pagedirty = false + end + + local ev = input.waitForEvent() + if ev.type == EV_KEY and ev.value == EVENT_VALUE_KEY_PRESS then + ev.code = adjustFWKey(ev.code) + if ev.code == KEY_FW_UP then + prevItem() + elseif ev.code == KEY_FW_DOWN then + nextItem() + elseif ev.code == KEY_PGFWD then + if self.page < (self.items / perpage) then + if self.current + self.page*perpage > self.items then + self.current = self.items - self.page*perpage + end + self.page = self.page + 1 + pagedirty = true + else + self.current = self.items - (self.page-1)*perpage + markerdirty = true + end + elseif ev.code == KEY_PGBCK then + if self.page > 1 then + self.page = self.page - 1 + pagedirty = true + else + self.current = 1 + markerdirty = true + end + elseif ev.code == KEY_ENTER or ev.code == KEY_FW_PRESS then + if self.items == 0 then + return nil + else + return (perpage*(self.page-1) + self.current) + end + elseif ev.code == KEY_BACK then + return nil + end + end + end +end