mirror of
https://github.com/koreader/koreader.git
synced 2025-08-10 00:52:38 +00:00
Vocabulary builder: add search ability (#9881)
And allow circle multiswipe to reload words.
This commit is contained in:
@@ -41,8 +41,13 @@ end
|
||||
|
||||
function VocabularyBuilder:selectCount(vocab_widget)
|
||||
local db_conn = SQ3.open(db_location)
|
||||
local where_clause = vocab_widget:check_reverse() and " WHERE due_time <= " .. vocab_widget.reload_time or ""
|
||||
local sql = "SELECT count(0) FROM vocabulary INNER JOIN title ON filter=true AND title_id=id" .. where_clause .. ";"
|
||||
local sql
|
||||
if vocab_widget.search_text_sql then
|
||||
sql = "SELECT count(0) FROM vocabulary WHERE word LIKE '" .. vocab_widget.search_text_sql .. "'"
|
||||
else
|
||||
local where_clause = vocab_widget:check_reverse() and " WHERE due_time <= " .. vocab_widget.reload_time or ""
|
||||
sql = "SELECT count(0) FROM vocabulary INNER JOIN title ON filter=true AND title_id=id" .. where_clause .. ";"
|
||||
end
|
||||
local count = tonumber(db_conn:rowexec(sql))
|
||||
db_conn:close()
|
||||
return count
|
||||
@@ -138,10 +143,12 @@ function VocabularyBuilder:insertLookupData(db_conn)
|
||||
end
|
||||
end
|
||||
|
||||
function VocabularyBuilder:_select_items(items, start_idx, reload_time)
|
||||
function VocabularyBuilder:_select_items(items, start_idx, reload_time, search_text)
|
||||
local conn = SQ3.open(db_location)
|
||||
local sql
|
||||
if not reload_time then
|
||||
if search_text then
|
||||
sql = string.format("SELECT * FROM vocabulary INNER JOIN title ON title_id = title.id WHERE word LIKE '%s' LIMIT 32 OFFSET %d", search_text, start_idx-1)
|
||||
elseif not reload_time then
|
||||
sql = string.format("SELECT * FROM vocabulary INNER JOIN title ON title_id = title.id AND filter = true ORDER BY due_time limit %d OFFSET %d;", 32, start_idx-1)
|
||||
else
|
||||
sql = string.format([[SELECT * FROM vocabulary INNER JOIN title
|
||||
@@ -199,7 +206,7 @@ function VocabularyBuilder:select_items(vocab_widget, start_idx, end_idx)
|
||||
end
|
||||
|
||||
if not start_cursor then return end
|
||||
self:_select_items(items, start_cursor, vocab_widget:check_reverse() and vocab_widget.reload_time)
|
||||
self:_select_items(items, start_cursor, vocab_widget:check_reverse() and vocab_widget.reload_time, vocab_widget.search_text_sql)
|
||||
end
|
||||
|
||||
|
||||
|
||||
@@ -26,7 +26,9 @@ local HorizontalGroup = require("ui/widget/horizontalgroup")
|
||||
local HorizontalSpan = require("ui/widget/horizontalspan")
|
||||
local IconButton = require("ui/widget/iconbutton")
|
||||
local IconWidget = require("ui/widget/iconwidget")
|
||||
local InfoMessage = require("ui/widget/infomessage")
|
||||
local InputContainer = require("ui/widget/container/inputcontainer")
|
||||
local InputDialog = require("ui/widget/inputdialog")
|
||||
local LeftContainer = require("ui/widget/container/leftcontainer")
|
||||
local LineWidget = require("ui/widget/linewidget")
|
||||
local MovableContainer = require("ui/widget/container/movablecontainer")
|
||||
@@ -54,8 +56,8 @@ local word_face = Font:getFace("x_smallinfofont")
|
||||
local subtitle_face = Font:getFace("cfont", 12)
|
||||
local subtitle_italic_face = Font:getFace("NotoSans-Italic.ttf", 12)
|
||||
local subtitle_color = Blitbuffer.COLOR_DARK_GRAY
|
||||
local dim_color = Blitbuffer.Color8(0x22)
|
||||
local settings = G_reader_settings:readSetting("vocabulary_builder", {enabled = true})
|
||||
local dim_color = Blitbuffer.COLOR_GRAY_3
|
||||
local settings = G_reader_settings:readSetting("vocabulary_builder", {enabled = false, with_context = true})
|
||||
|
||||
local function resetButtonOnLookupWindow()
|
||||
if not settings.enabled then -- auto add words
|
||||
@@ -312,12 +314,20 @@ function MenuDialog:init()
|
||||
show_sync_settings()
|
||||
end
|
||||
}
|
||||
local search_button = {
|
||||
text = _("Search"),
|
||||
callback = function()
|
||||
UIManager:close(self)
|
||||
self.show_parent:showSearchDialog()
|
||||
end
|
||||
}
|
||||
|
||||
local buttons = ButtonTable:new{
|
||||
width = width,
|
||||
buttons = {
|
||||
{reverse_button},
|
||||
{sync_button},
|
||||
{search_button},
|
||||
{filter_button, edit_button},
|
||||
{reset_button, clean_button},
|
||||
},
|
||||
@@ -938,6 +948,7 @@ function VocabItemWidget:resetProgress()
|
||||
self.item.due_time = os.time()
|
||||
self.item.review_time = self.item.due_time
|
||||
self.item.last_due_time = nil
|
||||
self.item.is_dim = false
|
||||
self:initItemWidget()
|
||||
UIManager:setDirty(self.show_parent, function()
|
||||
return "ui", self[1].dimen end)
|
||||
@@ -952,6 +963,7 @@ function VocabItemWidget:undo()
|
||||
self.item.last_review_count = nil
|
||||
self.item.last_review_time = nil
|
||||
self.item.last_due_time = nil
|
||||
self.item.is_dim = false
|
||||
self:initItemWidget()
|
||||
UIManager:setDirty(self.show_parent, function()
|
||||
return "ui", self[1].dimen end)
|
||||
@@ -1086,7 +1098,7 @@ function VocabItemWidget:onShowBookAssignment(title_changed_cb)
|
||||
face = Font:getFace("smallinfofontbold"),
|
||||
callback = function()
|
||||
local dialog
|
||||
dialog = require("ui/widget/inputdialog"):new{
|
||||
dialog = InputDialog:new{
|
||||
title = _("Enter book title:"),
|
||||
input = "",
|
||||
input_type = "text",
|
||||
@@ -1202,127 +1214,8 @@ function VocabularyBuilderWidget:init()
|
||||
}
|
||||
}
|
||||
end
|
||||
local padding = Size.padding.large
|
||||
self.width_widget = self.dimen.w - 2 * padding
|
||||
self.item_width = self.dimen.w - 2 * padding
|
||||
self.footer_center_width = math.floor(self.width_widget * (32/100))
|
||||
self.footer_button_width = math.floor(self.width_widget * (12/100))
|
||||
self.footer_left_corner_width = math.floor(self.width_widget * (8/100))
|
||||
self.footer_right_corner_width = math.floor(self.width_widget * (12/100))
|
||||
-- group for footer
|
||||
local chevron_left = "chevron.left"
|
||||
local chevron_right = "chevron.right"
|
||||
local chevron_first = "chevron.first"
|
||||
local chevron_last = "chevron.last"
|
||||
if BD.mirroredUILayout() then
|
||||
chevron_left, chevron_right = chevron_right, chevron_left
|
||||
chevron_first, chevron_last = chevron_last, chevron_first
|
||||
end
|
||||
self.footer_left = Button:new{
|
||||
icon = chevron_left,
|
||||
width = self.footer_button_width,
|
||||
callback = function() self:prevPage() end,
|
||||
bordersize = 0,
|
||||
radius = 0,
|
||||
show_parent = self,
|
||||
}
|
||||
self.footer_right = Button:new{
|
||||
icon = chevron_right,
|
||||
width = self.footer_button_width,
|
||||
callback = function() self:nextPage() end,
|
||||
bordersize = 0,
|
||||
radius = 0,
|
||||
show_parent = self,
|
||||
}
|
||||
self.footer_first_up = Button:new{
|
||||
icon = chevron_first,
|
||||
width = self.footer_button_width,
|
||||
callback = function()
|
||||
self:goToPage(1)
|
||||
end,
|
||||
bordersize = 0,
|
||||
radius = 0,
|
||||
show_parent = self,
|
||||
}
|
||||
self.footer_last_down = Button:new{
|
||||
icon = chevron_last,
|
||||
width = self.footer_button_width,
|
||||
callback = function()
|
||||
self:goToPage(self.pages)
|
||||
end,
|
||||
bordersize = 0,
|
||||
radius = 0,
|
||||
show_parent = self,
|
||||
}
|
||||
|
||||
self.footer_sync = Button:new{
|
||||
text = "⇅",
|
||||
width = self.footer_left_corner_width,
|
||||
text_font_size = 18,
|
||||
bordersize = 0,
|
||||
radius = 0,
|
||||
padding = Size.padding.large,
|
||||
show_parent = self,
|
||||
callback = function()
|
||||
if not settings.server then
|
||||
local sync_settings = SyncService:new{}
|
||||
sync_settings.onClose = function(this)
|
||||
UIManager:close(this)
|
||||
end
|
||||
sync_settings.onConfirm = function(server)
|
||||
settings.server = server
|
||||
saveSettings()
|
||||
DB:batchUpdateItems(self.item_table)
|
||||
SyncService.sync(server, DB.path, DB.onSync, false)
|
||||
self:reloadItems()
|
||||
end
|
||||
UIManager:show(sync_settings)
|
||||
else
|
||||
-- manual sync
|
||||
DB:batchUpdateItems(self.item_table)
|
||||
UIManager:nextTick(function()
|
||||
SyncService.sync(settings.server, DB.path, DB.onSync, false)
|
||||
self:reloadItems()
|
||||
end)
|
||||
end
|
||||
end
|
||||
}
|
||||
self.footer_sync.label_widget.fgcolor = Blitbuffer.COLOR_GRAY_3
|
||||
|
||||
self.footer_page = Button:new{
|
||||
text = "",
|
||||
hold_input = {
|
||||
title = _("Enter page number"),
|
||||
hint_func = function()
|
||||
return "(" .. "1 - " .. self.pages .. ")"
|
||||
end,
|
||||
type = "number",
|
||||
deny_blank_input = true,
|
||||
callback = function(input)
|
||||
local page = tonumber(input)
|
||||
if page and page >= 1 and page <= self.pages then
|
||||
self:goToPage(page)
|
||||
end
|
||||
end,
|
||||
ok_text = _("Go to page"),
|
||||
},
|
||||
call_hold_input_on_tap = true,
|
||||
bordersize = 0,
|
||||
margin = 0,
|
||||
text_font_face = "pgfont",
|
||||
text_font_bold = false,
|
||||
width = self.footer_center_width,
|
||||
show_parent = self,
|
||||
}
|
||||
self.page_info = HorizontalGroup:new{
|
||||
self.footer_sync,
|
||||
self.footer_first_up,
|
||||
self.footer_left,
|
||||
self.footer_page,
|
||||
self.footer_right,
|
||||
self.footer_last_down,
|
||||
HorizontalSpan:new{ width = self.footer_right_corner_width }
|
||||
}
|
||||
self.page_info = HorizontalGroup:new{}
|
||||
self:refreshFooter()
|
||||
|
||||
local bottom_line = LineWidget:new{
|
||||
dimen = Geom:new{ w = self.item_width, h = Size.line.thick },
|
||||
@@ -1344,7 +1237,7 @@ function VocabularyBuilderWidget:init()
|
||||
title_face = Font:getFace("smallinfofontbold"),
|
||||
bottom_line_color = Blitbuffer.COLOR_LIGHT_GRAY,
|
||||
with_bottom_line = true,
|
||||
bottom_line_h_padding = padding,
|
||||
bottom_line_h_padding = Size.padding.large,
|
||||
left_icon = "appbar.menu",
|
||||
left_icon_tap_callback = function() self:showMenu() end,
|
||||
title = self.title,
|
||||
@@ -1392,6 +1285,211 @@ function VocabularyBuilderWidget:init()
|
||||
}
|
||||
end
|
||||
|
||||
function VocabularyBuilderWidget:refreshFooter()
|
||||
local has_sync = settings.server ~= nil
|
||||
local has_search = self.search_text_sql
|
||||
if self.footer_left ~= nil then -- check whether refresh needed
|
||||
local should_refresh = has_sync and self.page_info[1] ~= self.footer_sync
|
||||
or not has_sync and self.page_info[1] == self.footer_sync
|
||||
if not should_refresh then
|
||||
should_refresh = has_search and self.page_info[#self.page_info] ~= self.footer_search
|
||||
or not has_search and self.page_info[#self.page_info] == self.footer_search
|
||||
end
|
||||
if not should_refresh then return end
|
||||
end
|
||||
|
||||
self.page_info:clear()
|
||||
local padding = Size.padding.large
|
||||
self.width_widget = self.dimen.w - 2 * padding
|
||||
self.item_width = self.dimen.w - 2 * padding
|
||||
self.footer_center_width = math.floor(self.width_widget * (32/100))
|
||||
self.footer_button_width = math.floor(self.width_widget * (12/100))
|
||||
local left_ratio = 10
|
||||
local right_ratio = 10
|
||||
if has_sync and not has_search then
|
||||
left_ratio = 9
|
||||
right_ratio = 11
|
||||
end
|
||||
self.footer_left_corner_width = math.floor(self.width_widget * left_ratio/100)
|
||||
self.footer_right_corner_width = math.floor(self.width_widget * right_ratio/100)
|
||||
-- group for footer
|
||||
local chevron_left = "chevron.left"
|
||||
local chevron_right = "chevron.right"
|
||||
local chevron_first = "chevron.first"
|
||||
local chevron_last = "chevron.last"
|
||||
if BD.mirroredUILayout() then
|
||||
chevron_left, chevron_right = chevron_right, chevron_left
|
||||
chevron_first, chevron_last = chevron_last, chevron_first
|
||||
end
|
||||
self.footer_left = Button:new{
|
||||
icon = chevron_left,
|
||||
width = self.footer_button_width,
|
||||
callback = function() self:prevPage() end,
|
||||
bordersize = 0,
|
||||
radius = 0,
|
||||
show_parent = self,
|
||||
}
|
||||
self.footer_right = Button:new{
|
||||
icon = chevron_right,
|
||||
width = self.footer_button_width,
|
||||
callback = function() self:nextPage() end,
|
||||
bordersize = 0,
|
||||
radius = 0,
|
||||
show_parent = self,
|
||||
}
|
||||
self.footer_first_up = Button:new{
|
||||
icon = chevron_first,
|
||||
width = self.footer_button_width,
|
||||
callback = function()
|
||||
self:goToPage(1)
|
||||
end,
|
||||
bordersize = 0,
|
||||
radius = 0,
|
||||
show_parent = self,
|
||||
}
|
||||
self.footer_last_down = Button:new{
|
||||
icon = chevron_last,
|
||||
width = self.footer_button_width,
|
||||
callback = function()
|
||||
self:goToPage(self.pages)
|
||||
end,
|
||||
bordersize = 0,
|
||||
radius = 0,
|
||||
show_parent = self,
|
||||
}
|
||||
local footer_height = self.footer_last_down:getSize().h
|
||||
local sync_size = TextWidget:getFontSizeToFitHeight("cfont", footer_height, Size.padding.buttontable*2)
|
||||
self.footer_sync = Button:new{
|
||||
text = "⇅",
|
||||
width = self.footer_left_corner_width - Size.padding.large * 2,
|
||||
text_font_size = sync_size,
|
||||
text_font_bold = false,
|
||||
bordersize = 0,
|
||||
radius = 0,
|
||||
padding_h = Size.padding.large,
|
||||
padding_v = Size.padding.button,
|
||||
margin = 0,
|
||||
show_parent = self,
|
||||
callback = function()
|
||||
if not settings.server then
|
||||
local sync_settings = SyncService:new{}
|
||||
sync_settings.onClose = function(this)
|
||||
UIManager:close(this)
|
||||
end
|
||||
sync_settings.onConfirm = function(server)
|
||||
settings.server = server
|
||||
saveSettings()
|
||||
DB:batchUpdateItems(self.item_table)
|
||||
SyncService.sync(server, DB.path, DB.onSync, false)
|
||||
self:reloadItems()
|
||||
end
|
||||
UIManager:show(sync_settings)
|
||||
else
|
||||
-- manual sync
|
||||
DB:batchUpdateItems(self.item_table)
|
||||
UIManager:nextTick(function()
|
||||
SyncService.sync(settings.server, DB.path, DB.onSync, false)
|
||||
self:reloadItems()
|
||||
end)
|
||||
end
|
||||
end
|
||||
}
|
||||
self.footer_sync.label_widget.fgcolor = Blitbuffer.COLOR_GRAY_3
|
||||
|
||||
self.footer_search = Button:new{
|
||||
icon = "appbar.search",
|
||||
width = self.footer_right_corner_width,
|
||||
icon_width = math.floor(footer_height - Size.padding.large),
|
||||
icon_height = math.floor(footer_height - Size.padding.large),
|
||||
callback = function()
|
||||
self:showSearchDialog()
|
||||
end,
|
||||
bordersize = 0,
|
||||
radius = 0,
|
||||
show_parent = self,
|
||||
}
|
||||
self.footer_page = Button:new{
|
||||
text = "",
|
||||
hold_input = {
|
||||
title = _("Enter page number"),
|
||||
hint_func = function()
|
||||
return "(" .. "1 - " .. self.pages .. ")"
|
||||
end,
|
||||
type = "number",
|
||||
deny_blank_input = true,
|
||||
callback = function(input)
|
||||
local page = tonumber(input)
|
||||
if page and page >= 1 and page <= self.pages then
|
||||
self:goToPage(page)
|
||||
end
|
||||
end,
|
||||
ok_text = _("Go to page"),
|
||||
},
|
||||
call_hold_input_on_tap = true,
|
||||
bordersize = 0,
|
||||
margin = 0,
|
||||
text_font_face = "pgfont",
|
||||
text_font_bold = false,
|
||||
width = self.footer_center_width,
|
||||
show_parent = self,
|
||||
}
|
||||
table.insert(self.page_info, has_sync and self.footer_sync or HorizontalSpan:new{width=self.footer_left_corner_width})
|
||||
table.insert(self.page_info, self.footer_first_up)
|
||||
table.insert(self.page_info, self.footer_left)
|
||||
table.insert(self.page_info, self.footer_page)
|
||||
table.insert(self.page_info, self.footer_right)
|
||||
table.insert(self.page_info, self.footer_last_down)
|
||||
table.insert(self.page_info, has_search and self.footer_search or HorizontalSpan:new{ width = self.footer_right_corner_width })
|
||||
end
|
||||
|
||||
function VocabularyBuilderWidget:showSearchDialog()
|
||||
local dialog
|
||||
dialog = InputDialog:new{
|
||||
title = _("Search words"),
|
||||
input = self.search_text or "",
|
||||
input_hint = _("Search empty content to exit"),
|
||||
input_type = "text",
|
||||
buttons = {
|
||||
{
|
||||
{
|
||||
text = _("Cancel"),
|
||||
callback = function()
|
||||
UIManager:close(dialog)
|
||||
end,
|
||||
},
|
||||
{
|
||||
text = _("Info"),
|
||||
callback = function()
|
||||
local text_info = _([[You can use two wildcards when searching: the percent sign (%) and the underscore (_).
|
||||
% represents any zero or more number of characters and _ represents any single character.
|
||||
|
||||
If no wildcard is used, the searched text will be enclosed with two %'s by default.]])
|
||||
UIManager:show(InfoMessage:new{ text = text_info })
|
||||
end,
|
||||
},
|
||||
{
|
||||
text = _("Search"),
|
||||
is_enter_default = true,
|
||||
callback = function()
|
||||
self.search_text = dialog:getInputText()
|
||||
if self.search_text == "" then
|
||||
self.search_text_sql = nil
|
||||
elseif self.search_text:find("%", 1, true) or self.search_text:find("_") then
|
||||
self.search_text_sql = self.search_text:gsub("'", "''")
|
||||
else
|
||||
self.search_text_sql = "%" .. self.search_text:gsub("'", "''") .. "%"
|
||||
end
|
||||
UIManager:close(dialog)
|
||||
self:reloadItems()
|
||||
end,
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
UIManager:show(dialog)
|
||||
dialog:onShowKeyboard()
|
||||
end
|
||||
|
||||
function VocabularyBuilderWidget:setupItemHeight()
|
||||
local item_height = Screen:scaleBySize(self.is_edit_mode and 54 or 72)
|
||||
self.item_height = item_height
|
||||
@@ -1470,7 +1568,10 @@ function VocabularyBuilderWidget:_populateItems()
|
||||
item
|
||||
)
|
||||
end
|
||||
table.insert(self.layout, #self.layout, {self.footer_sync})
|
||||
self:refreshFooter()
|
||||
if settings.server then
|
||||
table.insert(self.layout, #self.layout, {self.footer_sync})
|
||||
end
|
||||
if #self.main_content == 0 then
|
||||
table.insert(self.main_content, HorizontalSpan:new{width = self.item_width})
|
||||
end
|
||||
@@ -1481,9 +1582,14 @@ function VocabularyBuilderWidget:_populateItems()
|
||||
self.footer_page:disableWithoutDimming()
|
||||
end
|
||||
if self.pages == 0 then
|
||||
local has_filtered_book = DB:hasFilteredBook()
|
||||
local text = has_filtered_book and _("Filter in effect") or
|
||||
self:check_reverse() and _("No reviewable items") or _("No items")
|
||||
local text
|
||||
if self.search_text_sql then
|
||||
text = _("Search in effect")
|
||||
else
|
||||
local has_filtered_book = DB:hasFilteredBook()
|
||||
text = has_filtered_book and _("Filter in effect") or
|
||||
self:check_reverse() and _("No reviewable items") or _("No items")
|
||||
end
|
||||
self.footer_page:setText(text, self.footer_center_width)
|
||||
self.footer_first_up:hide()
|
||||
self.footer_last_down:hide()
|
||||
@@ -1612,7 +1718,7 @@ end
|
||||
|
||||
function VocabularyBuilderWidget:showChangeBookTitleDialog(sort_item, onSuccess)
|
||||
local dialog
|
||||
dialog = require("ui/widget/inputdialog"):new {
|
||||
dialog = InputDialog:new {
|
||||
title = _("Change book title to:"),
|
||||
input = sort_item.text,
|
||||
input_type = "text",
|
||||
@@ -1700,10 +1806,24 @@ function VocabularyBuilderWidget:onSwipe(arg, ges_ev)
|
||||
end
|
||||
|
||||
function VocabularyBuilderWidget:onMultiSwipe(arg, ges_ev)
|
||||
-- For consistency with other fullscreen widgets where swipe south can't be
|
||||
-- used to close and where we then allow any multiswipe to close, allow any
|
||||
-- multiswipe to close this widget too.
|
||||
self:onClose()
|
||||
-- if user is drawing a circle or half circle (full circle not always easy), reload
|
||||
local space_count = 0
|
||||
for space in ges_ev.multiswipe_directions:gmatch(" ") do
|
||||
space_count = space_count + 1
|
||||
if space_count == 2 then break end
|
||||
end
|
||||
if space_count == 2 and (
|
||||
string.find("east south west north east south west", ges_ev.multiswipe_directions)
|
||||
or string.find("east north west south east north west", ges_ev.multiswipe_directions)
|
||||
) then
|
||||
self:reloadItems()
|
||||
UIManager:show(Notification:new{ text = _("Words reloaded") })
|
||||
else
|
||||
-- For consistency with other fullscreen widgets where swipe south can't be
|
||||
-- used to close and where we then allow any multiswipe to close, allow any
|
||||
-- multiswipe to close this widget too.
|
||||
self:onClose()
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user