Редактирование виджета с автоматическим завершением или всплывающее окно - PullRequest
0 голосов
/ 10 октября 2019

Я пишу приложение на 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()

1 Ответ

0 голосов
/ 10 октября 2019

Я нашел решение по крайней мере для первой части проблемы (видимость курсора): переопределение метода render в классе AutoCompleteEdit:

    def render(self, size, focus=False):
        if self.is_open():
            focus = True
        return self.__super.render(size, focus)

Вторая проблема,дублирование кода, до сих пор остается. На самом деле это больше, чем просто дублирование кода, потому что некоторые функции (перемещение курсора в самое начало или в самый конец) используют аргумент size метода нажатия клавиши. Я не знаю размер, поэтому я не могу просто скопировать его оттуда. Так что, если кто-то знает лучший способ, я был бы благодарен.

...