pyqt5 textedit delete lines as they move past specified line - python

I'd like to setup the PYQT Qtextedit widget and use it to monitor another applications activity log(like tail -f on Linux). Long term I worry about it running for too long and using a lot of ram with the text that builds up. Is it possible to set a limit so that text moving past line x gets deleted? From what I've found it seems to require custom work and I'd like to find a limiter setting if one exists.

QPlainTextEdit is an advanced viewer/editor supporting plain text. It is optimized to handle large documents and to respond quickly to user input.
To limit the number of visible lines you must use setMaximumBlockCount, in the following example I show the use:
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
counter = 0
def addText():
global counter
w.appendHtml("<font size=\"3\" color=\"red\">{}</font>".format(counter))
counter += 1
if __name__ == "__main__":
app = QApplication(sys.argv)
w = QPlainTextEdit()
timer = QTimer()
timer.timeout.connect(addText)
timer.start(1000)
w.setMaximumBlockCount(4)
w.show()
sys.exit(app.exec_())
If you want to use fonts you can do it easily using HTML.

Related

Interact with continuously running Python Gui by external and exchangeable python script

my long term goal is to build a gui for an experiment in experimental physics which has a continuously running gui. By pushing a button I would like to be able to run a pyhton script of my choice which can interact with the running gui. For example setting a number to a spin box.
I attached a starting project. A spinbox and a button. If the button is pressed a random number is set to the spinbox and as soon as the number in the spinbox changes, it prints the number.
Is there a way to call a script (at the moment with a hard coded path) by pushing the button, which then sets the number in the gui to my choice. The content of the script (in this case the number which is set to the spin box) has to be editable during the runtime of the gui.
If you could provide an example for this, I would be grateful and could build the rest myself.
Thanks in advance!
import sys
import random
from PyQt5 import QtCore, QtGui
from PyQt5.QtWidgets import QApplication, QWidget, QDoubleSpinBox, QPushButton
class GuiInteraction(QWidget):
def __init__(self):
super().__init__()
self.initGUI()
self.CallBackFunctions()
def initGUI(self):
self.resize(400, 500)
self.move(300, 300)
self.setWindowTitle('Gui Interaction')
self.doubleSpinBox = QDoubleSpinBox(self)
self.doubleSpinBox.setGeometry(QtCore.QRect(120, 130, 120, 25))
self.doubleSpinBox.setDecimals(5)
self.doubleSpinBox.setMaximum(1000)
self.doubleSpinBox.setObjectName("doubleSpinBox")
self.pushButton = QPushButton("Run Script", self)
self.pushButton.setGeometry(QtCore.QRect(100, 300, 100, 40))
self.pushButton.setObjectName("pushButton")
def CallBackFunctions(self):
self.pushButton.clicked.connect(self.buttonClicked)
self.doubleSpinBox.valueChanged.connect(self.valueChanged)
def buttonClicked(self):
self.doubleSpinBox.setValue(random.uniform(1, 200))
def valueChanged(self):
print(self.doubleSpinBox.value())
if __name__ == '__main__':
app = QApplication(sys.argv)
MyWindow = GuiInteraction()
MyWindow.show()
sys.exit(app.exec_())
I'm thinking you can call a FileDialog, pick a script and use:
mod = __import__(path)
, and than should the script be adequately built with a "run" function of some kind you can just launch it by:
mod.run()
Check this question also.
One way would be to just communicate with an external script through stdin/stdout
my_script.py
def main():
print '4.2'
if __name__ == '__main__':
main()
gui_script.py
import subprocess
...
def buttonClicked(self):
try:
value = float(subprocess.check_output(['python', 'my_script.py']).strip())
except ValueError:
value = 1.0
self.doubleSpinBox.setValue(value)
If you need to pass arguments to your function you can just pass them as additional arguments to the subprocess.check_output call, and them read them from sys.argv (they'll all come in as strings, so you'd have to convert the types if necessary, or use a library like argparse, which can do the type-casting for you) in the called script.
I went for the solution of #armatita, even though I changed it a little. After a brief research __import__ seems to be replaced by the libimport libary, which I now use. The following lines have been added:
Header:
import importlib
import threading
and in the main function:
def buttonClicked(self):
ScriptControlModule = importlib.import_module("ExternalScript")
ScriptControlModule = importlib.reload(ScriptControlModule)
ScriptControlThread = threading.Thread(target=ScriptControlModule.execute,args=(self,))
ScriptControlThread.start()
My question is answered by the importlib lines. I also wanted to start the script in a subthread, so in the case it crashes due to a typo or anything else, the whole gui does not follow the crash.
The ExternalScript is in the same folder and named ExternalScript.py
import random
def execute(self):
self.doubleSpinBox.setValue(random.uniform(1, 5))
Three simple lines of code. I can change these lines while running and get different values in the SpinBox. Works out perfectly!

