Handling File Actions

The File menu is probably the most widely implemented menu in main-window-style applications, and in most cases it offers, at the least, "new", "save", and "quit" (or "exit") options.

def fileNew(self):

if not self.okToContinue(): return dialog = newimagedlg.NewImageDlg(self) if dialog.exec_():

self.addRecentFile(self.filename) self.image = QImage()

for action, check in self.resetableActions:

action.setChecked(check) self.image = dialog.image() self.filename = None self.dirty = True self.showImage()

self.sizeLabel.setText("%d x %d" % (self.image.width(), self.image.height())) self.updateStatus("Created new image")

okToCon- When the user asks to work on a new file we begin by seeing whether it is "okay Unuef) to continue". This gives the user the chance to save or discard any unsaved 186 "sal changes, or to change their mind entirely and cancel the action.

Figure 6.9 The New Image dialog

If the user continues, we pop up a modal NewImageDlg in which they can specify the size, color, and brush pattern of the image they want to create. This dialog, shown in Figure 6.9, is created and used just like the dialogs we created in the preceding chapter. However, the New Image dialog's user interface was mk-

pyqt.py and

Make

PyQt sidebar

We set the filename to be None and the dirty flag to be True to ensure that the user will be prompted to save the image and asked for a filename, if they terminate the application or attempt to create or load another image.

We then call showImage() which displays the image in the imageLabel, scaled according to the zoom factor. Finally, we update the size label in the status bar, and call updateStatus().

def updateStatus(self, message):

self.statusBar().showMessage(message, 5000) self.listWidget.addItem(message) if self.filename is not None:

self.setWindowTitle("Image Changer - %s[*]" % \

os.path.basename(self.filename)) elif not self.image.isNull():

self.setWindowTitle("Image Changer - Unnamed[*]") else:

self.setWindowTitle("Image Changer[*]") self.setWindowModified(self.dirty)

We begin by showing the message that has been passed, with a timeout of five seconds. We also add the message to the log widget to keep a log of every action that has taken place.

If the user has opened an existing file, or has saved the current file, we will have a filename. We put the filename in the window's title using Python's os.path.basename() function to get the filename without the path. We could just as easily have written QFileInfo(fname).fileName() instead, as we did earlier. If there is no filename and the image variable is not a null image, it means that the user has created a new image, but has not yet saved it; so we use a fake filename of "Unnamed". The last case is where no file has been opened or created.

Regardless of what we set the window title to be, we include the string "[*]" somewhere inside it. This string is never displayed as it is: Instead it is used to indicate whether the file is dirty. On Linux and Windows this means that created using Qt Designer, and the user interface file must be converted into a module file, using pyuic4, for the dialog to be usable. This can be done directly by running pyuic4, or by running either mkpyqt.py or Make PyQt, both of which are easier since they work out the correct command-line arguments automatically. We will cover all of these matters in the next chapter.

If the user accepts the dialog, we add the current filename (if any) to the recently used files list. Then we set the current image to be a null image, to ensure that any changes to checkable actions have no effect on the image. Next we go through the actions that we want to be reset when a new image is created or loaded, setting each one to our preferred default value. Now we can safely set the image to the one created by the dialog.

the filename will be shown unadorned if it has no unsaved changes, and with an asterisk (*) replacing the "[*]" string otherwise. On Mac OS X, the close button will be shown with a dot in it if there are unsaved changes. The mechanism depends on the window modified status, so we make sure we set that to the state of the dirty flag.

def fileOpen(self):

if not self.okToContinue(): return dir = os.path.dirname(self.filename) \

if self.filename is not None else "." formats = ["*.%s" % unicode(format).lower() \

for format in QImageReader.supportedImageFormats()] fname = unicode(QFileDialog.getOpenFileName(self,

"Image Changer - Choose Image", dir, "Image files (%s)" % " ".join(formats)))

if fname:

self.loadFile(fname)

If the user asks to open an existing image, we first make sure that they have had the chance to save or discard any unsaved changes, or to cancel the action entirely.

If the user has decided to continue, as a courtesy, we want to pop up a file open dialog set to a sensible directory. If we already have an image filename, we use its path; otherwise, we use ".", the current directory. We have also chosen to pass in a file filter string that limits the image file types the file open dialog can show. Such file types are defined by their extensions, and are passed as a string. The string may specify multiple extensions for a single type, and multiple types. For example, a text editor might pass a string of:

"Text files (*.txt)\nHTML files (*.htm *.html)"

If there is more than one type, we must separate them with newlines. If a type can handle more than one extension, we must separate the extensions with spaces. The string shown will produce a file type combobox with two items, "Text files" and "HTML files", and will ensure that the only file types shown in the dialog are those that have an extension of .txt, .htm, or .html.

List compre hen-sions

In the case of the Image Changer application, we use the list of image type extensions for the image types that can be read by the version of PyQt that the application is using. At the very least, this is likely to include .bmp, .jpg (and .jpeg, the same as .jpg), and .png. The list comprehension iterates over the readable image extensions and creates a list of strings of the form "*.bmp", "*.jpg", and so on; these are joined, space-separated, into a single string by the string join() method.

The QFileDialog.getOpenFileName() method returns a QString which either holds a filename (with the full path), or is empty (if the user canceled). If the user chose a filename, we call loadFile() to load it.

Here, and throughout the program, when we have needed the application's name we have simply written it. But since we set the name in the application object in main() to simplify our QSettings usage, we could instead retrieve the name whenever it was required. In this case, the relevant code would then become:

