Exemple #1
0
    def __init__(self, base):
        Window.__init__(self, base, i18n.get('image_preview'))

        self.loader = BarLoadIndicator()

        self.view = QLabel()
        self.view.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
        self.view.setScaledContents(True)

        scroll_area = QScrollArea()
        scroll_area.setBackgroundRole(QPalette.Dark)
        scroll_area.setWidget(self.view)
        scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

        self.error_label = QLabel(i18n.get('error_loading_image'))
        self.error_label.setAlignment(Qt.AlignHCenter)
        self.error_label.setStyleSheet("QLabel {background-color: #ffecec;}")

        layout = QVBoxLayout()
        layout.setSpacing(0)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.loader)
        layout.addWidget(self.error_label)
        layout.addWidget(scroll_area)

        self.setLayout(layout)
        self.__clear()
Exemple #2
0
class PipedImagerPQ(QMainWindow):
    '''
    A PyQt graphics viewer that receives images and commands through
    a pipe.

    A command is a dictionary with string keys.  For example,
        { "action":"save",
          "filename":"ferret.png",
          "fileformat":"png" }

    The command { "action":"exit" } will shutdown the viewer.
    '''
    def __init__(self, cmndpipe, rspdpipe):
        '''
        Create a PyQt viewer which reads commands from the Pipe
        cmndpipe and writes responses back to rspdpipe.
        '''
        super(PipedImagerPQ, self).__init__()
        self.__cmndpipe = cmndpipe
        self.__rspdpipe = rspdpipe
        # ignore Ctrl-C
        signal.signal(signal.SIGINT, signal.SIG_IGN)
        # unmodified image for creating the scene
        self.__sceneimage = None
        # bytearray of data for the above image
        self.__scenedata = None
        # flag set if in the process of reading image data from commands
        self.__loadingimage = False
        # width and height of the unmodified scene image
        # when the image is defined
        # initialize the width and height to values that will create
        # a viewer (mainWindow) of the right size
        self.__scenewidth = int(10.8 * self.physicalDpiX())
        self.__sceneheight = int(8.8 * self.physicalDpiY())
        # by default pay attention to any alpha channel values in colors
        self.__noalpha = False
        # initial default color for the background (opaque white)
        self.__lastclearcolor = QColor(0xFFFFFF)
        self.__lastclearcolor.setAlpha(0xFF)
        # scaling factor for creating the displayed scene
        self.__scalefactor = 1.0
        # automatically adjust the scaling factor to fit the window frame?
        self.__autoscale = True
        # minimum label width and height (for minimum scaling factor)
        # and minimum image width and height (for error checking)
        self.__minsize = 128
        # create the label, that will serve as the canvas, in a scrolled area
        self.__scrollarea = QScrollArea(self)
        self.__label = QLabel(self.__scrollarea)
        # set the initial label size and other values for the scrolled area
        self.__label.setMinimumSize(self.__scenewidth, self.__sceneheight)
        self.__label.resize(self.__scenewidth, self.__sceneheight)
        # setup the scrolled area
        self.__scrollarea.setWidget(self.__label)
        self.__scrollarea.setBackgroundRole(QPalette.Dark)
        self.setCentralWidget(self.__scrollarea)
        # default file name and format for saving the image
        self.__lastfilename = "ferret.png"
        self.__lastformat = "png"
        # control whether the window will be destroyed or hidden
        self.__shuttingdown = False
        # command helper object
        self.__helper = CmndHelperPQ(self)
        # create the menubar
        self.__scaleact = QAction(
            self.tr("&Scale"),
            self,
            shortcut=self.tr("Ctrl+S"),
            statusTip=self.tr(
                "Scale the image (canvas and image change size)"),
            triggered=self.inquireSceneScale)
        self.__saveact = QAction(self.tr("Save &As..."),
                                 self,
                                 shortcut=self.tr("Ctrl+A"),
                                 statusTip=self.tr("Save the image to file"),
                                 triggered=self.inquireSaveFilename)
        self.__redrawact = QAction(
            self.tr("&Redraw"),
            self,
            shortcut=self.tr("Ctrl+R"),
            statusTip=self.tr("Clear and redraw the image"),
            triggered=self.redrawScene)
        self.__aboutact = QAction(
            self.tr("&About"),
            self,
            statusTip=self.tr("Show information about this viewer"),
            triggered=self.aboutMsg)
        self.__aboutqtact = QAction(
            self.tr("About &Qt"),
            self,
            statusTip=self.tr("Show information about the Qt library"),
            triggered=self.aboutQtMsg)
        self.createMenus()
        # set the initial size of the viewer
        self.__framedelta = 4
        mwwidth = self.__scenewidth + self.__framedelta
        mwheight = self.__sceneheight + self.__framedelta \
                 + self.menuBar().height() \
                 + self.statusBar().height()
        self.resize(mwwidth, mwheight)
        # check the command queue any time there are no window events to deal with
        self.__timer = QTimer(self)
        self.__timer.timeout.connect(self.checkCommandPipe)
        self.__timer.setInterval(0)
        self.__timer.start()

    def createMenus(self):
        '''
        Create the menu items for the viewer
        using the previously created actions.
        '''
        menuBar = self.menuBar()
        sceneMenu = menuBar.addMenu(menuBar.tr("&Image"))
        sceneMenu.addAction(self.__scaleact)
        sceneMenu.addAction(self.__saveact)
        sceneMenu.addAction(self.__redrawact)
        helpMenu = menuBar.addMenu(menuBar.tr("&Help"))
        helpMenu.addAction(self.__aboutact)
        helpMenu.addAction(self.__aboutqtact)

    def resizeEvent(self, event):
        '''
        Monitor resizing in case auto-scaling of the image is selected.
        '''
        if self.__autoscale:
            if self.autoScaleScene():
                # continue with the window resize
                event.accept()
            else:
                # another resize coming in, so ignore this one
                event.ignore()
        else:
            # continue with the window resize
            event.accept()

    def closeEvent(self, event):
        '''
        Override so will just minimize if not shutting down
        '''
        # If shutting down or minimized (escape hatch),
        # go ahead and close/exit
        if self.__shuttingdown or self.isMinimized():
            self.__timer.stop()
            event.accept()
            return
        # Otherwise just minimize the window
        event.ignore()
        self.showMinimized()

    def exitViewer(self):
        '''
        Close and exit the viewer.
        '''
        self.__shuttingdown = True
        self.close()

    def aboutMsg(self):
        QMessageBox.about(self, self.tr("About PipedImagerPQ"),
            self.tr("\n" \
            "PipedImagerPQ is a graphics viewer application that " \
            "receives its displayed image and commands primarily from " \
            "another application through a pipe.  A limited number " \
            "of commands are provided by the viewer itself to allow " \
            "saving and some manipulation of the displayed image.  " \
            "The controlling application, however, may be unaware " \
            "of these modifications made to the image. " \
            "\n\n" \
            "'Closing' this window (the window frame 'X' button) " \
            "actually only minimizes this viewer as PyFerret expects " \
            "it to exist until PyFerret closes it.  Selecting the " \
            "close menu option from the *minimized* window (using " \
            "a right mouse click) will actually close (exit) the " \
            "viewer if PyFerret exited and left it up.  " \
            "\n\n" \
            "PipedImagerPQ was developed by the Thermal Modeling and Analysis " \
            "Project (TMAP) of the National Oceanographic and Atmospheric " \
            "Administration's (NOAA) Pacific Marine Environmental Lab (PMEL). "))

    def aboutQtMsg(self):
        QMessageBox.aboutQt(self, self.tr("About Qt"))

    def ignoreAlpha(self):
        '''
        Return whether the alpha channel in colors should always be ignored.
        '''
        return self.__noalpha

    def updateScene(self):
        '''
        Clear the displayed scene using self.__lastclearcolor,
        then draw the scaled current image.
        '''
        # get the scaled scene size
        labelwidth = int(self.__scalefactor * self.__scenewidth + 0.5)
        labelheight = int(self.__scalefactor * self.__sceneheight + 0.5)
        # Create the new pixmap for the label to display
        newpixmap = QPixmap(labelwidth, labelheight)
        newpixmap.fill(self.__lastclearcolor)
        if self.__sceneimage != None:
            # Draw the scaled image to the pixmap
            mypainter = QPainter(newpixmap)
            trgrect = QRectF(0.0, 0.0, float(labelwidth), float(labelheight))
            srcrect = QRectF(0.0, 0.0, float(self.__scenewidth),
                             float(self.__sceneheight))
            mypainter.drawImage(trgrect, self.__sceneimage, srcrect,
                                Qt.AutoColor)
            mypainter.end()
        # Assign the new pixmap to the label
        self.__label.setPixmap(newpixmap)
        # set the label size and values
        # so the scrollarea knows of the new size
        self.__label.setMinimumSize(labelwidth, labelheight)
        self.__label.resize(labelwidth, labelheight)
        # update the label from the new pixmap
        self.__label.update()

    def clearScene(self, bkgcolor=None):
        '''
        Deletes the scene image and fills the label with bkgcolor.
        If bkgcolor is None or an invalid color, the color used is 
        the one used from the last clearScene or redrawScene call 
        with a valid color (or opaque white if a color has never 
        been specified).
        '''
        # get the color to use for clearing (the background color)
        if bkgcolor:
            if bkgcolor.isValid():
                self.__lastclearcolor = bkgcolor
        # Remove the image and its bytearray
        self.__sceneimage = None
        self.__scenedata = None
        # Update the scene label using the current clearing color and image
        self.updateScene()

    def redrawScene(self, bkgcolor=None):
        '''
        Clear and redraw the displayed scene.
        '''
        # get the background color
        if bkgcolor:
            if bkgcolor.isValid():
                self.__lastclearcolor = bkgcolor
        # Update the scene label using the current clearing color and image
        QApplication.setOverrideCursor(Qt.WaitCursor)
        self.statusBar().showMessage(self.tr("Redrawing image"))
        try:
            self.updateScene()
        finally:
            self.statusBar().clearMessage()
            QApplication.restoreOverrideCursor()

    def resizeScene(self, width, height):
        '''
        Resize the scene to the given width and height in units of pixels.
        If the size changes, this deletes the current image and clear the
        displayed scene.
        '''
        newwidth = int(width + 0.5)
        if newwidth < self.__minsize:
            newwidth = self.__minsize
        newheight = int(height + 0.5)
        if newheight < self.__minsize:
            newheight = self.__minsize
        if (newwidth != self.__scenewidth) or (newheight !=
                                               self.__sceneheight):
            # set the new size for the empty scene
            self.__scenewidth = newwidth
            self.__sceneheight = newheight
            # If auto-scaling, set scaling factor to 1.0 and resize the window
            if self.__autoscale:
                self.__scalefactor = 1.0
                barheights = self.menuBar().height() + self.statusBar().height(
                )
                self.resize(newwidth + self.__framedelta,
                            newheight + self.__framedelta + barheights)
            # clear the scene with the last clearing color
            self.clearScene(None)

    def loadNewSceneImage(self, imageinfo):
        '''
        Create a new scene image from the information given in this
        and subsequent dictionaries imageinfo.  The image is created
        from multiple calls to this function since there is a limit
        on the size of a single object passed through a pipe.
        
        The first imageinfo dictionary given when creating an image
        must define the following key and value pairs:
            "width": width of the image in pixels
            "height": height of the image in pixels
            "stride": number of bytes in one line of the image
                      in the bytearray
        The scene image data is initialized to all zero (transparent)
        at this time.

        This initialization call must be followed by (multiple) calls
        to this method with imageinfo dictionaries defining the key
        and value pairs:
            "blocknum": data block number (1, 2, ... numblocks)
            "numblocks": total number of image data blocks
            "startindex": index in the bytearray of image data
                          where this block of image data starts
            "blockdata": this block of data as a bytearray

        On receipt of the last block of data (blocknum == numblocks)
        the scene image will be created and the scene will be updated. 

        Raises:
            KeyError - if one of the above keys is not given
            ValueError - if a value for a key is not valid
        '''
        if not self.__loadingimage:
            # prepare for a new image data from subsequent calls
            # get dimensions of the new image
            myimgwidth = int(imageinfo["width"])
            myimgheight = int(imageinfo["height"])
            myimgstride = int(imageinfo["stride"])
            if (myimgwidth < self.__minsize) or (myimgheight < self.__minsize):
                raise ValueError(
                    "image width and height cannot be less than %s" %
                    str(self.__minsize))
            # Newer PyQt versions allow separate specification of the stride
            if myimgstride != 4 * myimgwidth:
                raise ValueError(
                    "image stride is not four times the image width")
            # create the bytearray to contain the new scene data
            # automatically initialized to zero
            self.__scenedata = bytearray(myimgstride * myimgheight)
            self.__scenewidth = myimgwidth
            self.__sceneheight = myimgheight
            # set the flag for subsequent calls to this method
            self.__loadingimage = True
            # change the cursor to warn the user this may take some time
            QApplication.setOverrideCursor(Qt.WaitCursor)
            # put up an appropriate status message
            self.statusBar().showMessage(self.tr("Loading new image"))
            return
        # loading an image; add the next block of data
        myblocknum = int(imageinfo["blocknum"])
        mynumblocks = int(imageinfo["numblocks"])
        mystartindex = int(imageinfo["startindex"])
        myblockdata = imageinfo["blockdata"]
        if (myblocknum < 1) or (myblocknum > mynumblocks):
            self.statusBar().clearMessage()
            QApplication.restoreOverrideCursor()
            raise ValueError(
                "invalid image data block number or number of blocks")
        if (mystartindex < 0) or (mystartindex >= len(self.__scenedata)):
            self.statusBar().clearMessage()
            QApplication.restoreOverrideCursor()
            raise ValueError("invalid start index for an image data block")
        myblocksize = len(myblockdata)
        myendindex = mystartindex + myblocksize
        if (myblocksize < 1) or (myendindex > len(self.__scenedata)):
            self.statusBar().clearMessage()
            QApplication.restoreOverrideCursor()
            raise ValueError("invalid length of an image data block")
        # update the status message to show progress
        self.statusBar().showMessage( self.tr("Loading new image (block %1 of %2)") \
                                          .arg(str(myblocknum)).arg(str(mynumblocks)) )
        # assign the data
        self.__scenedata[mystartindex:myendindex] = myblockdata
        # if this is the last block of data, create and display the scene image
        if myblocknum == mynumblocks:
            self.__loadingimage = False
            self.statusBar().showMessage(self.tr("Creating new image"))
            try:
                self.__sceneimage = QImage(self.__scenedata, self.__scenewidth,
                                           self.__sceneheight,
                                           QImage.Format_ARGB32_Premultiplied)
                self.statusBar().showMessage(self.tr("Drawing new image"))
                # update the displayed scene in the label
                self.updateScene()
            finally:
                # clear the status message
                self.statusBar().clearMessage()
                # restore the cursor back to normal
                QApplication.restoreOverrideCursor()

    def inquireSceneScale(self):
        '''
        Prompt the user for the desired scaling factor for the scene.
        '''
        labelwidth = int(self.__scenewidth * self.__scalefactor + 0.5)
        labelheight = int(self.__sceneheight * self.__scalefactor + 0.5)
        scaledlg = ScaleDialogPQ(self.__scalefactor, labelwidth, labelheight,
                                 self.__minsize, self.__minsize,
                                 self.__autoscale, self)
        if scaledlg.exec_():
            (newscale, autoscale, okay) = scaledlg.getValues()
            if okay:
                if autoscale:
                    self.__autoscale = True
                    self.autoScaleScene()
                else:
                    self.__autoscale = False
                    self.scaleScene(newscale, False)

    def autoScaleScene(self):
        '''
        Selects a scaling factor that maximizes the scene within the window 
        frame without requiring scroll bars.  Intended to be called when
        the window size is changed by the user and auto-scaling is turn on.

        Returns:
            True if scaling of this scene is done (no window resize)
            False if the a window resize command was issued
        '''
        barheights = self.menuBar().height() + self.statusBar().height()

        # get the size for the central widget
        cwheight = self.height() - barheights - self.__framedelta
        heightsf = float(cwheight) / float(self.__sceneheight)

        cwwidth = self.width() - self.__framedelta
        widthsf = float(cwwidth) / float(self.__scenewidth)

        if heightsf < widthsf:
            factor = heightsf
        else:
            factor = widthsf

        newcwheight = int(factor * self.__sceneheight + 0.5)
        newcwwidth = int(factor * self.__scenewidth + 0.5)

        # if the window does not have the correct aspect ratio, resize it so
        # it will; this will generate another call to this method.  Otherwise,
        # scale the scene and be done.
        if self.isMaximized() or \
           ( (abs(cwheight - newcwheight) <= self.__framedelta) and \
             (abs(cwwidth - newcwwidth) <= self.__framedelta) ):
            self.scaleScene(factor, False)
            return True
        else:
            self.resize(newcwwidth + self.__framedelta,
                        newcwheight + self.__framedelta + barheights)
            return False

    def scaleScene(self, factor, resizewin):
        '''
        Scales both the horizontal and vertical directions by factor.
        Scaling factors are not accumulative.  So if the scene was
        already scaled, that scaling is "removed" before this scaling
        factor is applied.  If resizewin is True, the main window is 
        resized to accommodate this new scaled scene size.

        If factor is zero, just switch to auto-scaling at the current
        window size.  If factor is negative, rescale using the absolute
        value (possibly resizing the window) then switch to auto-scaling.
        '''
        fltfactor = float(factor)
        if fltfactor != 0.0:
            if resizewin:
                # from command - turn off autoscaling for the following
                # then turn back on if appropriate
                self.__autoscale = False
            newfactor = abs(fltfactor)
            newlabwidth = int(newfactor * self.__scenewidth + 0.5)
            newlabheight = int(newfactor * self.__sceneheight + 0.5)
            if (newlabwidth < self.__minsize) or (newlabheight <
                                                  self.__minsize):
                # Set to minimum size
                if self.__scenewidth <= self.__sceneheight:
                    newfactor = float(self.__minsize) / float(
                        self.__scenewidth)
                else:
                    newfactor = float(self.__minsize) / float(
                        self.__sceneheight)
                newlabwidth = int(newfactor * self.__scenewidth + 0.5)
                newlabheight = int(newfactor * self.__sceneheight + 0.5)
            oldlabwidth = int(self.__scalefactor * self.__scenewidth + 0.5)
            oldlabheight = int(self.__scalefactor * self.__sceneheight + 0.5)
            if (newlabwidth != oldlabwidth) or (newlabheight != oldlabheight):
                # Set the new scaling factor
                self.__scalefactor = newfactor
                # Update the scene label using the current clearing color and image
                QApplication.setOverrideCursor(Qt.WaitCursor)
                self.statusBar().showMessage(self.tr("Scaling image"))
                try:
                    self.updateScene()
                finally:
                    self.statusBar().clearMessage()
                    QApplication.restoreOverrideCursor()
            if resizewin:
                # resize the main window (if possible)
                barheights = self.menuBar().height() + self.statusBar().height(
                )
                mwheight = newlabheight + barheights + self.__framedelta
                mwwidth = newlabwidth + self.__framedelta
                # Do not exceed the available real estate on the screen.
                # If autoscaling is in effect, the resize will trigger
                # any required adjustments.
                scrnrect = QApplication.desktop().availableGeometry()
                if mwwidth > 0.95 * scrnrect.width():
                    mwwidth = int(0.9 * scrnrect.width() + 0.5)
                if mwheight > 0.95 * scrnrect.height():
                    mwheight = int(0.9 * scrnrect.height() + 0.5)
                self.resize(mwwidth, mwheight)
        if fltfactor <= 0.0:
            # From command - turn on autoscaling
            self.__autoscale = True
            self.autoScaleScene()

    def inquireSaveFilename(self):
        '''
        Prompt the user for the name of the file into which to save the scene.
        The file format will be determined from the filename extension.
        '''
        formattypes = [
            ("png", self.tr("PNG - Portable Networks Graphics (*.png)")),
            ("jpeg",
             self.
             tr("JPEG - Joint Photographic Experts Group (*.jpeg *.jpg *.jpe)")
             ),
            ("tiff",
             self.tr("TIFF - Tagged Image File Format (*.tiff *.tif)")),
            ("bmp", self.tr("BMP - Windows Bitmap (*.bmp)")),
            ("ppm", self.tr("PPM - Portable Pixmap (*.ppm)")),
            ("xpm", self.tr("XPM - X11 Pixmap (*.xpm)")),
            ("xbm", self.tr("XBM - X11 Bitmap (*.xbm)")),
        ]
        # tr returns QStrings so the following does not work
        # filters = ";;".join( [ t[1] for t in formattypes ] )
        filters = QString(formattypes[0][1])
        for typePair in formattypes[1:]:
            filters.append(";;")
            filters.append(typePair[1])
        # getSaveFileNameAndFilter does not want to accept a default filter
        # for (fmt, fmtQName) in formattypes:
        #     if self.__lastformat == fmt:
        #         dfltfilter = fmtQName
        #         break
        # else:
        #     dfltfilter = formattypes[0][1]
        # getSaveFileNameAndFilter is a PyQt (but not Qt?) method
        (fileName, fileFilter) = QFileDialog.getSaveFileNameAndFilter(
            self, self.tr("Save the current image as "), self.__lastfilename,
            filters)
        if fileName:
            for (fmt, fmtQName) in formattypes:
                if fmtQName.compare(fileFilter) == 0:
                    fileFormat = fmt
                    break
            else:
                raise RuntimeError("Unexpected file format name '%s'" %
                                   fileFilter)
            self.saveSceneToFile(fileName, fileFormat, None, None)
            self.__lastfilename = fileName
            self.__lastformat = fileFormat

    def saveSceneToFile(self, filename, imageformat, transparent, rastsize):
        '''
        Save the current scene to the named file.
        
        If imageformat is empty or None, the format is guessed from
        the filename extension.

        If transparent is False, the entire scene is initialized
        to the last clearing color.

        If given, rastsize is the pixels size of the saved image.
        If rastsize is not given, the saved image will be saved
        at the current scaled image size.  
        '''
        # This could be called when there is no image present.
        # If this is the case, ignore the call.
        if (self.__sceneimage == None):
            return
        if not imageformat:
            # Guess the image format from the filename extension
            # This is only done to silently change gif to png
            fileext = (os.path.splitext(filename)[1]).lower()
            if fileext == '.gif':
                myformat = 'gif'
            else:
                # let QImage figure out the format
                myformat = None
        else:
            myformat = imageformat.lower()

        if myformat == 'gif':
            # Silently convert gif filename and format to png
            myformat = 'png'
            myfilename = os.path.splitext(filename)[0] + ".png"
        else:
            myfilename = filename
        # set the cursor and status message to indicate a save is happending
        QApplication.setOverrideCursor(Qt.WaitCursor)
        self.statusBar().showMessage(self.tr("Saving image"))
        try:
            if rastsize:
                imagewidth = int(rastsize.width() + 0.5)
                imageheight = int(rastsize.height() + 0.5)
            else:
                imagewidth = int(self.__scenewidth * self.__scalefactor + 0.5)
                imageheight = int(self.__sceneheight * self.__scalefactor +
                                  0.5)
            myimage = QImage(QSize(imagewidth, imageheight),
                             QImage.Format_ARGB32_Premultiplied)
            # Initialize the image
            if not transparent:
                # Clear the image with self.__lastclearcolor
                fillint = self.__helper.computeARGB32PreMultInt(
                    self.__lastclearcolor)
            else:
                fillint = 0
            myimage.fill(fillint)
            # draw the scaled scene to this QImage
            mypainter = QPainter(myimage)
            trgrect = QRectF(0.0, 0.0, float(imagewidth), float(imageheight))
            srcrect = QRectF(0.0, 0.0, float(self.__scenewidth),
                             float(self.__sceneheight))
            mypainter.drawImage(trgrect, self.__sceneimage, srcrect,
                                Qt.AutoColor)
            mypainter.end()
            # save the image to file
            if not myimage.save(myfilename, myformat):
                raise ValueError("Unable to save the plot as " + myfilename)
        finally:
            self.statusBar().clearMessage()
            QApplication.restoreOverrideCursor()

    def checkCommandPipe(self):
        '''
        Get and perform commands waiting in the pipe.
        Stop when no more commands or if more than 50
        milliseconds have passed.
        '''
        try:
            starttime = time.clock()
            # Wait up to 2 milliseconds waiting for a command.
            # This prevents unchecked spinning when there is
            # nothing to do (Qt immediately calling this method
            # again only for this method to immediately return).
            while self.__cmndpipe.poll(0.002):
                cmnd = self.__cmndpipe.recv()
                self.processCommand(cmnd)
                # Continue to try to process commands until
                # more than 50 milliseconds have passed.
                # This reduces Qt overhead when there are lots
                # of commands waiting in the queue.
                if (time.clock() - starttime) > 0.050:
                    break
        except Exception:
            # EOFError should never arise from recv since
            # the call is after poll returns True
            (exctype, excval) = sys.exc_info()[:2]
            try:
                if excval:
                    self.__rspdpipe.send("**ERROR %s: %s" %
                                         (str(exctype), str(excval)))
                else:
                    self.__rspdpipe.send("**ERROR %s" % str(exctype))
            except Exception:
                pass
            self.exitViewer()

    def processCommand(self, cmnd):
        '''
        Examine the action of cmnd and call the appropriate
        method to deal with this command.  Raises a KeyError
        if the "action" key is missing.
        '''
        try:
            cmndact = cmnd["action"]
        except KeyError:
            raise ValueError("Unknown command '%s'" % str(cmnd))

        if cmndact == "clear":
            try:
                bkgcolor = self.__helper.getColorFromCmnd(cmnd)
            except KeyError:
                bkgcolor = None
            self.clearScene(bkgcolor)
        elif cmndact == "exit":
            self.exitViewer()
        elif cmndact == "hide":
            self.showMinimized()
        elif cmndact == "screenInfo":
            scrnrect = QApplication.desktop().availableGeometry()
            info = (self.physicalDpiX(), self.physicalDpiY(), scrnrect.width(),
                    scrnrect.height())
            self.__rspdpipe.send(info)
        elif cmndact == "redraw":
            try:
                bkgcolor = self.__helper.getColorFromCmnd(cmnd)
            except KeyError:
                bkgcolor = None
            self.redrawScene(bkgcolor)
        elif cmndact == "rescale":
            self.scaleScene(float(cmnd["factor"]), True)
        elif cmndact == "resize":
            mysize = self.__helper.getSizeFromCmnd(cmnd)
            self.resizeScene(mysize.width(), mysize.height())
        elif cmndact == "newImage":
            self.loadNewSceneImage(cmnd)
        elif cmndact == "save":
            filename = cmnd["filename"]
            fileformat = cmnd.get("fileformat", None)
            try:
                bkgcolor = self.__helper.getColorFromCmnd(cmnd)
            except KeyError:
                bkgcolor = None
            rastsize = self.__helper.getSizeFromCmnd(cmnd["rastsize"])
            self.saveSceneToFile(filename, fileformat, bkgcolor, rastsize)
        elif cmndact == "setTitle":
            self.setWindowTitle(cmnd["title"])
        elif cmndact == "imgname":
            myvalue = cmnd.get("name", None)
            if myvalue:
                self.__lastfilename = myvalue
            myvalue = cmnd.get("format", None)
            if myvalue:
                self.__lastformat = myvalue.lower()
        elif cmndact == "show":
            if not self.isVisible():
                self.show()
        elif cmndact == "noalpha":
            # ignore any alpha channel values in colors
            self.__noalpha = True
        else:
            raise ValueError("Unknown command action %s" % str(cmndact))
