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