Python realtime mousedata with pyqtgraph

In my epic struggle to process raw mouse data on my ubuntu (14.04) OS with python, with alot help from here i am stuck again. It seems very hard for me to understand the "easyness" of pyqtgraph. all i want to do is to wrap the code i have now into a nice little gui with a start/pause/stop button, a list widget to show the numbers and a plot to let me see whats happening. I guess the main problem for me is, that i dont quite understand this whole event thing in pyqt.
anyway taking a "easy" example which has the widgets i want, i fail to implement my code(edited more minimalistic):
#!/usr/bin/python
import threading
import struct
import time
import numpy as np
from PyQt4 import QtGui # (the example applies equally well to PySide)
from PyQt4 import QtCore
import pyqtgraph as pg
##
data =[(0,0)]
sum_data = [(0,0)]
file = open("/dev/input/mouse2", "rb")
def getMouseEvent():
buf = file.read(3);
#python 2 & 3 compatibility
button = buf[0] if isinstance(buf[0], int) else ord(buf[0])
x,y = struct.unpack( "bb", buf[1:] );
print x,y
return x, y
def mouseCollect():
while True:
data.append(getMouseEvent())
sum_data.append(tuple(map(sum,zip(sum_data[-1],data[-1]))))
plot.plot(sum_data[0], clear=True)
pg.QtGui.QApplication.processEvents()
print sum_data[-1]
## Always start by initializing Qt (only once per application)
app = QtGui.QApplication([])
## Define a top-level widget to hold everything
w = QtGui.QWidget()
## Create some widgets to be placed inside
btn1 = QtGui.QPushButton('Start')
listw = QtGui.QListWidget()
plot = pg.PlotWidget()
def start_btn():
print 'test'
threading.Thread(target=mouseCollect).start()
btn1.clicked.connect(start_btn)
## Create a grid layout to manage the widgets size and position
layout = QtGui.QGridLayout()
w.setLayout(layout)
## Add widgets to the layout in their proper positions
layout.addWidget(btn1, 0, 0) # button goes in upper-left
layout.addWidget(plot, 0, 1, 4, 1)
## Display the widget as a new window
w.show()
## Start the Qt event loop
app.exec_()
##------------------------------------------------------------------
when i press the start button, the window just freezes and nothing happens. my thought was, if i press the button, it connects to the methot state there and it just doing its stuff. okay i have an infinite loop, but at least i thought i should see something. any help is apreciated, also any tips for a good reading about the matter is very welcome.
regards
Edit: inserted a thread as suggested by echocage
The best approach for repetitive updates is to use a QTimer. In this way you allow program control to go back to the Qt event loop after every update (no infinite loops allowed), and Qt periodically invokes your update function for you.
For example, see one of the many the updating plot examples included with pyqtgraph: https://github.com/pyqtgraph/pyqtgraph/blob/develop/examples/Plotting.py#L58
Threading is very difficult to do correctly and when it's done wrong you usually end up with crashing that is difficult to debug. I recommend to avoid threading until you are very confident with the event system and familiar with the pitfalls of threading.
I finally found it. I somehow never realized, that you HAVE to use numpy arrays. also i didnt use curve.setdata for plotting.
the more or less final code(not full code) now looks like this:
class mouseCollect(QtCore.QThread):
def __init__(self):
QtCore.QThread.__init__(self)
def run (self):
global e, curve, curve2, data1, data2
while e.wait():
tmp = getMouseEvent() #returns tuples in form of (x,y)
data1 = np.append(data1, tmp[0] )
data2 = np.append(data2, tmp[1] )
sum1= np.cumsum(data1)
sum2= np.cumsum(data2)
curve.setData(y=sum1)
curve2.setData(y=sum2)
guiPE # process event thingy
def stop(self):
e.clear()
well, it is a not really written efficiently, but it works :)

