+
+#include "core/logging.h"
+#include "infotextview.h"
+
+InfoTextView::InfoTextView(QWidget *parent) : QTextBrowser(parent), last_width_(-1), recursion_filter_(false) {
+
+ setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ setOpenExternalLinks(true);
+
+}
+
+void InfoTextView::resizeEvent(QResizeEvent *e) {
+
+ const int w = qMax(100, width());
+ if (w == last_width_) return;
+ last_width_ = w;
+
+ document()->setTextWidth(w);
+ setMinimumHeight(document()->size().height());
+
+ QTextBrowser::resizeEvent(e);
+
+}
+
+QSize InfoTextView::sizeHint() const { return minimumSize(); }
+
+void InfoTextView::wheelEvent(QWheelEvent *e) { e->ignore(); }
+
+void InfoTextView::SetHtml(const QString &html) {
+
+ QString copy(html.trimmed());
+
+ // Simplify newlines
+ copy.replace(QRegularExpression(QStringLiteral("\\r\\n?")), QStringLiteral("\n"));
+
+ // Convert two or more newlines to , convert single newlines to
+ copy.replace(QRegularExpression(QStringLiteral("([^>])([\\t ]*\\n){2,}")), QStringLiteral("\\1
"));
+ copy.replace(QRegularExpression(QStringLiteral("([^>])[\\t ]*\\n")), QStringLiteral("\\1
"));
+
+ // Strip any newlines from the end
+ copy.replace(QRegularExpression(QStringLiteral("((<\\s*br\\s*/?\\s*>)|(<\\s*/?\\s*p\\s*/?\\s*>))+$")), QLatin1String(""));
+
+ setHtml(copy);
+
+}
+
+// Prevents QTextDocument from trying to load remote images before they are ready.
+QVariant InfoTextView::loadResource(int type, const QUrl &name) {
+
+ if (recursion_filter_) {
+ recursion_filter_ = false;
+ return QVariant();
+ }
+ recursion_filter_ = true;
+ if (type == QTextDocument::ImageResource && name.scheme() == QLatin1String("http")) {
+ if (document()->resource(type, name).isNull()) {
+ return QVariant();
+ }
+ }
+ return QTextBrowser::loadResource(type, name);
+
+}
diff --git a/src/widgets/infotextview.h b/src/widgets/infotextview.h
new file mode 100644
index 000000000..3da33b8cd
--- /dev/null
+++ b/src/widgets/infotextview.h
@@ -0,0 +1,52 @@
+/*
+ * Strawberry Music Player
+ * This file was part of Clementine.
+ * Copyright 2010, David Sansome
+ *
+ * Strawberry 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.
+ *
+ * Strawberry 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 Strawberry. If not, see .
+ *
+ */
+
+#ifndef INFOTEXTVIEW_H
+#define INFOTEXTVIEW_H
+
+#include
+#include
+#include
+
+class QResizeEvent;
+class QWheelEvent;
+
+class InfoTextView : public QTextBrowser {
+ Q_OBJECT
+
+ public:
+ explicit InfoTextView(QWidget *parent = nullptr);
+
+ QSize sizeHint() const override;
+
+ public Q_SLOTS:
+ void SetHtml(const QString &html);
+
+ protected:
+ void resizeEvent(QResizeEvent *e) override;
+ void wheelEvent(QWheelEvent *e) override;
+ QVariant loadResource(int type, const QUrl &name) override;
+
+ private:
+ int last_width_;
+ bool recursion_filter_;
+};
+
+#endif // INFOTEXTVIEW_H
diff --git a/src/widgets/prettyimage.cpp b/src/widgets/prettyimage.cpp
new file mode 100644
index 000000000..475f65beb
--- /dev/null
+++ b/src/widgets/prettyimage.cpp
@@ -0,0 +1,257 @@
+/*
+ * Strawberry Music Player
+ * This file was part of Clementine.
+ * Copyright 2010, David Sansome
+ *
+ * Strawberry 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.
+ *
+ * Strawberry 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 Strawberry. If not, see .
+ *
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "core/logging.h"
+#include "core/networkaccessmanager.h"
+#include "core/iconloader.h"
+
+#include "prettyimage.h"
+
+const int PrettyImage::kTotalHeight = 200;
+const int PrettyImage::kReflectionHeight = 40;
+const int PrettyImage::kImageHeight = PrettyImage::kTotalHeight - PrettyImage::kReflectionHeight;
+
+const int PrettyImage::kMaxImageWidth = 300;
+
+const char *PrettyImage::kSettingsGroup = "PrettyImageView";
+
+PrettyImage::PrettyImage(const QUrl &url, QNetworkAccessManager *network, QWidget *parent)
+ : QWidget(parent),
+ network_(network),
+ state_(State_WaitingForLazyLoad),
+ url_(url),
+ menu_(nullptr) {
+
+ setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+
+ LazyLoad();
+
+}
+
+void PrettyImage::LazyLoad() {
+
+ if (state_ != State_WaitingForLazyLoad) return;
+
+ // Start fetching the image
+ QNetworkReply *reply = network_->get(QNetworkRequest(url_));
+ state_ = State_Fetching;
+ QObject::connect(reply, &QNetworkReply::finished, this, [this, reply]() { ImageFetched(reply); });
+
+}
+
+QSize PrettyImage::image_size() const {
+
+ if (state_ != State_Finished) return QSize(kImageHeight * 1.6, kImageHeight);
+
+ QSize ret = image_.size();
+ ret.scale(kMaxImageWidth, kImageHeight, Qt::KeepAspectRatio);
+ return ret;
+
+}
+
+QSize PrettyImage::sizeHint() const {
+ return QSize(image_size().width(), kTotalHeight);
+}
+
+void PrettyImage::ImageFetched(QNetworkReply *reply) {
+
+ reply->deleteLater();
+
+ QImage image = QImage::fromData(reply->readAll());
+ if (image.isNull()) {
+ qLog(Debug) << "Image failed to load" << reply->request().url() << reply->error();
+ deleteLater();
+ }
+ else {
+ state_ = State_CreatingThumbnail;
+ image_ = image;
+ (void)QtConcurrent::run([=]{ ImageScaled(image_.scaled(image_size(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); });
+ }
+
+}
+
+void PrettyImage::ImageScaled(QImage image) {
+
+ thumbnail_ = QPixmap::fromImage(image);
+ state_ = State_Finished;
+
+ updateGeometry();
+ update();
+ emit Loaded();
+
+}
+
+void PrettyImage::paintEvent(QPaintEvent*) {
+
+ // Draw at the bottom of our area
+ QRect image_rect(QPoint(0, 0), image_size());
+ image_rect.moveBottom(kImageHeight);
+
+ QPainter p(this);
+
+ // Draw the main image
+ DrawThumbnail(&p, image_rect);
+
+ // Draw the reflection
+ // Figure out where to draw it
+ QRect reflection_rect(image_rect);
+ reflection_rect.moveTop(image_rect.bottom());
+
+ // Create the reflected pixmap
+ QImage reflection(reflection_rect.size(), QImage::Format_ARGB32_Premultiplied);
+ reflection.fill(palette().color(QPalette::Base).rgba());
+ QPainter reflection_painter(&reflection);
+
+ // Set up the transformation
+ QTransform transform;
+ transform.scale(1.0, -1.0);
+ transform.translate(0.0, -reflection_rect.height());
+ reflection_painter.setTransform(transform);
+
+ QRect fade_rect(reflection.rect().bottomLeft() - QPoint(0, kReflectionHeight), reflection.rect().bottomRight());
+
+ // Draw the reflection into the buffer
+ DrawThumbnail(&reflection_painter, reflection.rect());
+
+ // Make it fade out towards the bottom
+ QLinearGradient fade_gradient(fade_rect.topLeft(), fade_rect.bottomLeft());
+ fade_gradient.setColorAt(0.0, QColor(0, 0, 0, 0));
+ fade_gradient.setColorAt(1.0, QColor(0, 0, 0, 128));
+
+ reflection_painter.setCompositionMode(QPainter::CompositionMode_DestinationIn);
+ reflection_painter.fillRect(fade_rect, fade_gradient);
+
+ reflection_painter.end();
+
+ // Draw the reflection on the image
+ p.drawImage(reflection_rect, reflection);
+
+}
+
+void PrettyImage::DrawThumbnail(QPainter *p, const QRect &rect) {
+
+ switch (state_) {
+ case State_WaitingForLazyLoad:
+ case State_Fetching:
+ case State_CreatingThumbnail:
+ p->setPen(palette().color(QPalette::Disabled, QPalette::Text));
+ p->drawText(rect, Qt::AlignHCenter | Qt::AlignBottom, tr("Loading..."));
+ break;
+
+ case State_Finished:
+ p->drawPixmap(rect, thumbnail_);
+ break;
+ }
+
+}
+
+void PrettyImage::contextMenuEvent(QContextMenuEvent *e) {
+
+ if (e->pos().y() >= kImageHeight) return;
+
+ if (!menu_) {
+ menu_ = new QMenu(this);
+ menu_->addAction(IconLoader::Load(QStringLiteral("zoom-in")), tr("Show fullsize..."), this, &PrettyImage::ShowFullsize);
+ menu_->addAction(IconLoader::Load(QStringLiteral("document-save")), tr("Save image") + QLatin1String("..."), this, &PrettyImage::SaveAs);
+ }
+
+ menu_->popup(e->globalPos());
+
+}
+
+void PrettyImage::ShowFullsize() {
+
+ // Create the window
+ QScrollArea *pwindow = new QScrollArea;
+ pwindow->setAttribute(Qt::WA_DeleteOnClose, true);
+ pwindow->setWindowTitle(tr("%1 image viewer").arg(QLatin1String("Strawberry")));
+
+ // Work out how large to make the window, based on the size of the screen
+#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
+ QScreen *screen = QWidget::screen();
+#else
+ QScreen *screen = (window() && window()->windowHandle() ? window()->windowHandle()->screen() : QGuiApplication::primaryScreen());
+#endif
+ if (screen) {
+ QRect desktop_rect(screen->availableGeometry());
+ QSize window_size(qMin(desktop_rect.width() - 20, image_.width()), qMin(desktop_rect.height() - 20, image_.height()));
+ pwindow->resize(window_size);
+ }
+
+ // Create the label that displays the image
+ QLabel *label = new QLabel(pwindow);
+ label->setPixmap(QPixmap::fromImage(image_));
+
+ // Show the label in the window
+ pwindow->setWidget(label);
+ pwindow->setFrameShape(QFrame::NoFrame);
+ pwindow->show();
+
+}
+
+void PrettyImage::SaveAs() {
+
+ QString filename = QFileInfo(url_.path()).fileName();
+
+ if (filename.isEmpty()) filename = QLatin1String("artwork.jpg");
+
+ QSettings s;
+ s.beginGroup(kSettingsGroup);
+ QString last_save_dir = s.value("last_save_dir", QDir::homePath()).toString();
+
+ QString path = last_save_dir.isEmpty() ? QDir::homePath() : last_save_dir;
+ QFileInfo path_info(path);
+ if (path_info.isDir()) {
+ path += QLatin1Char('/') + filename;
+ }
+ else {
+ path = path_info.path() + QLatin1Char('/') + filename;
+ }
+
+ filename = QFileDialog::getSaveFileName(this, tr("Save image"), path);
+ if (filename.isEmpty()) return;
+
+ image_.save(filename);
+
+ s.setValue("last_save_dir", last_save_dir);
+
+ s.endGroup();
+
+}
diff --git a/src/widgets/prettyimage.h b/src/widgets/prettyimage.h
new file mode 100644
index 000000000..ee0f4d068
--- /dev/null
+++ b/src/widgets/prettyimage.h
@@ -0,0 +1,92 @@
+/*
+ * Strawberry Music Player
+ * This file was part of Clementine.
+ * Copyright 2010, David Sansome
+ *
+ * Strawberry 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.
+ *
+ * Strawberry 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 Strawberry. If not, see .
+ *
+ */
+
+#ifndef PRETTYIMAGE_H
+#define PRETTYIMAGE_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+class QMenu;
+class QNetworkAccessManager;
+class QNetworkReply;
+class QContextMenuEvent;
+class QPaintEvent;
+
+class PrettyImage : public QWidget {
+ Q_OBJECT
+
+ public:
+ PrettyImage(const QUrl &url, QNetworkAccessManager *network, QWidget *parent = nullptr);
+
+ static const int kTotalHeight;
+ static const int kReflectionHeight;
+ static const int kImageHeight;
+
+ static const int kMaxImageWidth;
+
+ static const char *kSettingsGroup;
+
+ QSize sizeHint() const override;
+ QSize image_size() const;
+
+signals:
+ void Loaded();
+
+ public slots:
+ void LazyLoad();
+ void SaveAs();
+ void ShowFullsize();
+
+ protected:
+ void contextMenuEvent(QContextMenuEvent*) override;
+ void paintEvent(QPaintEvent*) override;
+
+ private slots:
+ void ImageFetched(QNetworkReply *reply);
+ void ImageScaled(QImage image);
+
+ private:
+ enum State {
+ State_WaitingForLazyLoad,
+ State_Fetching,
+ State_CreatingThumbnail,
+ State_Finished,
+ };
+
+ void DrawThumbnail(QPainter *p, const QRect &rect);
+
+ private:
+ QNetworkAccessManager *network_;
+ State state_;
+ QUrl url_;
+
+ QImage image_;
+ QPixmap thumbnail_;
+
+ QMenu *menu_;
+ QString last_save_dir_;
+};
+
+#endif // PRETTYIMAGE_H
diff --git a/src/widgets/prettyimageview.cpp b/src/widgets/prettyimageview.cpp
new file mode 100644
index 000000000..e59e2139a
--- /dev/null
+++ b/src/widgets/prettyimageview.cpp
@@ -0,0 +1,189 @@
+/*
+ * Strawberry Music Player
+ * This file was part of Clementine.
+ * Copyright 2010, David Sansome
+ *
+ * Strawberry 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.
+ *
+ * Strawberry 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 Strawberry. If not, see .
+ *
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "core/networkaccessmanager.h"
+
+#include "prettyimage.h"
+#include "prettyimageview.h"
+
+PrettyImageView::PrettyImageView(QNetworkAccessManager *network, QWidget* parent)
+ : QScrollArea(parent),
+ network_(network),
+ container_(new QWidget(this)),
+ layout_(new QHBoxLayout(container_)),
+ current_index_(-1),
+ scroll_animation_(new QPropertyAnimation(horizontalScrollBar(), "value", this)),
+ recursion_filter_(false) {
+
+ setWidget(container_);
+ setWidgetResizable(true);
+ setMinimumHeight(PrettyImage::kTotalHeight + 10);
+ setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
+ setFrameShape(QFrame::NoFrame);
+ setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+
+ scroll_animation_->setDuration(250);
+ scroll_animation_->setEasingCurve(QEasingCurve::InOutCubic);
+ connect(horizontalScrollBar(), SIGNAL(sliderReleased()), SLOT(ScrollBarReleased()));
+ connect(horizontalScrollBar(), SIGNAL(actionTriggered(int)), SLOT(ScrollBarAction(int)));
+
+ layout_->setSizeConstraint(QLayout::SetMinAndMaxSize);
+ layout_->setContentsMargins(6, 6, 6, 6);
+ layout_->setSpacing(6);
+ layout_->addSpacing(200);
+ layout_->addSpacing(200);
+
+ container_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
+
+}
+
+bool PrettyImageView::eventFilter(QObject *obj, QEvent *event) {
+
+ // Work around infinite recursion in QScrollArea resizes.
+ if (recursion_filter_) {
+ return false;
+ }
+ recursion_filter_ = true;
+ bool ret = QScrollArea::eventFilter(obj, event);
+ recursion_filter_ = false;
+ return ret;
+
+}
+
+void PrettyImageView::AddImage(const QUrl &url) {
+
+ PrettyImage *image = new PrettyImage(url, network_, container_);
+ connect(image, SIGNAL(destroyed()), SLOT(ScrollToCurrent()));
+ connect(image, SIGNAL(Loaded()), SLOT(ScrollToCurrent()));
+
+ layout_->insertWidget(layout_->count() - 1, image);
+ if (current_index_ == -1) ScrollTo(0);
+
+}
+
+void PrettyImageView::mouseReleaseEvent(QMouseEvent *e) {
+
+ // Find the image that was clicked on
+ QWidget *widget = container_->childAt(container_->mapFrom(this, e->pos()));
+ if (!widget) return;
+
+ // Get the index of that image
+ const int index = layout_->indexOf(widget) - 1;
+ if (index == -1) return;
+
+ if (index == current_index_) {
+ // Show the image fullsize
+ PrettyImage* pretty_image = qobject_cast(widget);
+ if (pretty_image) {
+ pretty_image->ShowFullsize();
+ }
+ }
+ else {
+ // Scroll to the image
+ ScrollTo(index);
+ }
+
+}
+
+void PrettyImageView::ScrollTo(const int index, const bool smooth) {
+
+ current_index_ = qBound(0, index, layout_->count() - 3);
+ const int layout_index = current_index_ + 1;
+
+ const QWidget *target_widget = layout_->itemAt(layout_index)->widget();
+ if (!target_widget) return;
+
+ const int current_x = horizontalScrollBar()->value();
+ const int target_x = target_widget->geometry().center().x() - width() / 2;
+
+ if (current_x == target_x) return;
+
+ if (smooth) {
+ scroll_animation_->setStartValue(current_x);
+ scroll_animation_->setEndValue(target_x);
+ scroll_animation_->start();
+ }
+ else {
+ scroll_animation_->stop();
+ horizontalScrollBar()->setValue(target_x);
+ }
+
+}
+
+void PrettyImageView::ScrollToCurrent() { ScrollTo(current_index_); }
+
+void PrettyImageView::ScrollBarReleased() {
+
+ // Find the nearest widget to where the scroll bar was released
+ const int current_x = horizontalScrollBar()->value() + width() / 2;
+ int layout_index = 1;
+ for (; layout_index < layout_->count() - 1; ++layout_index) {
+ const QWidget *widget = layout_->itemAt(layout_index)->widget();
+ if (widget && widget->geometry().right() > current_x) {
+ break;
+ }
+ }
+
+ ScrollTo(layout_index - 1);
+
+}
+
+void PrettyImageView::ScrollBarAction(const int action) {
+
+ switch (action) {
+ case QAbstractSlider::SliderSingleStepAdd:
+ case QAbstractSlider::SliderPageStepAdd:
+ ScrollTo(current_index_ + 1);
+ break;
+
+ case QAbstractSlider::SliderSingleStepSub:
+ case QAbstractSlider::SliderPageStepSub:
+ ScrollTo(current_index_ - 1);
+ break;
+ }
+
+}
+
+void PrettyImageView::resizeEvent(QResizeEvent *e) {
+
+ QScrollArea::resizeEvent(e);
+ ScrollTo(current_index_, false);
+
+}
+
+void PrettyImageView::wheelEvent(QWheelEvent *e) {
+
+ const int d = e->angleDelta().x() > 0 ? -1 : 1;
+ ScrollTo(current_index_ + d, true);
+
+}
diff --git a/src/widgets/prettyimageview.h b/src/widgets/prettyimageview.h
new file mode 100644
index 000000000..fe4b20eba
--- /dev/null
+++ b/src/widgets/prettyimageview.h
@@ -0,0 +1,74 @@
+/*
+ * Strawberry Music Player
+ * This file was part of Clementine.
+ * Copyright 2010, David Sansome
+ *
+ * Strawberry 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.
+ *
+ * Strawberry 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 Strawberry. If not, see .
+ *
+ */
+
+#ifndef PRETTYIMAGEVIEW_H
+#define PRETTYIMAGEVIEW_H
+
+#include
+#include
+#include
+
+class QNetworkAccessManager;
+class QNetworkReply;
+class QMenu;
+class QHBoxLayout;
+class QPropertyAnimation;
+class QTimeLine;
+class QMouseEvent;
+class QResizeEvent;
+class QWheelEvent;
+
+class PrettyImageView : public QScrollArea {
+ Q_OBJECT
+
+ public:
+ PrettyImageView(QNetworkAccessManager *network, QWidget *parent = nullptr);
+
+ static const char* kSettingsGroup;
+
+ public Q_SLOTS:
+ void AddImage(const QUrl& url);
+
+ protected:
+ void mouseReleaseEvent(QMouseEvent*) override;
+ void resizeEvent(QResizeEvent *e) override;
+ void wheelEvent(QWheelEvent *e) override;
+
+ private Q_SLOTS:
+ void ScrollBarReleased();
+ void ScrollBarAction(const int action);
+ void ScrollTo(const int index, const bool smooth = true);
+ void ScrollToCurrent();
+
+ private:
+ bool eventFilter(QObject*, QEvent*) override;
+
+ QNetworkAccessManager *network_;
+
+ QWidget *container_;
+ QHBoxLayout *layout_;
+
+ int current_index_;
+ QPropertyAnimation *scroll_animation_;
+
+ bool recursion_filter_;
+};
+
+#endif // PRETTYIMAGEVIEW_H
diff --git a/src/widgets/widgetfadehelper.cpp b/src/widgets/widgetfadehelper.cpp
new file mode 100644
index 000000000..cc55b09e8
--- /dev/null
+++ b/src/widgets/widgetfadehelper.cpp
@@ -0,0 +1,187 @@
+/*
+ * Strawberry Music Player
+ * This file was part of Clementine.
+ * Copyright 2010, David Sansome
+ *
+ * Strawberry 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.
+ *
+ * Strawberry 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 Strawberry. If not, see .
+ *
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "widgetfadehelper.h"
+#include "core/qt_blurimage.h"
+
+const int WidgetFadeHelper::kLoadingPadding = 9;
+const int WidgetFadeHelper::kLoadingBorderRadius = 10;
+
+WidgetFadeHelper::WidgetFadeHelper(QWidget *parent, const int msec)
+ : QWidget(parent),
+ parent_(parent),
+ blur_timeline_(new QTimeLine(msec, this)),
+ fade_timeline_(new QTimeLine(msec, this)) {
+
+ parent->installEventFilter(this);
+
+ connect(blur_timeline_, SIGNAL(valueChanged(qreal)), SLOT(update()));
+ connect(fade_timeline_, SIGNAL(valueChanged(qreal)), SLOT(update()));
+ connect(fade_timeline_, SIGNAL(finished()), SLOT(FadeFinished()));
+
+ hide();
+
+}
+
+bool WidgetFadeHelper::eventFilter(QObject *obj, QEvent *event) {
+
+ // We're only interested in our parent's resize events
+ if (obj != parent_ || event->type() != QEvent::Resize) return false;
+
+ // Don't care if we're hidden
+ if (!isVisible()) return false;
+
+ QResizeEvent *re = static_cast(event);
+ if (re->oldSize() == re->size()) {
+ // Ignore phoney resize events
+ return false;
+ }
+
+ // Get a new capture of the parent
+ hide();
+ CaptureParent();
+ show();
+ return false;
+
+}
+
+void WidgetFadeHelper::StartBlur() {
+
+ CaptureParent();
+
+ // Cover the parent
+ raise();
+ show();
+
+ // Start the timeline
+ blur_timeline_->stop();
+ blur_timeline_->start();
+
+ setAttribute(Qt::WA_TransparentForMouseEvents, false);
+
+}
+
+void WidgetFadeHelper::CaptureParent() {
+
+ // Take a "screenshot" of the window
+ original_pixmap_ = parent_->grab();
+ QImage original_image = original_pixmap_.toImage();
+
+ // Blur it
+ QImage blurred(original_image.size(), QImage::Format_ARGB32_Premultiplied);
+ blurred.fill(Qt::transparent);
+
+ QPainter blur_painter(&blurred);
+ blur_painter.save();
+ qt_blurImage(&blur_painter, original_image, 10.0, true, false);
+ blur_painter.restore();
+
+ // Draw some loading text over the top
+ QFont loading_font(font());
+ loading_font.setBold(true);
+ QFontMetrics loading_font_metrics(loading_font);
+
+ const QString loading_text = tr("Loading...");
+#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
+ const QSize loading_size(kLoadingPadding * 2 + loading_font_metrics.horizontalAdvance(loading_text), kLoadingPadding * 2 + loading_font_metrics.height());
+#else
+ const QSize loading_size(kLoadingPadding * 2 + loading_font_metrics.width(loading_text), kLoadingPadding * 2 + loading_font_metrics.height());
+#endif
+ const QRect loading_rect((blurred.width() - loading_size.width()) / 2, 100, loading_size.width(), loading_size.height());
+
+ blur_painter.setRenderHint(QPainter::Antialiasing);
+
+ blur_painter.translate(0.5, 0.5);
+ blur_painter.setPen(QColor(200, 200, 200, 255));
+ blur_painter.setBrush(QColor(200, 200, 200, 192));
+ blur_painter.drawRoundedRect(loading_rect, kLoadingBorderRadius, kLoadingBorderRadius);
+
+ blur_painter.setPen(palette().brush(QPalette::Text).color());
+ blur_painter.setFont(loading_font);
+ blur_painter.drawText(loading_rect.translated(-1, -1), Qt::AlignCenter, loading_text);
+ blur_painter.translate(-0.5, -0.5);
+
+ blur_painter.end();
+
+ blurred_pixmap_ = QPixmap::fromImage(blurred);
+
+ resize(parent_->size());
+
+}
+
+void WidgetFadeHelper::StartFade() {
+
+ if (blur_timeline_->state() == QTimeLine::Running) {
+ // Blur timeline is still running, so we need render the current state
+ // into a new pixmap.
+ QPixmap pixmap(original_pixmap_);
+ QPainter painter(&pixmap);
+ painter.setOpacity(blur_timeline_->currentValue());
+ painter.drawPixmap(0, 0, blurred_pixmap_);
+ painter.end();
+ blurred_pixmap_ = pixmap;
+ }
+ blur_timeline_->stop();
+ original_pixmap_ = QPixmap();
+
+ // Start the timeline
+ fade_timeline_->stop();
+ fade_timeline_->start();
+
+ setAttribute(Qt::WA_TransparentForMouseEvents, true);
+
+}
+
+void WidgetFadeHelper::paintEvent(QPaintEvent *event) {
+
+ Q_UNUSED(event)
+
+ QPainter p(this);
+
+ if (fade_timeline_->state() != QTimeLine::Running) {
+ // We're fading in the blur
+ p.drawPixmap(0, 0, original_pixmap_);
+ p.setOpacity(blur_timeline_->currentValue());
+ }
+ else {
+ // Fading out the blur into the new image
+ p.setOpacity(1.0 - fade_timeline_->currentValue());
+ }
+
+ p.drawPixmap(0, 0, blurred_pixmap_);
+
+}
+
+void WidgetFadeHelper::FadeFinished() {
+
+ hide();
+ original_pixmap_ = QPixmap();
+ blurred_pixmap_ = QPixmap();
+
+}
diff --git a/src/widgets/widgetfadehelper.h b/src/widgets/widgetfadehelper.h
new file mode 100644
index 000000000..ec3d01341
--- /dev/null
+++ b/src/widgets/widgetfadehelper.h
@@ -0,0 +1,63 @@
+/*
+ * Strawberry Music Player
+ * This file was part of Clementine.
+ * Copyright 2010, David Sansome
+ *
+ * Strawberry 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.
+ *
+ * Strawberry 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 Strawberry. If not, see .
+ *
+ */
+
+#ifndef WIDGETFADEHELPER_H
+#define WIDGETFADEHELPER_H
+
+#include
+#include
+
+class QTimeLine;
+class QPaintEvent;
+class QEvent;
+
+class WidgetFadeHelper : public QWidget {
+ Q_OBJECT
+
+ public:
+ WidgetFadeHelper(QWidget *parent, const int msec = 500);
+
+ public Q_SLOTS:
+ void StartBlur();
+ void StartFade();
+
+ protected:
+ void paintEvent(QPaintEvent *event) override;
+ bool eventFilter(QObject *obj, QEvent *event) override;
+
+ private Q_SLOTS:
+ void FadeFinished();
+
+ private:
+ void CaptureParent();
+
+ private:
+ static const int kLoadingPadding;
+ static const int kLoadingBorderRadius;
+
+ QWidget *parent_;
+ QTimeLine *blur_timeline_;
+ QTimeLine *fade_timeline_;
+
+ QPixmap original_pixmap_;
+ QPixmap blurred_pixmap_;
+};
+
+#endif // WIDGETFADEHELPER_H