Make the pretty image view even prettier
This commit is contained in:
parent
58f455ec15
commit
fb2accea1a
@ -5,4 +5,3 @@ QScrollArea {
|
||||
QTextEdit {
|
||||
border: 0px;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -1403,9 +1403,6 @@ msgstr ""
|
||||
msgid "Previous track"
|
||||
msgstr ""
|
||||
|
||||
msgid "Problem loading image"
|
||||
msgstr ""
|
||||
|
||||
msgid "Progress"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1404,9 +1404,6 @@ msgstr ""
|
||||
msgid "Previous track"
|
||||
msgstr ""
|
||||
|
||||
msgid "Problem loading image"
|
||||
msgstr ""
|
||||
|
||||
msgid "Progress"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1433,9 +1433,6 @@ msgstr "Previsualitza"
|
||||
msgid "Previous track"
|
||||
msgstr "Pista anterior"
|
||||
|
||||
msgid "Problem loading image"
|
||||
msgstr ""
|
||||
|
||||
msgid "Progress"
|
||||
msgstr "Progrés"
|
||||
|
||||
|
@ -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"
|
||||
|
||||
|
@ -1409,9 +1409,6 @@ msgstr ""
|
||||
msgid "Previous track"
|
||||
msgstr "Forrige spor"
|
||||
|
||||
msgid "Problem loading image"
|
||||
msgstr ""
|
||||
|
||||
msgid "Progress"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1434,9 +1434,6 @@ msgstr "Vorschau"
|
||||
msgid "Previous track"
|
||||
msgstr "Vorheriges Stück"
|
||||
|
||||
msgid "Problem loading image"
|
||||
msgstr ""
|
||||
|
||||
msgid "Progress"
|
||||
msgstr "Fortschritt"
|
||||
|
||||
|
@ -1437,9 +1437,6 @@ msgstr "Προεπισκόπηση"
|
||||
msgid "Previous track"
|
||||
msgstr "Προηγούμενο κομμάτι"
|
||||
|
||||
msgid "Problem loading image"
|
||||
msgstr ""
|
||||
|
||||
msgid "Progress"
|
||||
msgstr "Πρόοδος"
|
||||
|
||||
|
@ -1408,9 +1408,6 @@ msgstr ""
|
||||
msgid "Previous track"
|
||||
msgstr "Previous track"
|
||||
|
||||
msgid "Problem loading image"
|
||||
msgstr ""
|
||||
|
||||
msgid "Progress"
|
||||
msgstr "Progress"
|
||||
|
||||
|
@ -1405,9 +1405,6 @@ msgstr ""
|
||||
msgid "Previous track"
|
||||
msgstr "Previous track"
|
||||
|
||||
msgid "Problem loading image"
|
||||
msgstr ""
|
||||
|
||||
msgid "Progress"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1438,9 +1438,6 @@ msgstr "Vista previa"
|
||||
msgid "Previous track"
|
||||
msgstr "Pista anterior"
|
||||
|
||||
msgid "Problem loading image"
|
||||
msgstr ""
|
||||
|
||||
msgid "Progress"
|
||||
msgstr "Progreso"
|
||||
|
||||
|
@ -1406,9 +1406,6 @@ msgstr "Esikatselu"
|
||||
msgid "Previous track"
|
||||
msgstr "Edellinen kappale"
|
||||
|
||||
msgid "Problem loading image"
|
||||
msgstr ""
|
||||
|
||||
msgid "Progress"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1442,9 +1442,6 @@ msgstr "Aperçu"
|
||||
msgid "Previous track"
|
||||
msgstr "Piste précédente"
|
||||
|
||||
msgid "Problem loading image"
|
||||
msgstr ""
|
||||
|
||||
msgid "Progress"
|
||||
msgstr "Progression"
|
||||
|
||||
|
@ -1410,9 +1410,6 @@ msgstr ""
|
||||
msgid "Previous track"
|
||||
msgstr ""
|
||||
|
||||
msgid "Problem loading image"
|
||||
msgstr ""
|
||||
|
||||
msgid "Progress"
|
||||
msgstr ""
|
||||
|
||||
|
@ -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"
|
||||
|
||||
|
@ -1442,9 +1442,6 @@ msgstr "Anteprima"
|
||||
msgid "Previous track"
|
||||
msgstr "Traccia precedente"
|
||||
|
||||
msgid "Problem loading image"
|
||||
msgstr ""
|
||||
|
||||
msgid "Progress"
|
||||
msgstr "Avanzamento"
|
||||
|
||||
|
@ -1405,9 +1405,6 @@ msgstr ""
|
||||
msgid "Previous track"
|
||||
msgstr ""
|
||||
|
||||
msgid "Problem loading image"
|
||||
msgstr ""
|
||||
|
||||
msgid "Progress"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1404,9 +1404,6 @@ msgstr ""
|
||||
msgid "Previous track"
|
||||
msgstr ""
|
||||
|
||||
msgid "Problem loading image"
|
||||
msgstr ""
|
||||
|
||||
msgid "Progress"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1407,9 +1407,6 @@ msgstr ""
|
||||
msgid "Previous track"
|
||||
msgstr "Forrige spor"
|
||||
|
||||
msgid "Problem loading image"
|
||||
msgstr ""
|
||||
|
||||
msgid "Progress"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1437,9 +1437,6 @@ msgstr "Voorbeeld"
|
||||
msgid "Previous track"
|
||||
msgstr "Vorige track"
|
||||
|
||||
msgid "Problem loading image"
|
||||
msgstr ""
|
||||
|
||||
msgid "Progress"
|
||||
msgstr "Voortgang"
|
||||
|
||||
|
@ -1403,9 +1403,6 @@ msgstr ""
|
||||
msgid "Previous track"
|
||||
msgstr "Pista precedenta"
|
||||
|
||||
msgid "Problem loading image"
|
||||
msgstr ""
|
||||
|
||||
msgid "Progress"
|
||||
msgstr "Progression"
|
||||
|
||||
|
@ -1433,9 +1433,6 @@ msgstr "Podgląd"
|
||||
msgid "Previous track"
|
||||
msgstr "Poprzedni utwór"
|
||||
|
||||
msgid "Problem loading image"
|
||||
msgstr ""
|
||||
|
||||
msgid "Progress"
|
||||
msgstr "Postęp"
|
||||
|
||||
|
@ -1434,9 +1434,6 @@ msgstr "Antevisão"
|
||||
msgid "Previous track"
|
||||
msgstr "Faixa anterior"
|
||||
|
||||
msgid "Problem loading image"
|
||||
msgstr ""
|
||||
|
||||
msgid "Progress"
|
||||
msgstr "Evolução"
|
||||
|
||||
|
@ -1422,9 +1422,6 @@ msgstr "Pré-visualização"
|
||||
msgid "Previous track"
|
||||
msgstr "Faixa anterior"
|
||||
|
||||
msgid "Problem loading image"
|
||||
msgstr ""
|
||||
|
||||
msgid "Progress"
|
||||
msgstr "Andamento"
|
||||
|
||||
|
@ -1404,9 +1404,6 @@ msgstr ""
|
||||
msgid "Previous track"
|
||||
msgstr "Piesa precedentă"
|
||||
|
||||
msgid "Problem loading image"
|
||||
msgstr ""
|
||||
|
||||
msgid "Progress"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1426,9 +1426,6 @@ msgstr "Предпросмотр"
|
||||
msgid "Previous track"
|
||||
msgstr "Предыдущая композиция"
|
||||
|
||||
msgid "Problem loading image"
|
||||
msgstr ""
|
||||
|
||||
msgid "Progress"
|
||||
msgstr "Ход выполнения"
|
||||
|
||||
|
@ -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"
|
||||
|
||||
|
@ -1427,9 +1427,6 @@ msgstr "Predogled"
|
||||
msgid "Previous track"
|
||||
msgstr "Predhodna skladba"
|
||||
|
||||
msgid "Problem loading image"
|
||||
msgstr ""
|
||||
|
||||
msgid "Progress"
|
||||
msgstr "Potek"
|
||||
|
||||
|
@ -1409,9 +1409,6 @@ msgstr "Преглед"
|
||||
msgid "Previous track"
|
||||
msgstr "Претходна нумера"
|
||||
|
||||
msgid "Problem loading image"
|
||||
msgstr ""
|
||||
|
||||
msgid "Progress"
|
||||
msgstr "Напредак"
|
||||
|
||||
|
@ -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"
|
||||
|
||||
|
@ -1430,9 +1430,6 @@ msgstr "Önizleme"
|
||||
msgid "Previous track"
|
||||
msgstr "Önceki parça"
|
||||
|
||||
msgid "Problem loading image"
|
||||
msgstr ""
|
||||
|
||||
msgid "Progress"
|
||||
msgstr "İlerleme"
|
||||
|
||||
|
@ -1394,9 +1394,6 @@ msgstr ""
|
||||
msgid "Previous track"
|
||||
msgstr ""
|
||||
|
||||
msgid "Problem loading image"
|
||||
msgstr ""
|
||||
|
||||
msgid "Progress"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1427,9 +1427,6 @@ msgstr "Перегляд"
|
||||
msgid "Previous track"
|
||||
msgstr "Попередня доріжка"
|
||||
|
||||
msgid "Problem loading image"
|
||||
msgstr ""
|
||||
|
||||
msgid "Progress"
|
||||
msgstr "Поступ"
|
||||
|
||||
|
@ -1403,9 +1403,6 @@ msgstr ""
|
||||
msgid "Previous track"
|
||||
msgstr "上一音轨"
|
||||
|
||||
msgid "Problem loading image"
|
||||
msgstr ""
|
||||
|
||||
msgid "Progress"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1409,9 +1409,6 @@ msgstr "試聽"
|
||||
msgid "Previous track"
|
||||
msgstr "上一首歌曲"
|
||||
|
||||
msgid "Problem loading image"
|
||||
msgstr ""
|
||||
|
||||
msgid "Progress"
|
||||
msgstr "進展"
|
||||
|
||||
|
214
src/widgets/prettyimage.cpp
Normal file
214
src/widgets/prettyimage.cpp
Normal 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
78
src/widgets/prettyimage.h
Normal 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
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user