Make the pretty image view even prettier

This commit is contained in:
David Sansome 2010-10-09 16:07:20 +00:00
parent 58f455ec15
commit fb2accea1a
41 changed files with 398 additions and 479 deletions

View File

@ -5,4 +5,3 @@ QScrollArea {
QTextEdit {
border: 0px;
}

View File

@ -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

View File

@ -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);

View File

@ -1403,9 +1403,6 @@ msgstr ""
msgid "Previous track"
msgstr ""
msgid "Problem loading image"
msgstr ""
msgid "Progress"
msgstr ""

View File

@ -1404,9 +1404,6 @@ msgstr ""
msgid "Previous track"
msgstr ""
msgid "Problem loading image"
msgstr ""
msgid "Progress"
msgstr ""

View File

@ -1433,9 +1433,6 @@ msgstr "Previsualitza"
msgid "Previous track"
msgstr "Pista anterior"
msgid "Problem loading image"
msgstr ""
msgid "Progress"
msgstr "Progrés"

View File

@ -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"

View File

@ -1409,9 +1409,6 @@ msgstr ""
msgid "Previous track"
msgstr "Forrige spor"
msgid "Problem loading image"
msgstr ""
msgid "Progress"
msgstr ""

View File

@ -1434,9 +1434,6 @@ msgstr "Vorschau"
msgid "Previous track"
msgstr "Vorheriges Stück"
msgid "Problem loading image"
msgstr ""
msgid "Progress"
msgstr "Fortschritt"

View File

@ -1437,9 +1437,6 @@ msgstr "Προεπισκόπηση"
msgid "Previous track"
msgstr "Προηγούμενο κομμάτι"
msgid "Problem loading image"
msgstr ""
msgid "Progress"
msgstr "Πρόοδος"

View File

@ -1408,9 +1408,6 @@ msgstr ""
msgid "Previous track"
msgstr "Previous track"
msgid "Problem loading image"
msgstr ""
msgid "Progress"
msgstr "Progress"

View File

@ -1405,9 +1405,6 @@ msgstr ""
msgid "Previous track"
msgstr "Previous track"
msgid "Problem loading image"
msgstr ""
msgid "Progress"
msgstr ""

View File

@ -1438,9 +1438,6 @@ msgstr "Vista previa"
msgid "Previous track"
msgstr "Pista anterior"
msgid "Problem loading image"
msgstr ""
msgid "Progress"
msgstr "Progreso"

View File

@ -1406,9 +1406,6 @@ msgstr "Esikatselu"
msgid "Previous track"
msgstr "Edellinen kappale"
msgid "Problem loading image"
msgstr ""
msgid "Progress"
msgstr ""

View File

@ -1442,9 +1442,6 @@ msgstr "Aperçu"
msgid "Previous track"
msgstr "Piste précédente"
msgid "Problem loading image"
msgstr ""
msgid "Progress"
msgstr "Progression"

View File

@ -1410,9 +1410,6 @@ msgstr ""
msgid "Previous track"
msgstr ""
msgid "Problem loading image"
msgstr ""
msgid "Progress"
msgstr ""

View File

@ -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"

View File

@ -1442,9 +1442,6 @@ msgstr "Anteprima"
msgid "Previous track"
msgstr "Traccia precedente"
msgid "Problem loading image"
msgstr ""
msgid "Progress"
msgstr "Avanzamento"

View File

@ -1405,9 +1405,6 @@ msgstr ""
msgid "Previous track"
msgstr ""
msgid "Problem loading image"
msgstr ""
msgid "Progress"
msgstr ""

View File

@ -1404,9 +1404,6 @@ msgstr ""
msgid "Previous track"
msgstr ""
msgid "Problem loading image"
msgstr ""
msgid "Progress"
msgstr ""

View File

@ -1407,9 +1407,6 @@ msgstr ""
msgid "Previous track"
msgstr "Forrige spor"
msgid "Problem loading image"
msgstr ""
msgid "Progress"
msgstr ""

View File

@ -1437,9 +1437,6 @@ msgstr "Voorbeeld"
msgid "Previous track"
msgstr "Vorige track"
msgid "Problem loading image"
msgstr ""
msgid "Progress"
msgstr "Voortgang"

View File

@ -1403,9 +1403,6 @@ msgstr ""
msgid "Previous track"
msgstr "Pista precedenta"
msgid "Problem loading image"
msgstr ""
msgid "Progress"
msgstr "Progression"

View File

@ -1433,9 +1433,6 @@ msgstr "Podgląd"
msgid "Previous track"
msgstr "Poprzedni utwór"
msgid "Problem loading image"
msgstr ""
msgid "Progress"
msgstr "Postęp"

