Handling Edit Actions

Most of the functionality of the file actions was provided by the MainWindow subclass itself. The only work passed on was the image loading and saving, which the QImage instance variable was required to do. This particular division of responsibilities between a main window and the data structure that holds the data is very common. The main window handles the high-level file new, open, save, and recently used files functionality, and the data structure handles loading and saving.

It is also common for most, or even all, of the editing functionality to be provided either by the view widget or by the data structure. In the Image Changer application, all the data manipulation is handled by the data structure (the image QImage), and the presentation of the data is handled by the data viewer (the imageLabel QLabel). Again, this is a very common separation of responsibilities.

In this section, we will review most of the edit actions, omitting a couple that are almost identical to ones that are shown. We will be quite brief here, since the functionality is specific to the Image Changer application.

def editInvert(self, on): if self.image.isNull(): return self.image.invertPixels() self.showImage() self.dirty = True self.updateStatus("Inverted" if on else "Uninverted")

If the user invokes the "edit invert" action, it will be checked (or unchecked). In either case, we simply invert the image's pixels using the functionality provided by QImage, show the changed image, set the dirty flag, and call updateStatus() so that the status bar briefly shows the action that was performed, and an additional item is added to the log.

discussion of it until later. The technique it uses is shown in the Printing Images sidebar, and coverage of the filePrint() method itself is in Chapter 13 (from page 400), where we also discuss approaches to printing documents in general.

The only file action we have not reviewed is the "file quit" action. This action is connected to the main window's close() method, which in turn causes a close- close event to be put on the event queue. We provided a reimplementation of Event() the closeEvent() handler in which we made sure the user had the chance to 185 'sa save unsaved changes, using a call to okToContinue(), and where we saved the application's settings.

The editSwapRedAndBlue() method (not shown) is the same except that it uses the QImage.rgbSwapped() method, and it has different status text.

def editMirrorHorizontal(self, on): if self.image.isNull(): return self.image = self.image.mirrored(True, False) self.showImage()

self.mirroredhorizontally = not self.mirroredhorizontally self.dirty = True self.updateStatus("Mirrored Horizontally" \ if on else "Unmirrored Horizontally")

This method is structurally the same as editInvert() and editSwapRedAndBlue(). The QImage.mirrored() method takes two Boolean flags, the first for horizontal mirroring and the second for vertical mirroring. In the Image Changer application, we have deliberately restricted what mirroring is allowed, so users can only have no mirroring, vertical mirroring, or horizontal mirroring, but not a combination of vertical and horizontal. We also keep an instance variable that keeps track of whether the image is horizontally mirrored.

The editMirrorVertical() method,not shown, is virtually identical.

def editUnMirror(self, on): if self.image.isNull(): return if self.mirroredhorizontally:

self.editMirrorHorizontal(False) if self.mirroredvertically:

self.editMirrorVertical(False)

This method switches off whichever mirroring is in force, or does nothing if the image is not mirrored. It does not set the dirty flag or update the status: It leaves that for editMirrorHorizontal() or editMirrorVertical(), if it calls either of them.

The application provides two means by which the user can change the zoom factor. They can interact with the zoom spinbox in the toolbar—its valueChanged() signal is connected to the showImage() slot that we will review shortly—or they can invoke the "edit zoom" action in the Edit menu. If they use the "edit zoom" action, the editZoom() method is called.

def editZoom(self):

if self.image.isNull(): return percent, ok = QInputDialog.getInteger(self, "Image Changer - Zoom", "Percent:", self.zoomSpinBox.value(), 1, 400)

self.zoomSpinBox.setValue(percent)

We begin by using one of the QInputDialog class's static methods to obtain a zoom factor. The getInteger() method takes a parent (over which the dialog will center itself), a caption, text describing what data is wanted, an initial value, and, optionally, minimum and maximum values.

The QInputDialog provides some other static convenience methods, including getDouble() to get a floating-point value, getItem() to choose a string from a list, and getText() to get a string. For all of them, the return value is a two-tuple, containing the value and a Boolean flag indicating whether the user entered and accepted a valid value.

If the user clicked OK, we set the zoom spinbox's value to the given integer. If this value is different from the current value, the spinbox will emit a val-ueChanged() signal. This signal is connected to the showImage() slot, so the slot will be called if the user chose a new zoom percentage value.

def showImage(self, percent=None): if self.image.isNull():

return if percent is None:

percent = self.zoomSpinBox.value() factor = percent / 100.0 width = self.image.width() * factor height = self.image.height() * factor image = self.image.scaled(width, height, Qt.KeepAspectRatio) self.imageLabel.setPixmap(QPixmap.fromImage(image))

This slot is called when a new image is created or loaded, whenever a transformation is applied, and in response to the zoom spinbox's valueChanged() signal. This signal is emitted whenever the user changes the toolbar zoom spinbox's value, either directly using the mouse, or indirectly through the "edit zoom" action described earlier.

We retrieve the percentage and turn it into a zoom factor that we can use to produce the image's new width and height. We then create a copy of the image scaled to the new size and preserving the aspect ratio, and set the imageLabel to display this image. The label requires an image as a QPixmap, so we use the static QPixmap.fromImage() method to convert the QImage to a QPixmap.

Notice that zooming the image in this way has no effect on the original image; it is purely a change in view, not an edit. This is why the dirty flag does not need to be set.

According to PyQt's documentation, QPixmaps are optimized for on-screen display (so they are fast to draw), and QImages are optimized for editing (which is why we have used them to hold the image data).

Was this article helpful?

0 0
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