Ejemplo n.º 1
0
    def __init__(self, images, imgFilter, scaleFactor=1.0, tagSelect=True):
        super(ImageViewer, self).__init__()

        self.images = images
        self.imgFilter = imgFilter
        self.selection = LazyList(self.imgFilter.filter(self.images))
        self.scaleFactor = scaleFactor
        self.cur = 0

        self.imageInfoDialog = ImageInfoDialog()

        if tagSelect:
            taglist = set()
            for i in images:
                taglist |= i.tags
            self.tagSelectDialog = TagSelectDialog(taglist)
        else:
            self.tagSelectDialog = None

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

        self.scrollArea = QtGui.QScrollArea()
        self.scrollArea.setBackgroundRole(QtGui.QPalette.Dark)
        self.scrollArea.setWidget(self.imageLabel)
        self.scrollArea.setAlignment(QtCore.Qt.AlignCenter)
        self.setCentralWidget(self.scrollArea)

        maxSize = 0.95 * QtGui.QApplication.desktop().screenGeometry().size()
        self.setMaximumSize(maxSize)

        self.closeAct = QtGui.QAction("&Close", self, shortcut="q", triggered=self.close)
        self.zoomInAct = QtGui.QAction("Zoom &In", self, shortcut=">", triggered=self.zoomIn)
        self.zoomOutAct = QtGui.QAction("Zoom &Out", self, shortcut="<", triggered=self.zoomOut)
        self.zoomFitHeightAct = QtGui.QAction("Zoom to Fit &Height", self, triggered=self.zoomFitHeight)
        self.zoomFitWidthAct = QtGui.QAction("Zoom to Fit &Width", self, triggered=self.zoomFitWidth)
        self.rotateLeftAct = QtGui.QAction("Rotate &Left", self, shortcut="l", triggered=self.rotateLeft)
        self.rotateRightAct = QtGui.QAction("Rotate &Right", self, shortcut="r", triggered=self.rotateRight)
        self.fullScreenAct = QtGui.QAction(
            "Show &Full Screen", self, shortcut="f", checkable=True, triggered=self.fullScreen
        )
        self.imageInfoAct = QtGui.QAction("Image &Info", self, shortcut="i", triggered=self.imageInfo)
        self.prevImageAct = QtGui.QAction(
            "&Previous Image", self, shortcut="p", enabled=False, triggered=self.prevImage
        )
        self.nextImageAct = QtGui.QAction(
            "&Next Image", self, shortcut="n", enabled=(self._haveNext()), triggered=self.nextImage
        )
        self.selectImageAct = QtGui.QAction("&Select Image", self, shortcut="s", triggered=self.selectImage)
        self.deselectImageAct = QtGui.QAction("&Deselect Image", self, shortcut="d", triggered=self.deselectImage)
        self.tagSelectAct = QtGui.QAction("&Tags", self, shortcut="t", enabled=tagSelect, triggered=self.tagSelect)

        self.fileMenu = QtGui.QMenu("&File", self)
        self.fileMenu.addAction(self.closeAct)
        self.menuBar().addMenu(self.fileMenu)

        self.viewMenu = QtGui.QMenu("&View", self)
        self.viewMenu.addAction(self.zoomInAct)
        self.viewMenu.addAction(self.zoomOutAct)
        self.viewMenu.addAction(self.zoomFitHeightAct)
        self.viewMenu.addAction(self.zoomFitWidthAct)
        self.viewMenu.addAction(self.rotateLeftAct)
        self.viewMenu.addAction(self.rotateRightAct)
        self.viewMenu.addSeparator()
        self.viewMenu.addAction(self.fullScreenAct)
        self.viewMenu.addSeparator()
        self.viewMenu.addAction(self.imageInfoAct)
        self.menuBar().addMenu(self.viewMenu)

        self.imageMenu = QtGui.QMenu("&Image", self)
        self.imageMenu.addAction(self.prevImageAct)
        self.imageMenu.addAction(self.nextImageAct)
        self.imageMenu.addAction(self.selectImageAct)
        self.imageMenu.addAction(self.deselectImageAct)
        self.imageMenu.addSeparator()
        self.imageMenu.addAction(self.tagSelectAct)
        self.menuBar().addMenu(self.imageMenu)

        self.show()
        self._extraSize = self.size() - self.scrollArea.viewport().size()
        self._loadImage()
        self._checkActions()
