QPlainTextEdit thinks it's modified if it has an empty text - python

I'm using PyQt to build a simple IDE and getting weird errors if you load an empty file. A small example script is posted below:
#!/usr/bin/env python
import sys
from PyQt4 import QtGui
class TestApp(QtGui.QMainWindow):
def __init__(self, filename=None):
super(TestApp, self).__init__()
self._editor = QtGui.QPlainTextEdit()
self._editor.modificationChanged.connect(self._change_modified)
self.setCentralWidget(self._editor)
self._editor.setPlainText('a')
def _change_modified(self, have_change):
print(have_change)
if __name__ == '__main__':
a = QtGui.QApplication([])
app = TestApp()
app.show()
sys.exit(a.exec_())
As expected, this shows a window with a plain text editor. As soon as the setPlainText method is called, the editor emits two events: One modificationChanged event with changes=True, a second with changes=False.
A bit weird, but fine.
However, if you change setPlainText('a') to setPlainText(''), only a single event is emitted, this time with changes=True. Even worse, after telling the editor it's not modified with setModified(False), it insists it's been changed somehow.
Does anyone know what's causing this and how I can work around this issue?
Update: It seems to be a bug & also affects QPlainTextEdit.clear().
The workaround below places a wrapper around the QPlainTextEdit to fix clear() and setPlainText('').
#!/usr/bin/env python
import sys
from PyQt4 import QtGui
class TestApp(QtGui.QMainWindow):
def __init__(self, filename=None):
super(TestApp, self).__init__()
self._editor = PlainTextEdit()
self._editor.modificationChanged.connect(self._change_modified)
self.setCentralWidget(self._editor)
self._editor.setPlainText('')
def _change_modified(self, have_change):
print(have_change)
class PlainTextEdit(QtGui.QPlainTextEdit):
def clear(self):
self.selectAll()
cursor = self.textCursor()
cursor.removeSelectedText()
doc = self.document()
doc.clearUndoRedoStacks()
doc.setModified(False)
self.modificationChanged.emit(False)
def setPlainText(self, text):
if text:
super(PlainTextEdit, self).setPlainText(text)
else:
self.clear()
if __name__ == '__main__':
a = QtGui.QApplication([])
app = TestApp()
app.show()
sys.exit(a.exec_())

It's a Qt bug, and the straightforward workaround is to check for empty contents if modifications are indicated.

Related

How to keep QMainWindow open?

I have a UI file that I created using Qt Creator. When I execute the application through PyCharm, the main window opens briefly, then closes. I assume it is being garbage collected, but I'm not sure how to get this to work. Any ideas?
Calculator.py
from PyQt5.QtWidgets import QApplication
import MainWindow
import sys
class Calculator(QApplication):
def __init__(self):
args = sys.argv
QApplication.__init__(self, args)
self.initializeApplication()
def initializeApplication(self):
app = MainWindow.MainWindow()
app.show()
if __name__ == '__main__':
app = Calculator()
sys.exit(app.exec_())
MainWindow.py
from PyQt5 import uic
from PyQt5.QtWidgets import QMainWindow
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self, None)
uic.loadUi(r'interface/MainWindow.ui', self)
self.initializeUI()
def initializeUI(self):
self.setWindowTitle('Calculator')
I'm new to Python so please bear with me. I have looked at a few different examples, but nothing that really covers when your application spans multiple source files. Thanks.
The comment that the garbage collector is deleting it is correct since the variables created in a function only exist while the function is called. Also to be able to execute a GUI, you must call exec_() to generate the main loop that is needed.
class Calculator(QApplication):
def __init__(self):
args = sys.argv
QApplication.__init__(self, args)
self.initializeApplication()
self.exec_()
def initializeApplication(self):
self.app = MainWindow.MainWindow()
self.app.show()
if __name__ == '__main__':
app = Calculator()

slot of QClipboard::dataChanged() was called twice

The slot detectClipboardUrl of QClipboard::dataChanged() was called twice sometimes when I copy url in Google Chrome's address bar in this code, tested with PyQt5.7,Python3.5 on Win7 32bit, also on Linux Mint 18,
while I need the slot to be called only once , is this a bug ? any solutions ?
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class MainWindow(QTableView):
def __init__(self, parent=None):
super().__init__(parent)
self.clipboard = QApplication.clipboard()
self.clipboard.dataChanged.connect(self.detectClipboardUrl)
#pyqtSlot()
def detectClipboardUrl(self):
print(self.clipboard.text())
if __name__ == "__main__":
app = QApplication(sys.argv)
ui = MainWindow()
ui.show()
sys.exit(app.exec_())
If the changes are duplicates, you can do something like:
class MainWindow(QTableView):
def __init__(self, parent=None):
self.clipboard = QApplication.clipboard()
self._cb_last = hash(self.clipboard.text())
self.clipboard.dataChanged.connect(self.detectClipboardUrl)
#pyqtSlot()
def detectClipboardUrl(self):
text = self.clipboard.text()
cb_current = hash(text)
if cb_current != self._cb_last:
print('clipboard text changed:', text)
self._cb_last = cb_current
The reason for using hash is simply to avoid keeping very large strings in memory.
Alternatively, if the two signals arrive very close together, you could use a QTimer to block any changes that occur within a few milliseconds of the first one.
UPDATE:
As I suspected, the problem is caused by a bug in Chromium: see Issue 173691.