fname = unicode(QFileDialog.getOpenFileName(self,

"%s - Choose Image" % QApplication.applicationName(), dir, "Image files (%s)" % " ".join(formats)))

It is surprising how frequently the name of the application is used. The file imagechanger.pyw is less than 500 lines, but it uses the application's name a dozen times. Some developers prefer to use the method call to guarantee consistency. We will discuss string handling further in Chapter 17, when we cover internationalization.

If the user opens a file, the loadFile() method is called to actually perform the loading. We will look at this method in two parts.

def loadFile(self, fname=None): if fname is None:

action = self.sender() if isinstance(action, QAction):

fname = unicode(action.data().toString()) if not self.okToContinue(): return else:

return

If the method is called from the fileOpen() method or from the loadInitial-File() method, it is passed the filename to open. But if it is called from a recently used file action, no filename is passed. We can use this difference to distinguish the two cases. If a recently used file action was invoked, we retrieve the sending object. This should be a QAction, but we check to be safe, and then extract the action's user data, in which we stored the recently used file's full name including its path. User data is held as a QVariant, so we must convert it to a suitable type. At this point, we check to see whether it is okay to continue. We do not have to make this test in the "file open" case, because there, the check is made before the user is even asked for the name of a file to open. So now, if the method has not returned, we know that we have a filename in fname that we must try to load.

if fname:

self.filename = None image = QImage(fname)

if image.isNull():

message = "Failed to read %s" % fname else:

self.addRecentFile(fname) self.image = QImage()

for action, check in self.resetableActions:

action.setChecked(check) self.image = image self.filename = fname self.showImage() self.dirty = False self.sizeLabel.setText("%d x %d" % (

image.width(), image.height())) message = "Loaded %s" % os.path.basename(fname) self.updateStatus(message)

We begin by making the current filename None and then we attempt to read the image into a local variable. PyQt does not use exception handling, so errors must always be discovered indirectly. In this case, a null image means that for add- some reason we failed to load the image. If the load was successful we add the new filename to the recently used files list, where it will appear only if another file is subsequently opened, or if this one is saved under another name. Next, we set the instance image variable to be a null image: This means that we are free to reset the checkable actions to our preferred defaults without any side effects. This works because when the checkable actions are changed, although the relevant methods will be called due to the signal-slot connections, the methods do nothing if the image is null.

After the preliminaries, we assign the local image to the image instance variable and the local filename to the filename instance variable. Next, we call showImage() to show the image at the current zoom factor, clear the dirty flag, and update the size label. Finally, we call updateStatus() to show the message in the status bar, and to update the log widget.

def fileSave(self):

if self.image.isNull(): return if self.filename is None:

self.fileSaveAs() else:

if self.image.save(self.filename, None):

self.updateStatus("Saved as %s" % self.filename) self.dirty = False else:

self.updateStatus("Failed to save %s" % self.filename)

Recent-File()

The fileSave() method, and many others, act on the application's data (a QImage instance), but make no sense if there is no image data. For this reason, many of the methods do nothing and return immediately if there is no image data for them to work on.

If there is image data, and the filename is None, the user must have invoked the "file new" action, and is now saving their image for the first time. For this case, we pass on the work to the fileSaveAs() method.

If we have a filename, we attempt to save the image using QImage.save(). This method returns a Boolean success/failure flag, in response to which we update the status accordingly. (We have deferred coverage of loading and saving custom file formats to Chapter 8, since we are concentrating purely on main window functionality in this chapter.)

def fileSaveAs(self):

if self.image.isNull(): return fname = self.filename if self.filename is not None else "." formats = ["*.%s" % unicode(format).lower() \

for format in QImageWriter.supportedImageFormats()] fname = unicode(QFileDialog.getSaveFileName(self,

"Image Changer - Save Image", fname, "Image files (%s)" % " ".join(formats)))

if fname:

if "." not in fname: fname += ".png" self.addRecentFile(fname) self.filename = fname self.fileSave()

When the "file save as" action is triggered we begin by retrieving the current filename. If the filename is None, we set it to be ".", the current directory. We then use the QFileDialog.getSaveFileName() dialog to prompt the user to give us a filename to save under. If the current filename is not None, we use that as the default nameā€”the file save dialog takes care of giving a warning yes/no dialog if the user chooses the name of a file that already exists. We use the same technique for setting the file filters string as we used for the "file open" action, but this time using the list of image formats that this version of PyQt can write (which may be different from the list of formats it can read).

If the user entered a filename that does not include a dot, that is, it has no extension, we set the extension to be .png. Next, we add the filename to the recently used files list (so that it will appear if a different file is subsequently opened, or if this one is saved under a new name), set the filename instance variable to the name, and pass the work of saving to the fileSave() method that we have just reviewed.

The last file action we must consider is "file print". When this action is invoked the filePrint() method is called. This method paints the image on a printer. Since the method uses techniques that we have not covered yet, we will defer

Printing

Images sidebar

Was this article helpful?

0 -1
Tuberminator

Tuberminator

The main focus of this report is to show how to get involved in video marketing on the run, how to rank quickly on YouTube and Google using FREE semi-automatic tools and services. QUICKLY AND FREE. I will show methods and techniques I use to rank my videos, as well as free resources and tools to make video clips, to get backlinks and free traffic.

Get My Free Ebook


Post a comment