Exemple #3
0
class pngDisplay(QWidget):
    def __init__(self, parent=None):
        super(pngDisplay, self).__init__(parent)
        #self.profiler = cProfile.Profile()
        # create the label that displays the image - cribbing from the Image
        # Viewer example in the Qt docs.
        self.imLabel = QLabel()
        self.imLabel.setBackgroundRole(QPalette.Base)
        self.imLabel.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
        self.imLabel.setScaledContents(True)
        # Create a gray field to use when no png is available
        self.defaultPM = QPixmap(700,900)
        self.defaultPM.fill(QColor("gray"))
        # Create a scroll area within which to display our imLabel, this
        # enables the creation of horizontal and vertical scroll bars, when
        # the imLabel exceeds the size of the scroll area.
        self.scarea = QScrollArea()
        # The following two lines make sure that page up/dn gets through
        # the scrollarea widget and up to us.
        self.setFocusPolicy(Qt.ClickFocus)
        self.scarea.setFocusProxy(self)
        self.scarea.setBackgroundRole(QPalette.Dark)
        #self.scarea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
        #self.scarea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
        self.scarea.setWidget(self.imLabel)
        # create the text label that will have the page number in it
        self.txLabel = QLabel(u"No image")
        self.txLabel.setAlignment(Qt.AlignBottom | Qt.AlignHCenter)
        self.txLabel.setFrameStyle(QFrame.Sunken | QFrame.StyledPanel)
        # Create a spinbox to set the zoom from 15 to 200 with a label:
        # (originally a slider, hence the name)
        self.minZoom = 0.15
        self.maxZoom = 2.00
        self.zlider = QSpinBox()
        self.zlider.setRange(int(100*self.minZoom),int(100*self.maxZoom))
        # connect the value change signal to a slot to handle it
        self.connect(self.zlider, SIGNAL("valueChanged(int)"), self.newZoomFactor)
        # create the to-width and to-height zoom buttons
        zoomWidthButton = QPushButton(u'to Width')
        self.connect(zoomWidthButton, SIGNAL("clicked()"), self.zoomToWidth)
        zoomHeightButton = QPushButton(u'to Height')
        self.connect(zoomHeightButton, SIGNAL("clicked()"), self.zoomToHeight)
        # Make an hbox to contain the spinbox and two pushbuttons, with
        # stretch on left and right to center the group.
        zlabel = QLabel(
            u'&Zoom ' + str(self.zlider.minimum())
            + '-' + str(self.zlider.maximum()) + '%')
        zlabel.setBuddy(self.zlider)
        zhbox = QHBoxLayout()
        zhbox.addStretch(1)
        zhbox.addWidget(zlabel,0,Qt.AlignLeft)
        zhbox.addWidget(self.zlider,0)
        zhbox.addStretch(1)
        zhbox.addWidget(zoomWidthButton)
        zhbox.addWidget(zoomHeightButton)
        zhbox.addStretch(1)
        # With all the pieces in hand, create our layout basically a
        # vertical stack: scroll area, label, slider box.
        vbox = QVBoxLayout()
        # the image gets a high stretch and default alignment, the text
        # label hugs the bottom and doesn't stretch at all.
        vbox.addWidget(self.txLabel,0,Qt.AlignBottom)
        vbox.addWidget(self.scarea,10)
        vbox.addLayout(zhbox,0)
        self.setLayout(vbox)
        # Initialize assuming no book is open.
        self.ready = False # nothing to display
        # Recover the last-set zoom factor from the settings object, default 1.0
        qv = IMC.settings.value("pngs/zoomFactor",QVariant(1.0))
        self.zoomFactor = qv.toFloat()[0]
        # The following causes entry into newZoomFactor, below, which tests
        # self.ready, hence the latter has to be assigned-to first.
        self.zlider.setValue(int(self.zoomFactor*100))
        self.clear()

    # local subroutine to initialize our contents for an empty edit.
    # called from _init_ and from newPosition when we discover the file
    # has been cleared on us. Don't reset the zoomFactor, leave it as
    # the user last set it.
    def clear(self):
        # Clear the page name, used by pqNotes
        IMC.currentImageNumber = None # will be name of last png file e.g. "002"
        # Clear the page filename, used in our caption label
        self.lastPage = QString() # last file name e.g. "002.png"
        # Clear the path to the pngs folder, used to fetch image files
        self.pngPath = QString()
        # Clear the index of the last-shown page in the page table
        # -1 means no page is being displayed.
        self.lastIndex = -1
        # Clear the index of the next page to display, normally same as last
        self.nextIndex = -1
        # Set not-ready to indicate no pngs directory available.
        self.ready = False
        # Clear out & release storage of our QImage and QPixmaps
        self.pixmap = QPixmap() # null pixmap
        self.image = QImage()
        self.noImage() # show gray image

    # Display a blank gray frame and "No Image" below.
    # Called from clear() above and from showPage when no valid image.
    def noImage(self) :
        self.imLabel.setPixmap(self.defaultPM)
        self.txLabel.setText(u"No image")
        self.lastIndex = -1 # didn't see a prior page
        self.nextIndex = -1

    # This slot gets the main window's signal shuttingDown.
    # We save our current zoom factor into IMC.settings.
    def shuttingDown(self):
        IMC.settings.setValue("pngs/zoomFactor",QVariant(self.zoomFactor))

    # This slot gets pqMain's signal docHasChanged(QString), telling
    # us that a different document has been loaded. This could be for
    # a successful File>Open, or a failed File>Open or File>New.
    # The bookPath is a null QString for File>New, or the full bookPath.
    # If the latter, we convert that into the path to the pngs folder,
    # and see if bookPath/pngs is a directory. If so, we set self.ready
    # to true, indicating it is worthwhile to try opening image files.
    # At this point the gray image is displayed and previously would remain displayed
    # until the user moved the cursor in some way, generating cursorPositionChanged.
    # That's a minor annoyance, to avoid it we will fake that signal now.
    def newFile(self, bookPath):
        if not bookPath.isNull(): # this was successful File>Open
            finf = QFileInfo(bookPath)
            self.pngPath = finf.absolutePath().append(u"/pngs/")
            finf = QFileInfo(self.pngPath)
            if finf.exists() and finf.isDir(): # looking good
                self.ready = True
                self.newPosition()
            else:
                # We could inform the user we couldn't find a pngs folder,
                # but you know -- the user is probably already aware of that.
                self.clear() # just put up the gray default image
        else: # It was a File>New
            self.clear()

    # This function is the slot that is connected to the editor's
    # cursorPositionChanged signal. Its input is cursor position and
    # the page table. Its output is to set self.nextIndex to the
    # desired next image table row, and to call showPage.
    def newPosition(self):
        if self.ready :
            # We have a book and some pngs. Find the position of the higher end
            # of the current selection.
            pos = IMC.editWidget.textCursor().selectionEnd()
            # Get the page table index that matches this position, or -1
            # if that is above the first psep, or there is no page data
            self.nextIndex = IMC.pageTable.getIndex(pos)
        else :# No file loaded or no pngs folder found.
            self.nextIndex = -1
        if self.nextIndex != self.lastIndex :
            self.showPage()

    # Display the page indexed by self.nextIndex. This is called when the cursor
    # moves to a new page (newPosition, above), or when the PageUp/Dn keys are used,
    # (keyPressEvent, below) or when the zoom factor changes in any of several ways.
    def showPage(self):
        # If self.lastIndex is different from self.nextIndex, the page has
        # changed, and we need to load a new image.
        if self.lastIndex != self.nextIndex :
            self.lastIndex = self.nextIndex # don't come here again until it changes.
            if self.lastIndex > -1 :
                # Form the image filename as a Qstring, e.g. "025" and save that for
                # use by pqNotes:
                IMC.currentImageNumber = IMC.pageTable.getScan(self.lastIndex)
                # dbg = unicode(IMC.currentImageNumber)
                # Form the complete filename by appending ".png" and save as
                # self.lastPage for use in forming our caption label.
                self.lastPage = QString(IMC.currentImageNumber).append(QString(u".png"))
                # dbg = unicode(self.lastPage)
                # Form the full path to the image. Try to load it as a QImage.
                pngName = QString(self.pngPath).append(self.lastPage)
                self.image = QImage(pngName,'PNG')
                # dbg = unicode(self.image)
                # dbg = self.image.isNull()
                # If that successfully loaded an image, make sure it is one byte/pixel.
                if not self.image.isNull() \
                   and self.image.format() != QImage.Format_Indexed8 :
                    # It might be Format_Mono (1 bit/pixel) or even Format_RGB32.
                    self.image = self.image.convertToFormat(QImage.Format_Indexed8,Qt.ColorOnly)
                # Convert the image to a pixmap. If it's null, so is the pixmap.
                self.pixmap = QPixmap.fromImage(self.image,Qt.ColorOnly)
            else :
                IMC.currentImageNumber = QString(u"n.a.")
                self.lastPage = QString()
                self.image = QImage()
                self.pixmap = QPixmap()
        if not self.pixmap.isNull():
            # We successfully found and loaded an image and converted it to pixmap.
            # Load it in our label for display, set the zoom factor, and the caption.
            # We do this every time through (even if lastIndex equalled nextIndex)
            # because the zoomfactor might have changed.
            self.imLabel.setPixmap(self.pixmap)
            self.imLabel.resize( self.zoomFactor * self.pixmap.size() )
            folio = IMC.pageTable.getDisplay(self.lastIndex)
            self.txLabel.setText(u"image {0} (folio {1})".format(self.lastPage,folio))
        else: # no file was loaded. It's ok if pages are missing
            self.noImage() # display the gray image.

    # Catch the signal from the Zoom spinbox with a new value.
    # Store the new value as a float, and if we have a page, repaint it.
    def newZoomFactor(self,new_value):
        self.zoomFactor = new_value / 100
        if self.ready :
            self.showPage()

    # Catch the click on zoom-to-width and zoom-to height. The job is basically
    # the same for both. 1: Using the QImage that should be in self.image,
    # scan the pixels to find the width (height) of the nonwhite area.
    # 2. Get the ratio of that to our image label's viewport width (height).
    # 4. Set that ratio as the zoom factor and redraw the image. And finally
    # 5. Set the scroll position(s) of our scroll area to left-justify the text.
    #
    # We get access to the pixels using QImage:bits() which gives us a PyQt4
    # "voidptr" that we can index to get byte values.
    #
    def zoomToWidth(self):
        if (not self.ready) or (self.image.isNull()) :
            return # nothing to do here
        #self.profiler.enable() #dbg
        # Query the Color look-up table and build a list of the Green values
        # corresponding to each possible pixel value. Probably there are just
        # two colors so colortab is [0,255] but there could be more, depending
        # on how the PNG was defined, 16 or 32 or even 255 grayscale.
        colortab = [ int((self.image.color(c) >> 4) & 255)
                     for c in range(self.image.colorCount()) ]
        ncols = self.image.width() # number of logical pixels across
        stride = (ncols + 3) & (-4) # number of bytes per scanline
        nrows = self.image.height() # number of pixels high
        vptr = self.image.bits() # uchar * bunch-o-pixel-bytes
        vptr.setsize(stride * nrows) # make the pointer indexable

        # Scan in from left and right to find the outermost dark spots.
        # Looking for single pixels yeilds too many false positives, so we
        # look for three adjacent pixels that sum to less than 32.
        # Most pages start with many lines of white pixels so in hopes of
        # establishing the outer edge early, we start at the middle, go to
        # the end, then do the top half.
        left_side = int(ncols/2) # leftmost dark spot seen so far
        # scan from the middle down
        for r in xrange(int(nrows/2)*stride, (nrows-1)*stride, stride) :
            pa, pb = 255, 255 # virtual white outside border
            for c in xrange(left_side):
                pc = colortab[ ord(vptr[c + r]) ]
                if (pa + pb + pc) < 32 : # black or dark gray pair
                    left_side = c # new, further-left, left margin
                    break # no need to look further on this line
                pa = pb
                pb = pc
        # scan from the top to the middle, hopefully left_side is small now
        for r in xrange(0, int(nrows/2)*stride, stride) :
            pa, pb = 255, 255 # virtual white outside border
            for c in xrange(left_side):
                pc = colortab[ ord(vptr[c + r]) ]
                if (pa + pb + pc) < 32 : # black or dark gray pair
                    left_side = c # new, further-left, left margin
                    break # no need to look further on this line
                pa = pb
                pb = pc
        # Now do the same for the right margin.
        right_side = int(ncols/2) # rightmost dark spot seen so far
        for r in xrange(int(nrows/2)*stride, (nrows-1)*stride, stride) :
            pa, pb = 255, 255 # virtual white outside border
            for c in xrange(ncols-1,right_side,-1) :
                pc = colortab[ ord(vptr[c + r]) ]
                if (pa + pb + pc) < 32 : # black or dark gray pair
                    right_side = c # new, further-right, right margin
                    break
                pa = pb
                pb = pc
        for r in xrange(0, int(nrows/2)*stride, stride)  :
            pa, pb = 255, 255 # virtual white outside border
            for c in xrange(ncols-1,right_side,-1) :
                pc = colortab[ ord(vptr[c + r]) ]
                if (pa + pb + pc) < 32 : # black or dark gray pair
                    right_side = c # new, further-right, right margin
                    break
                pa = pb
                pb = pc
        # The area with color runs from left_side to right_side. How does
        # that compare to the size of our viewport? Scale to that and redraw.
        #print('ls {0} rs {1} vp {2}'.format(left_side,right_side,self.scarea.viewport().width()))
        text_size = right_side - left_side + 2
        port_width = self.scarea.viewport().width()
        self.zoomFactor = max( self.minZoom, min( self.maxZoom, port_width / text_size ) )
        # the next line signals newZoomFactor, which calls showPage.
        self.zlider.setValue(int(100*self.zoomFactor))
        # Set the scrollbar to show the page from its left margin.
        self.scarea.horizontalScrollBar().setValue(int( left_side * self.zoomFactor) )
        #self.profiler.disable() #dbg
        #pstats.Stats(self.profiler).print_stats() # dbg


    def zoomToHeight(self):
        if (not self.ready) or (self.image.isNull()) :
            return # nothing to do here
        # Query the Color look-up table and build a list of the Green values
        # corresponding to each possible pixel value. Probably there are just
        # two colors so colortab is [0,255] but there could be more, depending
        # on how the PNG was defined, 16 or 32 or even 255 grayscale.
        colortab = [ int((self.image.color(c) >> 4) & 255)
                     for c in range(self.image.colorCount()) ]
        ncols = self.image.width() # number of logical pixels across
        stride = (ncols + 3) & (-4) # number of bytes per scanline
        nrows = self.image.height() # number of pixels high
        vptr = self.image.bits() # uchar * bunch-o-pixel-bytes
        vptr.setsize(stride * nrows) # make the pointer indexable
        # Scan in from top and bottom to find the outermost rows with
        # significant pixels.
        top_side = -1 # The uppermost row with a significant spot of black
        offset = 0 # vptr index to the first/next pixel row
        for r in range(nrows) :
            pa, pb = 255, 255 # virtual white outside border
            for c in range(ncols) :
                pc = colortab[ ord(vptr[offset + c]) ]
                if (pa + pb + pc) < 32 : # black or dark gray triplet
                    top_side = r # that's the row,
                    break # ..so stop scanning
                pa, pb = pb, pc
            if top_side >= 0 : # we hit
                break # ..so don't scan down any further
            offset += stride # continue to next row
        # top_side indexes the first row with a significant blot
        if top_side == -1 : # never found one: an all-white page. bug out.
            return
        bottom_side = nrows # The lowest row with a significant blot
        offset = stride * nrows # vptr index to last/next row of pixels
        for r in range(nrows,top_side,-1) :
            offset -= stride
            pa, pb = 255, 255 # virtual white outside border
            for c in range(ncols) :
                pc = colortab[ ord(vptr[offset + c]) ]
                if (pa + pb + pc) < 32 : # black or dark gray triplet
                    bottom_side = r
                    break
                pa, pb = pb, pc
            if bottom_side < nrows : # we hit
                break
        # bottom_side is the lowest row with significant pixels. It must be
        # < nrows, there is at least one row (top_side) with a dot in it.
        # However if the page is mostly white, don't zoom to that extent.
        if bottom_side < (top_side+100) :
            return # seems to be a mostly-white page, give up
        # The text area runs from scanline top_side to bottom_side.
        text_height = bottom_side - top_side + 1
        port_height = self.scarea.viewport().height()
        self.zoomFactor = max( self.minZoom, min( self.maxZoom, port_height / text_height ) )
        self.zlider.setValue(int(100*self.zoomFactor)) # signals newZoomFactor->showPage
        # Set the scrollbar to show the page from its top margin.
        self.scarea.verticalScrollBar().setValue(int( top_side * self.zoomFactor) )

    # Re-implement the parent's keyPressEvent in order to provide zoom:
    # ctrl-plus increases the image size by 1.25
    # ctrl-minus decreases the image size by 0.8
    # Also trap pageup/dn and use to walk through images.
    # At this point we do not reposition the editor to match the page viewed.
    # we page up/dn but as soon as focus returns to the editor and the cursor
    # moves, this display will snap back to the edited page. As a user that
    # seems best, come over to Pngs and page ahead to see what's coming, then
    # back to the editor to read or type.
    def keyPressEvent(self, event):
        # assume we will not handle this key and clear its accepted flag
        event.ignore()
        # If we are initialized and have displayed some page, look at the key
        if self.ready:
            kkey = int( int(event.modifiers()) & IMC.keypadDeModifier) | int(event.key())
            if kkey in IMC.zoomKeys :
                # ctl/cmd + or -, do the zoom
                event.accept()
                fac = (0.8) if (kkey == IMC.ctl_minus) else (1.25)
                fac *= self.zoomFactor # target zoom factor
                if (fac >= self.minZoom) and (fac <= self.maxZoom): # keep in bounds
                    self.zoomFactor = fac
                    self.zlider.setValue(int(100*fac))
                    self.showPage()
            elif (event.key() == Qt.Key_PageUp) or (event.key() == Qt.Key_PageDown) :
                event.accept() # real pgUp or pgDn, we do it
                fac = 1 if (event.key() == Qt.Key_PageDown) else -1
                fac += self.lastIndex
                if (fac >= 0) and (fac < IMC.pageTable.size()) :
                    # not off either end of the book, so,
                    self.nextIndex = fac
                    self.showPage()
        if not event.isAccepted() : # we don't do those, pass them on
            super(pngDisplay, self).keyPressEvent(event)
