urwid自动完成编辑小部件或无焦点弹出窗口

2024-06-16 11:36:07 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在用python和urwid编写一个应用程序。 我需要一个带有自动完成功能的Edit小部件。在documentation中还没有看到一个,所以我尝试在pop_up example的基础上实现它

然而,我面对的事实是弹出窗口小部件有焦点,这是一个问题,因为:

  • 编辑小部件中的光标不可见。当使用左右箭头键而不计算您按下它们的频率时,您不知道下一个字符将插入何处
  • 所有用户输入都会进入弹出窗口小部件,而不是PopUpLauncher,尽管大多数关键事件都是针对edit小部件的。我不能调用Edit.keypress,因为我不知道Edit小部件的大小。因此,我需要从urwid复制代码

如何设置PopUpLauncher以获得焦点

从那以后this answer可能会有所帮助


我的自定义小部件类的一个非常简化的版本:

#!/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()

Tags: keytextposselfgetreturnifdef
1条回答
网友
1楼 · 发布于 2024-06-16 11:36:07

我至少为问题的第一部分(光标的可见性)找到了解决方案:重写AutoCompleteEdit类中的render方法:

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

第二个问题,代码重复,仍然存在。 实际上,这不仅仅是代码复制,因为某些功能(将光标移动到最开始或最末尾)正在使用keypress方法的size参数。我不知道尺寸,所以我不能把它复制出来。 所以如果有人知道更好的方法我会很感激的

相关问题 更多 >