diff --git a/frontend/ui/device.lua b/frontend/ui/device.lua index a263cbe2f..1cbffeea0 100644 --- a/frontend/ui/device.lua +++ b/frontend/ui/device.lua @@ -68,6 +68,10 @@ function Device:hasNoKeyboard() return self:isTouchDevice() or (self.model == "Kindle4") end +function Device:hasKeyboard() + return not self:hasNoKeyboard() +end + function Device:isTouchDevice() if not self.model then self.model = self:getModel() diff --git a/frontend/ui/menu.lua b/frontend/ui/menu.lua index c6546e6c7..4c6a765c5 100644 --- a/frontend/ui/menu.lua +++ b/frontend/ui/menu.lua @@ -458,7 +458,7 @@ override this function to process the item selected in a different manner ]]-- function Menu:onMenuSelect(item) if item.sub_item_table == nil then - UIManager:close(self) + self.close_callback() self:onMenuChoice(item) else -- save menu title for later resume diff --git a/frontend/ui/notification.lua b/frontend/ui/notification.lua new file mode 100644 index 000000000..7a63842d8 --- /dev/null +++ b/frontend/ui/notification.lua @@ -0,0 +1,52 @@ +require "ui/ui" +require "ui/widget" + +--[[ +Widget that displays a tiny notification on top of screen +--]] +Notification = InputContainer:new{ + face = Font:getFace("infofont", 20), + text = "Null Message", + timeout = nil, + + key_events = { + AnyKeyPressed = { { Input.group.Any }, seqtext = "any key", doc = "close dialog" } + } +} + +function Notification:init() + -- we construct the actual content here because self.text is only available now + self[1] = CenterContainer:new{ + dimen = Geom:new{ + w = Screen:getWidth(), + h = Screen:getHeight()/10, + }, + ignore = "height", + FrameContainer:new{ + background = 0, + radius = 0, + HorizontalGroup:new{ + align = "center", + TextBoxWidget:new{ + text = self.text, + face = self.face, + } + } + } + } +end + +function Notification:onShow() + -- triggered by the UIManager after we got successfully shown (not yet painted) + if self.timeout then + UIManager:scheduleIn(self.timeout, function() UIManager:close(self) end) + end + return true +end + +function Notification:onAnyKeyPressed() + -- triggered by our defined key events + UIManager:close(self) + return true +end + diff --git a/frontend/ui/reader/readerbookmark.lua b/frontend/ui/reader/readerbookmark.lua new file mode 100644 index 000000000..015dcd0d0 --- /dev/null +++ b/frontend/ui/reader/readerbookmark.lua @@ -0,0 +1,148 @@ +require "ui/notification" + +ReaderBookmark = InputContainer:new{ + bm_menu_title = "Bookmarks", + bookmarks = nil, +} + +function ReaderBookmark:init() + if Device:hasKeyboard() then + self.key_events = { + ShowToc = { + { "B" }, + doc = "show bookmarks" }, + } + elseif Device:isTouchDevice() then + self.ges_events = { + AddBookmark = { + GestureRange:new{ + ges = "double_tap", + range = Geom:new{ + x = Screen:getWidth()/2, y = 0, + w = Screen:getWidth()/2, + h = Screen:getHeight()/2 + } + } + }, + } + end + self.ui.menu:registerToMainMenu(self) +end + +function ReaderBookmark:onReadSettings(config) + self.bookmarks = config:readSetting("bookmarks") or {} +end + +function ReaderBookmark:onCloseDocument() + self.ui.doc_settings:saveSetting("bookmarks", self.bookmarks) +end + +function ReaderBookmark:onSetDimensions(dimen) + self.dimen = dimen +end + +function ReaderBookmark:onAddBookmark() + local pn_or_xp = nil + if self.ui.document.getXPointer then + pn_or_xp = self.ui.document:getXPointer() + else + pn_or_xp = self.view.state.page + end + + local noti_text = "Bookmark added." + if not self:addBookmark(pn_or_xp) then + noti_text = "Page already marked!" + end + UIManager:show(Notification:new{ + text = noti_text, + timeout = 3 + }) + return true +end + +function ReaderBookmark:onShowBookmark() + -- build up item_table + for k, v in ipairs(self.bookmarks) do + local page = v.page + -- for CREngine, bookmark page is xpointer + if type(page) == "string" then + page = self.ui.document:getPageFromXPointer(v.page) + end + v.text = "Page "..page.." "..v.notes.." @ "..v.datetime + end + + local bm_menu = Menu:new{ + title = "Bookmarks", + item_table = self.bookmarks, + width = Screen:getWidth()-20, + height = Screen:getHeight(), + } + -- buid up menu widget method as closure + local doc = self.ui.document + local sendEv = function(ev) + self.ui:handleEvent(ev) + end + function bm_menu:onMenuChoice(item) + if doc.info.has_pages then + sendEv(Event:new("PageUpdate", item.page)) + elseif self.view.view_mode == "page" then + sendEv(Event:new("PageUpdate", doc:getPageFromXPointer(item.page))) + else + sendEv(Event:new("PosUpdate", doc:getPosFromXPointer(item.page))) + end + end + + local menu_container = CenterContainer:new{ + dimen = Screen:getSize(), + bm_menu, + } + bm_menu.close_callback = function() + UIManager:close(menu_container) + end + + UIManager:show(menu_container) + return true +end + +function ReaderBookmark:addToMainMenu(item_table) + -- insert table to main reader menu + table.insert(item_table, { + text = self.bm_menu_title, + callback = function() + self:onShowBookmark() + end, + }) +end + +--[[ +return nil if page already marked, otherwise, return true +for CREngine, bookmark page is xpointer instead of page number +--]] +function ReaderBookmark:addBookmark(pn_or_xp) + for k,v in ipairs(self.bookmarks) do + if v.page == pn_or_xp then + return nil + end + end + -- build notes from TOC + local notes = self.ui.toc:getTocTitleByPage(pn_or_xp) + if notes ~= "" then + notes = "in "..notes + end + mark_item = { + page = pn_or_xp, + datetime = os.date("%Y-%m-%d %H:%M:%S"), + notes = notes, + } + table.insert(self.bookmarks, mark_item) + table.sort(self.bookmarks, function(a,b) + return self:isBookmarkInSequence(a, b) + end) + return true +end + +function ReaderBookmark:isBookmarkInSequence(a, b) + return a.page < b.page +end + + diff --git a/frontend/ui/reader/readermenu.lua b/frontend/ui/reader/readermenu.lua index 4f7214267..d2ea485a4 100644 --- a/frontend/ui/reader/readermenu.lua +++ b/frontend/ui/reader/readermenu.lua @@ -80,6 +80,7 @@ function ReaderMenu:onShowMenu() end local menu_container = CenterContainer:new{ + ignore = "height", dimen = Screen:getSize(), main_menu, } diff --git a/frontend/ui/reader/readerpanning.lua b/frontend/ui/reader/readerpanning.lua index 3090a2927..740635dc0 100644 --- a/frontend/ui/reader/readerpanning.lua +++ b/frontend/ui/reader/readerpanning.lua @@ -1,12 +1,4 @@ ReaderPanning = InputContainer:new{ - key_events = { - -- these will all generate the same event, just with different arguments - MoveUp = { {"Up"}, doc = "move visible area up", event = "Panning", args = {0, -1} }, - MoveDown = { {"Down"}, doc = "move visible area down", event = "Panning", args = {0, 1} }, - MoveLeft = { {"Left"}, doc = "move visible area left", event = "Panning", args = {-1, 0} }, - MoveRight = { {"Right"}, doc = "move visible area right", event = "Panning", args = {1, 0} }, - }, - -- defaults panning_steps = { normal = 50, @@ -16,6 +8,27 @@ ReaderPanning = InputContainer:new{ }, } +function ReaderPanning:init() + if Device:isTouchDevice() then + else + self.key_events = { + -- these will all generate the same event, just with different arguments + MoveUp = { + { "Up" }, doc = "move visible area up", + event = "Panning", args = {0, -1} }, + MoveDown = { + { "Down" }, doc = "move visible area down", + event = "Panning", args = {0, 1} }, + MoveLeft = { + { "Left" }, doc = "move visible area left", + event = "Panning", args = {-1, 0} }, + MoveRight = { + { "Right" }, doc = "move visible area right", + event = "Panning", args = {1, 0} }, + } + end +end + function ReaderPanning:onSetDimensions(dimensions) self.dimen = dimensions end diff --git a/frontend/ui/reader/readertoc.lua b/frontend/ui/reader/readertoc.lua index 9671154c2..1c7b9407f 100644 --- a/frontend/ui/reader/readertoc.lua +++ b/frontend/ui/reader/readertoc.lua @@ -1,12 +1,18 @@ ReaderToc = InputContainer:new{ - key_events = { - ShowToc = { {"T"}, doc = "show Table of Content menu"}, - }, - dimen = Geom:new{ w = Screen:getWidth()-20, h = Screen:getHeight()-20}, - current_page = 0, - current_pos = 0, + toc_menu_title = "Table of contents", } +function ReaderToc:init() + if not Device:hasNoKeyboard() then + self.key_events = { + ShowToc = { + { "T" }, + doc = "show Table of Content menu" }, + } + end + self.ui.menu:registerToMainMenu(self) +end + function ReaderToc:cleanUpTocTitle(title) return (title:gsub("\13", "")) end @@ -15,20 +21,24 @@ function ReaderToc:onSetDimensions(dimen) self.dimen = dimen end ---function ReaderToc:fillToc() - --self.toc = self.doc:getToc() ---end +function ReaderToc:fillToc() + self.toc = self.ui.document:getToc() +end --- getTocTitleByPage wrapper, so specific reader +-- _getTocTitleByPage wrapper, so specific reader -- can tranform pageno according its need -function ReaderToc:getTocTitleByPage(pageno) - return self:_getTocTitleByPage(pageno) +function ReaderToc:getTocTitleByPage(pn_or_xp) + local page = pn_or_xp + if type(pn_or_xp) == "string" then + page = self.ui.document:getPageFromXPointer(pn_or_xp) + end + return self:_getTocTitleByPage(page) end function ReaderToc:_getTocTitleByPage(pageno) if not self.toc then - -- build toc when needed. - self:fillToc() + -- build toc when needed. + self:fillToc() end -- no table of content @@ -56,29 +66,36 @@ function ReaderToc:onShowToc() for _,v in ipairs(items) do v.text = (" "):rep(v.depth-1)..self:cleanUpTocTitle(v.title) end + local toc_menu = Menu:new{ title = "Table of Contents", item_table = items, - dimen = self.dimen, - ui = self.ui + ui = self.ui, + width = Screen:getWidth()-20, + height = Screen:getHeight(), } function toc_menu:onMenuChoice(item) self.ui:handleEvent(Event:new("PageUpdate", item.page)) end - UIManager:show(toc_menu) + local menu_container = CenterContainer:new{ + dimen = Screen:getSize(), + toc_menu, + } + toc_menu.close_callback = function() + UIManager:close(menu_container) + end + + UIManager:show(menu_container) + return true end -function ReaderToc:onSetDimensions(dimen) - self.dimen = dimen +function ReaderToc:addToMainMenu(item_table) + -- insert table to main reader menu + table.insert(item_table, { + text = self.toc_menu_title, + callback = function() + self:onShowToc() + end, + }) end - -function ReaderToc:onPageUpdate(new_page_no) - self.current_page = new_page_no -end - -function ReaderToc:onPosUpdate(new_pos) - self.current_pos = new_pos -end - - diff --git a/frontend/ui/reader/readerview.lua b/frontend/ui/reader/readerview.lua index cfe995d58..b22be0eb7 100644 --- a/frontend/ui/reader/readerview.lua +++ b/frontend/ui/reader/readerview.lua @@ -122,6 +122,8 @@ function ReaderView:PanningUpdate(dx, dy) UIManager:setDirty(self.dialog) DEBUG("on pan: page_area", self.page_area) DEBUG("on pan: visible_area", self.visible_area) + self.ui:handleEvent( + Event:new("ViewRecalculate", self.visible_area, self.page_area)) end return true end diff --git a/frontend/ui/readerui.lua b/frontend/ui/readerui.lua index 19d953540..d8bb9dcfb 100644 --- a/frontend/ui/readerui.lua +++ b/frontend/ui/readerui.lua @@ -6,6 +6,7 @@ require "ui/reader/readerrotation" require "ui/reader/readerpaging" require "ui/reader/readerrolling" require "ui/reader/readertoc" +require "ui/reader/readerbookmark" require "ui/reader/readerfont" require "ui/reader/readermenu" @@ -18,7 +19,6 @@ it works using data gathered from a document interface ReaderUI = InputContainer:new{ key_events = { Close = { {"Home"}, doc = "close document", event = "Close" }, - Back = { {"Back"}, doc = "close document", event = "Close" }, }, -- our own size @@ -41,6 +41,12 @@ function ReaderUI:init() self.dialog = self end + if Device:hasKeyboard() then + self.key_events.Back = { + { "Back" }, doc = "close document", + event = "Close" } + end + self.doc_settings = DocSettings:open(self.document.file) -- a view container (so it must be child #1!) @@ -56,18 +62,26 @@ function ReaderUI:init() view = self[1], ui = self } - -- Toc menu controller - self[3] = ReaderToc:new{ + -- reader menu controller + self[3] = ReaderMenu:new{ + view = self[1], + ui = self + } + self.menu = self[3] -- hold reference to menu widget + -- Table of content controller + self[4] = ReaderToc:new{ dialog = self.dialog, view = self[1], ui = self } - -- reader menu controller - self[4] = ReaderMenu:new{ + self.toc = self[4] -- hold reference to bm widget + -- bookmark controller + local reader_bm = ReaderBookmark:new{ + dialog = self.dialog, view = self[1], ui = self } - self.menu = self[4] -- hold reference to menu widget + table.insert(self, reader_bm) if self.document.info.has_pages then -- for page specific controller diff --git a/frontend/ui/widget.lua b/frontend/ui/widget.lua index df50504d0..30a29d879 100644 --- a/frontend/ui/widget.lua +++ b/frontend/ui/widget.lua @@ -126,9 +126,15 @@ function CenterContainer:paintTo(bb, x, y) -- 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)/2) + 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 --[[ @@ -605,6 +611,10 @@ function InputContainer:_init() 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)