Exemple #1
0
 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)
Exemple #2
0
 def __init__(self, idxfile=None, imgdir=None, hashalg=['md5']):
     super().__init__()
     self.directory = None
     self.idxfile = None
     self.items = []
     if idxfile:
         self.read(idxfile)
     if imgdir:
         imgdir = Path(imgdir).resolve()
         if not self.directory:
             self.directory = imgdir
         if idxfile:
             self.extend_dir(imgdir, hashalg)
         else:
             newitems = _readdir(imgdir, self.directory, hashalg)
             self.items = LazyList(newitems)
Exemple #3
0
 def __init__(self, idxfile=None, imgdir=None, hashalg=["md5"]):
     super(Index, self).__init__()
     self.directory = None
     self.idxfilename = None
     self.items = []
     if idxfile:
         self.read(idxfile)
     if imgdir:
         imgdir = os.path.abspath(imgdir)
         if not self.directory:
             self.directory = imgdir
         if idxfile:
             known = {i.filename for i in self.items}
             newitems = _readdir(imgdir, self.directory, hashalg, known)
             self.items.extend(newitems)
         else:
             newitems = _readdir(imgdir, self.directory, hashalg)
             self.items = LazyList(newitems)
Exemple #4
0
class Index(MutableSequence):

    defIdxFilename = ".index.yaml"

    def __init__(self, idxfile=None, imgdir=None, hashalg=["md5"]):
        super(Index, self).__init__()
        self.directory = None
        self.idxfilename = None
        self.items = []
        if idxfile:
            self.read(idxfile)
        if imgdir:
            imgdir = os.path.abspath(imgdir)
            if not self.directory:
                self.directory = imgdir
            if idxfile:
                known = {i.filename for i in self.items}
                newitems = _readdir(imgdir, self.directory, hashalg, known)
                self.items.extend(newitems)
            else:
                newitems = _readdir(imgdir, self.directory, hashalg)
                self.items = LazyList(newitems)

    def __len__(self):
        return len(self.items)

    def __getitem__(self, index):
        return self.items.__getitem__(index)

    def __setitem__(self, index, value):
        self.items.__setitem__(index, value)

    def __delitem__(self, index):
        self.items.__delitem__(index)

    def insert(self, index, value):
        self.items.insert(index, value)

    def _idxfilename(self, idxfile):
        """Determine the index file name for reading and writing.
        """
        if idxfile is not None:
            idxfile = os.path.abspath(idxfile)
            if os.path.isdir(idxfile):
                idxfile = os.path.join(idxfile, self.defIdxFilename)
            return idxfile
        elif self.idxfilename is not None:
            return self.idxfilename
        else:
            d = self.directory if self.directory is not None else os.getcwd()
            return os.path.abspath(os.path.join(d, self.defIdxFilename))

    def read(self, idxfile=None):
        """Read the index from a file.
        """
        self.idxfilename = self._idxfilename(idxfile)
        self.directory = os.path.dirname(self.idxfilename)
        with open(self.idxfilename, "rt") as f:
            self.items = [IdxItem(data=i) for i in yaml.load(f)]

    def write(self, idxfile=None):
        """Write the index to a file.
        """
        with open(self._idxfilename(idxfile), "wt") as f:
            items = [i.as_dict() for i in self.items]
            yaml.dump(items, f, default_flow_style=False)
Exemple #5
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()
Exemple #6
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)
Exemple #7
0
class Index(MutableSequence):

    defIdxFilename = Path(".index.yaml")

    def __init__(self, idxfile=None, imgdir=None, hashalg=['md5']):
        super().__init__()
        self.directory = None
        self.idxfile = None
        self.items = []
        if idxfile:
            self.read(idxfile)
        if imgdir:
            imgdir = Path(imgdir).resolve()
            if not self.directory:
                self.directory = imgdir
            if idxfile:
                self.extend_dir(imgdir, hashalg)
            else:
                newitems = _readdir(imgdir, self.directory, hashalg)
                self.items = LazyList(newitems)

    def extend_dir(self, imgdir, hashalg=['md5']):
        imgdir = Path(imgdir).resolve()
        known = {i.filename for i in self.items}
        newitems = _readdir(imgdir, self.directory, hashalg, known)
        self.items.extend(newitems)

    def close(self):
        if self.idxfile:
            self.idxfile.close()
            self.idxfile = None

    def __enter__(self):
        return self

    def __exit__(self, type, value, tb):
        self.close()

    def __del__(self):
        self.close()

    def __len__(self):
        return len(self.items)

    def __getitem__(self, index):
        return self.items.__getitem__(index)

    def __setitem__(self, index, value):
        self.items.__setitem__(index, value)

    def __delitem__(self, index):
        self.items.__delitem__(index)

    def index(self, value, *args):
        return self.items.index(value, *args)

    def insert(self, index, value):
        self.items.insert(index, value)

    def _get_idxfile(self, fname, flags):
        if fname is not None:
            self.close()
            fname = Path(fname)
            if fname.is_dir():
                fname = fname / self.defIdxFilename
            self.directory = fname.parent.resolve()
            fd = os.open(str(fname), flags, mode=0o666)
            self.idxfile = os.fdopen(fd, "r+t")
        elif self.idxfile:
            self.idxfile.seek(0)
        else:
            if not self.directory:
                self.directory = Path.cwd()
            fname = self.directory / self.defIdxFilename
            fd = os.open(str(fname), flags, mode=0o666)
            self.idxfile = os.fdopen(fd, "r+t")

    def _lockf(self, mode=fcntl.LOCK_SH):
        try:
            fcntl.lockf(self.idxfile.fileno(), mode | fcntl.LOCK_NB)
        except OSError as e:
            if e.errno in (errno.EACCES, errno.EAGAIN):
                e = AlreadyLockedError(*e.args)
            raise e

    def read(self, idxfile=None):
        """Read the index from a file.
        """
        self._get_idxfile(idxfile, os.O_RDWR)
        self._lockf()
        self.items = [IdxItem(data=i) for i in yaml.safe_load(self.idxfile)]

    def write(self, idxfile=None):
        """Write the index to a file.
        """
        items = [i.as_dict() for i in self.items]
        self._get_idxfile(idxfile, os.O_RDWR | os.O_CREAT)
        self._lockf(mode=fcntl.LOCK_EX)
        yaml.dump(items, self.idxfile, default_flow_style=False)
        self.idxfile.truncate()
        self.idxfile.flush()
        self._lockf()