From d163f8281d1d43d8321e954bd6519375c825c624 Mon Sep 17 00:00:00 2001 From: Robert Date: Sat, 13 Jan 2018 23:38:53 +0100 Subject: [PATCH] Menu: configure number of items per page, wrap entries (#3589) Configure number of items per page (from 6 to 24) - default is 14 Allow filenames to wrap so that we can see the full name Used by File browser, History, Search Result, Bookmarks, Table of contents (only single line), File chooser, OPDS catalog --- frontend/apps/filemanager/filemanager.lua | 1 + .../filemanager/filemanagerfilesearcher.lua | 1 + frontend/apps/filemanager/filemanagermenu.lua | 20 ++ .../filemanager/filemanagersetdefaults.lua | 1 + .../apps/reader/modules/readerbookmark.lua | 2 + frontend/apps/reader/modules/readertoc.lua | 3 + .../ui/elements/filemanager_menu_order.lua | 1 + frontend/ui/widget/filechooser.lua | 1 + frontend/ui/widget/menu.lua | 230 +++++++++++++----- frontend/ui/widget/spinwidget.lua | 201 +++++++++++++++ 10 files changed, 399 insertions(+), 62 deletions(-) create mode 100644 frontend/ui/widget/spinwidget.lua diff --git a/frontend/apps/filemanager/filemanager.lua b/frontend/apps/filemanager/filemanager.lua index bb60d04df..0c57ae380 100644 --- a/frontend/apps/filemanager/filemanager.lua +++ b/frontend/apps/filemanager/filemanager.lua @@ -148,6 +148,7 @@ function FileManager:init() is_popout = false, is_borderless = true, has_close_button = true, + perpage = G_reader_settings:readSetting("items_per_page"), file_filter = function(filename) if DocumentRegistry:getProvider(filename) then return true diff --git a/frontend/apps/filemanager/filemanagerfilesearcher.lua b/frontend/apps/filemanager/filemanagerfilesearcher.lua index c2d099966..6be83617f 100644 --- a/frontend/apps/filemanager/filemanagerfilesearcher.lua +++ b/frontend/apps/filemanager/filemanagerfilesearcher.lua @@ -152,6 +152,7 @@ function FileSearcher:showSearchResults() show_parent = menu_container, onMenuHold = self.onMenuHold, cface = Font:getFace("smallinfofont"), + perpage = G_reader_settings:readSetting("items_per_page") or 14, _manager = self, } table.insert(menu_container, self.search_menu) diff --git a/frontend/apps/filemanager/filemanagermenu.lua b/frontend/apps/filemanager/filemanagermenu.lua index b4b90a9f6..baf7e5164 100644 --- a/frontend/apps/filemanager/filemanagermenu.lua +++ b/frontend/apps/filemanager/filemanagermenu.lua @@ -91,6 +91,26 @@ function FileManagerMenu:setUpdateItemTable() checked_func = function() return self.ui.file_chooser.show_hidden end, callback = function() self.ui:toggleHiddenFiles() end } + self.menu_items.items_per_page = { + text = _("Items per page"), + callback = function() + local SpinWidget = require("ui/widget/spinwidget") + local curr_items = G_reader_settings:readSetting("items_per_page") or 14 + local items = SpinWidget:new{ + width = Screen:getWidth() * 0.6, + value = curr_items, + value_min = 6, + value_max = 24, + ok_text = _("Set items"), + title_text = _("Items per page"), + callback = function(spin) + G_reader_settings:saveSetting("items_per_page", spin.value) + self.ui:onRefresh() + end + } + UIManager:show(items) + end + } self.menu_items.sort_by = self.ui:getSortingMenuTable() self.menu_items.reverse_sorting = { text = _("Reverse sorting"), diff --git a/frontend/apps/filemanager/filemanagersetdefaults.lua b/frontend/apps/filemanager/filemanagersetdefaults.lua index 3cce9c966..d668a5424 100644 --- a/frontend/apps/filemanager/filemanagersetdefaults.lua +++ b/frontend/apps/filemanager/filemanagersetdefaults.lua @@ -91,6 +91,7 @@ function SetDefaults:init() width = Screen:getWidth()-15, height = Screen:getHeight()-15, cface = Font:getFace("smallinfofont"), + perpage = G_reader_settings:readSetting("items_per_page") or 14, show_parent = menu_container, _manager = self, } diff --git a/frontend/apps/reader/modules/readerbookmark.lua b/frontend/apps/reader/modules/readerbookmark.lua index b0e03146b..abbe4df56 100644 --- a/frontend/apps/reader/modules/readerbookmark.lua +++ b/frontend/apps/reader/modules/readerbookmark.lua @@ -218,6 +218,8 @@ function ReaderBookmark:onShowBookmark() width = Screen:getWidth(), height = Screen:getHeight(), cface = Font:getFace("x_smallinfofont"), + perpage = G_reader_settings:readSetting("items_per_page") or 14, + line_color = require("ffi/blitbuffer").COLOR_WHITE, on_close_ges = { GestureRange:new{ ges = "two_finger_swipe", diff --git a/frontend/apps/reader/modules/readertoc.lua b/frontend/apps/reader/modules/readertoc.lua index b5bd89d5a..dc6c6baf3 100644 --- a/frontend/apps/reader/modules/readertoc.lua +++ b/frontend/apps/reader/modules/readertoc.lua @@ -317,6 +317,9 @@ function ReaderToc:onShowToc() width = Screen:getWidth(), height = Screen:getHeight(), cface = Font:getFace("x_smallinfofont"), + single_line = true, + perpage = G_reader_settings:readSetting("items_per_page") or 14, + line_color = require("ffi/blitbuffer").COLOR_WHITE, on_close_ges = { GestureRange:new{ ges = "two_finger_swipe", diff --git a/frontend/ui/elements/filemanager_menu_order.lua b/frontend/ui/elements/filemanager_menu_order.lua index 707f1f666..ba1cfe111 100644 --- a/frontend/ui/elements/filemanager_menu_order.lua +++ b/frontend/ui/elements/filemanager_menu_order.lua @@ -8,6 +8,7 @@ local order = { setting = { "filemanager_display_mode", "show_hidden_files", + "items_per_page", "----------------------------", "sort_by", "reverse_sorting", diff --git a/frontend/ui/widget/filechooser.lua b/frontend/ui/widget/filechooser.lua index 479923527..3ac67c6b5 100644 --- a/frontend/ui/widget/filechooser.lua +++ b/frontend/ui/widget/filechooser.lua @@ -34,6 +34,7 @@ local FileChooser = Menu:extend{ collate = "strcoll", -- or collate = "access", reverse_collate = false, path_items = {}, -- store last browsed location(item index) for each path + perpage = G_reader_settings:readSetting("items_per_page"), } function FileChooser:init() diff --git a/frontend/ui/widget/menu.lua b/frontend/ui/widget/menu.lua index 689a95c8f..d65c2a289 100644 --- a/frontend/ui/widget/menu.lua +++ b/frontend/ui/widget/menu.lua @@ -21,6 +21,7 @@ local OverlapGroup = require("ui/widget/overlapgroup") local RenderText = require("ui/rendertext") local RightContainer = require("ui/widget/container/rightcontainer") local Size = require("ui/size") +local TextBoxWidget = require("ui/widget/textboxwidget") local TextWidget = require("ui/widget/textwidget") local UIManager = require("ui/uimanager") local UnderlineContainer = require("ui/widget/container/underlinecontainer") @@ -129,10 +130,15 @@ local MenuItem = InputContainer:new{ detail = nil, face = Font:getFace("cfont", 30), info_face = Font:getFace("infont", 15), + font = "cfont", + font_size = 24, + infont = "infont", + infont_size = 18, dimen = nil, shortcut = nil, shortcut_style = "square", _underline_container = nil, + linesize = Size.line.medium, } function MenuItem:init() @@ -179,38 +185,114 @@ function MenuItem:init() text_ellipsis_mandatory_padding = Size.span.horizontal_small end local mandatory = self.mandatory and ""..self.mandatory or "" - local mandatory_w = RenderText:sizeUtf8Text(0, self.dimen.w, self.info_face, - ""..mandatory, true, self.bold).x local state_button_width = self.state_size.w or 0 - local my_text = self.text and ""..self.text or "" - local w = RenderText:sizeUtf8Text(0, self.dimen.w, self.face, my_text, true, self.bold).x - if w + mandatory_w + state_button_width + text_mandatory_padding >= self.content_width then - if Device:hasKeyboard() then - self.active_key_events.ShowItemDetail = { - {"Right"}, doc = "show item detail" - } - end - local indicator = "\226\128\166 " -- an ellipsis - local indicator_w = RenderText:sizeUtf8Text(0, self.dimen.w, self.face, - indicator, true, self.bold).x - self.text = RenderText:getSubTextByWidth(my_text, self.face, - self.content_width - indicator_w - mandatory_w - state_button_width - text_ellipsis_mandatory_padding, - true, self.bold) .. indicator - end - local state_button = self.state or HorizontalSpan:new{ width = state_button_width, } local state_indent = self.state and self.state.indent or "" + local item_name + local mandatory_widget + + if self.single_line then -- items only in single line + self.info_face = Font:getFace(self.infont, self.infont_size) + self.face = Font:getFace(self.font, self.font_size) + + local mandatory_w = RenderText:sizeUtf8Text(0, self.dimen.w, self.info_face, ""..mandatory, true, self.bold).x + + local my_text = self.text and ""..self.text or "" + local w = RenderText:sizeUtf8Text(0, self.dimen.w, self.face, my_text, true, self.bold).x + if w + mandatory_w + state_button_width + text_mandatory_padding >= self.content_width then + if Device:hasKeyboard() then + self.active_key_events.ShowItemDetail = { + {"Right"}, doc = "show item detail" + } + end + local indicator = "\226\128\166 " -- an ellipsis + local indicator_w = RenderText:sizeUtf8Text(0, self.dimen.w, self.face, + indicator, true, self.bold).x + self.text = RenderText:getSubTextByWidth(my_text, self.face, + self.content_width - indicator_w - mandatory_w - state_button_width - text_ellipsis_mandatory_padding, + true, self.bold) .. indicator + end + + item_name = TextWidget:new{ + text = self.text, + face = self.face, + bold = self.bold, + fgcolor = self.dim and Blitbuffer.COLOR_GREY or nil, + } + mandatory_widget = TextWidget:new{ + text = mandatory, + face = self.info_face, + bold = self.bold, + fgcolor = self.dim and Blitbuffer.COLOR_GREY or nil, + } + else + while true do + -- Free previously made widgets to avoid memory leaks + if mandatory_widget then + mandatory_widget:free() + end + mandatory_widget = TextWidget:new { + text = mandatory, + face = Font:getFace(self.infont, self.infont_size), + bold = self.bold, + fgcolor = self.dim and Blitbuffer.COLOR_GREY or nil, + } + local height = mandatory_widget:getSize().h + + + if height < self.dimen.h - 2 * self.linesize then -- we fit ! + break + end + -- Don't go too low + if self.infont_size < 12 then + break; + else + -- If we don't fit, decrease font size + self.infont_size = self.infont_size - 1 + end + end + self.info_face = Font:getFace(self.infont, self.infont_size) + + local mandatory_w = RenderText:sizeUtf8Text(0, self.dimen.w, self.info_face, "" .. mandatory, true, self.bold).x + while true do + -- Free previously made widgets to avoid memory leaks + if item_name then + item_name:free() + end + item_name = TextBoxWidget:new { + text = self.text, + face = Font:getFace(self.font, self.font_size), + width = self.content_width - mandatory_w - state_button_width - text_mandatory_padding, + alignment = "left", + bold = self.bold, + fgcolor = self.dim and Blitbuffer.COLOR_GREY or nil, + } + local height = item_name:getSize().h + if height < self.dimen.h - 2 * self.linesize then -- we fit ! + break + end + -- Don't go too low, and then truncate text + if self.font_size < 12 then + self.text = self.text:sub(1, -5) .. "…" + else + -- If we don't fit, decrease font size + self.font_size = self.font_size - 2 + end + end + self.face = Font:getFace(self.font, self.font_size) + end + local state_container = LeftContainer:new{ dimen = Geom:new{w = self.content_width/2, h = self.dimen.h}, HorizontalGroup:new{ HorizontalSpan:new{ width = RenderText:sizeUtf8Text(0, self.dimen.w, self.face, - state_indent, true, self.bold).x, + state_indent, true, self.bold).x, }, - state_button + state_button, } } local text_container = LeftContainer:new{ @@ -219,27 +301,20 @@ function MenuItem:init() HorizontalSpan:new{ width = self.state_size.w, }, - TextWidget:new{ - text = self.text, - face = self.face, - bold = self.bold, - fgcolor = self.dim and Blitbuffer.COLOR_GREY or nil, - } + item_name, } } local mandatory_container = RightContainer:new{ dimen = Geom:new{w = self.content_width, h = self.dimen.h}, - TextWidget:new{ - text = mandatory, - face = self.info_face, - bold = self.bold, - fgcolor = self.dim and Blitbuffer.COLOR_GREY or nil, - } + mandatory_widget, } self._underline_container = UnderlineContainer:new{ + color = self.line_color, + linesize = self.linesize, vertical_align = "center", + padding = 0, dimen = Geom:new{ w = self.content_width, h = self.dimen.h @@ -282,7 +357,7 @@ function MenuItem:onFocus() end function MenuItem:onUnfocus() - self._underline_container.color = Blitbuffer.COLOR_WHITE + self._underline_container.color = self.line_color self.key_events = {} return true end @@ -394,22 +469,37 @@ local Menu = FocusManager:new{ has_close_button = true, -- close_callback is a function, which is executed when menu is closed -- it is usually set by the widget which creates the menu - close_callback = nil + close_callback = nil, + linesize = Size.line.medium, + perpage = G_reader_settings:readSetting("items_per_page") or 14, + line_color = Blitbuffer.COLOR_GREY, } function Menu:_recalculateDimen() + self.perpage = G_reader_settings:readSetting("items_per_page") or 14 + self.span_width = 0 self.dimen.w = self.width + self.dimen.h = self.height + if self.dimen.h > Screen:getHeight() or self.dimen.h == nil then + self.dimen.h = Screen:getHeight() + end self.item_dimen = Geom:new{ w = self.dimen.w, - h = Screen:scaleBySize(46), -- hardcoded for now + h = Screen:scaleBySize(46), } - -- if height not given, dynamically calculate it - self.dimen.h = self.height or (#self.item_table + 2) * self.item_dimen.h - if self.dimen.h > Screen:getHeight() then - self.dimen.h = Screen:getHeight() + local height_dim + local bottom_height = 0 + local top_height = 0 + if self.page_return_arrow and self.page_info_text then + bottom_height = math.max(self.page_return_arrow:getSize().h, self.page_info_text:getSize().h) + + 2 * Size.padding.button end - -- header and footer should approximately take up space of 2 items - self.perpage = math.floor(self.dimen.h / self.item_dimen.h) - (self.no_title and 1 or 2) + if self.menu_title and not self.no_title then + top_height = self.menu_title:getSize().h + 2 * Size.padding.small + end + height_dim = self.dimen.h - bottom_height - top_height + self.item_dimen.h = math.floor(height_dim / self.perpage) + self.span_width = math.floor((height_dim - (self.perpage * (self.item_dimen.h ))) / 2 -1 ) self.page_num = math.ceil(#self.item_table / self.perpage) -- fix current page if out of range if self.page_num > 0 and self.page > self.page_num then self.page = self.page_num end @@ -418,7 +508,11 @@ end function Menu:init() self.show_parent = self.show_parent or self self.item_table_stack = {} - self:_recalculateDimen() + self.dimen.w = self.width + self.dimen.h = self.height + if self.dimen.h > Screen:getHeight() or self.dimen.h == nil then + self.dimen.h = Screen:getHeight() + end self.page = 1 ----------------------------------- @@ -534,29 +628,30 @@ function Menu:init() } } - local content + self:_recalculateDimen() + self.vertical_span = HorizontalGroup:new{ + VerticalSpan:new{ width = self.span_width } + } if self.no_title then - content = OverlapGroup:new{ - dimen = self.dimen:copy(), - VerticalGroup:new{ - align = "left", - body, - }, - page_return, - footer, + self.content_group = VerticalGroup:new{ + align = "left", + self.vertical_span, + body, } else - content = OverlapGroup:new{ - dimen = self.dimen:copy(), - VerticalGroup:new{ - align = "left", - header, - body, - }, - page_return, - footer, + self.content_group = VerticalGroup:new{ + align = "left", + header, + self.vertical_span, + body, } end + local content = OverlapGroup:new{ + dimen = self.dimen:copy(), + self.content_group, + page_return, + footer, + } self[1] = FrameContainer:new{ background = Blitbuffer.COLOR_WHITE, @@ -566,7 +661,6 @@ function Menu:init() radius = self.is_popout and math.floor(self.dimen.w/20) or 0, content } - ------------------------------------------ -- start to set up input event callback -- ------------------------------------------ @@ -655,12 +749,18 @@ function Menu:updateItems(select_number) self.item_group:clear() self.page_info:resetLayout() self.return_button:resetLayout() + self.vertical_span:clear() + self.content_group:resetLayout() self:_recalculateDimen() -- default to select the first item if not select_number then select_number = 1 end + --font size between 12 and 18 for better matching + local infont_size = math.floor(18 - (self.perpage - 6) / 3) + --font size between 14 and 24 for better matching + local font_size = math.floor(24 - ((self.perpage - 6)/ 18) * 10 ) for c = 1, math.min(self.perpage, #self.item_table) do -- calculate index in item_table @@ -688,12 +788,18 @@ function Menu:updateItems(select_number) mandatory = self.item_table[i].mandatory, bold = self.item_table.current == i or self.item_table[i].bold == true, dim = self.item_table[i].dim, - face = self.cface, + font = "smallinfofont", + font_size = font_size, + infont = "infont", + infont_size = infont_size, dimen = self.item_dimen:new(), shortcut = item_shortcut, shortcut_style = shortcut_style, table = self.item_table[i], menu = self, + linesize = self.linesize, + single_line = self.single_line, + line_color = self.line_color, } table.insert(self.item_group, item_tmp) -- this is for focus manager diff --git a/frontend/ui/widget/spinwidget.lua b/frontend/ui/widget/spinwidget.lua new file mode 100644 index 000000000..428132c8e --- /dev/null +++ b/frontend/ui/widget/spinwidget.lua @@ -0,0 +1,201 @@ +local Blitbuffer = require("ffi/blitbuffer") +local ButtonTable = require("ui/widget/buttontable") +local CenterContainer = require("ui/widget/container/centercontainer") +local CloseButton = require("ui/widget/closebutton") +local Device = require("device") +local FrameContainer = require("ui/widget/container/framecontainer") +local Geom = require("ui/geometry") +local GestureRange = require("ui/gesturerange") +local Font = require("ui/font") +local HorizontalGroup = require("ui/widget/horizontalgroup") +local InputContainer = require("ui/widget/container/inputcontainer") +local LineWidget = require("ui/widget/linewidget") +local OverlapGroup = require("ui/widget/overlapgroup") +local NumberPickerWidget = require("ui/widget/numberpickerwidget") +local Size = require("ui/size") +local TextWidget = require("ui/widget/textwidget") +local UIManager = require("ui/uimanager") +local VerticalGroup = require("ui/widget/verticalgroup") +local WidgetContainer = require("ui/widget/container/widgetcontainer") +local _ = require("gettext") +local Screen = Device.screen + +local SpinWidget = InputContainer:new{ + title_face = Font:getFace("x_smalltfont"), + width = Screen:getWidth() * 0.95, + height = Screen:getHeight(), + value = 1, + value_max = 20, + value_min = 0, + ok_text = _("OK"), + cancel_text = _("Cancel"), +} + +function SpinWidget:init() + self.medium_font_face = Font:getFace("ffont") + self.light_bar = {} + self.screen_width = Screen:getWidth() + self.screen_height = Screen:getHeight() + if Device:hasKeys() then + self.key_events = { + Close = { {"Back"}, doc = "close spin widget" } + } + end + if Device:isTouchDevice() then + self.ges_events = { + TapClose = { + GestureRange:new{ + ges = "tap", + range = Geom:new{ + w = self.screen_width, + h = self.screen_height, + } + }, + }, + } + end + self:update() +end + +function SpinWidget:update() + local value_widget = NumberPickerWidget:new{ + show_parent = self, + width = self.screen_width * 0.2, + value = self.value, + value_min = self.value_min, + value_max = self.value_max, + value_step = 1, + value_hold_step = 4, + } + local value_group = HorizontalGroup:new{ + align = "center", + value_widget, + } + + local value_title = FrameContainer:new{ + padding = Size.padding.default, + margin = Size.margin.title, + bordersize = 0, + TextWidget:new{ + text = self.title_text, + face = self.title_face, + bold = true, + width = self.width, + }, + } + local value_line = LineWidget:new{ + dimen = Geom:new{ + w = self.width, + h = Size.line.thick, + } + } + local value_bar = OverlapGroup:new{ + dimen = { + w = self.width, + h = value_title:getSize().h + }, + value_title, + CloseButton:new{ window = self, padding_top = Size.margin.title, }, + } + local buttons = { + { + { + text = self.cancel_text, + callback = function() + self:onClose() + end, + }, + { + text = self.ok_text, + callback = function() + if self.callback then + self.value = value_widget:getValue() + self:callback(self) + end + self:onClose() + end, + }, + } + } + + local ok_cancel_buttons = ButtonTable:new{ + width = self.width - 2*Size.padding.default, + buttons = buttons, + zero_sep = true, + show_parent = self, + } + + self.spin_frame = FrameContainer:new{ + radius = Size.radius.window, + padding = 0, + margin = 0, + background = Blitbuffer.COLOR_WHITE, + VerticalGroup:new{ + align = "left", + value_bar, + value_line, + CenterContainer:new{ + dimen = Geom:new{ + w = self.width, + h = value_group:getSize().h + self.screen_height * 0.1, + }, + value_group + }, + CenterContainer:new{ + dimen = Geom:new{ + w = self.width, + h = ok_cancel_buttons:getSize().h, + }, + ok_cancel_buttons + } + } + } + self[1] = WidgetContainer:new{ + align = "center", + dimen =Geom:new{ + x = 0, y = 0, + w = self.screen_width, + h = self.screen_height, + }, + FrameContainer:new{ + bordersize = 0, + self.spin_frame, + } + } + UIManager:setDirty(self, function() + return "ui", self.spin_frame.dimen + end) +end + +function SpinWidget:onCloseWidget() + UIManager:setDirty(nil, function() + return "partial", self.spin_frame.dimen + end) + return true +end + +function SpinWidget:onShow() + UIManager:setDirty(self, function() + return "ui", self.spin_frame.dimen + end) + return true +end + +function SpinWidget:onAnyKeyPressed() + UIManager:close(self) + return true +end + +function SpinWidget:onTapClose(arg, ges_ev) + if ges_ev.pos:notIntersectWith(self.spin_frame.dimen) then + self:onClose() + end + return true +end + +function SpinWidget:onClose() + UIManager:close(self) + return true +end + +return SpinWidget