git repos / SagoImageBrowser

src/fsdirmodel.h

browsing at commit = 40efdfde67a0b772c31bbbab7fbc9b1ca53e07bd

raw · blame · history

#pragma once

#include <QAbstractItemModel>
#include <QByteArray>
#include <QIcon>
#include <QList>

// A directory-tree model that uses std::filesystem for enumeration so that
// directory entries with non-UTF-8 filenames (e.g. Latin-1 encoded names on
// a UTF-8 locale) are listed correctly instead of being silently dropped or
// mangled by QDir / QFileSystemModel.
//
// The model is lazily populated: child directories are enumerated only when
// the view first expands a node (via canFetchMore / fetchMore).
class FsDirModel : public QAbstractItemModel
{
    Q_OBJECT

public:
    explicit FsDirModel(QObject *parent = nullptr);
    ~FsDirModel() override;

    // Set the directory shown as the tree root.  Call rootIndex() afterwards
    // and pass the result to QTreeView::setRootIndex() so the root node itself
    // is hidden and only its children are displayed as top-level items.
    void setRootPath(const QByteArray &nativePath);
    void setRootPath(const QString &path);   // convenience: converts via QFile::encodeName

    // Model index of the root-path node (the one whose *children* are shown
    // as top-level items when passed to QTreeView::setRootIndex).
    QModelIndex rootIndex() const;

    // Raw native OS byte path for the node at index.
    QByteArray filePath(const QModelIndex &index) const;

    // Walk the tree to find (and lazily populate) the node for nativePath.
    // Returns an invalid index if the path is not under the current root.
    QModelIndex indexForPath(const QByteArray &nativePath);

    // QAbstractItemModel interface
    QModelIndex index(int row, int column,
                      const QModelIndex &parent = QModelIndex()) const override;
    QModelIndex parent(const QModelIndex &index) const override;
    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    int columnCount(const QModelIndex &parent = QModelIndex()) const override;
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
    bool hasChildren(const QModelIndex &parent = QModelIndex()) const override;
    bool canFetchMore(const QModelIndex &parent) const override;
    void fetchMore(const QModelIndex &parent) override;

private:
    struct Node {
        QByteArray  nativePath;
        QString     displayName;
        Node       *parent   = nullptr;
        QList<Node *> children;
        bool        populated = false;
    };

    // Synchronously populate node->children from the filesystem.
    // Only call this inside fetchMore (with proper begin/endInsertRows around it).
    static QList<Node *> listSubdirs(Node *parent);

    Node *nodeForIndex(const QModelIndex &index) const;
    QModelIndex indexForNode(Node *node) const;
    static void destroyChildren(Node *node);

    Node   m_virtualRoot;   // hidden root; always has exactly 0 or 1 child
    QIcon  m_folderIcon;
};