PyQt4: Auto completion in Qscintilla and horizontal scrolling

I want to show all attributes and tags in auto completion list of a html file if auto completion threshold is set to 1. I have tried this code to use APIs i set this code after the file is loaded in new mdi child(sub window) but it is not working:
lexer=Qsci.QsciLexerHTML()
api = Qsci.QsciAPIs(lexer)
## Add autocompletion strings
api.add("aLongString")
api.add("aLongerString")
api.add("aDifferentString")
api.add("sOmethingElse")
## Compile the api for use in the lexer
api.prepare()
self.activeMdiChild().setAutoCompletionSource(Qsci.QsciScintilla.AcsAPIs)
self.activeMdiChild().setLexer(lexer)
and my horizontal scroll bar is visible all the time i want to set it as scrollbarasneeded. please tell how to do these two tasks.
Other than failing to set the auto-completion threshold, there doesn't seem to be anything wrong with your example code. Here's a minimal working example:
from PyQt4 import QtGui, Qsci
class Window(Qsci.QsciScintilla):
def __init__(self):
Qsci.QsciScintilla.__init__(self)
lexer = Qsci.QsciLexerHTML(self)
api = Qsci.QsciAPIs(lexer)
api.add('aLongString')
api.add('aLongerString')
api.add('aDifferentString')
api.add('sOmethingElse')
api.prepare()
self.setAutoCompletionThreshold(1)
self.setAutoCompletionSource(Qsci.QsciScintilla.AcsAPIs)
self.setLexer(lexer)
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
app.exec_()
The scrollbar-as-needed feature cannot really be solved, unless you are willing to reimplement everything yourself (which would not be easy). The underlying Scintilla control doesn't directly support automatic horizontal scrollbar hiding, because it involves a potentially very expensive calculation (i.e. determining the longest line). Most people who use Scintilla/Qscintilla just learn to put up with the ever-present horizontal scrollbar.

Carcass of QProgressDialog lingers - sometimes

