/**************************************************************************** ** Artriculate: Art comes tumbling down ** Copyright (C) 2016 Chaos Reins ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program. If not, see . ****************************************************************************/ #include "picturemodel.h" #include #include #include #include #include #include #include struct FSNode { FSNode(const QString& rname, const FSNode *pparent = nullptr); static QString qualifyNode(const FSNode *node); const QString name; const FSNode *parent; QSize size; qreal ratio; }; FSNode::FSNode(const QString& rname, const FSNode *pparent) : name(rname), parent(pparent) { } QString FSNode::qualifyNode(const FSNode *node) { QString qualifiedPath; while(node->parent != nullptr) { qualifiedPath = "/" + node->name + qualifiedPath; node = node->parent; } qualifiedPath = node->name + qualifiedPath; return qualifiedPath; } class FSNodeTree : public QObject { Q_OBJECT public: FSNodeTree(PictureModel *p); void addModelNode(const FSNode* parentNode); void setModelRoot(const QString& rootDir) { this->rootDir = rootDir; } int fileCount() const { return files.length(); } QList files; public slots: void populate(); signals: void countChanged(); private: QStringList extensions; QString rootDir; }; FSNodeTree::FSNodeTree(PictureModel *p) : QObject(nullptr) { connect(this, SIGNAL(countChanged()), p, SIGNAL(countChanged())); QMimeDatabase mimeDatabase; for(const QByteArray &m: QImageReader::supportedMimeTypes()) { for(const QString &suffix: mimeDatabase.mimeTypeForName(m).suffixes()) extensions.append(suffix); } if (extensions.isEmpty()) { qFatal("Your Qt install has no image format support"); } } void FSNodeTree::addModelNode(const FSNode* parentNode) { // TODO: Check for symlink recursion QDir parentDir(FSNode::qualifyNode(parentNode)); for(const QString ¤tDir : parentDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { const FSNode *dir = new FSNode(currentDir, parentNode); addModelNode(dir); } for(const QString ¤tFile : parentDir.entryList(QDir::Files)) { QString extension = currentFile.mid(currentFile.length() - 3); if (!extensions.contains(extension)) continue; FSNode *file = new FSNode(currentFile, parentNode); const QString fullPath = FSNode::qualifyNode(file); QSize size = QImageReader(fullPath).size(); bool rational = false; if (size.isValid()) { file->size = size; qreal ratio = qreal(size.width())/size.height(); if ((ratio < 0.01) || (ratio > 100)) { qDebug() << "Image" << fullPath << "has excessive ratio" << ratio << "excluded"; } else { rational = true; file->ratio = ratio; } } else { qDebug() << "Discarding" << fullPath << "due to invalid size"; } if (rational) { files << file; emit countChanged(); } else { delete file; } } } void FSNodeTree::populate() { QDir currentDir(rootDir); if (!currentDir.exists()) { qDebug() << "Being told to watch a non existent directory"; } addModelNode(new FSNode(rootDir)); } class PictureModel::PictureModelPrivate { public: PictureModelPrivate(PictureModel* p); ~PictureModelPrivate(); FSNodeTree *fsTree; private: QThread scanningThread; }; PictureModel::PictureModelPrivate::PictureModelPrivate(PictureModel* p) { QSettings settings; QString artPath = settings.value("artPath","/blackhole/media/art").toString(); settings.setValue("artPath", artPath); fsTree = new FSNodeTree(p); fsTree->setModelRoot(artPath); fsTree->moveToThread(&scanningThread); scanningThread.start(); QMetaObject::invokeMethod(fsTree, "populate", Qt::QueuedConnection); }; PictureModel::PictureModelPrivate::~PictureModelPrivate() { scanningThread.quit(); scanningThread.wait(5000); delete fsTree; fsTree = nullptr; }; PictureModel::PictureModel(QObject *parent) : QAbstractListModel(parent), d(new PictureModelPrivate(this)) { /**/ } PictureModel::~PictureModel() { delete d; d = nullptr; } int PictureModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent) return d->fsTree->fileCount(); } QVariant PictureModel::data(const QModelIndex &index, int role) const { if (index.row() < 0 || index.row() >= d->fsTree->fileCount()) { switch (role) { case SizeRole: return QSize(1222,900); case RatioRole: return 1222/900; case NameRole: return "Qt logo"; case PathRole: default: return QString("qrc:///qt_logo_green_rgb.png"); } } switch (role) { case SizeRole: return d->fsTree->files.at(index.row())->size; case RatioRole: return d->fsTree->files.at(index.row())->ratio; case NameRole: return d->fsTree->files.at(index.row())->name; case PathRole: default: return QUrl::fromLocalFile(FSNode::qualifyNode(d->fsTree->files.at(index.row()))); } return QVariant(); } QHash PictureModel::roleNames() const { QHash roles; roles[NameRole] = "name"; roles[PathRole] = "path"; roles[SizeRole] = "size"; roles[RatioRole] = "ratio"; return roles; } #include "picturemodel.moc"