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(-) 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
texteditor.h
+ + ImagesScrollArea + QScrollArea +
modinfodialogimages.h
+ 1 +
+ + ImagesThumbnails + QWidget +
modinfodialogimages.h
+ 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