diff --git a/frontend/document/documentregistry.lua b/frontend/document/documentregistry.lua index b33a298ea..a4dc1aaf9 100644 --- a/frontend/document/documentregistry.lua +++ b/frontend/document/documentregistry.lua @@ -2,8 +2,8 @@ This is a registry for document providers ]]-- -local ButtonDialogTitle = require("ui/widget/buttondialogtitle") local ConfirmBox = require("ui/widget/confirmbox") +local OpenWithDialog = require("ui/widget/openwithdialog") local UIManager = require("ui/uimanager") local gettext = require("gettext") local logger = require("logger") @@ -124,30 +124,36 @@ function DocumentRegistry:showSetProviderButtons(file, filemanager_instance, ui local filename_suffix = util.getFileNameSuffix(file) local buttons = {} + local radio_buttons = {} local providers = self:getProviders(file) for _, provider in ipairs(providers) do -- we have no need for extension, mimetype, weights, etc. here provider = provider.provider - table.insert(buttons, { + table.insert(radio_buttons, { { - text = string.format("** %s **", provider.provider_name), + text = provider.provider_name, + checked = self:getProvider(file) == provider, + provider = provider, }, }) - table.insert(buttons, { - { - text = gettext("Just once"), - callback = function() - filemanager_instance:onClose() - reader_ui:showReader(file, provider) - UIManager:close(self.set_provider_dialog) - end, - }, - }) - table.insert(buttons, { - { - text = gettext("This document"), - callback = function() + end + + table.insert(buttons, { + { + text = gettext("Cancel"), + callback = function() + UIManager:close(self.set_provider_dialog) + end, + }, + { + text = gettext("Open"), + is_enter_default = true, + callback = function() + local provider = self.set_provider_dialog.radio_button_table.checked_button.provider + + -- always for this file + if self.set_provider_dialog._check_file_button.checked then UIManager:show(ConfirmBox:new{ text = T(gettext("Always open '%2' with %1?"), provider.provider_name, filename_pure), @@ -160,13 +166,8 @@ function DocumentRegistry:showSetProviderButtons(file, filemanager_instance, ui UIManager:close(self.set_provider_dialog) end, }) - end, - }, - }) - table.insert(buttons, { - { - text = gettext("All documents"), - callback = function() + -- always for all files of this file type + elseif self.set_provider_dialog._check_global_button.checked then UIManager:show(ConfirmBox:new{ text = T(gettext("Always open %2 files with %1?"), provider.provider_name, filename_suffix), @@ -179,15 +180,19 @@ function DocumentRegistry:showSetProviderButtons(file, filemanager_instance, ui UIManager:close(self.set_provider_dialog) end, }) - end, - }, - }) - -- little trick for visual separation - table.insert(buttons, {}) - end + else + -- just once + filemanager_instance:onClose() + reader_ui:showReader(file, provider) + UIManager:close(self.set_provider_dialog) + end + end, + }, + }) - self.set_provider_dialog = ButtonDialogTitle:new{ + self.set_provider_dialog = OpenWithDialog:new{ title = T(gettext("Open %1 with:"), filename_pure), + radio_buttons = radio_buttons, buttons = buttons, } UIManager:show(self.set_provider_dialog) diff --git a/frontend/ui/widget/checkbutton.lua b/frontend/ui/widget/checkbutton.lua index 7fa8502e4..16c856c91 100644 --- a/frontend/ui/widget/checkbutton.lua +++ b/frontend/ui/widget/checkbutton.lua @@ -49,6 +49,8 @@ function CheckButton:initCheckButton(checked) self.checked = checked self._checkmark = CheckMark:new{ checked = self.checked, + parent = self.parent or self, + show_parent = self.show_parent or self, } self._textwidget = TextWidget:new{ text = self.text, @@ -64,8 +66,9 @@ function CheckButton:initCheckButton(checked) padding = 0, self._horizontalgroup, } - self[1] = self._frame self.dimen = self._frame:getSize() + self[1] = self._frame + if Device:isTouchDevice() then self.ges_events = { TapCheckButton = { diff --git a/frontend/ui/widget/openwithdialog.lua b/frontend/ui/widget/openwithdialog.lua new file mode 100644 index 000000000..39d731617 --- /dev/null +++ b/frontend/ui/widget/openwithdialog.lua @@ -0,0 +1,157 @@ +--[[-- +This widget displays an open with dialog. +]] + +local Blitbuffer = require("ffi/blitbuffer") +local CenterContainer = require("ui/widget/container/centercontainer") +local CheckButton = require("ui/widget/checkbutton") +local FrameContainer = require("ui/widget/container/framecontainer") +local Geom = require("ui/geometry") +local InputDialog = require("ui/widget/inputdialog") +local LeftContainer = require("ui/widget/container/leftcontainer") +local LineWidget = require("ui/widget/linewidget") +local RadioButtonTable = require("ui/widget/radiobuttontable") +local Size = require("ui/size") +local VerticalGroup = require("ui/widget/verticalgroup") +local VerticalSpan = require("ui/widget/verticalspan") +local _ = require("gettext") +local Screen = require("device").screen + +local OpenWithDialog = InputDialog:extend{} + +function OpenWithDialog:init() + -- init title and buttons in base class + InputDialog.init(self) + + self.radio_button_table = RadioButtonTable:new{ + radio_buttons = self.radio_buttons, + width = self.width * 0.9, + focused = true, + scroll = false, + parent = self, + } + + self._check_file_button = self._check_file_button or CheckButton:new{ + text = _("Always use this engine for this file"), + callback = function() + if self._check_file_button.checked then + self._check_file_button:unCheck() + else + self._check_file_button:check() + end + end, + + width = self.width * 0.9, + height = self.height, + + parent = self, + } + self._always_file_toggle = LeftContainer:new{ + bordersize = 0, + dimen = Geom:new{ + w = self.width * 0.9, + h = self._check_file_button:getSize().h, + }, + self._check_file_button, + } + + self._check_global_button = self._check_global_button or CheckButton:new{ + text = _("Always use this engine for file type"), + callback = function() + if self._check_global_button.checked then + self._check_global_button:unCheck() + else + self._check_global_button:check() + end + end, + + width = self.width * 0.9, + height = self.height, + + parent = self, + } + self._always_global_toggle = LeftContainer:new{ + bordersize = 0, + dimen = Geom:new{ + w = self.width * 0.9, + h = self._check_global_button:getSize().h, + }, + self._check_global_button, + } + + self.dialog_frame = FrameContainer:new{ + radius = Size.radius.window, + padding = 0, + margin = 0, + background = Blitbuffer.COLOR_WHITE, + VerticalGroup:new{ + align = "left", + self.title, + self.title_bar, + VerticalSpan:new{ + width = Size.span.vertical_large*2, + }, + CenterContainer:new{ + dimen = Geom:new{ + w = self.title_bar:getSize().w, + h = self.radio_button_table:getSize().h, + }, + self.radio_button_table, + }, + CenterContainer:new{ + dimen = Geom:new{ + w = self.title_bar:getSize().w, + h = Size.span.vertical_large*2, + }, + LineWidget:new{ + background = Blitbuffer.COLOR_GREY, + dimen = Geom:new{ + w = self.width * 0.9, + h = Size.line.medium, + } + }, + }, + CenterContainer:new{ + dimen = Geom:new{ + w = self.title_bar:getSize().w, + h = self._always_file_toggle:getSize().h, + }, + self._always_file_toggle, + }, + CenterContainer:new{ + dimen = Geom:new{ + w = self.title_bar:getSize().w, + h = self._always_global_toggle:getSize().h, + }, + self._always_global_toggle, + }, + VerticalSpan:new{ + width = Size.span.vertical_large*2, + }, + -- buttons + CenterContainer:new{ + dimen = Geom:new{ + w = self.title_bar:getSize().w, + h = self.button_table:getSize().h, + }, + self.button_table, + } + } + } + + self._input_widget = self.radio_button_table + + self[1] = CenterContainer:new{ + dimen = Geom:new{ + w = Screen:getWidth(), + h = Screen:getHeight(), + }, + self.dialog_frame, + } +end + +function OpenWithDialog:onCloseWidget() + return true +end + +return OpenWithDialog diff --git a/frontend/ui/widget/radiobutton.lua b/frontend/ui/widget/radiobutton.lua new file mode 100644 index 000000000..da55a1507 --- /dev/null +++ b/frontend/ui/widget/radiobutton.lua @@ -0,0 +1,167 @@ +--[[-- +Widget that shows a radiobutton checked (`◉`) or unchecked (`◯`) +or nothing of the same size. + +Example: + + local RadioButton = require("ui/widget/radiobutton") + local parent_widget = FrameContainer:new{} + table.insert(parent_widget, RadioButton:new{ + checkable = false, -- shows nothing when false, defaults to true + checked = function() end, -- whether the box is checked + }) + UIManager:show(parent_widget) + +]] + +local Blitbuffer = require("ffi/blitbuffer") +local Device = require("device") +local Font = require("ui/font") +local FrameContainer = require("ui/widget/container/framecontainer") +local Geom = require("ui/geometry") +local GestureRange = require("ui/gesturerange") +local InputContainer = require("ui/widget/container/inputcontainer") +local LeftContainer = require("ui/widget/container/leftcontainer") +local TextWidget = require("ui/widget/textwidget") +local UIManager = require("ui/uimanager") + +local RadioButton = InputContainer:new{ + checkable = true, + checked = false, + enabled = true, + face = Font:getFace("smallinfofont"), + background = Blitbuffer.COLOR_WHITE, + width = 0, + height = 0, +} + +function RadioButton:init() + self._checked_widget = TextWidget:new{ + text = "◉ " .. self.text, + face = self.face, + } + self._unchecked_widget = TextWidget:new{ + text = "◯ " .. self.text, + face = self.face, + } + self._empty_widget = TextWidget:new{ + text = "" .. self.text, + face = self.face, + } + self._widget_size = self._unchecked_widget:getSize() + if self.width == nil then + self.width = self._widget_size.w + end + self._radio_button = self.checkable + and (self.checked and self._checked_widget or self._unchecked_widget) + or self._empty_widget + self:update() + self.dimen = self.frame:getSize() + if Device:isTouchDevice() then + self.ges_events = { + TapCheckButton = { + GestureRange:new{ + ges = "tap", + range = self.dimen, + }, + doc = "Tap Button", + }, + HoldCheckButton = { + GestureRange:new{ + ges = "hold", + range = self.dimen, + }, + doc = "Hold Button", + } + } + end +end + +function RadioButton:update() + if self[1] then + self[1]:free() + end + self.frame = FrameContainer:new{ + margin = self.margin, + bordersize = self.bordersize, + background = self.background, + radius = self.radius, + padding = self.padding, + LeftContainer:new{ + dimen = Geom:new{ + w = self.width, + h = self._widget_size.h + }, + self._radio_button, + } + } + self[1] = self.frame +end + +function RadioButton:onFocus() + self.frame.invert = true + return true +end + +function RadioButton:onUnfocus() + self.frame.invert = false + return true +end + +function RadioButton:onTapCheckButton() + if self.enabled and self.callback then + if G_reader_settings:isFalse("flash_ui") then + self.callback() + else + UIManager:scheduleIn(0.0, function() + self.invert = true + UIManager:setDirty(self.show_parent, function() + return "ui", self.dimen + end) + end) + UIManager:scheduleIn(0.1, function() + self.callback() + self.invert = false + UIManager:setDirty(self.show_parent, function() + return "ui", self.dimen + end) + end) + end + elseif self.tap_input then + self:onInput(self.tap_input) + elseif type(self.tap_input_func) == "function" then + self:onInput(self.tap_input_func()) + end + return true +end + +function RadioButton:onHoldCheckButton() + if self.enabled and self.hold_callback then + self.hold_callback() + elseif self.hold_input then + self:onInput(self.hold_input) + elseif type(self.hold_input_func) == "function" then + self:onInput(self.hold_input_func()) + end + return true +end + +function RadioButton:check(callback) + self._radio_button = self._checked_widget + self.checked = true + self:update() + UIManager:setDirty(self.parent, function() + return "ui", self.dimen + end) +end + +function RadioButton:unCheck() + self._radio_button = self._unchecked_widget + self.checked = false + self:update() + UIManager:setDirty(self.parent, function() + return "ui", self.dimen + end) +end + +return RadioButton diff --git a/frontend/ui/widget/radiobuttontable.lua b/frontend/ui/widget/radiobuttontable.lua new file mode 100644 index 000000000..62aa829f7 --- /dev/null +++ b/frontend/ui/widget/radiobuttontable.lua @@ -0,0 +1,160 @@ +local Blitbuffer = require("ffi/blitbuffer") +local Device = require("device") +local FocusManager = require("ui/widget/focusmanager") +local Geom = require("ui/geometry") +local HorizontalGroup = require("ui/widget/horizontalgroup") +local LineWidget = require("ui/widget/linewidget") +local RadioButton = require("ui/widget/radiobutton") +local Size = require("ui/size") +local VerticalGroup = require("ui/widget/verticalgroup") +local VerticalSpan = require("ui/widget/verticalspan") +local dbg = require("dbg") +local Screen = Device.screen + +local RadioButtonTable = FocusManager:new{ + width = Screen:getWidth(), + radio_buttons = { + { + {text="Cancel", enabled=false, callback=nil}, + {text="OK", enabled=true, callback=nil}, + }, + }, + sep_width = Size.line.medium, + padding = Size.padding.button, + + zero_sep = false, + button_font_face = "cfont", + button_font_size = 20, + + _first_button = nil, + checked_button = nil, +} + +function RadioButtonTable:init() + self.selected = { x = 1, y = 1 } + self.radio_buttons_layout = {} + self.container = VerticalGroup:new{ width = self.width } + table.insert(self, self.container) + + if self.zero_sep then + -- If we're asked to add a first line, don't add a vspan before: caller + -- must do its own padding before. + -- Things look better when the first line is gray like the others. + self:addHorizontalSep(false, true, true) + else + self:addHorizontalSep(false, false, true) + end + + local row_cnt = #self.radio_buttons + + for i = 1, row_cnt do + self.radio_buttons_layout[i] = {} + local horizontal_group = HorizontalGroup:new{} + local row = self.radio_buttons[i] + local column_cnt = #row + local sizer_space = self.sep_width * (column_cnt - 1) + 2 + for j = 1, column_cnt do + local btn_entry = row[j] + local button = RadioButton:new{ + text = btn_entry.text, + enabled = btn_entry.enabled, + checked = btn_entry.checked, + provider = btn_entry.provider, + + width = (self.width - sizer_space)/column_cnt, + max_width = (self.width - sizer_space)/column_cnt - 2*self.sep_width - 2*self.padding, + bordersize = 0, + margin = 0, + padding = 0, + text_font_face = self.button_font_face, + text_font_size = self.button_font_size, + + show_parent = self.show_parent or self, + parent = self.parent or self, + } + local button_callback = function() + self:_checkButton(button) + end + button.callback = button_callback + + if i == 1 and j == 1 then + self._first_button = button + end + + if button.checked and not self.checked_button then + self.checked_button = button + elseif dbg.is_on and + button.checked and self.checked_button then + error("RadioButtonGroup: multiple checked RadioButtons") + end + + local button_dim = button:getSize() + local vertical_sep = LineWidget:new{ + background = Blitbuffer.COLOR_GREY, + dimen = Geom:new{ + w = self.sep_width, + h = button_dim.h, + } + } + self.radio_buttons_layout[i][j] = button + table.insert(horizontal_group, button) + if j < column_cnt then + table.insert(horizontal_group, vertical_sep) + end + end -- end for each button + table.insert(self.container, horizontal_group) + --if i < row_cnt then + --self:addHorizontalSep(true, true, true) + --end + end -- end for each button line + self:addHorizontalSep(true, false, false) + + -- check first entry unless otherwise specified + if not self.checked_button then + self._first_button:check() + self.checked_button = self._first_button + end + + if Device:hasDPad() or Device:hasKeyboard() then + self.layout = self.radio_buttons_layout + self.layout[1][1]:onFocus() + self.key_events.SelectByKeyPress = { {{"Press", "Enter"}} } + else + self.key_events = {} -- deregister all key press event listeners + end +end + +function RadioButtonTable:addHorizontalSep(vspan_before, add_line, vspan_after, black_line) + if vspan_before then + table.insert(self.container, + VerticalSpan:new{ width = Size.span.vertical_default }) + end + if add_line then + table.insert(self.container, LineWidget:new{ + background = black_line and Blitbuffer.COLOR_BLACK or Blitbuffer.COLOR_GREY, + dimen = Geom:new{ + w = self.width, + h = self.sep_width, + } + }) + end + if vspan_after then + table.insert(self.container, + VerticalSpan:new{ width = Size.span.vertical_default }) + end +end + +function RadioButtonTable:onSelectByKeyPress() + self:getFocusItem().callback() +end + +function RadioButtonTable:_checkButton(button) + -- nothing to do + if button.checked then return end + + self.checked_button:unCheck() + button:check() + self.checked_button = button +end + +return RadioButtonTable