2010-10-09 16:07:20 +00:00
|
|
|
/* This file is part of Clementine.
|
2010-11-20 13:27:10 +00:00
|
|
|
Copyright 2010, David Sansome <me@davidsansome.com>
|
2010-10-09 16:07:20 +00:00
|
|
|
|
|
|
|
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 <QApplication>
|
|
|
|
#include <QContextMenuEvent>
|
|
|
|
#include <QDir>
|
|
|
|
#include <QFileDialog>
|
2010-10-17 11:01:46 +00:00
|
|
|
#include <QFuture>
|
2010-10-09 16:07:20 +00:00
|
|
|
#include <QLabel>
|
|
|
|
#include <QMenu>
|
2010-12-30 17:35:10 +00:00
|
|
|
#include <QNetworkAccessManager>
|
2010-10-09 16:07:20 +00:00
|
|
|
#include <QNetworkReply>
|
|
|
|
#include <QPainter>
|
2020-01-04 21:24:31 +01:00
|
|
|
#include <QScreen>
|
2010-10-09 16:07:20 +00:00
|
|
|
#include <QScrollArea>
|
|
|
|
#include <QSettings>
|
2020-01-04 21:24:31 +01:00
|
|
|
#include <QWindow>
|
2010-10-17 11:01:46 +00:00
|
|
|
#include <QtConcurrentRun>
|
2010-10-09 16:07:20 +00:00
|
|
|
|
2015-11-26 18:34:41 +00:00
|
|
|
#include "core/closure.h"
|
2016-02-17 13:51:08 +00:00
|
|
|
#include "core/logging.h"
|
|
|
|
#include "core/network.h"
|
2015-11-26 18:34:41 +00:00
|
|
|
#include "ui/iconloader.h"
|
|
|
|
|
2010-10-09 16:07:20 +00:00
|
|
|
const int PrettyImage::kTotalHeight = 200;
|
|
|
|
const int PrettyImage::kReflectionHeight = 40;
|
2014-02-07 16:34:20 +01:00
|
|
|
const int PrettyImage::kImageHeight =
|
|
|
|
PrettyImage::kTotalHeight - PrettyImage::kReflectionHeight;
|
2010-10-09 16:07:20 +00:00
|
|
|
|
2010-10-17 11:07:56 +00:00
|
|
|
const int PrettyImage::kMaxImageWidth = 300;
|
2010-10-09 16:07:20 +00:00
|
|
|
|
|
|
|
const char* PrettyImage::kSettingsGroup = "PrettyImageView";
|
|
|
|
|
2010-12-30 17:35:10 +00:00
|
|
|
PrettyImage::PrettyImage(const QUrl& url, QNetworkAccessManager* network,
|
|
|
|
QWidget* parent)
|
2014-02-07 16:34:20 +01:00
|
|
|
: QWidget(parent),
|
|
|
|
network_(network),
|
|
|
|
state_(State_WaitingForLazyLoad),
|
|
|
|
url_(url),
|
|
|
|
menu_(nullptr) {
|
2010-10-09 16:07:20 +00:00
|
|
|
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
|
|
|
|
|
|
|
LazyLoad();
|
|
|
|
}
|
|
|
|
|
|
|
|
void PrettyImage::LazyLoad() {
|
2014-02-07 16:34:20 +01:00
|
|
|
if (state_ != State_WaitingForLazyLoad) return;
|
2010-10-09 16:07:20 +00:00
|
|
|
|
|
|
|
// Start fetching the image
|
2010-10-16 17:20:54 +00:00
|
|
|
QNetworkReply* reply = network_->get(QNetworkRequest(url_));
|
2016-02-17 13:51:08 +00:00
|
|
|
RedirectFollower* follower = new RedirectFollower(reply);
|
2010-10-17 11:01:46 +00:00
|
|
|
state_ = State_Fetching;
|
2016-02-17 13:51:08 +00:00
|
|
|
NewClosure(follower, SIGNAL(finished()), this,
|
|
|
|
SLOT(ImageFetched(RedirectFollower*)), follower);
|
2010-10-09 16:07:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QSize PrettyImage::image_size() const {
|
2014-02-07 16:34:20 +01:00
|
|
|
if (state_ != State_Finished) return QSize(kImageHeight * 1.6, kImageHeight);
|
2010-10-09 16:07:20 +00:00
|
|
|
|
|
|
|
QSize ret = image_.size();
|
|
|
|
ret.scale(kMaxImageWidth, kImageHeight, Qt::KeepAspectRatio);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
QSize PrettyImage::sizeHint() const {
|
|
|
|
return QSize(image_size().width(), kTotalHeight);
|
|
|
|
}
|
|
|
|
|
2016-02-17 13:51:08 +00:00
|
|
|
void PrettyImage::ImageFetched(RedirectFollower* follower) {
|
|
|
|
follower->deleteLater();
|
|
|
|
QNetworkReply* reply = follower->reply();
|
2010-10-09 16:07:20 +00:00
|
|
|
reply->deleteLater();
|
|
|
|
|
|
|
|
QImage image = QImage::fromData(reply->readAll());
|
|
|
|
if (image.isNull()) {
|
2016-06-28 15:06:46 +01:00
|
|
|
qLog(Debug) << "Image failed to load" << reply->request().url()
|
|
|
|
<< reply->error();
|
2010-10-09 16:07:20 +00:00
|
|
|
deleteLater();
|
|
|
|
} else {
|
2010-10-17 11:01:46 +00:00
|
|
|
state_ = State_CreatingThumbnail;
|
2010-10-09 16:07:20 +00:00
|
|
|
image_ = image;
|
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
QFuture<QImage> future =
|
|
|
|
QtConcurrent::run(image_, &QImage::scaled, image_size(),
|
|
|
|
Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
2015-11-26 18:34:41 +00:00
|
|
|
NewClosure(future, this, SLOT(ImageScaled(QFuture<QImage>)), future);
|
2010-10-09 16:07:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-26 18:34:41 +00:00
|
|
|
void PrettyImage::ImageScaled(QFuture<QImage> future) {
|
|
|
|
thumbnail_ = QPixmap::fromImage(future.result());
|
2010-10-17 11:01:46 +00:00
|
|
|
state_ = State_Finished;
|
|
|
|
|
|
|
|
updateGeometry();
|
|
|
|
update();
|
|
|
|
emit Loaded();
|
|
|
|
}
|
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
void PrettyImage::paintEvent(QPaintEvent*) {
|
2010-10-09 16:07:20 +00:00
|
|
|
// 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
|
2014-02-07 16:34:20 +01:00
|
|
|
QImage reflection(reflection_rect.size(),
|
|
|
|
QImage::Format_ARGB32_Premultiplied);
|
2010-10-09 16:07:20 +00:00
|
|
|
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));
|
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
reflection_painter.setCompositionMode(
|
|
|
|
QPainter::CompositionMode_DestinationIn);
|
2010-10-09 16:07:20 +00:00
|
|
|
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_) {
|
2014-02-07 16:34:20 +01:00
|
|
|
case State_WaitingForLazyLoad:
|
|
|
|
case State_Fetching:
|
|
|
|
case State_CreatingThumbnail:
|
|
|
|
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;
|
2010-10-09 16:07:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void PrettyImage::contextMenuEvent(QContextMenuEvent* e) {
|
2014-02-07 16:34:20 +01:00
|
|
|
if (e->pos().y() >= kImageHeight) return;
|
2010-10-09 16:07:20 +00:00
|
|
|
|
|
|
|
if (!menu_) {
|
|
|
|
menu_ = new QMenu(this);
|
2015-11-26 18:34:41 +00:00
|
|
|
menu_->addAction(IconLoader::Load("zoom-in", IconLoader::Base),
|
2015-10-13 20:01:08 -05:00
|
|
|
tr("Show fullsize..."), this, SLOT(ShowFullsize()));
|
|
|
|
menu_->addAction(IconLoader::Load("document-save", IconLoader::Base),
|
2014-02-07 16:34:20 +01:00
|
|
|
tr("Save image") + "...", this, SLOT(SaveAs()));
|
2010-10-09 16:07:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
menu_->popup(e->globalPos());
|
|
|
|
}
|
|
|
|
|
|
|
|
void PrettyImage::ShowFullsize() {
|
|
|
|
// Create the window
|
2020-01-04 21:18:12 +01:00
|
|
|
QScrollArea* pwindow = new QScrollArea;
|
|
|
|
pwindow->setAttribute(Qt::WA_DeleteOnClose, true);
|
|
|
|
pwindow->setWindowTitle(tr("Clementine image viewer"));
|
|
|
|
|
|
|
|
// Work out how large to make the window, based on the size of the screen
|
|
|
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
2020-01-05 02:51:08 +01:00
|
|
|
QScreen* screen = QWidget::screen();
|
2020-01-04 21:18:12 +01:00
|
|
|
#else
|
2020-01-04 21:24:31 +01:00
|
|
|
QScreen* screen =
|
|
|
|
(window() && window()->windowHandle() ? window()->windowHandle()->screen()
|
|
|
|
: QGuiApplication::primaryScreen());
|
2020-01-04 21:18:12 +01:00
|
|
|
#endif
|
|
|
|
if (screen) {
|
|
|
|
QRect desktop_rect(screen->availableGeometry());
|
|
|
|
QSize window_size(qMin(desktop_rect.width() - 20, image_.width()),
|
|
|
|
qMin(desktop_rect.height() - 20, image_.height()));
|
|
|
|
pwindow->resize(window_size);
|
|
|
|
}
|
2010-10-09 16:07:20 +00:00
|
|
|
|
|
|
|
// Create the label that displays the image
|
2020-01-04 21:18:12 +01:00
|
|
|
QLabel* label = new QLabel(pwindow);
|
2010-10-09 16:07:20 +00:00
|
|
|
label->setPixmap(QPixmap::fromImage(image_));
|
|
|
|
|
|
|
|
// Show the label in the window
|
2020-01-04 21:18:12 +01:00
|
|
|
pwindow->setWidget(label);
|
|
|
|
pwindow->setFrameShape(QFrame::NoFrame);
|
|
|
|
pwindow->show();
|
2010-10-09 16:07:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void PrettyImage::SaveAs() {
|
|
|
|
QString filename = QFileInfo(url_.path()).fileName();
|
|
|
|
|
2014-02-07 16:34:20 +01:00
|
|
|
if (filename.isEmpty()) filename = "artwork.jpg";
|
2010-10-09 16:07:20 +00:00
|
|
|
|
|
|
|
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);
|
2014-02-07 16:34:20 +01:00
|
|
|
if (filename.isEmpty()) return;
|
2010-10-09 16:07:20 +00:00
|
|
|
|
|
|
|
image_.save(filename);
|
|
|
|
|
|
|
|
s.setValue("last_save_dir", last_save_dir);
|
|
|
|
}
|