简体   繁体   English

Python Curses-输入文本时调整终端大小

[英]Python curses - resize terminal while inputting text

I am trying to do a simple GUI with curses but I am stuck with some problems when resizing and redrawing the screen. 我正在尝试使用curses做一个简单的GUI,但是在调整大小和重新绘制屏幕时遇到一些问题。 Here is my code: 这是我的代码:

import curses
import curses.textpad as textpad

class editor (object):
    def __init__(self, stdscreen):
        self.screen = stdscreen
        self.height,self.width = self.screen.getmaxyx()
        self.draw_screen()
        while True:
            # Check if the screen has been resized
            resize = curses.is_term_resized(self.height, self.width)
            if resize:
                self.height,self.width = self.screen.getmaxyx()
                self.draw_screen()
            curses.noecho()
            text = textpad.Textbox(self.text).edit(validate=self.validate)
            self.body.addstr(0,0, str(text), curses.A_NORMAL)
            self.body.refresh()
            self.text.erase()
    def draw_screen (self):
        self.screen.clear()
        self.header=self.screen.subwin(1,self.width,0,0)
        self.body=self.screen.subwin(self.height-3,self.width,1,0)
        self.text=self.screen.subwin(1,self.width,self.height-2,0)
        self.footer=self.screen.subwin(self.height-1,0)
        header_text = "{0:<{n}}".format("Header", n=self.width-1)
        self.header.addstr(0,0, header_text, curses.A_REVERSE)
        self.footer.addstr(0,0, "^W", curses.A_REVERSE)
        self.footer.addstr(0,2, " Quit", curses.A_NORMAL)
        self.header.noutrefresh()
        self.body.noutrefresh()
        self.footer.noutrefresh()
        self.text.noutrefresh()
        curses.doupdate()
    def validate (self, ch):
        # If resize event redraw screen
        if ch==curses.KEY_RESIZE:
            self.height,self.width = self.screen.getmaxyx()
            self.draw_screen()
        if ch==23: # ^w quit
            exit()
        else:
            return ch

if __name__=="__main__":
    curses.wrapper(editor)

When the terminal is not been resized it works like expected, but when I enlarge the terminal window, while the Header and footer moves to the top and bottom row respectively, the cursor remains in the previous position until the string is accepted by pressing Enter. 当不调整终端的大小时,它的工作原理与预期的一样,但是当我扩大终端窗口时,虽然页眉和页脚分别移至顶部和底部,但光标仍停留在先前的位置,直到按Enter接受字符串为止。 Same when shrinking, except that in this case the cursor goes over the footer and the content of the footer is returned. 缩小时相同,除了在这种情况下,光标移到页脚上方,然后返回页脚的内容。 Trying to debug the problem I found that the sub-windows seem to move all right, but the cursor is not moving with them. 尝试调试该问题时,我发现子窗口似乎可以正常移动,但是光标没有随它们一起移动。 I also tried a 我也尝试过

self.text.move(0,0)

to move the cursor back to the right position, but it didn't work. 将光标移回正确的位置,但是没有用。

Bonus: How can I preserve the text inserted before the resize action and add it to the resized displayed screen? 奖励:如何保留在调整大小操作之前插入的文本并将其添加到调整大小的显示屏幕中?

Edit: After some more debugging I came up with a pseudo solution that "kind of" works (but is ugly and hacky). 编辑:经过更多的调试后,我想出了一种“解决方案”有效的伪解决方案(但丑陋而笨拙)。

import curses
import curses.textpad as textpad

class my_textbox(textpad.Textbox):

    def edit(self, validate=None):
        while 1:
            ch = self.win.getch()
            # Return a list instead of a string if the last key was a resize
            if ch==curses.KEY_RESIZE:
                return[self.gather(),True]
            if validate:
                ch = validate(ch)
            if not ch:
                continue
            if not self.do_command(ch):
                break
            self.win.refresh()
        return self.gather()

