In PyQt, how can a terminal be embedded in a window? - python

I have a small script that is designed to embed an xterm in a PyQt GUI. On Linux, it works, creating a GUI like this:
However, running the same script on OS X yields two windows like this:
Does anyone know how to address this and prevent OS X from screwing up the GUI?
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class embeddedTerminal(QWidget):
def __init__(self):
QWidget.__init__(self)
self.resize(800, 600)
self.process = QProcess(self)
self.terminal = QWidget(self)
layout = QVBoxLayout(self)
layout.addWidget(self.terminal)
self.process.start('xterm', ['-into', str(self.terminal.winId())])
if __name__ == "__main__":
app = QApplication(sys.argv)
main = embeddedTerminal()
main.show()
sys.exit(app.exec_())

You could take a look at the qtconsole front end for Jupyter and try to use the bash kernel. Depending on your end goal, I know it is possible to embed an IPython kernel, in another application.

Related

Interrupting an IPython kernel embedded in a QT4 widget

I want to embed an Ipython terminal in a QT widget so I can access objects in my QT application. I understand that this is possible with examples such as this: https://github.com/gpoulin/python-test/blob/master/embedded_qtconsole.py
however, if the kernel is blocking (in an infinite while loop for example) the whole application is unresponsive and can only be killed with a keyboard interrupt from the terminal where the qt application was run. If I use the QtKernelManager instead of QtInProcessKernelManager I can successfully interrupt the kernel from the qt application, however, I cannot access objects within it as the kernel is embedded in a different process.
Is there a way to catch the keyboard interrupt when embedding the IPython terminal in the same process ? or should I use a different implementation of embedding the kernel ?
my adapted code is below
import os
os.environ['QT_API'] = 'pyqt'
import sip
sip.setapi("QString", 2)
sip.setapi("QVariant", 2)
import sys
from PyQt4 import QtGui, QtCore
from pkg_resources import require
require('ipython')
from IPython.qt.console.rich_ipython_widget import RichIPythonWidget
from IPython.qt.inprocess import QtInProcessKernelManager
class IPythonWidget(QtGui.QWidget):
def __init__(self, **kwarg):
super(IPythonWidget, self).__init__()
self.initUI()
def startIpKernel(self):
self.kernel_manager = QtInProcessKernelManager()
self.kernel_manager.start_kernel()
self.kernel_manager.kernel.gui = 'qt4'
self.kernel_client = kernel_client = self.kernel_manager.client()
self.kernel_client.start_channels()
def initUI(self):
self.startIpKernel()
self.addWidgets()
def addWidgets(self):
self.button = QtGui.QPushButton()
self.button.setText("test button")
self.console = RichIPythonWidget(self)
self.console.kernel_manager =self.kernel_manager
self.console.kernel_client = self.kernel_client
self.console.kernel_manager.kernel.shell.push({"button": self.button})#this can be wrapped into another method
vbox = QtGui.QVBoxLayout()
vbox.addWidget(self.console)
vbox.addWidget(self.button)
self.setLayout(vbox)
def mousePressEvent(self, event):
self.mousepos = event.pos()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
ex = IPythonWidget()
ex.show()
sys.exit(app.exec_())

how to use a terminal embedded in a PyQt GUI

