diff --git a/data/songinfo.css b/data/songinfo.css
index f1b041daa..5ac196183 100644
--- a/data/songinfo.css
+++ b/data/songinfo.css
@@ -5,4 +5,3 @@ QScrollArea {
QTextEdit {
border: 0px;
}
-
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 1d12974b2..bc10b1639 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -174,6 +174,7 @@ set(SOURCES
widgets/nowplayingwidget.cpp
widgets/osd.cpp
widgets/osdpretty.cpp
+ widgets/prettyimage.cpp
widgets/prettyimageview.cpp
widgets/progressitemdelegate.cpp
widgets/sliderwidget.cpp
@@ -312,6 +313,7 @@ set(HEADERS
widgets/nowplayingwidget.h
widgets/osd.h
widgets/osdpretty.h
+ widgets/prettyimage.h
widgets/prettyimageview.h
widgets/progressitemdelegate.h
widgets/sliderwidget.h
diff --git a/src/songinfo/artistinfoview.cpp b/src/songinfo/artistinfoview.cpp
index 296c4973d..61b96cabd 100644
--- a/src/songinfo/artistinfoview.cpp
+++ b/src/songinfo/artistinfoview.cpp
@@ -52,7 +52,7 @@ ArtistInfoView::ArtistInfoView(NetworkAccessManager* network, QWidget *parent)
container_widget->setBackgroundRole(QPalette::Base);
container_->setSizeConstraint(QLayout::SetMinAndMaxSize);
container_->setContentsMargins(0, 0, 0, 0);
- container_->setSpacing(0);
+ container_->setSpacing(6);
scroll_area_->setWidget(container_widget);
scroll_area_->setWidgetResizable(true);
diff --git a/src/translations/ar.po b/src/translations/ar.po
index bd121ce9f..906d6c293 100644
--- a/src/translations/ar.po
+++ b/src/translations/ar.po
@@ -1403,9 +1403,6 @@ msgstr ""
msgid "Previous track"
msgstr ""
-msgid "Problem loading image"
-msgstr ""
-
msgid "Progress"
msgstr ""
diff --git a/src/translations/bg.po b/src/translations/bg.po
index ceb19806f..aedbde0a6 100644
--- a/src/translations/bg.po
+++ b/src/translations/bg.po
@@ -1404,9 +1404,6 @@ msgstr ""
msgid "Previous track"
msgstr ""
-msgid "Problem loading image"
-msgstr ""
-
msgid "Progress"
msgstr ""
diff --git a/src/translations/ca.po b/src/translations/ca.po
index faa14d479..ac6ddb400 100644
--- a/src/translations/ca.po
+++ b/src/translations/ca.po
@@ -1433,9 +1433,6 @@ msgstr "Previsualitza"
msgid "Previous track"
msgstr "Pista anterior"
-msgid "Problem loading image"
-msgstr ""
-
msgid "Progress"
msgstr "Progrés"
diff --git a/src/translations/cs.po b/src/translations/cs.po
index 9a2d78016..71f9605b9 100644
--- a/src/translations/cs.po
+++ b/src/translations/cs.po
@@ -1408,9 +1408,6 @@ msgstr "Náhled"
msgid "Previous track"
msgstr "Předchozí skladba"
-msgid "Problem loading image"
-msgstr ""
-
msgid "Progress"
msgstr "Průběh"
diff --git a/src/translations/da.po b/src/translations/da.po
index e88e8aa8d..1f9e997d1 100644
--- a/src/translations/da.po
+++ b/src/translations/da.po
@@ -1409,9 +1409,6 @@ msgstr ""
msgid "Previous track"
msgstr "Forrige spor"
-msgid "Problem loading image"
-msgstr ""
-
msgid "Progress"
msgstr ""
diff --git a/src/translations/de.po b/src/translations/de.po
index 1c4cff36f..952149f03 100644
--- a/src/translations/de.po
+++ b/src/translations/de.po
@@ -1434,9 +1434,6 @@ msgstr "Vorschau"
msgid "Previous track"
msgstr "Vorheriges Stück"
-msgid "Problem loading image"
-msgstr ""
-
msgid "Progress"
msgstr "Fortschritt"
diff --git a/src/translations/el.po b/src/translations/el.po
index ae9df7d42..d7e263688 100644
--- a/src/translations/el.po
+++ b/src/translations/el.po
@@ -1437,9 +1437,6 @@ msgstr "Προεπισκόπηση"
msgid "Previous track"
msgstr "Προηγούμενο κομμάτι"
-msgid "Problem loading image"
-msgstr ""
-
msgid "Progress"
msgstr "Πρόοδος"
diff --git a/src/translations/en_CA.po b/src/translations/en_CA.po
index 89134af5b..854a25e2a 100644
--- a/src/translations/en_CA.po
+++ b/src/translations/en_CA.po
@@ -1408,9 +1408,6 @@ msgstr ""
msgid "Previous track"
msgstr "Previous track"
-msgid "Problem loading image"
-msgstr ""
-
msgid "Progress"
msgstr "Progress"
diff --git a/src/translations/en_GB.po b/src/translations/en_GB.po
index ed266e6fe..dccaa9315 100644
--- a/src/translations/en_GB.po
+++ b/src/translations/en_GB.po
@@ -1405,9 +1405,6 @@ msgstr ""
msgid "Previous track"
msgstr "Previous track"
-msgid "Problem loading image"
-msgstr ""
-
msgid "Progress"
msgstr ""
diff --git a/src/translations/es.po b/src/translations/es.po
index 8e1308f92..4cab720b3 100644
--- a/src/translations/es.po
+++ b/src/translations/es.po
@@ -1438,9 +1438,6 @@ msgstr "Vista previa"
msgid "Previous track"
msgstr "Pista anterior"
-msgid "Problem loading image"
-msgstr ""
-
msgid "Progress"
msgstr "Progreso"
diff --git a/src/translations/fi.po b/src/translations/fi.po
index cc5602db2..02a0efaa2 100644
--- a/src/translations/fi.po
+++ b/src/translations/fi.po
@@ -1406,9 +1406,6 @@ msgstr "Esikatselu"
msgid "Previous track"
msgstr "Edellinen kappale"
-msgid "Problem loading image"
-msgstr ""
-
msgid "Progress"
msgstr ""
diff --git a/src/translations/fr.po b/src/translations/fr.po
index 4c5a61abe..92c029974 100644
--- a/src/translations/fr.po
+++ b/src/translations/fr.po
@@ -1442,9 +1442,6 @@ msgstr "Aperçu"
msgid "Previous track"
msgstr "Piste précédente"
-msgid "Problem loading image"
-msgstr ""
-
msgid "Progress"
msgstr "Progression"
diff --git a/src/translations/gl.po b/src/translations/gl.po
index 64f3a4c5f..d551c11d1 100644
--- a/src/translations/gl.po
+++ b/src/translations/gl.po
@@ -1410,9 +1410,6 @@ msgstr ""
msgid "Previous track"
msgstr ""
-msgid "Problem loading image"
-msgstr ""
-
msgid "Progress"
msgstr ""
diff --git a/src/translations/hu.po b/src/translations/hu.po
index 0a6c5241d..2bc4bbcd5 100644
--- a/src/translations/hu.po
+++ b/src/translations/hu.po
@@ -1431,9 +1431,6 @@ msgstr "Előnézet"
msgid "Previous track"
msgstr "Előző szám"
-msgid "Problem loading image"
-msgstr ""
-
msgid "Progress"
msgstr "Folyamat"
diff --git a/src/translations/it.po b/src/translations/it.po
index 222cef8b9..e6edd293d 100644
--- a/src/translations/it.po
+++ b/src/translations/it.po
@@ -1442,9 +1442,6 @@ msgstr "Anteprima"
msgid "Previous track"
msgstr "Traccia precedente"
-msgid "Problem loading image"
-msgstr ""
-
msgid "Progress"
msgstr "Avanzamento"
diff --git a/src/translations/kk.po b/src/translations/kk.po
index fe2d72b1d..88fa7aece 100644
--- a/src/translations/kk.po
+++ b/src/translations/kk.po
@@ -1405,9 +1405,6 @@ msgstr ""
msgid "Previous track"
msgstr ""
-msgid "Problem loading image"
-msgstr ""
-
msgid "Progress"
msgstr ""
diff --git a/src/translations/lt.po b/src/translations/lt.po
index de88bbdf2..41a9d2fbb 100644
--- a/src/translations/lt.po
+++ b/src/translations/lt.po
@@ -1404,9 +1404,6 @@ msgstr ""
msgid "Previous track"
msgstr ""
-msgid "Problem loading image"
-msgstr ""
-
msgid "Progress"
msgstr ""
diff --git a/src/translations/nb.po b/src/translations/nb.po
index 8af0f6f50..1e15b8436 100644
--- a/src/translations/nb.po
+++ b/src/translations/nb.po
@@ -1407,9 +1407,6 @@ msgstr ""
msgid "Previous track"
msgstr "Forrige spor"
-msgid "Problem loading image"
-msgstr ""
-
msgid "Progress"
msgstr ""
diff --git a/src/translations/nl.po b/src/translations/nl.po
index b5228d30d..44c51eebb 100644
--- a/src/translations/nl.po
+++ b/src/translations/nl.po
@@ -1437,9 +1437,6 @@ msgstr "Voorbeeld"
msgid "Previous track"
msgstr "Vorige track"
-msgid "Problem loading image"
-msgstr ""
-
msgid "Progress"
msgstr "Voortgang"
diff --git a/src/translations/oc.po b/src/translations/oc.po
index 6a05f9421..5d8f0e31c 100644
--- a/src/translations/oc.po
+++ b/src/translations/oc.po
@@ -1403,9 +1403,6 @@ msgstr ""
msgid "Previous track"
msgstr "Pista precedenta"
-msgid "Problem loading image"
-msgstr ""
-
msgid "Progress"
msgstr "Progression"
diff --git a/src/translations/pl.po b/src/translations/pl.po
index abed9ce78..657f859fc 100644
--- a/src/translations/pl.po
+++ b/src/translations/pl.po
@@ -1433,9 +1433,6 @@ msgstr "Podgląd"
msgid "Previous track"
msgstr "Poprzedni utwór"
-msgid "Problem loading image"
-msgstr ""
-
msgid "Progress"
msgstr "Postęp"
diff --git a/src/translations/pt.po b/src/translations/pt.po
index 3e70392ab..bb80c7a84 100644
--- a/src/translations/pt.po
+++ b/src/translations/pt.po
@@ -1434,9 +1434,6 @@ msgstr "Antevisão"
msgid "Previous track"
msgstr "Faixa anterior"
-msgid "Problem loading image"
-msgstr ""
-
msgid "Progress"
msgstr "Evolução"
diff --git a/src/translations/pt_BR.po b/src/translations/pt_BR.po
index e6aa10489..11676ac37 100644
--- a/src/translations/pt_BR.po
+++ b/src/translations/pt_BR.po
@@ -1422,9 +1422,6 @@ msgstr "Pré-visualização"
msgid "Previous track"
msgstr "Faixa anterior"
-msgid "Problem loading image"
-msgstr ""
-
msgid "Progress"
msgstr "Andamento"
diff --git a/src/translations/ro.po b/src/translations/ro.po
index e7a5654ad..b2fed7dc9 100644
--- a/src/translations/ro.po
+++ b/src/translations/ro.po
@@ -1404,9 +1404,6 @@ msgstr ""
msgid "Previous track"
msgstr "Piesa precedentă"
-msgid "Problem loading image"
-msgstr ""
-
msgid "Progress"
msgstr ""
diff --git a/src/translations/ru.po b/src/translations/ru.po
index f025d8519..57117afda 100644
--- a/src/translations/ru.po
+++ b/src/translations/ru.po
@@ -1426,9 +1426,6 @@ msgstr "Предпросмотр"
msgid "Previous track"
msgstr "Предыдущая композиция"
-msgid "Problem loading image"
-msgstr ""
-
msgid "Progress"
msgstr "Ход выполнения"
diff --git a/src/translations/sk.po b/src/translations/sk.po
index ae01a555b..b2864ef91 100644
--- a/src/translations/sk.po
+++ b/src/translations/sk.po
@@ -1427,9 +1427,6 @@ msgstr "Náhľad"
msgid "Previous track"
msgstr "Predchádzajúca skladba"
-msgid "Problem loading image"
-msgstr ""
-
msgid "Progress"
msgstr "Priebeh"
diff --git a/src/translations/sl.po b/src/translations/sl.po
index a1ed4fcc0..c11f76cb9 100644
--- a/src/translations/sl.po
+++ b/src/translations/sl.po
@@ -1427,9 +1427,6 @@ msgstr "Predogled"
msgid "Previous track"
msgstr "Predhodna skladba"
-msgid "Problem loading image"
-msgstr ""
-
msgid "Progress"
msgstr "Potek"
diff --git a/src/translations/sr.po b/src/translations/sr.po
index c5733027a..2eea65722 100644
--- a/src/translations/sr.po
+++ b/src/translations/sr.po
@@ -1409,9 +1409,6 @@ msgstr "Преглед"
msgid "Previous track"
msgstr "Претходна нумера"
-msgid "Problem loading image"
-msgstr ""
-
msgid "Progress"
msgstr "Напредак"
diff --git a/src/translations/sv.po b/src/translations/sv.po
index be0b3aed8..2dc483e0b 100644
--- a/src/translations/sv.po
+++ b/src/translations/sv.po
@@ -1413,9 +1413,6 @@ msgstr "Förhandsvisning"
msgid "Previous track"
msgstr "Föregående spår"
-msgid "Problem loading image"
-msgstr ""
-
msgid "Progress"
msgstr "Förlopp"
diff --git a/src/translations/tr.po b/src/translations/tr.po
index 3ccde7b7c..66e19efba 100644
--- a/src/translations/tr.po
+++ b/src/translations/tr.po
@@ -1430,9 +1430,6 @@ msgstr "Önizleme"
msgid "Previous track"
msgstr "Önceki parça"
-msgid "Problem loading image"
-msgstr ""
-
msgid "Progress"
msgstr "İlerleme"
diff --git a/src/translations/translations.pot b/src/translations/translations.pot
index ac7e6e03e..70f3b9ff1 100644
--- a/src/translations/translations.pot
+++ b/src/translations/translations.pot
@@ -1394,9 +1394,6 @@ msgstr ""
msgid "Previous track"
msgstr ""
-msgid "Problem loading image"
-msgstr ""
-
msgid "Progress"
msgstr ""
diff --git a/src/translations/uk.po b/src/translations/uk.po
index c50c64535..b8d62c1a4 100644
--- a/src/translations/uk.po
+++ b/src/translations/uk.po
@@ -1427,9 +1427,6 @@ msgstr "Перегляд"
msgid "Previous track"
msgstr "Попередня доріжка"
-msgid "Problem loading image"
-msgstr ""
-
msgid "Progress"
msgstr "Поступ"
diff --git a/src/translations/zh_CN.po b/src/translations/zh_CN.po
index b757a1707..10b6f73d8 100644
--- a/src/translations/zh_CN.po
+++ b/src/translations/zh_CN.po
@@ -1403,9 +1403,6 @@ msgstr ""
msgid "Previous track"
msgstr "上一音轨"
-msgid "Problem loading image"
-msgstr ""
-
msgid "Progress"
msgstr ""
diff --git a/src/translations/zh_TW.po b/src/translations/zh_TW.po
index 1ad1f5e04..f3f30f5da 100644
--- a/src/translations/zh_TW.po
+++ b/src/translations/zh_TW.po
@@ -1409,9 +1409,6 @@ msgstr "試聽"
msgid "Previous track"
msgstr "上一首歌曲"
-msgid "Problem loading image"
-msgstr ""
-
msgid "Progress"
msgstr "進展"
diff --git a/src/widgets/prettyimage.cpp b/src/widgets/prettyimage.cpp
new file mode 100644
index 000000000..5d4610398
--- /dev/null
+++ b/src/widgets/prettyimage.cpp
@@ -0,0 +1,214 @@
+/* This file is part of Clementine.
+
+ Clementine 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.
+
+ Clementine 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 Clementine. If not, see .
+*/
+
+#include "prettyimage.h"
+#include "core/networkaccessmanager.h"
+#include "ui/iconloader.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+const int PrettyImage::kTotalHeight = 200;
+const int PrettyImage::kReflectionHeight = 40;
+const int PrettyImage::kImageHeight = PrettyImage::kTotalHeight -
+ PrettyImage::kReflectionHeight;
+
+const int PrettyImage::kMaxImageWidth = 400;
+
+const char* PrettyImage::kSettingsGroup = "PrettyImageView";
+
+PrettyImage::PrettyImage(const QUrl& url, NetworkAccessManager* network, QWidget* parent)
+ : QWidget(parent),
+ network_(network),
+ state_(State_WaitingForLazyLoad),
+ url_(url),
+ menu_(NULL)
+{
+ setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+
+ LazyLoad();
+}
+
+void PrettyImage::LazyLoad() {
+ if (state_ != State_WaitingForLazyLoad)
+ return;
+
+ // Start fetching the image
+ network_->Get(url_, this, "ImageFetched", 0);
+ state_ = State_Loading;
+}
+
+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(quint64 id, QNetworkReply* reply) {
+ reply->deleteLater();
+
+ QImage image = QImage::fromData(reply->readAll());
+ if (image.isNull()) {
+ deleteLater();
+ } else {
+ state_ = State_Finished;
+ image_ = image;
+ thumbnail_ = QPixmap::fromImage(image_.scaledToHeight(
+ kImageHeight, Qt::SmoothTransformation));
+
+ updateGeometry();
+ update();
+ }
+}
+
+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_Loading:
+ 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("zoom-in"), tr("Show fullsize..."),
+ this, SLOT(ShowFullsize()));
+ menu_->addAction(IconLoader::Load("document-save"), tr("Save image") + "...",
+ this, SLOT(SaveAs()));
+ }
+
+ menu_->popup(e->globalPos());
+}
+
+void PrettyImage::ShowFullsize() {
+ // Work out how large to make the window, based on the size of the screen
+ QRect desktop_rect(QApplication::desktop()->availableGeometry(this));
+ QSize window_size(qMin(desktop_rect.width() - 20, image_.width() + 2),
+ qMin(desktop_rect.height() - 20, image_.height() + 2));
+
+ // Create the window
+ QScrollArea* window = new QScrollArea;
+ window->setAttribute(Qt::WA_DeleteOnClose, true);
+ window->setWindowTitle(tr("Clementine image viewer"));
+ window->resize(window_size);
+
+ // Create the label that displays the image
+ QLabel* label = new QLabel(window);
+ label->setPixmap(QPixmap::fromImage(image_));
+
+ // Show the label in the window
+ window->setWidget(label);
+ window->show();
+}
+
+void PrettyImage::SaveAs() {
+ QString filename = QFileInfo(url_.path()).fileName();
+
+ if (filename.isEmpty())
+ filename = "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 += "/" + filename;
+ } else {
+ path = path_info.path() + "/" + filename;
+ }
+
+ filename = QFileDialog::getSaveFileName(this, tr("Save image"), path);
+ if (filename.isEmpty())
+ return;
+
+ image_.save(filename);
+
+ s.setValue("last_save_dir", last_save_dir);
+}
diff --git a/src/widgets/prettyimage.h b/src/widgets/prettyimage.h
new file mode 100644
index 000000000..d9568a4e7
--- /dev/null
+++ b/src/widgets/prettyimage.h
@@ -0,0 +1,78 @@
+/* This file is part of Clementine.
+
+ Clementine 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.
+
+ Clementine 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 Clementine. If not, see .
+*/
+
+#ifndef PRETTYIMAGE_H
+#define PRETTYIMAGE_H
+
+#include
+#include
+
+class NetworkAccessManager;
+
+class QMenu;
+class QNetworkReply;
+
+class PrettyImage : public QWidget {
+ Q_OBJECT
+
+public:
+ PrettyImage(const QUrl& url, NetworkAccessManager* network, QWidget* parent = 0);
+
+ static const int kTotalHeight;
+ static const int kReflectionHeight;
+ static const int kImageHeight;
+
+ static const int kMaxImageWidth;
+
+ static const char* kSettingsGroup;
+
+ QSize sizeHint() const;
+ QSize image_size() const;
+
+public slots:
+ void LazyLoad();
+ void SaveAs();
+ void ShowFullsize();
+
+protected:
+ void contextMenuEvent(QContextMenuEvent*);
+ void paintEvent(QPaintEvent*);
+
+private slots:
+ void ImageFetched(quint64 id, QNetworkReply* reply);
+
+private:
+ enum State {
+ State_WaitingForLazyLoad,
+ State_Loading,
+ State_Finished,
+ };
+
+ void DrawThumbnail(QPainter* p, const QRect& rect);
+
+private:
+ NetworkAccessManager* 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
index ec3f9c3c5..97b68c4ba 100644
--- a/src/widgets/prettyimageview.cpp
+++ b/src/widgets/prettyimageview.cpp
@@ -14,345 +14,124 @@
along with Clementine. If not, see .
*/
+#include "prettyimage.h"
#include "prettyimageview.h"
-#include "core/networkaccessmanager.h"
-#include "ui/iconloader.h"
-#include
-#include
-#include
-#include
-#include
+#include
#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
+#include
+#include
+#include
#include
-const int PrettyImageView::kEdgeWidth = 50;
-const int PrettyImageView::kImageHeight = 160;
-const int PrettyImageView::kTotalHeight = 200;
-const int PrettyImageView::kBorderHeight = 5;
-const int PrettyImageView::kEdgePadding = 5;
-
-const int PrettyImageView::kBaseAnimationDuration = 500; // msec
-const int PrettyImageView::kArrowAnimationDuration = 250; // msec
-
-const char* PrettyImageView::kSettingsGroup = "PrettyImageView";
-
PrettyImageView::PrettyImageView(NetworkAccessManager* network, QWidget* parent)
- : QWidget(parent),
+ : QScrollArea(parent),
network_(network),
- next_image_request_id_(1),
- current_index_(0),
- left_timeline_(new QTimeLine(kArrowAnimationDuration, this)),
- right_timeline_(new QTimeLine(kArrowAnimationDuration, this)),
- menu_(NULL)
+ container_(new QWidget(this)),
+ layout_(new QHBoxLayout(container_)),
+ current_index_(-1),
+ scroll_animation_(new QPropertyAnimation(horizontalScrollBar(), "value", this))
{
- setMouseTracking(true);
- setMinimumHeight(kTotalHeight);
+ setWidget(container_);
+ setWidgetResizable(true);
+ setMinimumHeight(PrettyImage::kTotalHeight + 10);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
+ setFrameShape(QFrame::NoFrame);
+ setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
- connect(left_timeline_, SIGNAL(valueChanged(qreal)), SLOT(update()));
- connect(right_timeline_, SIGNAL(valueChanged(qreal)), SLOT(update()));
+ scroll_animation_->setDuration(250);
+ scroll_animation_->setEasingCurve(QEasingCurve::InOutCubic);
+ connect(horizontalScrollBar(), SIGNAL(sliderReleased()), SLOT(ScrollBarReleased()));
+ connect(horizontalScrollBar(), SIGNAL(actionTriggered(int)), SLOT(ScrollBarAction(int)));
- QSettings s;
- s.beginGroup(kSettingsGroup);
- last_save_dir_ = s.value("last_save_dir", QDir::homePath()).toString();
-}
+ layout_->setSizeConstraint(QLayout::SetMinAndMaxSize);
+ layout_->setContentsMargins(6, 6, 6, 6);
+ layout_->setSpacing(6);
+ layout_->addSpacing(200);
+ layout_->addSpacing(200);
-QRect PrettyImageView::left() const {
- return QRect(0, 0, kEdgeWidth, height());
-}
-
-QRect PrettyImageView::right() const {
- return QRect(width() - kEdgeWidth, 0, kEdgeWidth, height());
-}
-
-QRect PrettyImageView::middle() const {
- return QRect(kEdgeWidth + kEdgePadding, kBorderHeight,
- width() - (kEdgeWidth + kEdgePadding) * 2,
- kImageHeight - kBorderHeight);
-}
-
-void PrettyImageView::Clear() {
- images_.clear();
- image_requests_.clear();
- current_index_ = 0;
- update();
+ container_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
}
void PrettyImageView::AddImage(const QUrl& url) {
- images_ << Image(url);
-}
-
-void PrettyImageView::LazyLoadImage(int index) {
- if (index < 0 || index >= images_.count())
- return;
- Image* image = &images_[index];
- if (image->state_ != Image::WaitingForLazyLoad)
- return;
-
- const int id = next_image_request_id_ ++;
-
- // Start fetching the image
- network_->Get(image->url_, this, "ImageFetched", id);
- image_requests_[id] = index;
- image->state_ = Image::Loading;
-}
-
-void PrettyImageView::ImageFetched(quint64 id, QNetworkReply* reply) {
- reply->deleteLater();
-
- if (!image_requests_.contains(id))
- return;
-
- Image& data = images_[image_requests_.take(id)];
-
- QImage image = QImage::fromData(reply->readAll());
- if (image.isNull()) {
- data.state_ = Image::Failed;
- } else {
- data.SetImage(image);
- data.state_ = Image::Loaded;
- }
- update();
-}
-
-void PrettyImageView::paintEvent(QPaintEvent*) {
- QPainter p(this);
- p.setRenderHint(QPainter::Antialiasing, true);
-
- const int next_index = current_index_ + 1;
- const int prev_index = current_index_ - 1;
- const int image_width = width() - kEdgeWidth * 2;
-
- const qreal next_opacity = 0.5 + right_timeline_->currentValue() * 0.5;
- const qreal prev_opacity = 0.5 + left_timeline_->currentValue() * 0.5;
-
- const QRect current_rect(kEdgeWidth + kEdgePadding, kBorderHeight,
- image_width - kEdgePadding*2, kImageHeight - kBorderHeight);
- const QRect next_rect(width() - kEdgeWidth, kBorderHeight,
- image_width, kImageHeight - kBorderHeight);
- const QRect prev_rect(-image_width + kEdgeWidth, kBorderHeight,
- image_width, kImageHeight - kBorderHeight);
-
- // Start the images loading if they're not already
- LazyLoadImage(current_index_);
- LazyLoadImage(next_index);
- LazyLoadImage(prev_index);
-
- // Draw the images
- DrawImage(&p, current_rect, Qt::AlignHCenter, 1.0, current_index_);
- DrawImage(&p, next_rect, Qt::AlignLeft, next_opacity, next_index);
- DrawImage(&p, prev_rect, Qt::AlignRight, prev_opacity, prev_index);
-}
-
-void PrettyImageView::DrawImage(QPainter* p, const QRect& rect, Qt::Alignment align,
- qreal opacity, int image_index) {
- if (image_index < 0 || image_index >= images_.count())
- return;
- const Image& image = images_[image_index];
-
- QSize image_size = image.image_.isNull() ? QSize(160, 100) : image.image_.size();
-
- // Scale the image rect to fit in the rectangle
- image_size.scale(rect.size(), Qt::KeepAspectRatio);
-
- // Center the image in the rectangle and move it to the bottom
- QRect draw_rect(QPoint(0, 0), image_size);
- if (align & Qt::AlignLeft)
- draw_rect.moveBottomLeft(rect.bottomLeft());
- else if (align & Qt::AlignRight)
- draw_rect.moveBottomRight(rect.bottomRight());
- else if (align & Qt::AlignHCenter)
- draw_rect.moveBottomLeft(rect.bottomLeft() + QPoint((rect.width() - draw_rect.width()) / 2, 0));
-
- // Draw the main image
- p->setOpacity(opacity);
- DrawThumbnail(p, draw_rect, align, image);
-
- // Draw the reflection
- // Figure out where to draw it
- QRect reflection_rect(draw_rect);
- reflection_rect.moveTop(reflection_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, -draw_rect.height());
- reflection_painter.setTransform(transform);
-
- QRect fade_rect(reflection.rect().bottomLeft() - QPoint(0, kTotalHeight - kImageHeight),
- reflection.rect().bottomRight());
-
- // Draw the reflection into the buffer
- DrawThumbnail(&reflection_painter, reflection.rect(), align, image);
-
- // 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 PrettyImageView::DrawThumbnail(QPainter* p, const QRect& rect,
- Qt::Alignment align, const Image& image) {
- switch (image.state_) {
- case Image::WaitingForLazyLoad:
- case Image::Loading:
- p->setPen(palette().color(QPalette::Disabled, QPalette::Text));
- p->drawText(rect, align | Qt::AlignBottom, tr("Loading..."));
- break;
-
- case Image::Failed:
- p->setPen(palette().color(QPalette::Disabled, QPalette::Text));
- p->drawText(rect, align | Qt::AlignBottom, tr("Problem loading image"));
- break;
-
- case Image::Loaded:
- p->drawPixmap(rect, image.thumbnail_);
- break;
- }
+ layout_->insertWidget(layout_->count() - 1, new PrettyImage(url, network_, container_));
+ if (current_index_ == -1)
+ ScrollTo(0);
}
void PrettyImageView::mouseReleaseEvent(QMouseEvent* e) {
- if (left().contains(e->pos()))
- current_index_ = qMax(0, current_index_ - 1);
- else if (right().contains(e->pos()))
- current_index_ = qMin(images_.count() - 1, current_index_ + 1);
- else if (middle().contains(e->pos()))
- ShowFullsize();
-
- update();
-}
-
-void PrettyImageView::mouseMoveEvent(QMouseEvent* e) {
- const bool in_left = left().contains(e->pos());
- const bool in_right = right().contains(e->pos());
- const bool in_middle = middle().contains(e->pos());
-
- SetTimeLineActive(left_timeline_, in_left);
- SetTimeLineActive(right_timeline_, in_right);
-
- setCursor((in_left || in_right || in_middle) ?
- QCursor(Qt::PointingHandCursor) : QCursor());
-}
-
-void PrettyImageView::leaveEvent(QEvent*) {
- SetTimeLineActive(left_timeline_, false);
- SetTimeLineActive(right_timeline_, false);
-}
-
-void PrettyImageView::SetTimeLineActive(QTimeLine* timeline, bool active) {
- const QTimeLine::Direction direction =
- active ? QTimeLine::Forward : QTimeLine::Backward;
-
- if (timeline->state() == QTimeLine::Running && timeline->direction() == direction)
+ // Find the image that was clicked on
+ QWidget* widget = container_->childAt(container_->mapFrom(this, e->pos()));
+ if (!widget)
return;
- timeline->setDirection(direction);
-
- if (timeline->state() != QTimeLine::Running)
- timeline->resume();
-}
-
-void PrettyImageView::Image::SetImage(const QImage& image) {
- image_ = image;
- thumbnail_ = QPixmap::fromImage(image_.scaledToHeight(
- kImageHeight, Qt::SmoothTransformation));
-}
-
-void PrettyImageView::contextMenuEvent(QContextMenuEvent* e) {
- if (!middle().contains(e->pos()))
- return;
- if (current_index_ < 0 || current_index_ >= images_.count())
+ // Get the index of that image
+ const int index = layout_->indexOf(widget) - 1;
+ if (index == -1)
return;
- if (!menu_) {
- menu_ = new QMenu(this);
- menu_->addAction(IconLoader::Load("zoom-in"), tr("Show fullsize..."),
- this, SLOT(ShowFullsize()));
- menu_->addAction(IconLoader::Load("document-save"), tr("Save image") + "...",
- this, SLOT(SaveAs()));
- }
-
- menu_->popup(e->globalPos());
-}
-
-void PrettyImageView::ShowFullsize() {
- if (current_index_ < 0 || current_index_ >= images_.count())
- return;
-
- const Image& image_data = images_[current_index_];
- if (image_data.state_ != Image::Loaded)
- return;
-
- const QImage& image = image_data.image_;
-
- // Work out how large to make the window, based on the size of the screen
- QRect desktop_rect(QApplication::desktop()->availableGeometry(this));
- QSize window_size(qMin(desktop_rect.width() - 20, image.width() + 2),
- qMin(desktop_rect.height() - 20, image.height() + 2));
-
- // Create the window
- QScrollArea* window = new QScrollArea;
- window->setAttribute(Qt::WA_DeleteOnClose, true);
- window->setWindowTitle(tr("Clementine image viewer"));
- window->resize(window_size);
-
- // Create the label that displays the image
- QLabel* label = new QLabel(window);
- label->setPixmap(QPixmap::fromImage(image));
-
- // Show the label in the window
- window->setWidget(label);
- window->show();
-}
-
-void PrettyImageView::SaveAs() {
- if (current_index_ < 0 || current_index_ >= images_.count())
- return;
-
- const Image& image_data = images_[current_index_];
- QString filename = QFileInfo(image_data.url_.path()).fileName();
- QImage image = image_data.image_;
-
- if (filename.isEmpty())
- filename = "artwork.jpg";
-
- QString path = last_save_dir_.isEmpty() ? QDir::homePath() : last_save_dir_;
- QFileInfo path_info(path);
- if (path_info.isDir()) {
- path += "/" + filename;
+ if (index == current_index_) {
+ // Show the image fullsize
+ PrettyImage* pretty_image = qobject_cast(widget);
+ if (pretty_image) {
+ pretty_image->ShowFullsize();
+ }
} else {
- path = path_info.path() + "/" + filename;
+ // Scroll to the image
+ ScrollTo(index);
}
+}
- filename = QFileDialog::getSaveFileName(this, tr("Save image"), path);
- if (filename.isEmpty())
+void PrettyImageView::ScrollTo(int index, 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;
- image.save(filename);
+ const int current_x = horizontalScrollBar()->value();
+ const int target_x = target_widget->geometry().center().x() - width() / 2;
- last_save_dir_ = filename;
- QSettings s;
- s.beginGroup(kSettingsGroup);
- s.setValue("last_save_dir", last_save_dir_);
+ 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::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_indexcount() - 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(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);
}
diff --git a/src/widgets/prettyimageview.h b/src/widgets/prettyimageview.h
index 017d35008..771b4ea9c 100644
--- a/src/widgets/prettyimageview.h
+++ b/src/widgets/prettyimageview.h
@@ -18,96 +18,45 @@
#define PRETTYIMAGEVIEW_H
#include
+#include
#include
-#include
class NetworkAccessManager;
+class QHBoxLayout;
class QMenu;
class QNetworkReply;
+class QPropertyAnimation;
class QTimeLine;
-class PrettyImageView : public QWidget {
+class PrettyImageView : public QScrollArea {
Q_OBJECT
public:
PrettyImageView(NetworkAccessManager* network, QWidget* parent = 0);
- static const int kTotalHeight;
- static const int kImageHeight;
- static const int kBorderHeight;
- static const int kEdgeWidth;
- static const int kEdgePadding;
-
- static const int kBaseAnimationDuration;
- static const int kArrowAnimationDuration;
-
static const char* kSettingsGroup;
public slots:
- void Clear();
void AddImage(const QUrl& url);
protected:
- void paintEvent(QPaintEvent*);
- void mouseMoveEvent(QMouseEvent*);
void mouseReleaseEvent(QMouseEvent*);
- void contextMenuEvent(QContextMenuEvent*);
- void leaveEvent(QEvent*);
+ void resizeEvent(QResizeEvent* e);
private slots:
- void ShowFullsize();
- void SaveAs();
-
-private:
- struct Image {
- Image(const QUrl& url) : state_(WaitingForLazyLoad), url_(url) {}
-
- void SetImage(const QImage& image);
-
- enum State {
- WaitingForLazyLoad,
- Loading,
- Failed,
- Loaded,
- };
-
- State state_;
- QUrl url_;
- QImage image_;
- QPixmap thumbnail_;
- };
-
- QRect left() const;
- QRect right() const;
- QRect middle() const;
-
- void SetTimeLineActive(QTimeLine* timeline, bool active);
-
- void DrawImage(QPainter* p, const QRect& rect, Qt::Alignment align,
- qreal opacity, int image_index);
- void DrawThumbnail(QPainter* p, const QRect& rect, Qt::Alignment align,
- const Image& image);
-
- void LazyLoadImage(int index);
-
-private slots:
- void ImageFetched(quint64 id, QNetworkReply* reply);
+ void ScrollBarReleased();
+ void ScrollBarAction(int action);
+ void ScrollTo(int index, bool smooth = true);
private:
NetworkAccessManager* network_;
- QMap image_requests_;
- quint64 next_image_request_id_;
+ QWidget* container_;
+ QHBoxLayout* layout_;
- QList images_;
int current_index_;
-
- QTimeLine* left_timeline_;
- QTimeLine* right_timeline_;
-
- QMenu* menu_;
- QString last_save_dir_;
+ QPropertyAnimation* scroll_animation_;
};
#endif // PRETTYIMAGEVIEW_H