Beispiel #1
0
    def __init__(self, directory, freezed=True):
        QAbstractItemModel.__init__(self)

        self.directory = directory
        #self.icon_for = icon_factory
        self.prompter = lambda *args: FSAction.PROMPT_CANCEL
        self.update_func = lambda *args: None
        self.color_directories = False

        self.copyStarting = Callback()
        self.moveStarting = Callback()
        self.deleteStarting = Callback()

        self.onDrop = Callback()
        self.preFSOp = Callback()

        self.modelReset = Callback()

        if not self.CACHING:
            self.data = self._data

        self.icon_mapping = {}

        self.overrides = {}
        self.cache = {}
Beispiel #2
0
class DirModel(QAbstractItemModel):
    """
    :ivar directory: A `directory`-like object
    :ivar prompter: A callable which is called when a user interaction
                    is needed by an FS Operation (copy, move), defaults to
                    lambda *args: FSAction.PROMPT_CANCEL
    :ivar icon_mapping: .ext -> icon_name
    :cvar columns: a dict column_number -> column_name
    :cvar CACHING: if true `data`s return values are cached
    """
    # pylint: disable-msg=C0103
    columns = dict(enumerate(('Filename', 'Size', 'Modified')))

    directoryChanged = pyqtSignal(object)
    preDirectoryChanged = pyqtSignal(object)

    CACHING = True

    def __init__(self, directory, freezed=True):
        QAbstractItemModel.__init__(self)

        self.directory = directory
        #self.icon_for = icon_factory
        self.prompter = lambda *args: FSAction.PROMPT_CANCEL
        self.update_func = lambda *args: None
        self.color_directories = False

        self.copyStarting = Callback()
        self.moveStarting = Callback()
        self.deleteStarting = Callback()

        self.onDrop = Callback()
        self.preFSOp = Callback()

        self.modelReset = Callback()

        if not self.CACHING:
            self.data = self._data

        self.icon_mapping = {}

        self.overrides = {}
        self.cache = {}

    def index(self, row, column, parent):
        if parent.isValid() or not self.hasIndex(row, column, parent):
            return QModelIndex()

        idx = self.createIndex(row, column, self.directory[row])
        return idx

    def path2index(self, pathobj):
        row = self.directory.files.index(pathobj)
        return self.index(row, 0, QModelIndex())

    def rowCount(self, parent):
        #print "RC", len(self.directory), len(self.directory.files)
        if not parent.isValid():
            return len(self.directory)
        else:
            return 0

    def columnCount(self, parent):
        return len(self.columns)

    def data(self, index, role):
        try:
            return self.cache[index.row(), index.column(), role]
        except KeyError:
            pass

        try:
            value = self._data(index, role)
        except OSError as exception:
            # File list invalid
            if exception.errno == errno.ENOENT:
                #print "NOENT"
                self.changeDirectory()
            else:
                raise
        else:
            self.cache[index.row(), index.column(), role] = value
            return value

    def _data(self, index, role):
        row = index.row()
        column = index.column()
        column_name = self.columns[column]

        filepath = index.internalPointer()

        obj = self.overrides.get((filepath, column, role), None)

        if obj is not None:
            return obj

        if filepath is None:
            import sys
            logger['DirModel'].error(
                "Fatal model error, requested index '%s' not found",
                row
            )
            QApplication.instance().exit()
            sys.exit()

        # Handle broken symlinks
        if not filepath.realpath().exists():
            if column_name == 'Filename':
                if role == Qt.DisplayRole:
                    return self.formatFilename(filepath.name)
                elif role == Qt.DecorationRole:
                    return self.icon_for(filepath)
                elif role == Qt.EditRole:
                    return filepath.name

            elif column_name == 'Modified' and role == Qt.DisplayRole:
                return "0000-00-00 00:00"

            elif column_name == 'Size' and role == Qt.DisplayRole:
                return "0"


        if role == Qt.BackgroundRole and self.color_directories:
            if filepath.isdir():
                return QPalette().color(QPalette.AlternateBase)

        if column_name == 'Filename':
            if role == Qt.DisplayRole:
                return self.formatFilename(filepath.name)

            elif role == Qt.DecorationRole:
                return self.icon_for(filepath)

            elif role == Qt.EditRole:
                return filepath.name

        elif column_name == 'Size' and not filepath.isdir():
            if role == Qt.DisplayRole:
                try:
                    size_str = "%.2f%s" % format_size(filepath.size)
                except OSError:
                    size_str = ""
                return size_str

        elif column_name == 'Modified':
            if role == Qt.DisplayRole:
                date_obj = datetime.fromtimestamp(filepath.mtime)
                date_str = date_obj.strftime('%Y-%m-%d %H:%M')

                return date_str

        return QVariant()

    def setData(self, idx, data, role):
        new_path = self.directory.path / unicode(data.toString())
        old_path = idx.internalPointer()
        old_path.rename(new_path)

        try:
            self.cache.pop((idx.row(), idx.column(), role))
        except KeyError:
            pass

        self.changeDirectory()

        return True

    def formatFilename(self, name):
        return name

    def flags(self, index):
        flags = QAbstractItemModel.flags(self, index)

        if index.isValid() and index.internalPointer().name != u'..':
            if index.column() == 0:
                flags |= Qt.ItemIsEditable

            return Qt.ItemIsDragEnabled | Qt.ItemIsDropEnabled | flags
        else:
            return Qt.ItemIsDropEnabled | flags

    def parent(self, index):
        return QModelIndex()

    def hasChildren(self, index):
        if index.row() == index.column() == -1:
            return True

        return False

    def headerData(self, section, orientation, role=Qt.DisplayRole):
        if role != Qt.DisplayRole or orientation == Qt.Vertical:
            return QVariant()

        return self.columns.get(section, QVariant())

    def sort(self, column, order=Qt.AscendingOrder):
        try:
            sort_attr = {0: 'name', 1: 'size', 2: 'mtime'}[int(column)]
        except ValueError:
            sort_attr = column

        self.directory.sort(sort_attr, order != Qt.AscendingOrder)
        self.reset()

    def supportedDropActions(self):
        return Qt.CopyAction

    def dropMimeData(self, data, action, row, column, parent):
        #print "DMD", data, action, row, column, parent.row(), parent.column()
        #print data.text(), data.urls(), hasattr(data, 'source_model')

        if hasattr(data, 'source_model'):
            filenames = [
                data.source_model.directory.path_for(url.toLocalFile())
                    for url in data.urls()
            ]
            relpath = data.source_model.directory.path
        else:
            filenames = [path(url.toLocalFile()) for url in data.urls()]
            relpath = filenames[0].parent

        row = parent.row()
        if row == -1:
            destination = self.directory.path
        else:
            destination = self.index(row, 0, QModelIndex()).internalPointer()

            if not destination.isdir():
                destination = self.directory.path

        #relpath = data.source_model.directory.path

        event_handled = self.onDrop.call_chain(
            self, data, action, row, column, parent,
            #filenames, destination, relpath
        )
        if event_handled:
            return True

        menu = QMenu()
        copy = menu.addAction('Copy files')
        move = menu.addAction('Move files')
        cancel = menu.addAction('Cancel')

        choice = menu.exec_(QCursor.pos())

        if choice != cancel and choice is not None:
            event_handled = self.preFSOp.call_chain(
                self, data, action, row, column, parent,
                {copy: 'copy', move: 'move'}[choice],
                filenames, destination, relpath
            )

            if event_handled:
                return event_handled

            thread = {
                copy: self.copy,
                move: self.move,
            }[choice](filenames, relpath, destination)

            try:
                thread.finished.connect(data.source_model.changeDirectory)
            except AttributeError:
                pass

            thread.finished.connect(self.changeDirectory)
            thread.start()
        else:
            return False

        return True

    def mimeTypes(self):
        return ['text/uri-list']

    def mimeData(self, indexes):
        data = QMimeData()
        urls = []
        for index in indexes:
            if index.column() == 0:
                urls.append(QUrl.fromLocalFile(index.internalPointer()))

        data.setUrls(urls)
        data.source_model = self

        return data

    def reset(self):
        self.cache.clear()
        return QAbstractItemModel.reset(self)

    def changeDirectory(self, newpath=None):
        """
        Changes the model's current directory to `newpath`
        If `newpath` is None it refreshs the contents of the current dir
        """
        def reset_model():
            self.reset()
            self.directoryChanged.emit(newpath)

        self.preDirectoryChanged.emit(self.directory.path)

        sync, defer = self.directory.chdir(newpath)

        if sync:
            reset_model()
        else:
            defer.connect(reset_model)
            @defer.connect_errback
            def errback(exception):
                print exception

    def setFilter(self, func):
        def filterwrapper(p):
            if func:
                return func(filters.make_path_props(p))
            else:
                return True

        if func:
            filterwrapper.text = func.text
        else:
            filterwrapper.text = ''

        self.directory.set_filter(filterwrapper)
        self.reset()

    def copy(self, filenames, relpath, destination):
        thread = FSOpThread(self,
            self.directory.copy(
                relpath,
                filenames,
                destination,
                self.prompter,
                self.update_func
            )
        )
        self.copyStarting.emit(filenames, destination, thread)

        return thread

    def move(self, filenames, relpath, destination):
        thread = FSOpThread(self,
            self.directory.move(
                relpath,
                filenames,
                destination,
                self.prompter,
                self.update_func
            )
        )
        self.moveStarting.emit(filenames, destination, thread)

        return thread

    def delete(self, indexes):
        filenames = [idx.internalPointer() for idx in indexes]
        relpath = self.directory.path

        print filenames

        thread = FSOpThread(self,
            self.directory.delete(
                relpath,
                filenames,
                self.prompter,
                self.update_func,
            )
        )

        self.deleteStarting.emit(filenames, thread)
        thread.finished.connect(
            self.changeDirectory
        )
        thread.start()

    def mkdir(self, dirname):
        fullname = self.directory.path / dirname
        fullname.mkdir()
        self.changeDirectory()

    def stats(self, index):
        pass

    def icon_for(self, filename):
        return QImage(self.icon_path_for(filename))

    def icon_path_for(self, filename):
        def splitext(filename):
            parts = filename.split('.')

            if len(parts) == 1:
                return ".%s" % parts[0]
            elif parts[-2] == 'tar':
                return ".%s.%s" % (parts[-2], parts[-1])
            else:
                return ".%s" % parts[-1]

        basepath = path('gfx/mimetypes')

        if filename.name == u"..":
            return basepath / u"up.png"

        if filename.isdir():
            return basepath / u"folder.png"

        ext = splitext(filename)

        try:
            return basepath / self.icon_mapping[ext.lower()]
        except KeyError:
            return basepath / self.icon_mapping['']