class editor (object):
    def __init__(self, stdscreen):
        self.screen = stdscreen
        self.height,self.width = self.screen.getmaxyx()
        # Initialize a variable to store the previous text
        self.ptext=None
        curses.noecho()
        self.draw_screen()
        while True:
            # Check if the screen has been resized
            resize = curses.is_term_resized(self.height, self.width)
            if resize:
                self.height,self.width = self.screen.getmaxyx()
                self.draw_screen()
            text = my_textbox(self.text).edit(validate=self.validate)
            # If the window has been resized (aka textbox returned a list)
            if isinstance (text, list):
                text=text[0]
                # Store the previous text
                self.ptext=text.strip()
                continue
            self.body.addstr(0,0, str(text), curses.A_NORMAL)
            self.body.refresh()
            self.text.erase()
    def draw_screen (self):
        self.screen.clear()
        self.header=self.screen.subwin(1,self.width,0,0)
        self.body=self.screen.subwin(self.height-3,self.width,1,0)
        self.text=self.screen.subwin(1,self.width,self.height-2,0)
        self.footer=self.screen.subwin(self.height-1,0)
        header_text = "{0:<{n}}".format("Header", n=self.width-1)
        self.header.addstr(0,0, header_text, curses.A_REVERSE)
        # If there was text previous to resize
        if self.ptext:
            # To prevent _curses.error: addwstr() returned ERR
            # when shrinking window cut out the last character
            # (!) Raises ERR anyway when shrinking with double click from
            # titlebar, also, when resizing is too fast, ptext become a series of ^?
            if len(self.ptext)<self.width-1:
                self.text.addstr(0,0,self.ptext.strip())
            else:
                self.ptext=self.ptext[:self.width-1]
                self.text.addstr(0,0,self.ptext.strip())
        self.footer.addstr(0,0, "^W", curses.A_REVERSE)
        self.footer.addstr(0,2, " Quit", curses.A_NORMAL)
        self.header.noutrefresh()
        self.body.noutrefresh()
        self.footer.noutrefresh()
        self.text.noutrefresh()
        curses.doupdate()

    def validate (self, ch):
        if ch==23: # ^w quit
            exit()
        else:
            return ch

if __name__=="__main__":
    curses.wrapper(editor)

what I've done : 我所做的

I redefined the edit method of the Textbox class, so that when the windows is resized it returns a list instead of a string. 我重新定义了Textbox类的edit方法,以便在调整窗口大小时返回列表而不是字符串。

In the input loop when the window is resized the previous text is stored ad a new cycle is started. 在输入循环中,当调整窗口大小时,将存储先前的文本并开始新的循环。

If previous text is present the drawing function adds it to the subwindow. 如果存在以前的文本,则绘图功能会将其添加到子窗口。

notes: 笔记:

Resizing by double-click on the titlebar throws throws an error and if resizing is "too fast" the string is replaced by garbage ("^?", which is actually 2^64-1 before conversion by curses). 双击标题栏进行大小调整会引发错误,如果调整大小“太快”,则将字符串替换为垃圾(“ ^?”,实际上是2 ^ 64-1,然后通过curses转换)。

I suppose that because most of the functionality of (n)curses have been created with non resizeable terminals in mind the implementation does make sense after all but, at least for someone relatively new to python like me, is not intuitive at all. 我猜想因为(n)curses的大多数功能都是在考虑到不可调整大小的终端的情况下创建的,所以该实现毕竟是有意义的,但是,至少对于像我这样的相对较新的python而言,这根本不直观。

The description sounds right: your application has to re-layout the text and redraw the contents of the screen. 描述听起来很正确:您的应用程序必须重新布置文本并重新绘制屏幕内容。

To do this, it has to keep a record of the information. 为此,它必须保留信息记录。 Some of that is predefined such as "Header" in this line 其中一些是预定义的,例如此行中的"Header"

header_text = "{0:<{n}}".format("Header", n=self.width-1)

But the contents of sub windows may be lost if the screen is made smaller (since resizeterm in ncurses will truncate those windows): 但是如果屏幕变小, 窗口的内容可能会丢失(因为ncurses中的resizeterm会截断那些窗口):

self.header=self.screen.subwin(1,self.width,0,0)

as well as any window, eg, created with newwin . 以及任何用newwin创建的窗口。 Python's textpad class does that. Python的textpad类可以做到这一点。

curses (in particular, ncurses which is used by Python) supports a quasi-window called "pad" , which you could use in some cases. curses(特别是Python使用的ncurses)支持称为“ pad”的准窗口,在某些情况下可以使用。 But it is not readily usable by its textpad class (since the underlying calls for refreshing the pad differ from those for a window ). 但是它的textpad类不容易使用(因为刷新键盘的基础调用与窗口的调用不同)。

However (this would take some real work), you could create a pad which is as large as the screen which you would like to manage, and make the textpad a child of that, and then do your own re-layout independent of resizeterm . 但是,(这将需要做一些实际的工作),您可以创建一个与要管理的屏幕一样大的键盘 ,并使该文本键盘成为该键盘的子面板 ,然后独立于resizeterm自己的重新布局。 Python's documentation for the related resize_term notes (copied from the ncurses manual ): 相关resize_term注释的Python文档(从ncurses 手册复制):

Backend function used by resizeterm() , performing most of the work; resizeterm()使用的后端函数,执行大部分工作; when resizing the windows, resize_term() blank-fills the areas that are extended. 调整窗口大小时, resize_term()空白填充扩展的区域。 The calling application should fill in these areas with appropriate data. 调用应用程序应在这些区域中填充适当的数据。 The resize_term() function attempts to resize all windows. resize_term()函数尝试调整所有窗口的大小。 However, due to the calling convention of pads , it is not possible to resize these without additional interaction with the application. 但是,由于pad的调用约定,如果不与应用程序进行其他交互,则无法调整它们的大小。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM