summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPetr Mrázek <peterix@gmail.com>2014-07-05 13:26:37 +0200
committerPetr Mrázek <peterix@gmail.com>2014-07-05 13:27:32 +0200
commita218d7b7f6a9e30671be72b756104302637eb33d (patch)
treedebebeb326aabf68f950b35beb3427e99b6c5e18
parentb5d6f50fb12f01f6e2bed24118dd777163339a06 (diff)
downloadMultiMC-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.cpp228
-rw-r--r--resources/multimc/multimc.qrc3
-rw-r--r--resources/multimc/scalable/screenshot-placeholder.svg86
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>