RkBlog

Hardware, programming and astronomy tutorials and reviews.

Rozbudowa edytora II

Wykorzystanie QFileSystemWatcher do monitorowania stanu otwartego pliku tekstowego i reagowanie na jego modyfikacje zapisane przez inne, zewnętrzne aplikacje

W tym artykule omówię kilka z ostatnich modyfikacji jakie wprowadziłem do edytora tekstowego. Pierwsza z nich to zastosowanie QFileSystemWatcher - monitora katalogów i plików umożliwiającego reagowanie na zmiany w pliku wprowadzone przez inne aplikacje. Druga modyfikacja to możliwość zapisu nowego pliku (gdy wpiszemy jakiś tekst w oknie edytora bez otwarcia pliku) za pomocą QFileDialog (getSaveFileName). Oto wynikowy start.py:
# -*- coding: utf-8 -*-
import sys
from PyQt4 import QtCore, QtGui
from edytor import Ui_notepad
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_())
Pojawiła się nowa metoda file_changed(self, path) wywoływana przez sygnał fileChanged(const QString&) z QFileSystemWatcher, który przekazuje jako pierwszy parametr ścieżkę do pliku jaki uległ modyfikacji:
	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)
Wykorzystujemy QMessageBox by wyświetlić okno ze stosownym komunikatem i umożliwić zapisanie otwartego pliku pod inną nazwą lub jego przeładowanie. Do zapisania używamy QFileDialog by móc podać nową nazwę pliku lub wybrać już istniejący. W kodzie aplikacji musimy pamiętać o dodawaniu i usuwaniu plików z listy obserwacji.

Pobierz źródła

Pobierz źródła
RkBlog

PyQt, 14 July 2008,

Comment article