View File

@ -1434,9 +1434,6 @@ msgstr "Antevisão"
msgid "Previous track"
msgstr "Faixa anterior"
msgid "Problem loading image"
msgstr ""
msgid "Progress"
msgstr "Evolução"

View File

@ -1422,9 +1422,6 @@ msgstr "Pré-visualização"
msgid "Previous track"
msgstr "Faixa anterior"
msgid "Problem loading image"
msgstr ""
msgid "Progress"
msgstr "Andamento"

View File

@ -1404,9 +1404,6 @@ msgstr ""
msgid "Previous track"
msgstr "Piesa precedentă"
msgid "Problem loading image"
msgstr ""
msgid "Progress"
msgstr ""

View File

@ -1426,9 +1426,6 @@ msgstr "Предпросмотр"
msgid "Previous track"
msgstr "Предыдущая композиция"
msgid "Problem loading image"
msgstr ""
msgid "Progress"
msgstr "Ход выполнения"

View File

@ -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"

View File

@ -1427,9 +1427,6 @@ msgstr "Predogled"
msgid "Previous track"
msgstr "Predhodna skladba"
msgid "Problem loading image"
msgstr ""
msgid "Progress"
msgstr "Potek"

View File

@ -1409,9 +1409,6 @@ msgstr "Преглед"
msgid "Previous track"
msgstr "Претходна нумера"
msgid "Problem loading image"
msgstr ""
msgid "Progress"
msgstr "Напредак"

View File

@ -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"

View File

@ -1430,9 +1430,6 @@ msgstr "Önizleme"
msgid "Previous track"
msgstr "Önceki parça"
msgid "Problem loading image"
msgstr ""
msgid "Progress"
msgstr "İlerleme"

View File

@ -1394,9 +1394,6 @@ msgstr ""
msgid "Previous track"
msgstr ""
msgid "Problem loading image"
msgstr ""
msgid "Progress"
msgstr ""

View File

@ -1427,9 +1427,6 @@ msgstr "Перегляд"
msgid "Previous track"
msgstr "Попередня доріжка"
msgid "Problem loading image"
msgstr ""
msgid "Progress"
msgstr "Поступ"

View File

@ -1403,9 +1403,6 @@ msgstr ""
msgid "Previous track"
msgstr "上一音轨"
msgid "Problem loading image"
msgstr ""
msgid "Progress"
msgstr ""

View File

@ -1409,9 +1409,6 @@ msgstr "試聽"
msgid "Previous track"
msgstr "上一首歌曲"
msgid "Problem loading image"
msgstr ""
msgid "Progress"
msgstr "進展"

214
src/widgets/prettyimage.cpp Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "prettyimage.h"
#include "core/networkaccessmanager.h"
#include "ui/iconloader.h"
#include <QApplication>
#include <QContextMenuEvent>
#include <QDesktopWidget>
#include <QDir>
#include <QFileDialog>
#include <QLabel>
#include <QMenu>
#include <QNetworkReply>
#include <QPainter>
#include <QScrollArea>
#include <QSettings>
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);
}

78
src/widgets/prettyimage.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef PRETTYIMAGE_H
#define PRETTYIMAGE_H
#include <QUrl>
#include <QWidget>
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

View File

@ -14,345 +14,124 @@
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include "prettyimage.h"
#include "prettyimageview.h"
#include "core/networkaccessmanager.h"
#include "ui/iconloader.h"
#include <QApplication>
#include <QDesktopWidget>
#include <QFileDialog>
#include <QLabel>
#include <QMenu>
#include <QHBoxLayout>
#include <QMouseEvent>
#include <QNetworkReply>
#include <QPainter>
#include <QScrollArea>
#include <QSettings>
#include <QStyle>
#include <QStyleOption>
#include <QTimeLine>
#include <QPropertyAnimation>
#include <QScrollBar>
#include <QTimer>
#include <QtDebug>
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<PrettyImage*>(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_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(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);
}

View File

@ -18,96 +18,45 @@
#define PRETTYIMAGEVIEW_H
#include <QMap>
#include <QScrollArea>
#include <QUrl>
#include <QWidget>
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<quint64, int> image_requests_;
quint64 next_image_request_id_;
QWidget* container_;
QHBoxLayout* layout_;
QList<Image> images_;
int current_index_;
QTimeLine* left_timeline_;
QTimeLine* right_timeline_;
QMenu* menu_;
QString last_save_dir_;
QPropertyAnimation* scroll_animation_;
};
#endif // PRETTYIMAGEVIEW_H