Ejemplo n.º 2
0
    def __init__(self, images, imgFilter, 
                 scaleFactor=1.0, readOnly=False, dirty=False):
        super().__init__()

        self.images = images
        self.imgFilter = imgFilter
        self.selection = LazyList(self._filteredImages())
        if readOnly:
            dirty = False
        self.scaleFactor = scaleFactor
        self.readOnly = readOnly
        self.dirty = dirty
        self.cur = 0

        self.imageInfoDialog = ImageInfoDialog(self.images.directory)
        self.overviewwindow = None

        if not self.readOnly:
            taglist = set()
            for i in images:
                taglist |= i.tags
            self.tagSelectDialog = TagSelectDialog(taglist)
        else:
            self.tagSelectDialog = None

        self.filterDialog = FilterDialog()

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

        self.scrollArea = QtGui.QScrollArea()
        self.scrollArea.setBackgroundRole(QtGui.QPalette.Dark)
        self.scrollArea.setWidget(self.imageLabel)
        self.scrollArea.setAlignment(QtCore.Qt.AlignCenter)
        self.setCentralWidget(self.scrollArea)

        maxSize = 0.95 * QtGui.QApplication.desktop().screenGeometry().size()
        self.setMaximumSize(maxSize)

        self.saveAct = QtGui.QAction("&Save index", self, 
                shortcut="Ctrl+s", enabled=(not self.readOnly), 
                triggered=self.saveIndex)
        self.closeAct = QtGui.QAction("&Close", self, 
                shortcut="q", triggered=self.close)
        self.filterOptsAct = QtGui.QAction("Filter Options", self,
                shortcut="Shift+Ctrl+f", triggered=self.filterOptions)
        self.zoomInAct = QtGui.QAction("Zoom &In", self,
                shortcut=">", triggered=self.zoomIn)
        self.zoomOutAct = QtGui.QAction("Zoom &Out", self,
                shortcut="<", triggered=self.zoomOut)
        self.zoomFitHeightAct = QtGui.QAction("Zoom to Fit &Height", self,
                triggered=self.zoomFitHeight)
        self.zoomFitWidthAct = QtGui.QAction("Zoom to Fit &Width", self,
                triggered=self.zoomFitWidth)
        self.rotateLeftAct = QtGui.QAction("Rotate &Left", self,
                shortcut="l", triggered=self.rotateLeft)
        self.rotateRightAct = QtGui.QAction("Rotate &Right", self,
                shortcut="r", triggered=self.rotateRight)
        self.fullScreenAct = QtGui.QAction("Show &Full Screen", self,
                shortcut="f", checkable=True, triggered=self.fullScreen)
        self.imageInfoAct = QtGui.QAction("Image &Info", self,
                shortcut="i", triggered=self.imageInfo)
        self.overviewAct = QtGui.QAction("&Overview Window", self,
                shortcut="o", triggered=self.overview)
        self.prevImageAct = QtGui.QAction("&Previous Image", self,
                shortcut="p", enabled=False, triggered=self.prevImage)
        self.nextImageAct = QtGui.QAction("&Next Image", self,
                shortcut="n", enabled=(self._haveNext()), 
                triggered=self.nextImage)
        self.selectImageAct = QtGui.QAction("&Select Image", self,
                shortcut="s", enabled=(not self.readOnly), 
                triggered=self.selectImage)
        self.deselectImageAct = QtGui.QAction("&Deselect Image", self,
                shortcut="d", enabled=(not self.readOnly), 
                triggered=self.deselectImage)
        self.pushForwardAct = QtGui.QAction("Push Image &Forward", self,
                shortcut="Ctrl+f", enabled=(not self.readOnly), 
                triggered=self.pushImageForward)
        self.pushBackwardAct = QtGui.QAction("Push Image &Backward", self,
                shortcut="Ctrl+b", enabled=(not self.readOnly), 
                triggered=self.pushImageBackward)
        self.tagSelectAct = QtGui.QAction("&Tags", self,
                shortcut="t", enabled=(not self.readOnly), 
                triggered=self.tagSelect)

        self.fileMenu = QtGui.QMenu("&File", self)
        self.fileMenu.addAction(self.saveAct)
        self.fileMenu.addAction(self.closeAct)
        self.fileMenu.addAction(self.filterOptsAct)
        self.menuBar().addMenu(self.fileMenu)

        self.viewMenu = QtGui.QMenu("&View", self)
        self.viewMenu.addAction(self.zoomInAct)
        self.viewMenu.addAction(self.zoomOutAct)
        self.viewMenu.addAction(self.zoomFitHeightAct)
        self.viewMenu.addAction(self.zoomFitWidthAct)
        self.viewMenu.addAction(self.rotateLeftAct)
        self.viewMenu.addAction(self.rotateRightAct)
        self.viewMenu.addSeparator()
        self.viewMenu.addAction(self.fullScreenAct)
        self.viewMenu.addSeparator()
        self.viewMenu.addAction(self.imageInfoAct)
        self.viewMenu.addAction(self.overviewAct)
        self.menuBar().addMenu(self.viewMenu)

        self.imageMenu = QtGui.QMenu("&Image", self)
        self.imageMenu.addAction(self.prevImageAct)
        self.imageMenu.addAction(self.nextImageAct)
        self.imageMenu.addAction(self.selectImageAct)
        self.imageMenu.addAction(self.deselectImageAct)
        self.imageMenu.addSeparator()
        self.imageMenu.addAction(self.pushForwardAct)
        self.imageMenu.addAction(self.pushBackwardAct)
        self.imageMenu.addSeparator()
        self.imageMenu.addAction(self.tagSelectAct)
        self.menuBar().addMenu(self.imageMenu)

        self.show()
        self._extraSize = self.size() - self.scrollArea.viewport().size()
        self._loadImage()
        self._checkActions()