progress = QtGui.QProgressDialog("Parsing Log", "Stop", 0,numberOfLinesInFile , self)
progress.setWindowModality(QtCore.Qt.WindowModal)
for lineNumber, line in enumerate(file):
# yield a bit to the Qt UI handler
QtGui.QApplication.processEvents()
progress.setValue(lineNumber + 1) # lineNumber is zero-based so need the plus one to match the more literal numberOfLinesInFile
if progress.wasCanceled():
progressWasCancelled = True
break
# ...read and parse lines from file (20mb takes ~10 seconds)
# crank the progress bar through to completion to get rid of it
# this seems to forgo the opportunity to use progress.wasCanceled() subsequently?
progress.setValue(numberOfLinesInFile)
if not progressWasCancelled:
self.updateTable(self.requestRoster)
After this, and regardless of the progress dialogue being cancelled or not, the progress dialogue is hidden (it slides back up into the toolbar). But if I switch application ('command tab' on the Mac) then switch back to my application, a ghost of the QProgressDialog is in front of the main application window! Its progress bar is at 100% and the stop button is blue but not pulsing. It is unresponsive. If I move the application window it disappears.
If I call progress.destroy() after progress.setValue(numberOfLinesInFile) that seems to help. But it seems worrying to copy the example from the docs and get bitten, and I don't know the ramifications of destroy().
I was using PySide, I switched to PyQt and same thing.
Also, sometimes progress.setValue(numberOfLinesInFile) causes subsequent reads of progress.wasCancelled() to return false (but sometimes it returns true!) which is why I set my own progressWasCancelled. Its randomness is disturbing.
I'm on Mac 10.6.8, Qt 4.8.2, Python 2.7. Tried with PySide 1.1.0 and PyQt 4.9.4.
Am I doing this all wrong?
I can't test on a Mac, but I'll try to make a few suggestions which could help solve your issues.
Firstly, if you use a modal progress dialog, there's no need to call processEvents(), as the dialog will handle this itself.
Secondly, this line in your code:
progress.setValue(lineNumber + 1)
is problematic, because to quote the Qt docs:
For the progress dialog to work as expected, you should initially set this property to 0 and finally set it to QProgressDialog::maximum(); you can call setValue() any number of times in-between.
so you should either call progress.setValue(0) before the loop, or just avoid adding the offset altogether. Also, on the final iteration, lineNumber + 1 will equal the maximum, which will reset the dialog at that point (unless autoReset has been set to False). It is for this reason that the Qt example calls setValue(maximum) after the loop has completed.
Finally, there is no problem with calling destroy() or deleteLater() after you've finished with the progress dialog - in fact, it's a good idea. When you pass self to the QProgressDialog constructor, it will become the parent of the dialog and keep a reference to it. So, unless you explicitly delete it, a new child dialog (plus all it's child objects) will be added every time you call the function that uses it (which could potentially waste a lot of memory).
Here's a demo script that may be improvement:
import sys, time
from PyQt4 import QtGui, QtCore
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.button = QtGui.QPushButton('Test', self)
self.button.clicked.connect(self.handleButton)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.button)
def handleButton(self):
file = range(30)
numberOfLinesInFile = len(file)
progressWasCancelled = False
progress = QtGui.QProgressDialog(
"Parsing Log", "Stop", 0, numberOfLinesInFile, self)
progress.setWindowModality(QtCore.Qt.WindowModal)
progress.setMinimumDuration(0)
for lineNumber, line in enumerate(file):
progress.setValue(lineNumber)
if progress.wasCanceled():
progressWasCancelled = True
break
time.sleep(0.05)
progress.setValue(numberOfLinesInFile)
print 'cancelled', progress.wasCanceled(), progressWasCancelled
progress.deleteLater()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())

PyQt: Qt.Popup widget sometimes loses focus without closing, becomes unclosable

I'm writing a very small application with PyQt. All of my testing has been on Ubuntu/gnome so far.
I want a single "Popup" style window, with no taskbar/panel entry, that will close itself (and the application) the moment it loses focus.
The Qt.Popup flag seems to fit the bill, but I'm having a weird problem. I've noticed that it's possible (pretty easy, in fact) to take focus away from the application as it's starting, leaving a Popup window with no focus -- and it is now impossible to close it, because it cannot lose focus.
Here's a simplified example:
#!/usr/bin/python
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
if __name__ == '__main__':
app = QApplication(sys.argv)
w = QDialog()
w.setWindowFlags(Qt.Popup)
w.exec_()
If you click around a bit within the same moment the program is starting, the QDialog will appear without focus, and will not close itself under any circumstance. Clicking on the popup does not restore focus or allow it to be closed.
I could add a close button to the popup (and I intend to!) but that doesn't fix the broken close-on-lost-focus behavior. Is there something else I should be doing with Qt.Popup windows to prevent this, or is there some way I can work around it?
Using QWidget::raise() seems to help here.
(Also took the liberty and fixed your app event loop)
#!/usr/bin/python
import sys
#import time
from PyQt4.QtCore import *
from PyQt4.QtGui import *
if __name__ == '__main__':
#time.sleep(2)
app = QApplication(sys.argv)
w = QDialog()
w.setWindowFlags(Qt.Popup)
w.setAttribute(Qt.WA_QuitOnClose)
w.show()
w.raise_()
sys.exit(app.exec_())

Categories