PyQt: how do I replace a widget using a button?

I'm trying to write a simple code which will change existing text widget to another one when user clicks a button.
So I have
title1=QtGui.QLabel('Hello')
title2=QtGui.QLabel('bye')
abutton=QtGui.QPushButton('Click me')
grid.addWidget(title1,1,5)
grid.addWidget(abutton,3,5)
and I have a function:
def myfunc(self):
self.grid.removeWidget(self.title1)
self.grid.addWidget(title2,1,5)
which I expect to change my "hello" to "bye" after I do this:
abutton.clicked.connect(self.myfunc)
but apparently that doesn't work. And I've checked: removeWidged works perfectly outside the function (my first thought was, maybe, i was doing something wrong in the function), and also the function does work itself(i checked it by making in print stuff and it did once I clicked the button, but the widget was still there)
what might I be doing wrong? thanks.
You had typo and you trying local vars like so many issues in your code. Here is a working example
from PyQt4 import QtGui, QtCore
import sys
class BASEGUICLS(QtGui.QDialog):
def __init__(self,parent=None):
super(BASEGUICLS, self).__init__(parent)
self.gridLayout = QtGui.QGridLayout()
self.title1=QtGui.QLabel('Hello')
self.title2=QtGui.QLabel('bye')
abutton=QtGui.QPushButton('Click me')
self.gridLayout.addWidget(self.title1,1,5)
self.gridLayout.addWidget(abutton,3,5)
self.setLayout(self.gridLayout)
abutton.clicked.connect(self.myfunc)
def myfunc(self):
self.gridLayout.removeWidget(self.title1)
self.title1.deleteLater()
self.gridLayout.addWidget(self.title2,1,5)
def main():
app = QtGui.QApplication(sys.argv)
ex = BASEGUICLS(None)
ex.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()

PyQt4 multithreading using QThread

There is an endless block when xml.etree.ElementTree.fromstring() function is called in the QThread. Also lots of other calls makes the QThread blocked like multiprocessing.Process().
Important to say that it is a pure block, no exception or break.
Here is the code (a little edited but same principle as the source):
from PyQt4.QtGui import *
from Ui_mainwindow import Ui_MainWindow
import sys
import xml.etree
class Bruton(QThread):
def __init__(self, mw):
super(Bruton, self).__init__(mw)
self.mw = mw
def run(self):
print("This message I see.")
tree = xml.etree.ElementTree.fromstring("<element>text</element>")
print("But this one never.")
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.init_bruton()
# When the form is shown...
def showEvent(self, arg1):
self.bruton.start()
def init_bruton(self):
self.bruton = Bruton(self)
app = QApplication(sys.argv)
mw = MainWindow()
mw.show()
sys.exit(app.exec_())
The code as posted doesn't actually run, but with a couple minor changes it runs and works fine. Here is the code with modifications:
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import sys
import xml.etree.ElementTree
class Bruton(QThread):
def __init__(self, mw):
super(Bruton, self).__init__(mw)
self.mw = mw
def run(self):
print("This message I see.")
tree = xml.etree.ElementTree.fromstring("<element>text</element>")
print("But this one never.")
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.init_bruton()
# When the form is shown...
def showEvent(self, arg1):
self.bruton.start()
def init_bruton(self):
self.bruton = Bruton(self)
app = QApplication(sys.argv)
mw = MainWindow()
mw.show()
sys.exit(app.exec_())
And here is the output:
$ python test.py
This message I see.
But this one never.
This is with Python 2.6.6, PyQt4 4.8.3, on Debian Unstable.
Can you try it in your environment and see if my modified example works for you? If so, you are on the road to a solution for your real code. =)
The code I've shown here is shortened (the source is divided to two files and __ini__.py). I noticed that the main module must be the module that starts the QApplication. So I added app.exec_() to the __init__.py which is the main module of my program.

PyQt: Trouble with asterisk on modification in QPlainTextEdit

I'm having a problem with a QPlainTextEdit. I want the "contents have been modified" asterisk to appear in the title bar whenever the contents have been modified.
In the example below, type a few letters. The asterisk appears as it should. Hit Ctrl+S, the asterisk disappears as it should. But then if you type a few more letters... why doesn't the asterisk appear again?
import os, sys
from PyQt4 import QtGui, QtCore
class MyTextEdit(QtGui.QPlainTextEdit):
def __init__(self):
QtGui.QPlainTextEdit.__init__(self)
save_seq = QtGui.QKeySequence.Save
self.save_shortcut = QtGui.QShortcut(save_seq, self, self.save)
QtCore.QObject.connect(self,
QtCore.SIGNAL("modificationChanged(bool)"),
self.on_change)
def on_change(self, is_modified):
print "on_change"
window.setWindowModified(is_modified)
def save(self):
window.setWindowModified(False)
#
app = QtGui.QApplication(sys.argv)
window = QtGui.QMainWindow()
edit = MyTextEdit()
window.setCentralWidget(edit)
window.setWindowTitle("None [*]")
window.show()
app.exec_()
Never mind, figured it out. The problem was that in the save method I should've been calling self.document().setModified(False) instead of window.setWindowModified(False)

Categories

Resources