Я пишу приложение на python с urwid . Мне нужен Edit
виджет с автозаполнением. Я не видел ни одной в документации , поэтому я попытался реализовать ее самостоятельно на основе pop_up примера .
Однако я столкнулся с тем, чтовиджет pop_up имеет фокус, который является проблемой, потому что:
- Курсор в виджете редактирования не виден. При использовании клавиш со стрелками влево и вправо без учета того, как часто вы нажимали их, вы не знаете, куда будет вставлен следующий символ.
- Весь пользовательский ввод идет в виджет pop_up, а не в PopUpLauncher, хотя большинствоиз ключевых событий предназначены для виджета редактирования. Я не могу вызвать Edit.keypress, потому что я не знаю размер виджета редактирования. Поэтому мне нужно продублировать код из urwid.
Как настроить PopUpLauncher на фокус?
С этого момента этот ответ может быть полезен.
Сильно упрощенная версия моего класса пользовательских виджетов:
#!/usr/bin/env python
import urwid
class AutoCompleteEdit(urwid.PopUpLauncher):
CMD_INSERT_SELECTED = "complete"
command_map = urwid.CommandMap()
command_map['tab'] = CMD_INSERT_SELECTED
def __init__(self, get_completions):
self.edit_widget = urwid.Edit()
self.__super.__init__(self.edit_widget)
self.get_completions = get_completions
# ------- user input ------
def keypress(self, size, key):
cmd = self.command_map[key]
if cmd is None:
out = self.__super.keypress(size, key)
self.update_completions()
return out
return self.__super.keypress(size, key)
def forwarded_keypress(self, key):
if self.edit_widget.valid_char(key):
#if (isinstance(key, text_type) and not isinstance(self._caption, text_type)):
# # screen is sending us unicode input, must be using utf-8
# # encoding because that's all we support, so convert it
# # to bytes to match our caption's type
# key = key.encode('utf-8')
self.edit_widget.insert_text(key)
self.update_completions()
return
cmd = self.command_map[key]
if cmd == self.CMD_INSERT_SELECTED:
self.insert_selected()
return
elif cmd == urwid.CURSOR_LEFT:
p = self.edit_widget.edit_pos
if p == 0:
return key
p = urwid.move_prev_char(self.edit_widget.edit_text, 0, p)
self.edit_widget.set_edit_pos(p)
elif cmd == urwid.CURSOR_RIGHT:
p = self.edit_widget.edit_pos
if p >= len(self.edit_widget.edit_text):
return key
p = urwid.move_next_char(self.edit_widget.edit_text, p, len(self.edit_widget.edit_text))
self.edit_widget.set_edit_pos(p)
elif key == "backspace":
self.edit_widget.pref_col_maxcol = None, None
if not self.edit_widget._delete_highlighted():
p = self.edit_widget.edit_pos
if p == 0:
return key
p = urwid.move_prev_char(self.edit_widget.edit_text,0,p)
self.edit_widget.set_edit_text(self.edit_widget.edit_text[:p] + self.edit_widget.edit_text[self.edit_widget.edit_pos:])
self.edit_widget.set_edit_pos(p)
elif key == "delete":
self.edit_widget.pref_col_maxcol = None, None
if not self.edit_widget._delete_highlighted():
p = self.edit_widget.edit_pos
if p >= len(self.edit_widget.edit_text):
return key
p = urwid.move_next_char(self.edit_widget.edit_text,p,len(self.edit_widget.edit_text))
self.edit_widget.set_edit_text(self.edit_widget.edit_text[:self.edit_widget.edit_pos] + self.edit_widget.edit_text[p:])
else:
return key
self.update_completions()
return key
def update_completions(self):
i = self.edit_widget.edit_pos
text = self.edit_widget.edit_text[:i]
prefix, completions = self.get_completions(text)
self.prefix = prefix
self.completions = completions
if not self.completions:
if self.is_open():
self.close_pop_up()
return
if not self.is_open():
self.open_pop_up()
self._pop_up_widget.update_completions(completions)
def insert_selected(self):
text = self._pop_up_widget.get_selected()
i = self.edit_widget.edit_pos - len(self.prefix)
assert i >= 0
text = text[i:]
self.edit_widget.insert_text(text)
self.close_pop_up()
# ------- internal ------
def is_open(self):
return self._pop_up_widget
# ------- implementation of abstract methods ------
def create_pop_up(self):
return PopUpList(self.forwarded_keypress)
def get_pop_up_parameters(self):
height = len(self.completions)
width = max(len(x) for x in self.completions)
return {'left':len(self.prefix), 'top':1, 'overlay_width':width, 'overlay_height':height}
class PopUpList(urwid.WidgetWrap):
ATTR = 'popup-button'
ATTR_FOCUS = 'popup-button-focus'
def __init__(self, keypress_callback):
self.body = urwid.SimpleListWalker([urwid.Text("")])
widget = urwid.ListBox(self.body)
widget = urwid.AttrMap(widget, self.ATTR)
self.__super.__init__(widget)
self.keypress_callback = keypress_callback
def update_completions(self, completions):
self.body.clear()
for x in completions:
widget = ListEntry(x)
widget = urwid.AttrMap(widget, self.ATTR, self.ATTR_FOCUS)
self.body.append(widget)
def get_selected(self):
focus_widget, focus_pos = self.body.get_focus()
return self.body[focus_pos].original_widget.text
def keypress(self, size, key):
key = self.keypress_callback(key)
if key:
return super().keypress(size, key)
class ListEntry(urwid.Text):
#https://stackoverflow.com/a/56759094
_selectable = True
signals = ["click"]
def keypress(self, size, key):
"""
Send 'click' signal on 'activate' command.
"""
if self._command_map[key] != urwid.ACTIVATE:
return key
self._emit('click')
def mouse_event(self, size, event, button, x, y, focus):
"""
Send 'click' signal on button 1 press.
"""
if button != 1 or not urwid.util.is_mouse_press(event):
return False
self._emit('click')
return True
if __name__ == '__main__':
palette = [
('popup-button', 'white', 'dark blue'),
('popup-button-focus', 'white,standout', 'dark blue'),
('error', 'dark red', 'default'),
]
completions = ["hello", "hi", "world", "earth", "universe"]
def get_completions(start):
i = start.rfind(" ")
if i == -1:
prefix = ""
else:
i += 1
prefix = start[:i]
start = start[i:]
return prefix, [word for word in completions if word.startswith(start)]
widget = AutoCompleteEdit(get_completions)
widget = urwid.Filler(widget)
#WARNING: note the pop_ups=True
urwid.MainLoop(widget, palette, pop_ups=True).run()