RkBlog

Hardware, programming and astronomy tutorials and reviews.

PyQT4 Text editor - final changes

Some advanced PyQT4 features

Here I'll describe few final changes to the text editor. The first one is the usage of QFileSystemWatcher to watch if the open file didn't changed while open in our editor. Next one is the option to save a new file (when we enter some text without opening any file). So here is the start.py:
# -*- coding: utf-8 -*-
import sys
from PyQt4 import QtCore, QtGui
from edytor import Ui_notepad
import codecs
import codecs
from os.path import isfile

class StartQT4(QtGui.QMainWindow):
	def __init__(self, parent=None):
		QtGui.QWidget.__init__(self, parent)
		self.ui = Ui_notepad()
		self.ui.setupUi(self)
		self.watcher = QtCore.QFileSystemWatcher(self)
		QtCore.QObject.connect(self.ui.button_open,QtCore.SIGNAL("clicked()"), self.file_dialog)
		QtCore.QObject.connect(self.ui.button_save,QtCore.SIGNAL("clicked()"), self.file_save)
		QtCore.QObject.connect(self.ui.editor_window,QtCore.SIGNAL("textChanged()"), self.enable_save)
		QtCore.QObject.connect(self.watcher,QtCore.SIGNAL("fileChanged(const QString&)"), self.file_changed)
		self.filename = False
	def file_dialog(self):
		response = False
		# buttons texts
		SAVE = 'Save'
		DISCARD = 'Discard Changes'
		CANCEL = 'Cancel'
		# if we have changes then ask about them
		if self.ui.button_save.isEnabled():
			message = QtGui.QMessageBox(self)
			message.setText('Changes haven't been saved')
			message.setWindowTitle('Notepad')
			message.setIcon(QtGui.QMessageBox.Question)
			message.addButton(SAVE, QtGui.QMessageBox.AcceptRole)
			message.addButton(DISCARD, QtGui.QMessageBox.DestructiveRole)
			message.addButton(CANCEL, QtGui.QMessageBox.RejectRole)
			message.setDetailedText('Unsaved Changes in: ' + str(self.filename))
			message.exec_()
			response = message.clickedButton().text()
			# save  file
			if response == SAVE:
				self.file_save()
				self.ui.button_save.setEnabled(False)
			# discard changes
			elif response == DISCARD:
				self.ui.button_save.setEnabled(False)
		# if we didn't canceled show the file dialog
		if response != CANCEL:
			fd = QtGui.QFileDialog(self)
			# remove old file from watcher
			if self.filename:
				self.watcher.removePath(self.filename)
			self.filename = fd.getOpenFileName()
			if isfile(self.filename):
				s = codecs.open(self.filename,'r','utf-8').read()
				self.ui.editor_window.setPlainText(s)
				self.ui.button_save.setEnabled(False)
				# add file to watcher
				self.watcher.addPath(self.filename)
	def enable_save(self):
		self.ui.button_save.setEnabled(True)
		
	def file_changed(self, path):
		response = False
		# buttons texts
		SAVE = 'Save As'
		RELOAD = 'Reload File'
		CANCEL = 'Cancel'
		message = QtGui.QMessageBox(self)
		message.setText('Open file have been changed !')
		message.setWindowTitle('Notepad')
		message.setIcon(QtGui.QMessageBox.Warning)
		message.addButton(SAVE, QtGui.QMessageBox.AcceptRole)
		message.addButton(RELOAD, QtGui.QMessageBox.DestructiveRole)
		message.addButton(CANCEL, QtGui.QMessageBox.RejectRole)
		message.setDetailedText('The file "' + str(path) + '" have been changed or removed by other application. What do you want to do ?')
		message.exec_()
		response = message.clickedButton().text()
		# save current file under a new or old name
		if response == SAVE:
			fd = QtGui.QFileDialog(self)
			newfile = fd.getSaveFileName()
			if newfile:
				s = codecs.open(newfile,'w','utf-8')
				s.write(unicode(self.ui.editor_window.toPlainText()))
				s.close()
				self.ui.button_save.setEnabled(False)
				# new file, remove old and add the new one to the watcher
				if self.filename and str(newfile) != str(self.filename):
					self.watcher.removePath(self.filename)
					self.watcher.addPath(newfile)
					self.filename = newfile
		# reload the text in the editor
		elif response == RELOAD:
			s = codecs.open(self.filename,'r','utf-8').read()
			self.ui.editor_window.setPlainText(s)
			self.ui.button_save.setEnabled(False)
		
	def file_save(self):
		# save changes to existing file
		if self.filename and isfile(self.filename):
			# don't react on our changes
			self.watcher.removePath(self.filename)
			s = codecs.open(self.filename,'w','utf-8')
			s.write(unicode(self.ui.editor_window.toPlainText()))
			s.close()
			self.ui.button_save.setEnabled(False)
			self.watcher.addPath(self.filename)
		# save a new file
		else:
			fd = QtGui.QFileDialog(self)
			newfile = fd.getSaveFileName()
			if newfile:
				s = codecs.open(newfile,'w','utf-8')
				s.write(unicode(self.ui.editor_window.toPlainText()))
				s.close()
				self.ui.button_save.setEnabled(False)

if __name__ == "__main__":
	app = QtGui.QApplication(sys.argv)
	myapp = StartQT4()
	myapp.show()
	sys.exit(app.exec_())
A new method file_changed(self, path) showed up. It is called by signal fileChanged(const QString&) from QFileSystemWatcher, which passes as the first argument the path to the file which changed:
	def file_changed(self, path):
		response = False
		# buttons texts
		SAVE = 'Save As'
		RELOAD = 'Reload File'
		CANCEL = 'Cancel'
		message = QtGui.QMessageBox(self)
		message.setText('Open file have been changed !')
		message.setWindowTitle('Notepad')
		message.setIcon(QtGui.QMessageBox.Warning)
		message.addButton(SAVE, QtGui.QMessageBox.AcceptRole)
		message.addButton(RELOAD, QtGui.QMessageBox.DestructiveRole)
		message.addButton(CANCEL, QtGui.QMessageBox.RejectRole)
		message.setDetailedText('The file "' + str(path) + '" have been changed or removed by other application. What do you want to do ?')
		message.exec_()
		response = message.clickedButton().text()
		# save current file under a new or old name
		if response == SAVE:
			fd = QtGui.QFileDialog(self)
			newfile = fd.getSaveFileName()
			if newfile:
				s = codecs.open(newfile,'w','utf-8')
				s.write(unicode(self.ui.editor_window.toPlainText()))
				s.close()
				self.ui.button_save.setEnabled(False)
				# new file, remove old and add the new one to the watcher
				if self.filename and str(newfile) != str(self.filename):
					self.watcher.removePath(self.filename)
					self.watcher.addPath(newfile)
					self.filename = newfile
		# reload the text in the editor
		elif response == RELOAD:
			s = codecs.open(self.filename,'r','utf-8').read()
			self.ui.editor_window.setPlainText(s)
			self.ui.button_save.setEnabled(False)
We use QMessageBox to show the message in a window and allow user to save current file under different name or reload the file. To save a file we use QFileDialog with getSaveFileName. Using QFileSystemWatcher we have to remember about adding and removing files from the watch list.

Download

Download source
RkBlog

PyQt and GUI, 14 July 2008,

Comment article