Qt Corner: term.py
Copyright 2020 Brian Davis - CC-BY-NC-SA
Part of the QtCorner series.
https://gitlab.com/intrepidhero/qt-corner/-/tree/master/term
term.py is a very minimal Qt based console program. It's not really a terminal, in that it provides no terminal emulation features, as you'll see. But when connected to a shell it does provide the GUI experience of terminal. The user types in commands, these are passed on an interpreter, and the output is returned. While that may sound simplistic and obvious, there are a couple points that eluded me for a long time. Primarily how to start a subprocess in a way that doesn't hang up the GUI, how to connect that process to the input/output console and finally how to make a single QTextEdit area behave like a traditional console, where commands can only be typed at the end of the most recent output.
QTextEdit Modifications
term.py only uses one widget: a QTextEdit. The Gui class subclasses QTextEdit, most importantly to override the default keyPressEvent. I've now spent a lot of time with this class. It's very powerful but simple enough that extending it to add behavior is doable. Combined with QTextCursor very complex interactions can be scripted.
The second import bit to point out is that Gui tracks a cursor position that I called the backstop. This is the end position of the most recent output and the point beyond which the (insertion) cursor should not be allowed to go.
The changes to keyPressEvent intercept the home, backspace, left arrow, up arrow, down arrow, tab and return keys. All others are passed on to QTextEdit.keyPressEvent. For home, backspace and left, the cursor position is first checked before allowing the key press. If processing the keypress will move the cursor past the backstop, the keypress is ignored.
Up/down are intercepted but ignored. They could be used for command history navigation if I get around to adding it later.
Tab is ignored but could be used for auto-completion.
Return enters the current command. This code takes the string from the backstop to the end of the last line sends it to the interpreter.
An Interpreter
Overtime I've solved this piece a number of ways. Initially I used subprocess to open a new process with pipes for the io streams. This had to be done from a QThread so that it didn't hang up the GUI if the process was long running.