diff --git a/frontend/ui/widget/sortwidget.lua b/frontend/ui/widget/sortwidget.lua index 81562a12a..7e571cb42 100644 --- a/frontend/ui/widget/sortwidget.lua +++ b/frontend/ui/widget/sortwidget.lua @@ -84,7 +84,7 @@ function SortItemWidget:init() TextWidget:new{ text = self.item.text, max_width = text_max_width, - face = self.face, + face = self.item.face or self.face, }, }, }, @@ -98,7 +98,11 @@ function SortItemWidget:onTap(_, ges) self.item:callback() end elseif self.show_parent.sort_disabled then - return true + if self.item.callback then + self.item:callback() + else + return true + end elseif self.show_parent.marked == self.index then self.show_parent.marked = 0 else @@ -109,7 +113,9 @@ function SortItemWidget:onTap(_, ges) end function SortItemWidget:onHold() - if self.item.callback then + if self.item.hold_callback then + self.item:hold_callback(function() self.show_parent:_populateItems() end) + elseif self.item.callback then self.item:callback() self.show_parent:_populateItems() end diff --git a/plugins/vocabbuilder.koplugin/db.lua b/plugins/vocabbuilder.koplugin/db.lua index 236505306..807a4be0d 100644 --- a/plugins/vocabbuilder.koplugin/db.lua +++ b/plugins/vocabbuilder.koplugin/db.lua @@ -65,7 +65,6 @@ function VocabularyBuilder:createDB() -- Update version db_conn:exec(string.format("PRAGMA user_version=%d;", DB_SCHEMA_VERSION)) elseif db_version < DB_SCHEMA_VERSION then - local logger = require("logger") local ok, re local log = function(msg) logger.warn("[vocab builder db migration]", msg) @@ -298,6 +297,36 @@ function VocabularyBuilder:toggleBookFilter(ids) conn:close() end +function VocabularyBuilder:updateBookIdOfWord(word, id) + if not word or type(id) ~= "number" then return end + local conn = SQ3.open(db_location) + local stmt = conn:prepare("UPDATE vocabulary SET title_id = ? WHERE word = ?;") + stmt:bind(id, word) + stmt:step() + stmt:clearbind():reset() + conn:close() +end + +function VocabularyBuilder:insertNewBook(title) + local conn = SQ3.open(db_location) + local stmt = conn:prepare("INSERT INTO title (name) VALUES (?);") + stmt:bind(title):step() + stmt:clearbind():reset() + stmt = conn:prepare("SELECT id FROM title WHERE name = ?") + local result = stmt:bind(title):step() + stmt:clearbind():reset() + conn:close() + return tonumber(result[1]) +end + +function VocabularyBuilder:changeBookTitle(old_title, title) + local conn = SQ3.open(db_location) + local stmt = conn:prepare("UPDATE title SET name = ? WHERE name = ?;") + stmt:bind(title, old_title):step() + stmt:clearbind():reset() + conn:close() +end + function VocabularyBuilder:selectBooks() local conn = SQ3.open(db_location) local sql = string.format("SELECT * FROM title") diff --git a/plugins/vocabbuilder.koplugin/main.lua b/plugins/vocabbuilder.koplugin/main.lua index 716a0a5b2..947724041 100644 --- a/plugins/vocabbuilder.koplugin/main.lua +++ b/plugins/vocabbuilder.koplugin/main.lua @@ -99,44 +99,6 @@ local function resetButtonOnLookupWindow() end end -local function onShowFilter(widget) - local sort_items = {} - local book_data = DB:selectBooks() - local toggled = {} - for _, ifo in pairs(book_data) do - table.insert(sort_items, { - text = ifo.name or "", - callback = function() - ifo.filter = not ifo.filter - if toggled[ifo.id] then - toggled[ifo.id] = nil - else - toggled[ifo.id] = true - end - end, - checked_func = function() - return ifo.filter - end, - ifo = ifo, - }) - end - - local sort_widget = SortWidget:new{ - title = _("Filter words from books"), - item_table = sort_items, - sort_disabled = true, - callback = function() - if #toggled then - DB:toggleBookFilter(toggled) - widget:reloadItems() - end - - UIManager:setDirty(nil, "ui") - end - } - UIManager:show(sort_widget) -end - local function saveSettings() G_reader_settings:saveSetting("vocabulary_builder", settings) end @@ -227,7 +189,7 @@ function MenuDialog:init() text = _("Filter books"), callback = function() self:onClose() - onShowFilter(self.show_parent) + self.show_parent:onShowFilter() end } @@ -337,7 +299,7 @@ function MenuDialog:init() } local type = server.type == "dropbox" and " (DropBox)" or " (WebDAV)" self.sync_dialogue = ButtonDialogTitle:new{ - title = T(_("Cloud storage:\n%1\n\nFolder path:\n%2\n\nSet up the same cloud folder on each device to sync across your devices"), + title = T(_("Cloud storage:\n%1\n\nFolder path:\n%2\n\nSet up the same cloud folder on each device to sync across your devices."), server.name.." "..type, SyncService.getReadablePath(server)), info_face = Font:getFace("smallinfofont"), buttons = buttons, @@ -481,6 +443,7 @@ local WordInfoDialog = InputContainer:extend{ reset_callback = nil, dismissable = true, -- set to false if any button callback is required } +local book_title_triangle = BD.mirroredUILayout() and " ⯇" or " ⯈" local word_info_dialog_width function WordInfoDialog:init() if self.dismissable then @@ -556,6 +519,24 @@ function WordInfoDialog:init() end, bordersize = 0, } + self.book_title_button = Button:new{ + text = self.book_title .. book_title_triangle, + width = width, + max_width = width, + text_font_face = "NotoSans-Italic.ttf", + text_font_size = 14, + text_font_bold = false, + align = self.title_align or "left", + padding = Size.padding.button, + bordersize = 0, + callback = function() + self.show_parent:onShowBookAssignment(function(new_book_title) + self.book_title = new_book_title + self.book_title_button:setText(new_book_title..book_title_triangle, width) + end) + end, + show_parent = self + } local has_context = self.prev_context or self.next_context self[1] = CenterContainer:new{ dimen = Screen:getSize(), @@ -582,12 +563,7 @@ function WordInfoDialog:init() HorizontalSpan:new{ width=Size.padding.default }, copy_button, }, - TextBoxWidget:new{ - text = self.book_title, - width = width, - face = Font:getFace("NotoSans-Italic.ttf", 15), - alignment = self.title_align or "left", - }, + self.book_title_button, VerticalSpan:new{width= Size.padding.default}, has_context and TextBoxWidget:new{ @@ -1047,7 +1023,7 @@ function VocabItemWidget:onTap(_, ges) end function VocabItemWidget:onHold(_, ges) - self:onTap(_, ges) + self:onShowBookAssignment() return true end @@ -1074,6 +1050,110 @@ function VocabItemWidget:onForgot(no_lookup) end +function VocabItemWidget:onShowBookAssignment(title_changed_cb) + local sort_items = {} + local book_data = DB:selectBooks() + local sort_widget + local book = self.item.book_title + local id + for _, info in pairs(book_data) do + table.insert(sort_items, { + text = info.name or "", + callback = function() + id = info.id + book = info.name + end, + checked_func = function() + return info.name == book + end, + hold_callback = function(sort_item, onSuccess) + self.show_parent:showChangeBookTitleDialog(sort_item, function() + onSuccess() + if self.item.book_title == info.name then + if book == self.item.book_title then + book = sort_item.text + end + self.item.book_title = sort_item.text + if title_changed_cb then title_changed_cb(sort_item.text) end + end + end) + end + }) + end + table.insert(sort_items, { + text = _("Add virtual book"), + face = Font:getFace("smallinfofontbold"), + callback = function() + local dialog + dialog = require("ui/widget/inputdialog"):new{ + title = _("Enter book title:"), + input = "", + input_type = "text", + buttons = { + { + { + text = _("Cancel"), + id = "close", + callback = function() + UIManager:close(dialog) + end, + }, + { + text = _("Add"), + is_enter_default = true, + callback = function() + if dialog:getInputText() == "" then return end + local new_book_title = dialog:getInputText() + local ok, new_id = pcall(DB.insertNewBook, DB, new_book_title) + if ok then + UIManager:close(dialog) + table.insert(sort_items, #sort_items, { + text = new_book_title, + callback = function() + id = new_id + book = new_book_title + end, + checked_func = function() + return new_book_title == book + end, + hold_callback = function(sort_item, onSuccess) + self.show_parent:showChangeBookTitleDialog(sort_item, onSuccess) + end + }) + sort_widget:goToPage(sort_widget.show_page) + else + UIManager:show(require("ui/widget/notification"):new{ + text = _("Book title already in use."), + timeout = 3 + }) + end + end, + }, + } + }, + } + UIManager:show(dialog) + dialog:onShowKeyboard() + end + }) + + sort_widget = SortWidget:new{ + title = T(_("Move \"%1\" to book:"), self.item.word), + item_table = sort_items, + sort_disabled = true, + callback = function() + if book ~= self.item.book_title then + self.item.book_title = book + DB:updateBookIdOfWord(self.item.word, id) + self:initItemWidget() + if title_changed_cb then title_changed_cb(book) end + end + UIManager:setDirty(nil, "ui") + end + } + UIManager:show(sort_widget) +end + --[[-- Container widget. Same as sortwidget @@ -1484,6 +1564,94 @@ function VocabularyBuilderWidget:check_reverse() return settings.reverse end + +function VocabularyBuilderWidget:onShowFilter() + local sort_items = {} + local book_data = DB:selectBooks() + local toggled = {} + for _, info in pairs(book_data) do + table.insert(sort_items, { + text = info.name or "", + callback = function() + info.filter = not info.filter + if toggled[info.id] then + toggled[info.id] = nil + else + toggled[info.id] = true + end + end, + checked_func = function() + return info.filter + end, + hold_callback = function(sort_item, onSuccess) + self:showChangeBookTitleDialog(sort_item, onSuccess) + end, + }) + end + + local sort_widget = SortWidget:new{ + title = _("Filter words from books"), + item_table = sort_items, + sort_disabled = true, + callback = function() + if #toggled then + DB:toggleBookFilter(toggled) + self:reloadItems() + end + + UIManager:setDirty(nil, "ui") + end + } + UIManager:show(sort_widget) +end + +function VocabularyBuilderWidget:showChangeBookTitleDialog(sort_item, onSuccess) + local dialog + dialog = require("ui/widget/inputdialog"):new { + title = _("Change book title to:"), + input = sort_item.text, + input_type = "text", + buttons = { + { + { + text = _("Cancel"), + id = "close", + callback = function() + UIManager:close(dialog) + end, + }, + { + text = _("Change title"), + is_enter_default = true, + callback = function() + if dialog:getInputText() == "" then return end + local new_book_title = dialog:getInputText() + local ok = pcall(DB.changeBookTitle, DB, sort_item.text, new_book_title) + if ok then + for i=1, #self.item_table do + if self.item_table[i].book_title == sort_item.text then + self.item_table[i].book_title = new_book_title + end + end + sort_item.text = new_book_title + UIManager:close(dialog) + if onSuccess then onSuccess() end + self:_populateItems() + else + UIManager:show(require("ui/widget/notification"):new { + text = _("Book title already in use."), + timeout = 3 + }) + end + end, + }, + } + }, + } + UIManager:show(dialog) + dialog:onShowKeyboard() +end + function VocabularyBuilderWidget:reloadItems() DB:batchUpdateItems(self.item_table) self.item_table = self:reload_items_callback() @@ -1516,7 +1684,7 @@ function VocabularyBuilderWidget:onSwipe(arg, ges_ev) self:onClose() elseif direction == "north" then -- open filter - onShowFilter(self) + self:onShowFilter() else -- diagonal swipe -- trigger full refresh UIManager:setDirty(nil, "full")