Ejemplo n.º 3
0
class ImageViewer(QtGui.QMainWindow):
    def __init__(self, images, imgFilter, scaleFactor=1.0, tagSelect=True):
        super(ImageViewer, self).__init__()

        self.images = images
        self.imgFilter = imgFilter
        self.selection = LazyList(self.imgFilter.filter(self.images))
        self.scaleFactor = scaleFactor
        self.cur = 0

        self.imageInfoDialog = ImageInfoDialog()

        if tagSelect:
            taglist = set()
            for i in images:
                taglist |= i.tags
            self.tagSelectDialog = TagSelectDialog(taglist)
        else:
            self.tagSelectDialog = None

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

        self.scrollArea = QtGui.QScrollArea()
        self.scrollArea.setBackgroundRole(QtGui.QPalette.Dark)
        self.scrollArea.setWidget(self.imageLabel)
        self.scrollArea.setAlignment(QtCore.Qt.AlignCenter)
        self.setCentralWidget(self.scrollArea)

        maxSize = 0.95 * QtGui.QApplication.desktop().screenGeometry().size()
        self.setMaximumSize(maxSize)

        self.closeAct = QtGui.QAction("&Close", self, shortcut="q", triggered=self.close)
        self.zoomInAct = QtGui.QAction("Zoom &In", self, shortcut=">", triggered=self.zoomIn)
        self.zoomOutAct = QtGui.QAction("Zoom &Out", self, shortcut="<", triggered=self.zoomOut)
        self.zoomFitHeightAct = QtGui.QAction("Zoom to Fit &Height", self, triggered=self.zoomFitHeight)
        self.zoomFitWidthAct = QtGui.QAction("Zoom to Fit &Width", self, triggered=self.zoomFitWidth)
        self.rotateLeftAct = QtGui.QAction("Rotate &Left", self, shortcut="l", triggered=self.rotateLeft)
        self.rotateRightAct = QtGui.QAction("Rotate &Right", self, shortcut="r", triggered=self.rotateRight)
        self.fullScreenAct = QtGui.QAction(
            "Show &Full Screen", self, shortcut="f", checkable=True, triggered=self.fullScreen
        )
        self.imageInfoAct = QtGui.QAction("Image &Info", self, shortcut="i", triggered=self.imageInfo)
        self.prevImageAct = QtGui.QAction(
            "&Previous Image", self, shortcut="p", enabled=False, triggered=self.prevImage
        )
        self.nextImageAct = QtGui.QAction(
            "&Next Image", self, shortcut="n", enabled=(self._haveNext()), triggered=self.nextImage
        )
        self.selectImageAct = QtGui.QAction("&Select Image", self, shortcut="s", triggered=self.selectImage)
        self.deselectImageAct = QtGui.QAction("&Deselect Image", self, shortcut="d", triggered=self.deselectImage)
        self.tagSelectAct = QtGui.QAction("&Tags", self, shortcut="t", enabled=tagSelect, triggered=self.tagSelect)

        self.fileMenu = QtGui.QMenu("&File", self)
        self.fileMenu.addAction(self.closeAct)
        self.menuBar().addMenu(self.fileMenu)

        self.viewMenu = QtGui.QMenu("&View", self)
        self.viewMenu.addAction(self.zoomInAct)
        self.viewMenu.addAction(self.zoomOutAct)
        self.viewMenu.addAction(self.zoomFitHeightAct)
        self.viewMenu.addAction(self.zoomFitWidthAct)
        self.viewMenu.addAction(self.rotateLeftAct)
        self.viewMenu.addAction(self.rotateRightAct)
        self.viewMenu.addSeparator()
        self.viewMenu.addAction(self.fullScreenAct)
        self.viewMenu.addSeparator()
        self.viewMenu.addAction(self.imageInfoAct)
        self.menuBar().addMenu(self.viewMenu)

        self.imageMenu = QtGui.QMenu("&Image", self)
        self.imageMenu.addAction(self.prevImageAct)
        self.imageMenu.addAction(self.nextImageAct)
        self.imageMenu.addAction(self.selectImageAct)
        self.imageMenu.addAction(self.deselectImageAct)
        self.imageMenu.addSeparator()
        self.imageMenu.addAction(self.tagSelectAct)
        self.menuBar().addMenu(self.imageMenu)

        self.show()
        self._extraSize = self.size() - self.scrollArea.viewport().size()
        self._loadImage()
        self._checkActions()

    def _haveNext(self):
        """Check whether there is a next image.
        This should be equivalent to self.cur < len(self.selection)-1,
        but without calling len(self.selection).
        """
        try:
            self.selection[self.cur + 1]
            return True
        except IndexError:
            return False

    def _setSize(self):
        size = self.scaleFactor * self.imageLabel.pixmap().size()
        winSize = size + self._extraSize
        maxSize = self.maximumSize()
        if winSize.height() > maxSize.height():
            # Take vertical scrollbar into account.
            sbw = self.scrollArea.verticalScrollBar().size().width()
            winSize += QtCore.QSize(sbw, 0)
        if winSize.width() > maxSize.width():
            # Take horizontal scrollbar into account.
            sbh = self.scrollArea.horizontalScrollBar().size().height()
            winSize += QtCore.QSize(0, sbh)
        self.imageLabel.resize(size)
        if not self.fullScreenAct.isChecked():
            self.resize(winSize)

    def _loadImage(self):
        try:
            item = self.selection[self.cur]
        except IndexError:
            # Nothing to view.
            self.imageLabel.hide()
            return
        fileName = os.path.join(self.images.directory, item.filename)
        image = QtGui.QImage(fileName)
        if image.isNull():
            print("Cannot load %s." % fileName, file=sys.stderr)
            del self.selection[self.cur]
            self._loadImage()
            return
        pixmap = QtGui.QPixmap.fromImage(image)
        rm = QtGui.QMatrix()
        if item.orientation:
            m = re.match(r"Rotate (\d+) CW", item.orientation)
            if m:
                rm = rm.rotate(int(m.group(1)))
        self.imageLabel.setPixmap(pixmap.transformed(rm))
        self.imageLabel.show()
        self._setSize()
        self.setWindowTitle(os.path.basename(fileName))

    def _checkActions(self):
        """Enable and disable actions as appropriate.
        """
        self.prevImageAct.setEnabled(self.cur > 0)
        self.nextImageAct.setEnabled(self._haveNext())
        try:
            item = self.selection[self.cur]
        except IndexError:
            # No current image.
            self.imageInfoAct.setEnabled(False)
            self.selectImageAct.setEnabled(False)
            self.deselectImageAct.setEnabled(False)
            self.tagSelectAct.setEnabled(False)
        else:
            self.imageInfoAct.setEnabled(True)
            self.selectImageAct.setEnabled(not item.selected)
            self.deselectImageAct.setEnabled(item.selected)
            self.tagSelectAct.setEnabled(self.tagSelectDialog is not None)

    def _reevalFilter(self):
        """Re-evaluate the filter on the current image.
        This is needed if relevant attributes have changed.
        """
        if not self.imgFilter(self.selection[self.cur]):
            del self.selection[self.cur]
            self._loadImage()
            self._checkActions()

    def zoomIn(self):
        self.scaleImage(1.6)

    def zoomOut(self):
        self.scaleImage(0.625)

    def zoomFitHeight(self):
        imgHeight = self.imageLabel.pixmap().size().height()
        winHeight = self.scrollArea.viewport().size().height()
        # Leave an internal padding of a few pixel
        winHeight -= 6
        self.scaleFactor = winHeight / imgHeight
        self._setSize()

    def zoomFitWidth(self):
        imgWidth = self.imageLabel.pixmap().size().width()
        winWidth = self.scrollArea.viewport().size().width()
        # Leave an internal padding of a few pixel
        winWidth -= 6
        self.scaleFactor = winWidth / imgWidth
        self._setSize()

    def rotateLeft(self):
        rm = QtGui.QMatrix().rotate(-90)
        self.imageLabel.setPixmap(self.imageLabel.pixmap().transformed(rm))
        self._setSize()

    def rotateRight(self):
        rm = QtGui.QMatrix().rotate(90)
        self.imageLabel.setPixmap(self.imageLabel.pixmap().transformed(rm))
        self._setSize()

    def fullScreen(self):
        if self.fullScreenAct.isChecked():
            self.showFullScreen()
        else:
            self.showNormal()
            self._setSize()

    def prevImage(self):
        if self.cur > 0:
            self.cur -= 1
            self._loadImage()
            self._setSize()
            self._checkActions()

    def nextImage(self):
        if self._haveNext():
            self.cur += 1
            self._loadImage()
            self._setSize()
            self._checkActions()

    def selectImage(self):
        try:
            self.selection[self.cur].selected = True
        except IndexError:
            # No current image.
            pass
        else:
            self.images.write()
            self.selectImageAct.setEnabled(False)
            self.deselectImageAct.setEnabled(True)
            self._reevalFilter()

    def deselectImage(self):
        try:
            self.selection[self.cur].selected = False
        except IndexError:
            # No current image.
            pass
        else:
            self.images.write()
            self.selectImageAct.setEnabled(True)
            self.deselectImageAct.setEnabled(False)
            self._reevalFilter()

    def imageInfo(self):
        self.imageInfoDialog.setinfo(self.selection[self.cur])
        self.imageInfoDialog.exec_()

    def tagSelect(self):
        self.tagSelectDialog.setCheckedTags(self.selection[self.cur].tags)
        if self.tagSelectDialog.exec_():
            self.selection[self.cur].tags = self.tagSelectDialog.checkedTags()
            self.images.write()
            self._reevalFilter()

    def scaleImage(self, factor):
        self.scaleFactor *= factor
        self._setSize()
