Extract a generic filtered list widget from the materials dialog.

develop
Alexander Gavrilov 2012-10-17 10:41:50 +04:00
parent b14e4e97f5
commit ad4f9908fb
4 changed files with 173 additions and 78 deletions

@ -149,8 +149,8 @@ ListBox = defclass(ListBox, MessageBox)
ListBox.focus_path = 'ListBox' ListBox.focus_path = 'ListBox'
ListBox.ATTRS{ ListBox.ATTRS{
selection = 1, with_filter = false,
choices = {}, cursor_pen = DEFAULT_NIL,
select_pen = DEFAULT_NIL, select_pen = DEFAULT_NIL,
on_select = DEFAULT_NIL on_select = DEFAULT_NIL
} }
@ -160,11 +160,16 @@ function ListBox:preinit(info)
end end
function ListBox:init(info) function ListBox:init(info)
local spen = gui.to_pen(COLOR_CYAN, info.select_pen, nil, false) local spen = gui.to_pen(COLOR_CYAN, self.select_pen, nil, false)
local cpen = gui.to_pen(COLOR_LIGHTCYAN, info.cursor_pen or info.select_pen, nil, true) local cpen = gui.to_pen(COLOR_LIGHTCYAN, self.cursor_pen or self.select_pen, nil, true)
local list_widget = widgets.List
if self.with_filter then
list_widget = widgets.FilteredList
end
self:addviews{ self:addviews{
widgets.List{ list_widget{
view_id = 'list', view_id = 'list',
selected = info.selected, selected = info.selected,
choices = info.choices, choices = info.choices,
@ -199,7 +204,7 @@ function ListBox:onInput(keys)
end end
end end
function showListPrompt(title, text, tcolor, choices, on_select, on_cancel, min_width) function showListPrompt(title, text, tcolor, choices, on_select, on_cancel, min_width, filter)
ListBox{ ListBox{
frame_title = title, frame_title = title,
text = text, text = text,
@ -208,6 +213,7 @@ function showListPrompt(title, text, tcolor, choices, on_select, on_cancel, min_
on_select = on_select, on_select = on_select,
on_cancel = on_cancel, on_cancel = on_cancel,
frame_width = min_width, frame_width = min_width,
with_filter = filter,
}:show() }:show()
end end

@ -43,28 +43,17 @@ function MaterialDialog:init(info)
frame = { r = 0, b = 0 }, frame = { r = 0, b = 0 },
auto_width = true, auto_width = true,
}, },
widgets.List{ widgets.FilteredList{
view_id = 'list', view_id = 'list',
frame = { l = 0, r = 0, t = 6, b = 2 }, not_found_label = 'No matching materials',
frame = { l = 0, r = 0, t = 4, b = 2 },
icon_width = 2, icon_width = 2,
on_submit = self:callback('onSubmitItem'), on_submit = self:callback('onSubmitItem'),
}, },
widgets.EditField{
view_id = 'edit',
frame = { l = 2, t = 4 },
on_change = self:callback('onFilterChoices'),
on_char = self:callback('onFilterChar'),
},
widgets.Label{
view_id = 'not_found',
text = 'No matching materials.',
text_pen = COLOR_LIGHTRED,
frame = { l = 2, r = 0, t = 6 },
},
widgets.Label{ widgets.Label{
text = { { text = { {
key = 'SELECT', text = ': Select', key = 'SELECT', text = ': Select',
disabled = function() return self.subviews.not_found.visible end disabled = function() return not self.subviews.list:canSubmit() end
} }, } },
frame = { l = 0, b = 0 }, frame = { l = 0, b = 0 },
} }
@ -198,18 +187,15 @@ function MaterialDialog:pushContext(name, choices)
else else
table.insert(self.back_stack, { table.insert(self.back_stack, {
context_str = self.context_str, context_str = self.context_str,
all_choices = self.all_choices, all_choices = self.subviews.list:getChoices(),
edit_text = self.subviews.edit.text, edit_text = self.subviews.list:getFilter(),
choices = self.choices, selected = self.subviews.list:getSelected(),
selected = self.subviews.list.selected,
}) })
self.subviews.back.visible = true self.subviews.back.visible = true
end end
self.context_str = name self.context_str = name
self.all_choices = choices self.subviews.list:setChoices(choices, 1)
self.subviews.edit.text = ''
self:setChoices(choices, 1)
end end
function MaterialDialog:onGoBack() function MaterialDialog:onGoBack()
@ -217,55 +203,8 @@ function MaterialDialog:onGoBack()
self.subviews.back.visible = (#self.back_stack > 0) self.subviews.back.visible = (#self.back_stack > 0)
self.context_str = save.context_str self.context_str = save.context_str
self.all_choices = save.all_choices self.subviews.list:setChoices(save.all_choices)
self.subviews.edit.text = save.edit_text self.subviews.list:setFilter(save.edit_text, save.selected)
self:setChoices(save.choices, save.selected)
end
function MaterialDialog:setChoices(choices, pos)
self.choices = choices
self.subviews.list:setChoices(self.choices, pos)
self.subviews.not_found.visible = (#self.choices == 0)
end
function MaterialDialog:onFilterChoices(text)
local tokens = utils.split_string(text, ' ')
local choices = {}
for i,v in ipairs(self.all_choices) do
local ok = true
local search_key = v.search_key or v.text
for _,key in ipairs(tokens) do
if key ~= '' and not string.match(search_key, '%f[^%s\x00]'..key) then
ok = false
break
end
end
if ok then
table.insert(choices, v)
end
end
self:setChoices(choices)
end
local bad_chars = {
['%'] = true, ['.'] = true, ['+'] = true, ['*'] = true,
['['] = true, [']'] = true, ['('] = true, [')'] = true,
}
function MaterialDialog:onFilterChar(char, text)
if bad_chars[char] then
return false
end
if char == ' ' then
if #self.choices == 1 then
self.subviews.list:submit()
return false
end
return string.match(text, '%S$')
end
return true
end end
function MaterialDialog:submitMaterial(typ, index) function MaterialDialog:submitMaterial(typ, index)

@ -133,13 +133,14 @@ function showInputPrompt(title, text, tcolor, input, min_width)
return wait() return wait()
end end
function showListPrompt(title, text, tcolor, choices, min_width) function showListPrompt(title, text, tcolor, choices, min_width, filter)
dlg.ListBox{ dlg.ListBox{
frame_title = title, frame_title = title,
text = text, text = text,
text_pen = tcolor, text_pen = tcolor,
choices = choices, choices = choices,
frame_width = min_width, frame_width = min_width,
with_filter = filter,
on_select = mkresume(true), on_select = mkresume(true),
on_cancel = mkresume(false), on_cancel = mkresume(false),
on_close = qresume(nil) on_close = qresume(nil)

@ -22,6 +22,14 @@ local function getval(obj)
end end
end end
local function map_opttab(tab,idx)
if tab then
return tab[idx]
else
return idx
end
end
------------ ------------
-- Widget -- -- Widget --
------------ ------------
@ -393,6 +401,10 @@ function List:setSelected(selected)
return self.selected return self.selected
end end
function List:getChoices()
return self.choices
end
function List:getSelected() function List:getSelected()
return self.selected, self.choices[self.selected] return self.selected, self.choices[self.selected]
end end
@ -533,4 +545,141 @@ function List:onInput(keys)
end end
end end
-------------------
-- Filtered List --
-------------------
FilteredList = defclass(FilteredList, Widget)
function FilteredList:init(info)
self.edit = EditField{
text_pen = info.cursor_pen,
frame = { l = info.icon_width, t = 0 },
on_change = self:callback('onFilterChange'),
on_char = self:callback('onFilterChar'),
}
self.list = List{
frame = { t = 2 },
text_pen = info.text_pen,
cursor_pen = info.cursor_pen,
inactive_pen = info.inactive_pen,
row_height = info.row_height,
scroll_keys = info.scroll_keys,
icon_width = info.icon_width,
}
if info.on_select then
self.list.on_select = function()
return info.on_select(self:getSelected())
end
end
if info.on_submit then
self.list.on_submit = function()
return info.on_submit(self:getSelected())
end
end
self.not_found = Label{
visible = false,
text = info.not_found_label or 'No matches',
text_pen = COLOR_LIGHTRED,
frame = { l = info.icon_width, t = 2 },
}
self:addviews{ self.list, self.edit, self.not_found }
self:setChoices(info.choices, info.selected)
end
function FilteredList:getChoices()
return self.choices
end
function FilteredList:setChoices(choices, pos)
choices = choices or {}
self.choices = choices
self.edit.text = ''
self.list:setChoices(choices, pos)
self.not_found.visible = (#choices == 0)
end
function FilteredList:submit()
return self.list:submit()
end
function FilteredList:canSubmit()
return not self.not_found.visible
end
function FilteredList:getSelected()
local i,v = self.list:getSelected()
return map_opttab(self.choice_index, i), v
end
function FilteredList:getContentWidth()
return self.list:getContentWidth()
end
function FilteredList:getContentHeight()
return self.list:getContentHeight() + 2
end
function FilteredList:getFilter()
return self.edit.text, self.list.choices
end
function FilteredList:setFilter(filter, pos)
local choices = self.choices
local cidx = nil
filter = filter or ''
self.edit.text = filter
if filter ~= '' then
local tokens = utils.split_string(filter, ' ')
local ipos = pos
choices = {}
cidx = {}
pos = nil
for i,v in ipairs(self.choices) do
local ok = true
local search_key = v.search_key or v.text
for _,key in ipairs(tokens) do
if key ~= '' and not string.match(search_key, '%f[^%s\x00]'..key) then
ok = false
break
end
end
if ok then
table.insert(choices, v)
cidx[#choices] = i
if ipos == i then
pos = #choices
end
end
end
end
self.choice_index = cidx
self.list:setChoices(choices, pos)
self.not_found.visible = (#choices == 0)
end
function FilteredList:onFilterChange(text)
self:setFilter(text)
end
local bad_chars = {
['%'] = true, ['.'] = true, ['+'] = true, ['*'] = true,
['['] = true, [']'] = true, ['('] = true, [')'] = true,
}
function FilteredList:onFilterChar(char, text)
if bad_chars[char] then
return false
end
if char == ' ' then
return string.match(text, '%S$')
end
return true
end
return _ENV return _ENV