There is an existing environment and framework usable via Bash terminal around which I want to make a GUI. What I have in mind is the following flow:
In a Bash session, the framework environment is set up. This results in everything from environment variables to authentications being set up in the session.
A Python GUI script is run in order to wrap around the existing session and make it easier to run subsequent steps.
The GUI appears, displaying on one side the Bash session in an embedded terminal and on the other side a set of buttons corresponding to various commands that can be run in the existing framework environment.
Buttons can be pressed in the GUI resulting in certain Bash commands being run. The results of the runs are displayed in the embedded terminal.
What is a good way to approach the creation of such a GUI? I realise that the idea of interacting with the existing environment could be tricky. If it is particularly tricky, I am open to recreating the environment in a session of the GUI. In any case, how can the GUI interact with the embedded terminal. How can commands be run and displayed in the embedded terminal when buttons of the GUI are pressed?
A basic start of the GUI (featuring an embedded terminal) is as follows:
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class embeddedTerminal(QWidget):
def __init__(self):
QWidget.__init__(self)
self.resize(800, 600)
self.process = QProcess(self)
self.terminal = QWidget(self)
layout = QVBoxLayout(self)
layout.addWidget(self.terminal)
self.process.start(
'xterm',
['-into', str(self.terminal.winId())]
)
if __name__ == "__main__":
app = QApplication(sys.argv)
main = embeddedTerminal()
main.show()
sys.exit(app.exec_())
How could I run, say, top on this embedded terminal following the press of a button in the GUI?
If it has to be a real terminal and a real shell (and not just accepting a line of input, running some command, then displaying output) -- how about tmux?
You could use something like tee to get the output back into your program.
Note that tmux sessions may persist across your program runs, so you'd need to read up on how that works and how to control it.
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class embeddedTerminal(QWidget):
def __init__(self):
QWidget.__init__(self)
self._processes = []
self.resize(800, 600)
self.terminal = QWidget(self)
layout = QVBoxLayout(self)
layout.addWidget(self.terminal)
self._start_process(
'xterm',
['-into', str(self.terminal.winId()),
'-e', 'tmux', 'new', '-s', 'my_session']
)
button = QPushButton('List files')
layout.addWidget(button)
button.clicked.connect(self._list_files)
def _start_process(self, prog, args):
child = QProcess()
self._processes.append(child)
child.start(prog, args)
def _list_files(self):
self._start_process(
'tmux', ['send-keys', '-t', 'my_session:0', 'ls', 'Enter'])
if __name__ == "__main__":
app = QApplication(sys.argv)
main = embeddedTerminal()
main.show()
sys.exit(app.exec_())
A bit more here: https://superuser.com/questions/492266/run-or-send-a-command-to-a-tmux-pane-in-a-running-tmux-session
If anyone else comes upon this made some slight mods to it to close the tmux session if it exists since the previous one did not close it on exit. Also set it up for PySide2
About the only thing it needs now is resize support.
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import psutil
import os
import platform
import sys
from pathlib import Path
from subprocess import call
from PySide2 import QtCore
from PySide2.QtCore import *
from PySide2.QtWidgets import QWidget, QVBoxLayout, QPushButton, QApplication
platform = platform.system()
print(str(platform))
term_dir = Path(os.path.abspath(os.path.dirname(__file__))) / 'terminus'
if platform == 'Windows':
term_bin = str(term_dir) + '/' + str(platform.lower()) + '/' + 'terminus.exe'
elif platform == 'Linux':
term_bin = str(term_dir) + '/' + str(platform.lower()) + '/' + 'terminus'
print(term_bin)
class embeddedTerminal(QWidget):
def __init__(self):
QWidget.__init__(self)
self._processes = []
self.resize(800, 600)
self.terminal = QWidget(self)
layout = QVBoxLayout(self)
layout.addWidget(self.terminal)
self._stop_process()
self._start_process(
'xterm',
['-into', str(self.terminal.winId()),
'-e', 'tmux', 'new', '-s', 'my_session']
)
button = QPushButton('List files')
layout.addWidget(button)
button.clicked.connect(self._list_files)
def _start_process(self, prog, args):
child = QProcess()
self._processes.append(child)
child.start(prog, args)
def _list_files(self):
self._start_process(
'tmux', ['send-keys', '-t', 'my_session:0', 'ls', 'Enter'])
#classmethod
def _stop_process(self):
call(["tmux", "kill-session", "-t", "my_session"])
if __name__ == "__main__":
app = QApplication(sys.argv)
main = embeddedTerminal()
main.show()
sys.exit(app.exec_())

interactive python - keeping console interactive with a GUI mainloop

I am wondering how one would create a GUI application, and interact with it from the console that started it.
As an example, I would like to create a GUI in PyQt and work with it from the console. This could be for testing settings without restarting the app, but in larger projects also for calling functions etc.
Here is a simple example using PyQt:
import sys
from PyQt4 import QtGui
def main():
app = QtGui.QApplication(sys.argv)
w = QtGui.QWidget()
w.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
when this is run with python -i example.py the console is blocked as long as the main-loop is executed.
How can I call w.resize(100,100) while the GUI is running?
ops, posted wrong answer before
there is a post in Stack about that
Execute Python code from within PyQt event loop
The following example uses the code module to run a console in the command prompt (be sure to run the script from the command line). Subclassing QThread provides a route by which the console can be run in a separate thread from that of the main GUI and enables some interaction with it. The stub example below should be easy enough to incorporate into a larger packaged PyQt program.
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import threading #used only to id the active thread
import code
import sys
class Worker(QThread): #Subclass QThread and re-define run()
signal = pyqtSignal()
def __init__(self):
super().__init__()
def raise_sys_exit(self): #more gracefully exit the console
print('(Deactivated Console)')
raise SystemExit
def setup_console(self,global_dict):
console_exit = {'exit': self.raise_sys_exit}
self.console = code.InteractiveConsole(locals=dict(global_dict,**console_exit))
def run(self):
try:
print('worker', threading.get_ident())
self.console.interact()
except SystemExit:
self.signal.emit()
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args,**kwargs)
self.data = [1,2,3,4] #some data we might want to look at
layout = QVBoxLayout()
self.b = QPushButton("Interact")
self.b.clicked.connect(self.b_clicked)
layout.addWidget(self.b)
w = QWidget()
w.setLayout(layout)
self.setCentralWidget(w)
self.worker = Worker()
self.worker.signal.connect(self.finished)
def finished(self):
self.b.setEnabled(True)
def b_clicked(self):
print('main',threading.get_ident())
self.worker.setup_console(globals()) #pass the global variables to the worker
self.worker.start()
self.b.setEnabled(False) #disable the GUI button until console is exited
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
The easiest way is to use IPython:
ipython --gui=qt4
See ipython --help or the online documentation for more options (e.g. gtk, tk, etc).

How to make a window that occupies the full screen without maximising?