Exemple #4
0
class WTestPng(Document.WDocument):
    """
    Test png widget
    """
    def __init__(self,
                 parent=None,
                 path=None,
                 filename=None,
                 extension=None,
                 nonameId=None,
                 remoteFile=True,
                 repoDest=None,
                 project=0):
        """
        Constructs WScript widget

        @param parent: 
        @type parent: 

        @param path: 
        @type path: 

        @param filename: 
        @type filename: 

        @param extension: 
        @type extension: 

        @param nonameId: 
        @type nonameId: 
        """
        Document.WDocument.__init__(self, parent, path, filename, extension,
                                    nonameId, remoteFile, repoDest, project)
        self.scaleFactor = 0.0
        self.rawContent = ''
        self.createWidgets()
        self.createActions()
        self.createToolbar()
        self.createConnections()

    def createActions(self):
        """
        Create qt actions
        """
        self.zoomInAct = QtHelper.createAction(self,
                                               "&Zoom &In (25%.",
                                               self.zoomIn,
                                               icon=QIcon(":/zoom-in.png"))
        self.zoomOutAct = QtHelper.createAction(self,
                                                "Zoom &Out (25%.",
                                                self.zoomOut,
                                                icon=QIcon(":/zoom-out.png"))
        self.normalSizeAct = QtHelper.createAction(
            self,
            "&Normal Size",
            self.normalSize,
            icon=QIcon(":/zoom-normal.png"))

    def createToolbar(self):
        """
        Toolbar creation
            
        ||------|------|||
        || Open | Save |||
        ||------|------|||
        """
        self.dockToolbar.setObjectName("Test Config toolbar")
        self.dockToolbar.addAction(self.zoomInAct)
        self.dockToolbar.addAction(self.zoomOutAct)
        self.dockToolbar.addAction(self.normalSizeAct)
        self.dockToolbar.addSeparator()
        self.dockToolbar.setIconSize(QSize(16, 16))

    def createWidgets(self):
        """
        QtWidgets creation
        """
        self.dockToolbar = QToolBar(self)
        self.dockToolbar.setStyleSheet(
            "QToolBar { border: 0px }")  # remove 3D border

        self.imageLabel = QLabel(self)
        self.imageLabel.setBackgroundRole(QPalette.Base)
        self.imageLabel.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
        self.imageLabel.setScaledContents(True)

        self.scrollArea = QScrollArea()
        self.scrollArea.setBackgroundRole(QPalette.Dark)
        self.scrollArea.setWidget(self.imageLabel)

        title = QLabel("Image:")
        title.setStyleSheet("QLabel { padding-left: 2px; padding-top: 2px }")
        font = QFont()
        font.setBold(True)
        title.setFont(font)

        layout = QVBoxLayout()
        layout.addWidget(title)
        layout.addWidget(self.dockToolbar)
        layout.addWidget(self.scrollArea)
        layout.setContentsMargins(2, 2, 2, 2)
        self.setLayout(layout)

    def zoomIn(self):
        """
        Zoom in
        """
        self.scaleImage(1.25)

    def zoomOut(self):
        """
        Zoom out
        """
        self.scaleImage(0.8)

    def normalSize(self):
        """
        Normal size
        """
        self.imageLabel.adjustSize()
        self.scaleFactor = 1.0

    def scaleImage(self, factor):
        """
        Scale image
        """
        self.scaleFactor *= factor
        self.imageLabel.resize(self.scaleFactor *
                               self.imageLabel.pixmap().size())

        self.adjustScrollBar(self.scrollArea.horizontalScrollBar(), factor)
        self.adjustScrollBar(self.scrollArea.verticalScrollBar(), factor)

    def adjustScrollBar(self, scrollBar, factor):
        """
        Adjust scrollbar
        """
        scrollBar.setValue(
            int(factor * scrollBar.value() +
                ((factor - 1) * scrollBar.pageStep() / 2)))

    def createConnections(self):
        """
        QtSignals connection
        """
        pass

    def write(self, force=False):
        """
        Save 
        """
        absPath = '%s/%s.%s' % (self.path, self.filename, self.extension)
        try:
            with open(absPath, mode='wb') as myfile:
                myfile.write(self.rawContent)
        except Exception as e:
            self.error("unable to write png file: %s" % e)
            return None
        else:
            self.setUnmodify()
            return True

    def load(self, content=None):
        """
        Open file
        """
        if content is None:
            absPath = '%s/%s.%s' % (self.path, self.filename, self.extension)
            file = QFile(absPath)
            if not file.open(QIODevice.ReadOnly):
                self.error("Error opening image file: %s" % absPath)
                return False
            else:
                content = file.readAll()

        self.rawContent = content
        image = QImage()
        image.loadFromData(content)
        if image.isNull():
            self.error("cannot load image")
            return False
        else:
            self.imageLabel.setPixmap(QPixmap.fromImage(QImage(image)))
            self.scaleFactor = 1.0
            self.imageLabel.adjustSize()

            return True

    def getraw_encoded(self):
        """
        Returns raw data encoded
        """
        encoded = ''
        try:
            encoded = base64.b64encode(self.rawContent)
        except Exception as e:
            self.error('unable to encode raw image: %s' % str(e))
        return encoded