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 { QTextEdit {
border: 0px; border: 0px;
} }

View File

@ -174,6 +174,7 @@ set(SOURCES
widgets/nowplayingwidget.cpp widgets/nowplayingwidget.cpp
widgets/osd.cpp widgets/osd.cpp
widgets/osdpretty.cpp widgets/osdpretty.cpp
widgets/prettyimage.cpp
widgets/prettyimageview.cpp widgets/prettyimageview.cpp
widgets/progressitemdelegate.cpp widgets/progressitemdelegate.cpp
widgets/sliderwidget.cpp widgets/sliderwidget.cpp
@ -312,6 +313,7 @@ set(HEADERS
widgets/nowplayingwidget.h widgets/nowplayingwidget.h
widgets/osd.h widgets/osd.h
widgets/osdpretty.h widgets/osdpretty.h
widgets/prettyimage.h
widgets/prettyimageview.h widgets/prettyimageview.h
widgets/progressitemdelegate.h widgets/progressitemdelegate.h
widgets/sliderwidget.h widgets/sliderwidget.h

View File

@ -52,7 +52,7 @@ ArtistInfoView::ArtistInfoView(NetworkAccessManager* network, QWidget *parent)
container_widget->setBackgroundRole(QPalette::Base); container_widget->setBackgroundRole(QPalette::Base);
container_->setSizeConstraint(QLayout::SetMinAndMaxSize); container_->setSizeConstraint(QLayout::SetMinAndMaxSize);
container_->setContentsMargins(0, 0, 0, 0); container_->setContentsMargins(0, 0, 0, 0);
container_->setSpacing(0); container_->setSpacing(6);
scroll_area_->setWidget(container_widget); scroll_area_->setWidget(container_widget);
scroll_area_->setWidgetResizable(true); scroll_area_->setWidgetResizable(true);

View File

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

View File

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

View File

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

View File

@ -1408,9 +1408,6 @@ msgstr "Náhled"
msgid "Previous track" msgid "Previous track"
msgstr "Předchozí skladba" msgstr "Předchozí skladba"
msgid "Problem loading image"
msgstr ""
msgid "Progress" msgid "Progress"
msgstr "Průběh" msgstr "Průběh"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1431,9 +1431,6 @@ msgstr "Előnézet"
msgid "Previous track" msgid "Previous track"
msgstr "Előző szám" msgstr "Előző szám"
msgid "Problem loading image"
msgstr ""
msgid "Progress" msgid "Progress"
msgstr "Folyamat" msgstr "Folyamat"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1427,9 +1427,6 @@ msgstr "Náhľad"
msgid "Previous track" msgid "Previous track"
msgstr "Predchádzajúca skladba" msgstr "Predchádzajúca skladba"
msgid "Problem loading image"
msgstr ""
msgid "Progress" msgid "Progress"
msgstr "Priebeh" msgstr "Priebeh"

View File

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

View File

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

View File

@ -1413,9 +1413,6 @@ msgstr "Förhandsvisning"
msgid "Previous track" msgid "Previous track"
msgstr "Föregående spår" msgstr "Föregående spår"
msgid "Problem loading image"
msgstr ""
msgid "Progress" msgid "Progress"
msgstr "Förlopp" msgstr "Förlopp"

View File

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

View File

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

View File

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

View File

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

View File

@ -1409,9 +1409,6 @@ msgstr "試聽"
msgid "Previous track" msgid "Previous track"
msgstr "上一首歌曲" msgstr "上一首歌曲"
msgid "Problem loading image"
msgstr ""
msgid "Progress" msgid "Progress"
msgstr "進展" 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/>. along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "prettyimage.h"
#include "prettyimageview.h" #include "prettyimageview.h"
#include "core/networkaccessmanager.h"
#include "ui/iconloader.h"
#include <QApplication> #include <QHBoxLayout>
#include <QDesktopWidget>
#include <QFileDialog>
#include <QLabel>
#include <QMenu>
#include <QMouseEvent> #include <QMouseEvent>
#include <QNetworkReply> #include <QPropertyAnimation>
#include <QPainter> #include <QScrollBar>
#include <QScrollArea> #include <QTimer>
#include <QSettings>
#include <QStyle>
#include <QStyleOption>
#include <QTimeLine>
#include <QtDebug> #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) PrettyImageView::PrettyImageView(NetworkAccessManager* network, QWidget* parent)
: QWidget(parent), : QScrollArea(parent),
network_(network), network_(network),
next_image_request_id_(1), container_(new QWidget(this)),
current_index_(0), layout_(new QHBoxLayout(container_)),
left_timeline_(new QTimeLine(kArrowAnimationDuration, this)), current_index_(-1),
right_timeline_(new QTimeLine(kArrowAnimationDuration, this)), scroll_animation_(new QPropertyAnimation(horizontalScrollBar(), "value", this))
menu_(NULL)
{ {
setMouseTracking(true); setWidget(container_);
setMinimumHeight(kTotalHeight); setWidgetResizable(true);
setMinimumHeight(PrettyImage::kTotalHeight + 10);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
setFrameShape(QFrame::NoFrame);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
connect(left_timeline_, SIGNAL(valueChanged(qreal)), SLOT(update())); scroll_animation_->setDuration(250);
connect(right_timeline_, SIGNAL(valueChanged(qreal)), SLOT(update())); scroll_animation_->setEasingCurve(QEasingCurve::InOutCubic);
connect(horizontalScrollBar(), SIGNAL(sliderReleased()), SLOT(ScrollBarReleased()));
connect(horizontalScrollBar(), SIGNAL(actionTriggered(int)), SLOT(ScrollBarAction(int)));
QSettings s; layout_->setSizeConstraint(QLayout::SetMinAndMaxSize);
s.beginGroup(kSettingsGroup); layout_->setContentsMargins(6, 6, 6, 6);
last_save_dir_ = s.value("last_save_dir", QDir::homePath()).toString(); layout_->setSpacing(6);
} layout_->addSpacing(200);
layout_->addSpacing(200);
QRect PrettyImageView::left() const { container_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
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();
} }
void PrettyImageView::AddImage(const QUrl& url) { void PrettyImageView::AddImage(const QUrl& url) {
images_ << Image(url); layout_->insertWidget(layout_->count() - 1, new PrettyImage(url, network_, container_));
} if (current_index_ == -1)
ScrollTo(0);
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;
}
} }
void PrettyImageView::mouseReleaseEvent(QMouseEvent* e) { void PrettyImageView::mouseReleaseEvent(QMouseEvent* e) {
if (left().contains(e->pos())) // Find the image that was clicked on
current_index_ = qMax(0, current_index_ - 1); QWidget* widget = container_->childAt(container_->mapFrom(this, e->pos()));
else if (right().contains(e->pos())) if (!widget)
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)
return; return;
timeline->setDirection(direction); // Get the index of that image
const int index = layout_->indexOf(widget) - 1;
if (timeline->state() != QTimeLine::Running) if (index == -1)
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())
return; return;
if (!menu_) { if (index == current_index_) {
menu_ = new QMenu(this); // Show the image fullsize
menu_->addAction(IconLoader::Load("zoom-in"), tr("Show fullsize..."), PrettyImage* pretty_image = qobject_cast<PrettyImage*>(widget);
this, SLOT(ShowFullsize())); if (pretty_image) {
menu_->addAction(IconLoader::Load("document-save"), tr("Save image") + "...", pretty_image->ShowFullsize();
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;
} else { } else {
path = path_info.path() + "/" + filename; // Scroll to the image
ScrollTo(index);
} }
}
filename = QFileDialog::getSaveFileName(this, tr("Save image"), path); void PrettyImageView::ScrollTo(int index, bool smooth) {
if (filename.isEmpty()) 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; 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; if (smooth) {
QSettings s; scroll_animation_->setStartValue(current_x);
s.beginGroup(kSettingsGroup); scroll_animation_->setEndValue(target_x);
s.setValue("last_save_dir", last_save_dir_); 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 #define PRETTYIMAGEVIEW_H
#include <QMap> #include <QMap>
#include <QScrollArea>
#include <QUrl> #include <QUrl>
#include <QWidget>
class NetworkAccessManager; class NetworkAccessManager;
class QHBoxLayout;
class QMenu; class QMenu;
class QNetworkReply; class QNetworkReply;
class QPropertyAnimation;
class QTimeLine; class QTimeLine;
class PrettyImageView : public QWidget { class PrettyImageView : public QScrollArea {
Q_OBJECT Q_OBJECT
public: public:
PrettyImageView(NetworkAccessManager* network, QWidget* parent = 0); 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; static const char* kSettingsGroup;
public slots: public slots:
void Clear();
void AddImage(const QUrl& url); void AddImage(const QUrl& url);
protected: protected:
void paintEvent(QPaintEvent*);
void mouseMoveEvent(QMouseEvent*);
void mouseReleaseEvent(QMouseEvent*); void mouseReleaseEvent(QMouseEvent*);
void contextMenuEvent(QContextMenuEvent*); void resizeEvent(QResizeEvent* e);
void leaveEvent(QEvent*);
private slots: private slots:
void ShowFullsize(); void ScrollBarReleased();
void SaveAs(); void ScrollBarAction(int action);
void ScrollTo(int index, bool smooth = true);
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);
private: private:
NetworkAccessManager* network_; NetworkAccessManager* network_;
QMap<quint64, int> image_requests_; QWidget* container_;
quint64 next_image_request_id_; QHBoxLayout* layout_;
QList<Image> images_;
int current_index_; int current_index_;
QPropertyAnimation* scroll_animation_;
QTimeLine* left_timeline_;
QTimeLine* right_timeline_;
QMenu* menu_;
QString last_save_dir_;
}; };
#endif // PRETTYIMAGEVIEW_H #endif // PRETTYIMAGEVIEW_H