Ejemplo n.º 4
0
class ImageViewer(QtGui.QMainWindow):

    def __init__(self, images, imgFilter, 
                 scaleFactor=1.0, readOnly=False, dirty=False):
        super().__init__()

        self.images = images
        self.imgFilter = imgFilter
        self.selection = LazyList(self._filteredImages())
        if readOnly:
            dirty = False
        self.scaleFactor = scaleFactor
        self.readOnly = readOnly
        self.dirty = dirty
        self.cur = 0

        self.imageInfoDialog = ImageInfoDialog(self.images.directory)
        self.overviewwindow = None

        if not self.readOnly:
            taglist = set()
            for i in images:
                taglist |= i.tags
            self.tagSelectDialog = TagSelectDialog(taglist)
        else:
            self.tagSelectDialog = None

        self.filterDialog = FilterDialog()

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

        self.scrollArea = QtGui.QScrollArea()
        self.scrollArea.setBackgroundRole(QtGui.QPalette.Dark)
        self.scrollArea.setWidget(self.imageLabel)
        self.scrollArea.setAlignment(QtCore.Qt.AlignCenter)
        self.setCentralWidget(self.scrollArea)

        maxSize = 0.95 * QtGui.QApplication.desktop().screenGeometry().size()
        self.setMaximumSize(maxSize)

        self.saveAct = QtGui.QAction("&Save index", self, 
                shortcut="Ctrl+s", enabled=(not self.readOnly), 
                triggered=self.saveIndex)
        self.closeAct = QtGui.QAction("&Close", self, 
                shortcut="q", triggered=self.close)
        self.filterOptsAct = QtGui.QAction("Filter Options", self,
                shortcut="Shift+Ctrl+f", triggered=self.filterOptions)
        self.zoomInAct = QtGui.QAction("Zoom &In", self,
                shortcut=">", triggered=self.zoomIn)
        self.zoomOutAct = QtGui.QAction("Zoom &Out", self,
                shortcut="<", triggered=self.zoomOut)
        self.zoomFitHeightAct = QtGui.QAction("Zoom to Fit &Height", self,
                triggered=self.zoomFitHeight)
        self.zoomFitWidthAct = QtGui.QAction("Zoom to Fit &Width", self,
                triggered=self.zoomFitWidth)
        self.rotateLeftAct = QtGui.QAction("Rotate &Left", self,
                shortcut="l", triggered=self.rotateLeft)
        self.rotateRightAct = QtGui.QAction("Rotate &Right", self,
                shortcut="r", triggered=self.rotateRight)
        self.fullScreenAct = QtGui.QAction("Show &Full Screen", self,
                shortcut="f", checkable=True, triggered=self.fullScreen)
        self.imageInfoAct = QtGui.QAction("Image &Info", self,
                shortcut="i", triggered=self.imageInfo)
        self.overviewAct = QtGui.QAction("&Overview Window", self,
                shortcut="o", triggered=self.overview)
        self.prevImageAct = QtGui.QAction("&Previous Image", self,
                shortcut="p", enabled=False, triggered=self.prevImage)
        self.nextImageAct = QtGui.QAction("&Next Image", self,
                shortcut="n", enabled=(self._haveNext()), 
                triggered=self.nextImage)
        self.selectImageAct = QtGui.QAction("&Select Image", self,
                shortcut="s", enabled=(not self.readOnly), 
                triggered=self.selectImage)
        self.deselectImageAct = QtGui.QAction("&Deselect Image", self,
                shortcut="d", enabled=(not self.readOnly), 
                triggered=self.deselectImage)
        self.pushForwardAct = QtGui.QAction("Push Image &Forward", self,
                shortcut="Ctrl+f", enabled=(not self.readOnly), 
                triggered=self.pushImageForward)
        self.pushBackwardAct = QtGui.QAction("Push Image &Backward", self,
                shortcut="Ctrl+b", enabled=(not self.readOnly), 
                triggered=self.pushImageBackward)
        self.tagSelectAct = QtGui.QAction("&Tags", self,
                shortcut="t", enabled=(not self.readOnly), 
                triggered=self.tagSelect)

        self.fileMenu = QtGui.QMenu("&File", self)
        self.fileMenu.addAction(self.saveAct)
        self.fileMenu.addAction(self.closeAct)
        self.fileMenu.addAction(self.filterOptsAct)
        self.menuBar().addMenu(self.fileMenu)

        self.viewMenu = QtGui.QMenu("&View", self)
        self.viewMenu.addAction(self.zoomInAct)
        self.viewMenu.addAction(self.zoomOutAct)
        self.viewMenu.addAction(self.zoomFitHeightAct)
        self.viewMenu.addAction(self.zoomFitWidthAct)
        self.viewMenu.addAction(self.rotateLeftAct)
        self.viewMenu.addAction(self.rotateRightAct)
        self.viewMenu.addSeparator()
        self.viewMenu.addAction(self.fullScreenAct)
        self.viewMenu.addSeparator()
        self.viewMenu.addAction(self.imageInfoAct)
        self.viewMenu.addAction(self.overviewAct)
        self.menuBar().addMenu(self.viewMenu)

        self.imageMenu = QtGui.QMenu("&Image", self)
        self.imageMenu.addAction(self.prevImageAct)
        self.imageMenu.addAction(self.nextImageAct)
        self.imageMenu.addAction(self.selectImageAct)
        self.imageMenu.addAction(self.deselectImageAct)
        self.imageMenu.addSeparator()
        self.imageMenu.addAction(self.pushForwardAct)
        self.imageMenu.addAction(self.pushBackwardAct)
        self.imageMenu.addSeparator()
        self.imageMenu.addAction(self.tagSelectAct)
        self.menuBar().addMenu(self.imageMenu)

        self.show()
        self._extraSize = self.size() - self.scrollArea.viewport().size()
        self._loadImage()
        self._checkActions()

    def saveIndex(self):
        try:
            self.images.write()
            self.dirty = False
        except photo.index.AlreadyLockedError:
            msgBox = QtGui.QMessageBox()
            msgBox.setWindowTitle("Index is locked")
            msgBox.setText("Saving the image index failed!")
            msgBox.setInformativeText("Another process is currently "
                                      "accessing the file")
            msgBox.setIcon(QtGui.QMessageBox.Critical)
            msgBox.exec_()

    def close(self):
        if self.dirty:
            msgBox = QtGui.QMessageBox()
            msgBox.setWindowTitle("Save index?")
            msgBox.setText("The image index been modified.")
            msgBox.setInformativeText("Save changes before closing?")
            msgBox.setIcon(QtGui.QMessageBox.Question)
            msgBox.setStandardButtons(QtGui.QMessageBox.Save | 
                                      QtGui.QMessageBox.Discard | 
                                      QtGui.QMessageBox.Cancel)
            msgBox.setDefaultButton(QtGui.QMessageBox.Save)
            ret = msgBox.exec_()
            if ret == QtGui.QMessageBox.Save:
                self.saveIndex()
                if self.dirty:
                    return
            elif ret == QtGui.QMessageBox.Discard:
                pass
            elif ret == QtGui.QMessageBox.Cancel:
                return
        if self.overviewwindow:
            self.overviewwindow.close()
        super().close()

    def _filteredImages(self):
        for item in self.images:
            if self.imgFilter(item):
                yield Image(self.images.directory, item)

    def _haveNext(self):
        """Check whether there is a next image.
        This should be equivalent to self.cur < len(self.selection)-1,
        but without calling len(self.selection).
        """
        try:
            self.selection[self.cur+1]
            return True
        except IndexError:
            return False

    def _setSize(self):
        maxSize = self.maximumSize()
        imgSize = self.imageLabel.pixmap().size()
        if self.scaleFactor is None:
            size = maxSize - self._extraSize
            hscale = size.width() / imgSize.width()
            vscale = size.height() / imgSize.height()
            self.scaleFactor = min(hscale, vscale)
        size = self.scaleFactor * imgSize
        winSize = size + self._extraSize
        if winSize.height() > maxSize.height():
            # Take vertical scrollbar into account.
            sbw = self.scrollArea.verticalScrollBar().size().width()
            winSize += QtCore.QSize(sbw, 0)
        if winSize.width() > maxSize.width():
            # Take horizontal scrollbar into account.
            sbh = self.scrollArea.horizontalScrollBar().size().height()
            winSize += QtCore.QSize(0, sbh)
        self.imageLabel.resize(size)
        if not self.fullScreenAct.isChecked():
            self.resize(winSize)

    def _loadImage(self):
        try:
            image = self.selection[self.cur]
        except IndexError:
            # Nothing to view.
            self.imageLabel.hide()
            if self.overviewwindow:
                self.overviewwindow.markActive(None)
            return
        try:
            pixmap = image.getPixmap()
        except Exception as e:
            print(str(e), file=sys.stderr)
            del self.selection[self.cur]
            if self.overviewwindow:
                self.overviewwindow.updateThumbs()
            self._loadImage()
            return
        self.imageLabel.setPixmap(pixmap)
        self.imageLabel.show()
        self._setSize()
        self.setWindowTitle(image.name)
        if self.overviewwindow:
            self.overviewwindow.markActive(image)

    def _checkActions(self):
        """Enable and disable actions as appropriate.
        """
        self.prevImageAct.setEnabled(self.cur > 0)
        self.pushForwardAct.setEnabled(not self.readOnly and self.cur > 0)
        self.nextImageAct.setEnabled(self._haveNext())
        self.pushBackwardAct.setEnabled(not self.readOnly and self._haveNext())
        try:
            item = self.selection[self.cur].item
        except IndexError:
            # No current image.
            self.imageInfoAct.setEnabled(False)
            self.selectImageAct.setEnabled(False)
            self.deselectImageAct.setEnabled(False)
            self.tagSelectAct.setEnabled(False)
            self.zoomInAct.setEnabled(False)
            self.zoomOutAct.setEnabled(False)
            self.zoomFitHeightAct.setEnabled(False)
            self.zoomFitWidthAct.setEnabled(False)
            self.rotateLeftAct.setEnabled(False)
            self.rotateRightAct.setEnabled(False)
        else:
            self.imageInfoAct.setEnabled(True)
            en_select = not self.readOnly and not item.selected
            en_deselect = not self.readOnly and item.selected
            self.selectImageAct.setEnabled(en_select)
            self.deselectImageAct.setEnabled(en_deselect)
            self.tagSelectAct.setEnabled(not self.readOnly)
            self.zoomInAct.setEnabled(True)
            self.zoomOutAct.setEnabled(True)
            self.zoomFitHeightAct.setEnabled(True)
            self.zoomFitWidthAct.setEnabled(True)
            self.rotateLeftAct.setEnabled(True)
            self.rotateRightAct.setEnabled(True)

    def _reevalFilter(self):
        """Re-evaluate the filter on the current image.
        This is needed if relevant attributes have changed.
        """
        if not self.imgFilter(self.selection[self.cur].item):
            del self.selection[self.cur]
            if self.overviewwindow:
                self.overviewwindow.updateThumbs()
            self._loadImage()
            self._checkActions()

    def zoomIn(self):
        self.scaleImage(1.6)

    def zoomOut(self):
        self.scaleImage(0.625)

    def zoomFitHeight(self):
        imgHeight = self.imageLabel.pixmap().size().height()
        winHeight = self.scrollArea.viewport().size().height()
        # Leave an internal padding of a few pixel
        winHeight -= 6
        self.scaleFactor = winHeight / imgHeight
        self._setSize()

    def zoomFitWidth(self):
        imgWidth = self.imageLabel.pixmap().size().width()
        winWidth = self.scrollArea.viewport().size().width()
        # Leave an internal padding of a few pixel
        winWidth -= 6
        self.scaleFactor = winWidth / imgWidth
        self._setSize()

    def rotateLeft(self):
        image = self.selection[self.cur]
        image.rotate(-90)
        self.imageLabel.setPixmap(image.getPixmap())
        self._setSize()
        if self.overviewwindow:
            w = self.overviewwindow.getThumbnailWidget(image)
            if w:
                w.setImagePixmap()

    def rotateRight(self):
        image = self.selection[self.cur]
        image.rotate(90)
        self.imageLabel.setPixmap(image.getPixmap())
        self._setSize()
        if self.overviewwindow:
            w = self.overviewwindow.getThumbnailWidget(image)
            if w:
                w.setImagePixmap()

    def fullScreen(self):
        if self.fullScreenAct.isChecked():
            self.showFullScreen()
        else:
            self.showNormal()
            self._setSize()

    def moveCurrentTo(self, newcur):
        if isinstance(newcur, Image):
            newcur = self.selection.index(newcur)
        self.cur = newcur
        self._loadImage()
        self._setSize()
        self._checkActions()

    def prevImage(self):
        if self.cur > 0:
            self.moveCurrentTo(self.cur - 1)

    def nextImage(self):
        if self._haveNext():
            self.moveCurrentTo(self.cur + 1)

    def selectImage(self):
        try:
            self.selection[self.cur].item.selected = True
        except IndexError:
            # No current image.
            pass
        else:
            self.dirty = True
            self.selectImageAct.setEnabled(False)
            self.deselectImageAct.setEnabled(True)
            self._reevalFilter()

    def deselectImage(self):
        try:
            self.selection[self.cur].item.selected = False
        except IndexError:
            # No current image.
            pass
        else:
            self.dirty = True
            self.selectImageAct.setEnabled(True)
            self.deselectImageAct.setEnabled(False)
            self._reevalFilter()

    def imageInfo(self):
        self.imageInfoDialog.setinfo(self.selection[self.cur].item)
        self.imageInfoDialog.exec_()

    def overview(self):
        if not self.overviewwindow:
            self.overviewwindow = OverviewWindow(self)
        self.overviewwindow.show()

    def tagSelect(self):
        item = self.selection[self.cur].item
        self.tagSelectDialog.setCheckedTags(item.tags)
        if self.tagSelectDialog.exec_():
            item.tags = self.tagSelectDialog.checkedTags()
            self.dirty = True
            self._reevalFilter()

    def filterOptions(self):
        self.filterDialog.setfilter(self.imgFilter)
        if self.filterDialog.exec_():
            if self.overviewwindow:
                # The overview window would need to be rebuild in any case.
                self.overviewwindow.close()
                self.overviewwindow = None
            try:
                curidx = self.images.index(self.selection[self.cur].item)
            except IndexError:
                curidx = None
            self.imgFilter = self.filterDialog.imgFilter
            self.selection = LazyList(self._filteredImages())
            if curidx:
                item_i = 0
                for img_i, img in enumerate(self.selection):
                    item_i = self.images.index(img.item, item_i)
                    if item_i >= curidx:
                        cur = img_i
                        break
                else:
                    cur = len(self.selection)
            else:
                cur = 0
            self.moveCurrentTo(cur)

    def scaleImage(self, factor):
        self.scaleFactor *= factor
        self._setSize()

    def pushImageForward(self):
        """Move the current image forward one position in the image order.
        """
        if self.cur > 0:
            i = self.images.index(self.selection[self.cur].item)
            pi = self.images.index(self.selection[self.cur - 1].item)
            item = self.images.pop(i)
            self.images.insert(pi, item)
            img = self.selection.pop(self.cur)
            self.selection.insert(self.cur - 1, img)
            self.dirty = True
            if self.overviewwindow:
                self.overviewwindow.updateThumbs()
            self.moveCurrentTo(self.cur - 1)

    def pushImageBackward(self):
        """Move the current image backward one position in the image order.
        """
        if self._haveNext():
            i = self.images.index(self.selection[self.cur].item)
            ni = self.images.index(self.selection[self.cur + 1].item)
            item = self.images.pop(i)
            self.images.insert(ni - 1, item)
            img = self.selection.pop(self.cur)
            self.selection.insert(self.cur + 1, img)
            self.dirty = True
            if self.overviewwindow:
                self.overviewwindow.updateThumbs()
            self.moveCurrentTo(self.cur + 1)