I'm writing in python using Qt
I want to create the application window (with decorations) to occupy the full screen size. Currently this is the code I have:
avGeom = QtGui.QDesktopWidget().availableGeometry()
self.setGeometry(avGeom)
the problem is that it ignores window decorations so the frame is larger... I googled and what not, found this:
http://harmattan-dev.nokia.com/docs/library/html/qt4/application-windows.html#window-geometry
which seems to indicate I need to set the frameGeometry to the avGeom however I haven't found a way to do that. Also, in the comments in the above link it says what I'm after may not be even possible as the programme can't set the frameGeometry before running... If that is the case I just want confirmation that my problem is not solvable.
EDIT:
So I played around with the code a bit and this gives what I want... however the number 24 is basically through trial and error until the window title is visible.... I want some better way to do this... which is window manager independent..
avGeom = QtGui.QDesktopWidget().availableGeometry()
avGeom.setTop(24)
self.setGeometry(avGeom)
Now I can do what I want but purely out of trial and error
Running Ubuntu, using Spyder as an IDE
thanks
Use QtGui.QApplication().desktop().availableGeometry() for the size of the window:
#!/usr/bin/env python
#-*- coding:utf-8 -*-
from PyQt4 import QtGui, QtCore
class MyWindow(QtGui.QWidget):
def __init__(self, parent=None):
super(MyWindow, self).__init__(parent)
self.pushButtonClose = QtGui.QPushButton(self)
self.pushButtonClose.setText("Close")
self.pushButtonClose.clicked.connect(self.on_pushButtonClose_clicked)
self.layoutVertical = QtGui.QVBoxLayout(self)
self.layoutVertical.addWidget(self.pushButtonClose)
titleBarHeight = self.style().pixelMetric(
QtGui.QStyle.PM_TitleBarHeight,
QtGui.QStyleOptionTitleBar(),
self
)
geometry = app.desktop().availableGeometry()
geometry.setHeight(geometry.height() - (titleBarHeight*2))
self.setGeometry(geometry)
#QtCore.pyqtSlot()
def on_pushButtonClose_clicked(self):
QtGui.QApplication.instance().quit()
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
app.setApplicationName('MyWindow')
main = MyWindow()
main.show()
sys.exit(app.exec_())
I've always found inheritting from the QMainWindow class to be particularly useful. Like this:
import sys
from PySide.QtGui import *
from PySide.QtCore import *
class Some_APP(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
### this line here is what you'd be looking for
self.setWindowState(Qt.WindowMaximized)
###
self.show()
def main():
app = QApplication(sys.argv)
some_app = Some_APP()
sys.exit(app.exec_())
if __name__ == "__main__":
main()

Qt QGraphicsDropShadowEffect is not showing

I am creating a custom widget my_widget inheriting from QWidget.
Here, I have a label to which I would like to apply QGraphicsDropShadowEffect however it does not seem to be working since I don't see any shadows.
My code is in Python and it's:
eff = QGraphicsDropShadowEffect()
self.my_widget_label.setGraphicsEffect(eff)
I tried various alterations to this code to no avail.
After doing a through search on Google, I came across many similar questions without answers.
What might be the cause? How can I get the shadow?
Works for me in C++. I did the following in a QDialog containing a QLabel object named titleLabel. I'm using Qt 4.8.4 on a Windows XP computer.
QGraphicsDropShadowEffect* eff = new QGraphicsDropShadowEffect(this);
eff->setBlurRadius(5);
titleLabel->setGraphicsEffect(eff);
See if this works for you:
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import sip
sip.setapi('QString', 2)
sip.setapi('QVariant', 2)
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class testShadow(QWidget):
def __init__(self, parent=None):
super(testShadow, self).__init__(parent)
self.resize(94, 35)
self.verticalLayout = QVBoxLayout(self)
self.verticalLayout.setObjectName("verticalLayout")
self.label = QLabel(self)
self.label.setText("Text Label")
self.shadow = QGraphicsDropShadowEffect(self)
self.shadow.setBlurRadius(5)
self.label.setGraphicsEffect(self.shadow)
self.verticalLayout.addWidget(self.label)
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
main = testShadow()
main.show()
sys.exit(app.exec_())
I have only every tried to use this (and used it successfully) in QGraphicsScene situations. This works for me, while trying to set it on a normal QWidget actually crashes the entire application:
from PyQt4 import QtGui
class Graphics(QtGui.QWidget):
def __init__(self):
super(Graphics, self).__init__()
layout = QtGui.QVBoxLayout(self)
layout.setMargin(0)
shad = QtGui.QGraphicsDropShadowEffect(self)
shad.setBlurRadius(5)
self.scene = QtGui.QGraphicsScene(self)
self.view = QtGui.QGraphicsView(self)
self.view.setScene(self.scene)
text = self.scene.addText("Drop Shadow!")
text.setGraphicsEffect(shad)
layout.addWidget(self.view)
if __name__ == "__main__":
app = QtGui.QApplication([])
main = Graphics()
main.show()
main.raise_()
app.exec_()

Categories