From 1ef0620f6e2d8a4e7d48b3b2b8eb96c3a8d3cd0d Mon Sep 17 00:00:00 2001
From: isanae <14251494+isanae@users.noreply.github.com>
Date: Tue, 25 Jun 2019 08:10:17 -0400
Subject: rework of the images tab to avoid using widgets at all, painting is
done manually
---
src/modinfodialog.ui | 19 +++-
src/modinfodialogconflicts.cpp | 8 --
src/modinfodialogimages.cpp | 237 +++++++++++++++++++++++++++++++++--------
src/modinfodialogimages.h | 71 ++++++++++--
4 files changed, 268 insertions(+), 67 deletions(-)
(limited to 'src')
diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui
index cdf4c80f..bcdf1007 100644
--- a/src/modinfodialog.ui
+++ b/src/modinfodialog.ui
@@ -177,11 +177,8 @@
0
-
-
-
- true
-
-
+
+
0
@@ -1163,6 +1160,18 @@ p, li { white-space: pre-wrap; }
QTextEdit
+
+ ImagesScrollArea
+ QScrollArea
+
+ 1
+
+
+ ImagesThumbnails
+ QWidget
+
+ 1
+
diff --git a/src/modinfodialogconflicts.cpp b/src/modinfodialogconflicts.cpp
index d1384d7a..05b69f05 100644
--- a/src/modinfodialogconflicts.cpp
+++ b/src/modinfodialogconflicts.cpp
@@ -887,7 +887,6 @@ ConflictItem GeneralConflictsTab::createOverwriteItem(
const FileEntry::AlternativesVector& alternatives)
{
const auto& ds = *m_core.directoryStructure();
-
QString altString;
for (const auto& alt : alternatives) {
@@ -898,9 +897,6 @@ ConflictItem GeneralConflictsTab::createOverwriteItem(
altString += ToQString(ds.getOriginByID(alt.first).getName());
}
- QStringList fields(relativeName);
- fields.append(altString);
-
const auto origin = ToQString(ds.getOriginByID(alternatives.back().first).getName());
return {altString, relativeName, "", index, fileName, true, origin, archive};
@@ -918,12 +914,8 @@ ConflictItem GeneralConflictsTab::createOverwrittenItem(
const QString& fileName, const QString& relativeName)
{
const auto& ds = *m_core.directoryStructure();
-
const FilesOrigin &realOrigin = ds.getOriginByID(fileOrigin);
- QStringList fields(relativeName);
- fields.append(ToQString(realOrigin.getName()));
-
return {
"", relativeName, ToQString(realOrigin.getName()),
index, fileName, true, ToQString(realOrigin.getName()), archive};
diff --git a/src/modinfodialogimages.cpp b/src/modinfodialogimages.cpp
index f4cdade8..1f5b258c 100644
--- a/src/modinfodialogimages.cpp
+++ b/src/modinfodialogimages.cpp
@@ -1,25 +1,68 @@
#include "modinfodialogimages.h"
#include "ui_modinfodialog.h"
-ScalableImage::ScalableImage(QImage original)
- : m_original(std::move(original)), m_border(1)
+void ImagesScrollArea::setTab(ImagesTab* tab)
+{
+ m_tab = tab;
+}
+
+void ImagesScrollArea::resizeEvent(QResizeEvent* e)
+{
+ if (m_tab) {
+ m_tab->scrollAreaResized(e->size());
+ }
+}
+
+
+void ImagesThumbnails::setTab(ImagesTab* tab)
+{
+ m_tab = tab;
+}
+
+void ImagesThumbnails::paintEvent(QPaintEvent* e)
+{
+ if (m_tab) {
+ m_tab->paintThumbnails(e);
+ }
+}
+
+void ImagesThumbnails::mousePressEvent(QMouseEvent* e)
+{
+ if (m_tab) {
+ m_tab->thumbnailsMouseEvent(e);
+ }
+}
+
+
+ScalableImage::ScalableImage(QString path)
+ : m_path(std::move(path)), m_border(1)
{
auto sp = sizePolicy();
sp.setHeightForWidth(true);
setSizePolicy(sp);
}
+void ScalableImage::setImage(const QString& path)
+{
+ m_path = path;
+ m_original = {};
+ m_scaled = {};
+
+ update();
+}
+
void ScalableImage::setImage(QImage image)
{
+ m_path.clear();
m_original = std::move(image);
m_scaled = {};
update();
}
-const QImage& ScalableImage::image() const
+void ScalableImage::clear()
{
- return m_original;
+ setImage(QImage());
}
bool ScalableImage::hasHeightForWidth() const
@@ -35,7 +78,15 @@ int ScalableImage::heightForWidth(int w) const
void ScalableImage::paintEvent(QPaintEvent* e)
{
if (m_original.isNull()) {
- return;
+ if (m_path.isNull()) {
+ return;
+ }
+
+ m_original.load(m_path);
+
+ if (m_original.isNull()) {
+ return;
+ }
}
const QRect widgetRect = rect();
@@ -72,83 +123,177 @@ void ScalableImage::paintEvent(QPaintEvent* e)
painter.drawImage(drawImageRect, m_scaled);
}
-void ScalableImage::mousePressEvent(QMouseEvent* e)
-{
- if (e->button() == Qt::LeftButton) {
- emit clicked(m_original);
- }
-}
-
ImagesTab::ImagesTab(
OrganizerCore& oc, PluginContainer& plugin,
QWidget* parent, Ui::ModInfoDialog* ui, int id) :
ModInfoDialogTab(oc, plugin, parent, ui, id),
- m_image(new ScalableImage)
+ m_image(new ScalableImage), m_margins(3), m_padding(5), m_border(1)
{
ui->imagesImage->layout()->addWidget(m_image);
- ui->imagesThumbnails->setLayout(new QVBoxLayout);
+ delete ui->imagesThumbnails->layout();
ui->tabImagesSplitter->setSizes({128, 1});
ui->tabImagesSplitter->setStretchFactor(0, 0);
ui->tabImagesSplitter->setStretchFactor(1, 1);
+
+ ui->imagesScrollArea->setWidgetResizable(false);
+ ui->imagesScrollArea->setTab(this);
+ ui->imagesThumbnails->setTab(this);
}
void ImagesTab::clear()
{
- m_image->setImage({});
+ m_image->clear();
+ m_files.clear();
+ setHasData(false);
+}
- while (ui->imagesThumbnails->layout()->count() > 0) {
- auto* item = ui->imagesThumbnails->layout()->takeAt(0);
- delete item->widget();
- delete item;
+bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath)
+{
+ QImageReader reader(fullPath);
+ if (reader.canRead()) {
+ m_files.push_back({fullPath});
+ return true;
}
- static_cast(ui->imagesThumbnails->layout())->addStretch(1);
- setHasData(false);
+ return false;
}
-bool ImagesTab::feedFile(const QString& rootPath, const QString& fullPath)
+void ImagesTab::update()
+{
+ setHasData(!m_files.empty());
+}
+
+void ImagesTab::scrollAreaResized(const QSize& s)
{
- static constexpr const char* extensions[] = {
- ".png", ".jpg"
- };
+ const int availableWidth = s.width();
- for (const auto* e : extensions) {
- if (fullPath.endsWith(e, Qt::CaseInsensitive)) {
- if (add(fullPath)) {
- setHasData(true);
+ const int thumbSize = availableWidth - (m_margins * 2);
+
+ int height = 0;
+ if (!m_files.empty()) {
+ height = thumbSize + static_cast((m_padding + thumbSize) * (m_files.size() - 1));
+ }
+
+ height += (m_margins * 2);
+
+ qDebug() << "new size: " << availableWidth << "x" << height;
+
+ ui->imagesThumbnails->setGeometry(QRect(0, 0, availableWidth, height));
+}
+
+void ImagesTab::paintThumbnails(QPaintEvent* e)
+{
+ const auto availableRect = ui->imagesThumbnails->rect();
+ const int thumbSize = availableRect.width() - (m_margins * 2);
+
+ const QRect topRect(
+ availableRect.left() + m_margins,
+ availableRect.top() + m_margins,
+ thumbSize, thumbSize);
+
+ const std::size_t begin = e->rect().top() / (thumbSize + m_padding);
+ const std::size_t end = e->rect().bottom() / (thumbSize + m_padding) + 1;
+
+ QPainter painter(ui->imagesThumbnails);
+
+ for (std::size_t i=begin; i(topRect.top() + (i * (thumbSize + m_padding))),
+ thumbSize, thumbSize);
+
+ painter.setPen(QColor(Qt::black));
+ painter.drawRect(borderRect);
+
+ auto& file = m_files[i];
+ if (file.failed) {
+ continue;
+ }
+
+ const QRect thumbRect = borderRect.adjusted(
+ m_border, m_border, -m_border, -m_border);
+
+ if (needsReload(file, thumbRect.size())) {
+ if (!file.original.load(file.path)) {
+ qCritical() << "failed to load image from " << file.path;
+ file.failed = true;
+ continue;
}
- return true;
+ file.thumbnail = file.original.scaled(
+ scaledImageSize(file.original.size(), thumbRect.size()),
+ Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
}
- }
- return false;
+ if (file.thumbnail.isNull()) {
+ continue;
+ }
+
+ const QRect scaledThumbRect(
+ (thumbRect.left()+thumbRect.width()/2) - file.thumbnail.width()/2,
+ (thumbRect.top()+thumbRect.height()/2) - file.thumbnail.height()/2,
+ file.thumbnail.width(), file.thumbnail.height());
+
+ painter.drawImage(scaledThumbRect, file.thumbnail);
+ }
}
-bool ImagesTab::add(const QString& fullPath)
+void ImagesTab::thumbnailsMouseEvent(QMouseEvent* e)
{
- QImage image = QImage(fullPath);
+ if (e->button() != Qt::LeftButton) {
+ return;
+ }
- if (image.isNull()) {
- qWarning() << "ImagesTab: '" << fullPath << "' is not a valid image";
- return false;
+ const auto availableRect = ui->imagesThumbnails->rect();
+ const int thumbSize = availableRect.width() - (m_margins * 2);
+
+ const QRect topRect(
+ availableRect.left() + m_margins,
+ availableRect.top() + m_margins,
+ thumbSize, thumbSize);
+
+ const std::size_t i = e->y() / (thumbSize + m_padding);
+ if (i >= m_files.size()) {
+ return;
}
- auto* thumbnail = new ScalableImage(std::move(image));
+ if (e->x() < topRect.left() || e->x() > (topRect.right() + 1)) {
+ return;
+ }
- QObject::connect(
- thumbnail, &ScalableImage::clicked,
- [&](const QImage& image){ onClicked(image); });
+ m_image->setImage(m_files[i].original);
+}
- static_cast(ui->imagesThumbnails->layout())->insertWidget(
- ui->imagesThumbnails->layout()->count() - 1, thumbnail);
+bool ImagesTab::needsReload(const File& file, const QSize& thumbSize) const
+{
+ if (file.failed) {
+ return false;
+ }
- return true;
+ if (file.original.isNull() || file.thumbnail.isNull()) {
+ return true;
+ }
+
+ if (file.thumbnail.size() != scaledImageSize(file.original.size(), thumbSize)) {
+ return true;
+ }
+
+ return false;
}
-void ImagesTab::onClicked(const QImage& original)
+QSize ImagesTab::scaledImageSize(
+ const QSize& originalSize, const QSize& thumbSize) const
{
- m_image->setImage(original);
+ const auto ratio = std::min({
+ 1.0,
+ static_cast(thumbSize.width()) / originalSize.width(),
+ static_cast(thumbSize.height()) / originalSize.height()});
+
+ const QSize scaledSize(
+ static_cast(std::round(originalSize.width() * ratio)),
+ static_cast(std::round(originalSize.height() * ratio)));
+
+ return scaledSize;
}
diff --git a/src/modinfodialogimages.h b/src/modinfodialogimages.h
index 6603660a..1c65d8bd 100644
--- a/src/modinfodialogimages.h
+++ b/src/modinfodialogimages.h
@@ -2,28 +2,62 @@
#define MODINFODIALOGIMAGES_H
#include "modinfodialogtab.h"
+#include
+
+class ImagesTab;
+
+class ImagesScrollArea : public QScrollArea
+{
+ Q_OBJECT;
+
+public:
+ using QScrollArea::QScrollArea;
+ void setTab(ImagesTab* tab);
+
+protected:
+ void resizeEvent(QResizeEvent* e) override;
+
+private:
+ ImagesTab* m_tab = nullptr;
+};
+
+
+class ImagesThumbnails : public QWidget
+{
+ Q_OBJECT;
+
+public:
+ using QWidget::QWidget;
+ void setTab(ImagesTab* tab);
+
+protected:
+ void paintEvent(QPaintEvent* e) override;
+ void mousePressEvent(QMouseEvent* e) override;
+
+private:
+ ImagesTab* m_tab = nullptr;
+};
+
class ScalableImage : public QWidget
{
Q_OBJECT;
public:
- ScalableImage(QImage image={});
+ ScalableImage(QString path={});
+ void setImage(const QString& path);
void setImage(QImage image);
- const QImage& image() const;
+ void clear();
bool hasHeightForWidth() const;
int heightForWidth(int w) const;
-signals:
- void clicked(const QImage& image);
-
protected:
void paintEvent(QPaintEvent* e) override;
- void mousePressEvent(QMouseEvent* e) override;
private:
+ QString m_path;
QImage m_original, m_scaled;
int m_border;
};
@@ -32,6 +66,8 @@ private:
class ImagesTab : public ModInfoDialogTab
{
Q_OBJECT;
+ friend class ImagesScrollArea;
+ friend class ImagesThumbnails;
public:
ImagesTab(
@@ -40,12 +76,31 @@ public:
void clear() override;
bool feedFile(const QString& rootPath, const QString& fullPath) override;
+ void update() override;
private:
+ struct File
+ {
+ QString path;
+ QImage original, thumbnail;
+ bool failed = false;
+
+ File(QString path)
+ : path(std::move(path))
+ {
+ }
+ };
+
ScalableImage* m_image;
+ std::vector m_files;
+ int m_margins, m_padding, m_border;
+
+ void scrollAreaResized(const QSize& s);
+ void paintThumbnails(QPaintEvent* e);
+ void thumbnailsMouseEvent(QMouseEvent* e);
- bool add(const QString& fullPath);
- void onClicked(const QImage& image);
+ bool needsReload(const File& file, const QSize& thumbSize) const;
+ QSize scaledImageSize(const QSize& originalSize, const QSize& thumbSize) const;
};
#endif // MODINFODIALOGIMAGES_H
--
cgit v1.3.1