diff options
author | Petr Mrázek <peterix@gmail.com> | 2014-07-05 13:26:37 +0200 |
---|---|---|
committer | Petr Mrázek <peterix@gmail.com> | 2014-07-05 13:27:32 +0200 |
commit | a218d7b7f6a9e30671be72b756104302637eb33d (patch) | |
tree | debebeb326aabf68f950b35beb3427e99b6c5e18 | |
parent | b5d6f50fb12f01f6e2bed24118dd777163339a06 (diff) | |
download | MultiMC-a218d7b7f6a9e30671be72b756104302637eb33d.tar MultiMC-a218d7b7f6a9e30671be72b756104302637eb33d.tar.gz MultiMC-a218d7b7f6a9e30671be72b756104302637eb33d.tar.lz MultiMC-a218d7b7f6a9e30671be72b756104302637eb33d.tar.xz MultiMC-a218d7b7f6a9e30671be72b756104302637eb33d.zip |
Improve screenshot view/model.
Changes to screenshots are tracked.
Thumbnails are generated in a thread pool.
-rw-r--r-- | gui/pages/ScreenshotsPage.cpp | 228 | ||||
-rw-r--r-- | resources/multimc/multimc.qrc | 3 | ||||
-rw-r--r-- | resources/multimc/scalable/screenshot-placeholder.svg | 86 |
3 files changed, 288 insertions, 29 deletions
diff --git a/gui/pages/ScreenshotsPage.cpp b/gui/pages/ScreenshotsPage.cpp index 051bc12d..f3ec0c1d 100644 --- a/gui/pages/ScreenshotsPage.cpp +++ b/gui/pages/ScreenshotsPage.cpp @@ -3,11 +3,14 @@ #include <QModelIndex> #include <QMutableListIterator> +#include <QMap> +#include <QSet> #include <QFileIconProvider> #include <QFileSystemModel> #include <QStyledItemDelegate> #include <QLineEdit> #include <QtGui/qevent.h> +#include <QtGui/QPainter> #include <pathutils.h> @@ -18,9 +21,156 @@ #include "logic/screenshots/ImgurAlbumCreation.h" #include "logic/tasks/SequentialTask.h" +template <typename K, typename V> +class RWStorage +{ +public: + void add(K key, V value) + { + QWriteLocker l(&lock); + cache[key] = value; + stale_entries.remove(key); + } + V get(K key) + { + QReadLocker l(&lock); + if(cache.contains(key)) + { + return cache[key]; + } + else return V(); + } + bool get(K key, V& value) + { + QReadLocker l(&lock); + if(cache.contains(key)) + { + value = cache[key]; + return true; + } + else return false; + } + bool has(K key) + { + QReadLocker l(&lock); + return cache.contains(key); + } + bool stale(K key) + { + QReadLocker l(&lock); + if(!cache.contains(key)) + return true; + return stale_entries.contains(key); + } + void setStale(K key) + { + QReadLocker l(&lock); + if(cache.contains(key)) + { + stale_entries.insert(key); + } + } + void clear() + { + QWriteLocker l(&lock); + cache.clear(); + } +private: + QReadWriteLock lock; + QMap<K, V> cache; + QSet<K> stale_entries; +}; + +typedef RWStorage<QString, QIcon> SharedIconCache; +typedef std::shared_ptr<SharedIconCache> SharedIconCachePtr; + +class ThumbnailingResult : public QObject +{ + Q_OBJECT +public Q_SLOTS: + inline void emitResultsReady(const QString &path) + { + emit resultsReady(path); + } + inline void emitResultsFailed(const QString &path) + { + emit resultsFailed(path); + } +Q_SIGNALS: + void resultsReady(const QString &path); + void resultsFailed(const QString &path); +}; + +class ThumbnailRunnable: public QRunnable +{ +public: + ThumbnailRunnable (QString path, SharedIconCachePtr cache) + { + m_path = path; + m_cache = cache; + } + void run() + { + QFileInfo info(m_path); + if(info.isDir()) + return; + if((info.suffix().compare("png", Qt::CaseInsensitive) != 0)) + return; + int tries = 5; + while(tries) + { + if(!m_cache->stale(m_path)) + return; + QImage image(m_path); + if (image.isNull()) + { + QThread::msleep(500); + tries--; + continue; + } + QImage small; + if(image.width() > image.height()) + small = image.scaledToWidth(512).scaledToWidth(256, Qt::SmoothTransformation); + else + small = image.scaledToHeight(512).scaledToHeight(256, Qt::SmoothTransformation); + auto smallSize = small.size(); + QPoint offset((256-small.width())/2, (256-small.height())/2); + QImage square(QSize(256,256), QImage::Format_ARGB32); + square.fill(Qt::transparent); + + QPainter painter(&square); + painter.drawImage(offset, small); + painter.end(); + + QIcon icon(QPixmap::fromImage(square)); + m_cache->add(m_path, icon); + m_resultEmitter.emitResultsReady(m_path); + return; + } + m_resultEmitter.emitResultsFailed(m_path); + } + QString m_path; + SharedIconCachePtr m_cache; + ThumbnailingResult m_resultEmitter; +}; + +// this is about as elegant and well written as a bag of bricks with scribbles done by insane asylum patients. class FilterModel : public QIdentityProxyModel { + Q_OBJECT public: + explicit FilterModel(QObject *parent = 0):QIdentityProxyModel(parent) + { + m_thumbnailingPool.setMaxThreadCount(4); + m_thumbnailCache = std::make_shared<SharedIconCache>(); + m_thumbnailCache->add("placeholder", QIcon::fromTheme("screenshot-placeholder")); + connect(&watcher, SIGNAL(fileChanged(QString)), SLOT(fileChanged(QString))); + // FIXME: the watched file set is not updated when files are removed + } + virtual ~FilterModel() + { + m_thumbnailingPool.waitForDone(500); + } virtual QVariant data(const QModelIndex &proxyIndex, int role = Qt::DisplayRole) const { auto model = sourceModel(); @@ -35,38 +185,23 @@ public: { QVariant result = sourceModel()->data(mapToSource(proxyIndex), QFileSystemModel::FilePathRole); QString filePath = result.toString(); - if(thumbnailCache.contains(filePath)) + QIcon temp; + if(!watched.contains(filePath)) { - return thumbnailCache[filePath]; + ((QFileSystemWatcher &)watcher).addPath(filePath); + ((QSet<QString> &)watched).insert(filePath); } - bool failed = false; - QFileInfo info(filePath); - failed |= info.isDir(); - failed |= (info.suffix().compare("png", Qt::CaseInsensitive) != 0); - // WARNING: really an IF! this is purely for using break instead of goto... - while(!failed) + if(m_thumbnailCache->get(filePath, temp)) { - QImage image(info.absoluteFilePath()); - if (image.isNull()) - { - // TODO: schedule a retry. - failed = true; - break; - } - QImage thumbnail = image.scaledToWidth(512).scaledToWidth(256, Qt::SmoothTransformation); - QIcon icon(QPixmap::fromImage(thumbnail)); - // the casts are a hack for the stupid method being const. - ((QMap<QString, QIcon> &)thumbnailCache).insert(filePath, icon); - return icon; + return temp; } - // we failed anyway... - return sourceModel()->data(mapToSource(proxyIndex), QFileSystemModel::FileIconRole); - } - else - { - QVariant result = sourceModel()->data(mapToSource(proxyIndex), role); - return result; + if(!m_failed.contains(filePath)) + { + ((FilterModel *)this)->thumbnailImage(filePath); + } + return(m_thumbnailCache->get("placeholder")); } + return sourceModel()->data(mapToSource(proxyIndex), role); } virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) @@ -85,7 +220,38 @@ public: return model->setData(mapToSource(index), value.toString() + ".png", role); } private: - QMap<QString, QIcon> thumbnailCache; + void thumbnailImage(QString path) + { + auto runnable = new ThumbnailRunnable(path, m_thumbnailCache); + connect(&(runnable->m_resultEmitter),SIGNAL(resultsReady(QString)), SLOT(thumbnailReady(QString))); + connect(&(runnable->m_resultEmitter),SIGNAL(resultsFailed(QString)), SLOT(thumbnailFailed(QString))); + ((QThreadPool &)m_thumbnailingPool).start(runnable); + } +private +slots: + void thumbnailReady(QString path) + { + emit layoutChanged(); + } + void thumbnailFailed(QString path) + { + m_failed.insert(path); + } + void fileChanged(QString filepath) + { + m_thumbnailCache->setStale(filepath); + thumbnailImage(filepath); + // reinsert the path... + watcher.removePath(filepath); + watcher.addPath(filepath); + } + +private: + SharedIconCachePtr m_thumbnailCache; + QThreadPool m_thumbnailingPool; + QSet<QString> m_failed; + QSet<QString> watched; + QFileSystemWatcher watcher; }; class CenteredEditingDelegate : public QStyledItemDelegate @@ -135,13 +301,15 @@ ScreenshotsPage::ScreenshotsPage(BaseInstance *instance, QWidget *parent) m_filterModel->setSourceModel(m_model.get()); m_model->setFilter(QDir::Files | QDir::Writable | QDir::Readable); m_model->setReadOnly(false); + m_model->setNameFilters({"*.png"}); + m_model->setNameFilterDisables(false); m_folder = PathCombine(instance->minecraftRoot(), "screenshots"); m_valid = ensureFolderPathExists(m_folder); ui->setupUi(this); ui->listView->setModel(m_filterModel.get()); ui->listView->setIconSize(QSize(128, 128)); - ui->listView->setGridSize(QSize(192, 128)); + ui->listView->setGridSize(QSize(192, 160)); ui->listView->setSpacing(9); // ui->listView->setUniformItemSizes(true); ui->listView->setLayoutMode(QListView::Batched); @@ -268,3 +436,5 @@ void ScreenshotsPage::opened() ui->listView->setRootIndex(m_filterModel->mapFromSource(m_model->index(path))); } } + +#include "ScreenshotsPage.moc" diff --git a/resources/multimc/multimc.qrc b/resources/multimc/multimc.qrc index ad5ae5a4..5c49017b 100644 --- a/resources/multimc/multimc.qrc +++ b/resources/multimc/multimc.qrc @@ -164,5 +164,8 @@ <file>24x24/noaccount.png</file> <file>32x32/noaccount.png</file> <file>48x48/noaccount.png</file> + + <!-- placeholder when loading screenshot images --> + <file>scalable/screenshot-placeholder.svg</file> </qresource> </RCC> diff --git a/resources/multimc/scalable/screenshot-placeholder.svg b/resources/multimc/scalable/screenshot-placeholder.svg new file mode 100644 index 00000000..a7a2a3d6 --- /dev/null +++ b/resources/multimc/scalable/screenshot-placeholder.svg @@ -0,0 +1,86 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="256" + height="256" + id="svg2" + version="1.1" + inkscape:version="0.48.5 r10040" + sodipodi:docname="screenshot-placeholder.svg"> + <defs + id="defs4" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="0.49497475" + inkscape:cx="-33.672765" + inkscape:cy="136.19802" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showgrid="false" + inkscape:snap-bbox="true" + inkscape:bbox-paths="true" + inkscape:bbox-nodes="true" + inkscape:snap-bbox-edge-midpoints="true" + inkscape:snap-bbox-midpoints="true" + inkscape:snap-nodes="false" + inkscape:window-width="1612" + inkscape:window-height="1026" + inkscape:window-x="1677" + inkscape:window-y="-4" + inkscape:window-maximized="1"> + <inkscape:grid + type="xygrid" + id="grid2985" + empspacing="5" + visible="true" + enabled="true" + snapvisiblegridlinesonly="true" + spacingx="8px" + spacingy="8px" /> + </sodipodi:namedview> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(0,-796.36218)"> + <g + id="g3009" + style="fill:#000000"> + <path + id="rect2987" + transform="translate(0,796.36218)" + d="M 24 8 C 15.136 8 8 15.136 8 24 L 8 232 C 8 240.864 15.136 248 24 248 L 232 248 C 240.864 248 248 240.864 248 232 L 248 24 C 248 15.136 240.864 8 232 8 L 24 8 z M 24 16 L 232 16 C 236.432 16 240 19.568 240 24 L 240 232 C 240 236.432 236.432 240 232 240 L 24 240 C 19.568 240 16 236.432 16 232 L 16 24 C 16 19.568 19.568 16 24 16 z " + style="opacity:0.75000000000000000;color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.75000000000000000;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + <path + style="fill:#000000;opacity:0.75000000000000000;stroke:none" + inkscape:connector-curvature="0" + d="m 85.749999,937.9006 c 0,24.30067 18.915811,43.99997 42.249991,43.99997 23.33419,0 42.25001,-19.6993 42.25001,-43.99997 0,-24.30069 -18.91582,-43.99999 -42.25001,-43.99999 -23.33418,0 -42.249991,19.6993 -42.249991,43.99999 z M 219,863.43909 h -45.5 c -3.25,-13.53846 -6.50001,-27.07691 -19.5,-27.07691 h -52 c -13.000001,0 -16.250001,13.53845 -19.500001,27.07691 H 37 c -7.15,0 -13,6.09231 -13,13.53846 v 121.84603 c 0,7.44632 5.85,13.53862 13,13.53862 h 182 c 7.15,0 13,-6.0923 13,-13.53862 V 876.97755 c 0,-7.44615 -5.85,-13.53846 -13,-13.53846 z m -91.00001,134.53849 c -31.860147,0 -57.687491,-26.89678 -57.687491,-60.07698 0,-33.1798 25.827344,-60.0769 57.687491,-60.0769 31.86057,0 57.68751,26.8971 57.68751,60.0769 0,33.1802 -25.82653,60.07698 -57.68751,60.07698 z M 219,904.05445 h -26 v -13.53846 h 26 v 13.53846 z" + id="path2996" /> + </g> + </g> +</svg> |