mirror of
https://github.com/RetroShare/RetroShare.git
synced 2025-01-10 06:59:42 -05:00
345 lines
11 KiB
C++
345 lines
11 KiB
C++
/*******************************************************************************
|
|
* retroshare-gui/src/gui/gxschannels/GxsChannelPostThumbnail.cpp *
|
|
* *
|
|
* Copyright 2020 by Retroshare Team <retroshare.project@gmail.com> *
|
|
* *
|
|
* This program is free software: you can redistribute it and/or modify *
|
|
* it under the terms of the GNU Affero General Public License as *
|
|
* published by the Free Software Foundation, either version 3 of the *
|
|
* License, or (at your option) any later version. *
|
|
* *
|
|
* This program 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 Affero General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU Affero General Public License *
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
|
|
* *
|
|
*******************************************************************************/
|
|
|
|
#include <math.h>
|
|
|
|
#include <QWheelEvent>
|
|
#include <QDateTime>
|
|
|
|
#include "gui/common/FilesDefs.h"
|
|
#include "gui/gxschannels/GxsChannelPostThumbnail.h"
|
|
|
|
// #define DEBUG_GXSCHANNELPOSTTHUMBNAIL 1
|
|
|
|
const float ChannelPostThumbnailView::DEFAULT_SIZE_IN_FONT_HEIGHT = 5.0;
|
|
const float ChannelPostThumbnailView::FONT_SCALE_FACTOR = 1.5;
|
|
|
|
ChannelPostThumbnailView::ChannelPostThumbnailView(const RsGxsChannelPost& post,uint32_t flags,QWidget *parent)
|
|
: QWidget(parent),mPostTitle(nullptr),mFlags(flags), mAspectRatio(ASPECT_RATIO_2_3)
|
|
{
|
|
// now fill the data
|
|
|
|
init(post);
|
|
}
|
|
|
|
ChannelPostThumbnailView::ChannelPostThumbnailView(QWidget *parent,uint32_t flags)
|
|
: QWidget(parent),mFlags(flags), mAspectRatio(ASPECT_RATIO_2_3)
|
|
{
|
|
init(RsGxsChannelPost());
|
|
}
|
|
|
|
ChannelPostThumbnailView::~ChannelPostThumbnailView()
|
|
{
|
|
delete mPostImage;
|
|
}
|
|
|
|
void ChannelPostThumbnailView::setText(const QString& s)
|
|
{
|
|
if(mPostTitle == NULL)
|
|
{
|
|
#ifdef DEBUG_GXSCHANNELPOSTTHUMBNAIL
|
|
std::cerr << "(EE) calling setText on a ChannelPostThumbnailView without SHOW_TEXT flag!"<< std::endl;
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
QString ss;
|
|
if(s.length() > 30)
|
|
ss = s.left(30)+"...";
|
|
else
|
|
ss =s;
|
|
|
|
mPostTitle->setText(ss);
|
|
}
|
|
|
|
void ChannelPostThumbnailView::setPixmap(const QPixmap& p, bool guess_aspect_ratio)
|
|
{
|
|
mPostImage->setPicture(p);
|
|
|
|
if(guess_aspect_ratio)// aspect ratio is automatically guessed.
|
|
{
|
|
// compute closest aspect ratio
|
|
float r = p.width()/(float)p.height();
|
|
|
|
if(r < 0.8)
|
|
setAspectRatio(ChannelPostThumbnailView::ASPECT_RATIO_2_3);
|
|
else if(r < 1.15)
|
|
setAspectRatio(ChannelPostThumbnailView::ASPECT_RATIO_1_1);
|
|
else
|
|
setAspectRatio(ChannelPostThumbnailView::ASPECT_RATIO_16_9);
|
|
}
|
|
}
|
|
|
|
void ChannelPostThumbnailView::setAspectRatio(AspectRatio r)
|
|
{
|
|
mAspectRatio = r;
|
|
|
|
QFontMetricsF fm(font());
|
|
int W = THUMBNAIL_OVERSAMPLE_FACTOR * thumbnail_w() * fm.height() ;
|
|
int H = THUMBNAIL_OVERSAMPLE_FACTOR * thumbnail_h() * fm.height() ;
|
|
|
|
mPostImage->setFixedSize(W,H);
|
|
mPostImage->reset();
|
|
mPostImage->updateView();
|
|
}
|
|
|
|
void ChannelPostThumbnailView::init(const RsGxsChannelPost& post)
|
|
{
|
|
QString msg = QString::fromUtf8(post.mMeta.mMsgName.c_str());
|
|
bool is_msg_new = IS_MSG_UNREAD(post.mMeta.mMsgStatus) || IS_MSG_NEW(post.mMeta.mMsgStatus);
|
|
|
|
QPixmap thumbnail;
|
|
|
|
if(post.mThumbnail.mSize > 0)
|
|
GxsIdDetails::loadPixmapFromData(post.mThumbnail.mData, post.mThumbnail.mSize, thumbnail,GxsIdDetails::ORIGINAL);
|
|
else if(post.mMeta.mPublishTs > 0) // this is for testing that the post is not an empty post (happens at the end of the last row)
|
|
thumbnail = FilesDefs::getPixmapFromQtResourcePath(CHAN_DEFAULT_IMAGE);
|
|
|
|
mPostImage = new ZoomableLabel(this);
|
|
mPostImage->setEnableZoom(mFlags & FLAG_ALLOW_PAN);
|
|
mPostImage->setScaledContents(true);
|
|
mPostImage->setPicture(thumbnail);
|
|
|
|
if(mFlags & FLAG_ALLOW_PAN)
|
|
mPostImage->setToolTip(tr("Use mouse to center and zoom\ninto the image, so as to\n crop it for your post."));
|
|
|
|
QVBoxLayout *layout = new QVBoxLayout(this);
|
|
|
|
layout->addWidget(mPostImage);
|
|
|
|
QFontMetricsF fm(font());
|
|
int W = THUMBNAIL_OVERSAMPLE_FACTOR * thumbnail_w() * fm.height() ;
|
|
int H = THUMBNAIL_OVERSAMPLE_FACTOR * thumbnail_h() * fm.height() ;
|
|
|
|
mPostImage->setFixedSize(W,H);
|
|
|
|
if(mFlags & FLAG_SHOW_TEXT)
|
|
{
|
|
mPostTitle = new QLabel(this);
|
|
layout->addWidget(mPostTitle);
|
|
|
|
QString ss = (msg.length() > 30)? (msg.left(30)+"..."):msg;
|
|
|
|
mPostTitle->setText(ss);
|
|
|
|
QFont font = mPostTitle->font();
|
|
|
|
if(mFlags & ChannelPostThumbnailView::FLAG_SCALE_FONT)
|
|
font.setPointSizeF(FONT_SCALE_FACTOR * DEFAULT_SIZE_IN_FONT_HEIGHT / 5.0 * font.pointSizeF());
|
|
else
|
|
font.setPointSizeF(DEFAULT_SIZE_IN_FONT_HEIGHT / 5.0 * font.pointSizeF());
|
|
|
|
if(is_msg_new)
|
|
font.setBold(true);
|
|
|
|
mPostTitle->setFont(font);
|
|
mPostTitle->setMaximumWidth(W);
|
|
mPostTitle->setWordWrap(true);
|
|
}
|
|
setSizePolicy(QSizePolicy::MinimumExpanding,QSizePolicy::MinimumExpanding);
|
|
|
|
layout->addStretch();
|
|
|
|
setLayout(layout);
|
|
adjustSize();
|
|
update();
|
|
}
|
|
|
|
ChannelPostThumbnailView::AspectRatio ChannelPostThumbnailView::bestAspectRatio()
|
|
{
|
|
if(mPostImage->originalImage().isNull())
|
|
return ASPECT_RATIO_1_1;
|
|
|
|
float as = mPostImage->originalImage().height() / (float)mPostImage->originalImage().width() ;
|
|
|
|
if(as <= 0.8)
|
|
return ASPECT_RATIO_16_9;
|
|
else if(as < 1.15)
|
|
return ASPECT_RATIO_1_1;
|
|
else
|
|
return ASPECT_RATIO_2_3;
|
|
}
|
|
QSize ChannelPostThumbnailView::actualSize() const
|
|
{
|
|
QFontMetricsF fm(font());
|
|
|
|
if(mPostTitle != nullptr)
|
|
{
|
|
QMargins cm = layout()->contentsMargins();
|
|
|
|
return QSize(width(),
|
|
mPostTitle->height() + mPostImage->height() + 0.5*fm.height()
|
|
+ cm.top() + cm.bottom() + layout()->spacing());
|
|
}
|
|
else
|
|
return size();
|
|
}
|
|
|
|
float ChannelPostThumbnailView::thumbnail_w() const
|
|
{
|
|
switch(mAspectRatio)
|
|
{
|
|
default:
|
|
case ASPECT_RATIO_1_1:
|
|
case ASPECT_RATIO_UNKNOWN: return DEFAULT_SIZE_IN_FONT_HEIGHT;
|
|
|
|
case ASPECT_RATIO_2_3: return DEFAULT_SIZE_IN_FONT_HEIGHT;
|
|
case ASPECT_RATIO_16_9: return DEFAULT_SIZE_IN_FONT_HEIGHT;
|
|
}
|
|
}
|
|
float ChannelPostThumbnailView::thumbnail_h() const
|
|
{
|
|
switch(mAspectRatio)
|
|
{
|
|
default:
|
|
case ASPECT_RATIO_1_1:
|
|
case ASPECT_RATIO_UNKNOWN: return DEFAULT_SIZE_IN_FONT_HEIGHT;
|
|
|
|
case ASPECT_RATIO_2_3: return DEFAULT_SIZE_IN_FONT_HEIGHT * 3.0/2.0;
|
|
case ASPECT_RATIO_16_9: return DEFAULT_SIZE_IN_FONT_HEIGHT * 9.0/16.0;
|
|
}
|
|
}
|
|
|
|
void ZoomableLabel::reset()
|
|
{
|
|
mCenterX = mFullImage.width()/2.0;
|
|
mCenterY = mFullImage.height()/2.0;
|
|
mZoomFactor = 1.0/std::max(width() / (float)mFullImage.width(), height()/(float)mFullImage.height());
|
|
|
|
updateView();
|
|
}
|
|
void ZoomableLabel::mouseMoveEvent(QMouseEvent *me)
|
|
{
|
|
if(!mZoomEnabled)
|
|
return;
|
|
|
|
float new_center_x = mCenterX - (me->x() - mLastX);
|
|
float new_center_y = mCenterY - (me->y() - mLastY);
|
|
|
|
mLastX = me->x();
|
|
mLastY = me->y();
|
|
|
|
if(new_center_x - 0.5 * width()*mZoomFactor < 0) return;
|
|
if(new_center_y - 0.5 *height()*mZoomFactor < 0) return;
|
|
|
|
if(new_center_x + 0.5 * width()*mZoomFactor >= mFullImage.width()) return;
|
|
if(new_center_y + 0.5 *height()*mZoomFactor >=mFullImage.height()) return;
|
|
|
|
mCenterX = new_center_x;
|
|
mCenterY = new_center_y;
|
|
|
|
updateView();
|
|
}
|
|
void ZoomableLabel::mousePressEvent(QMouseEvent *me)
|
|
{
|
|
mMoving = true;
|
|
mLastX = me->x();
|
|
mLastY = me->y();
|
|
|
|
emit clicked();
|
|
}
|
|
void ZoomableLabel::mouseReleaseEvent(QMouseEvent *)
|
|
{
|
|
mMoving = false;
|
|
}
|
|
void ZoomableLabel::wheelEvent(QWheelEvent *me)
|
|
{
|
|
if(!mZoomEnabled)
|
|
return;
|
|
|
|
float new_zoom_factor = (me->delta() > 0)?(mZoomFactor*1.05):(mZoomFactor/1.05);
|
|
float new_center_x = mCenterX;
|
|
float new_center_y = mCenterY;
|
|
|
|
// Try to find centerX and centerY so that the crop does not overlap the original image
|
|
|
|
float min_x = 0.5 * width()*new_zoom_factor;
|
|
float max_x = mFullImage.width() - 0.5 * width()*new_zoom_factor;
|
|
float min_y = 0.5 * height()*new_zoom_factor;
|
|
float max_y = mFullImage.height() - 0.5 * height()*new_zoom_factor;
|
|
|
|
if(min_x >= max_x) return;
|
|
if(min_y >= max_y) return;
|
|
|
|
if(new_center_x < min_x) new_center_x = min_x;
|
|
if(new_center_y < min_y) new_center_y = min_y;
|
|
if(new_center_x > max_x) new_center_x = max_x;
|
|
if(new_center_y > max_y) new_center_y = max_y;
|
|
|
|
mZoomFactor = new_zoom_factor;
|
|
mCenterX = new_center_x;
|
|
mCenterY = new_center_y;
|
|
|
|
updateView();
|
|
}
|
|
|
|
QPixmap ZoomableLabel::extractCroppedScaledPicture() const
|
|
{
|
|
QRect rect(mCenterX - 0.5 * width()*mZoomFactor, mCenterY - 0.5 * height()*mZoomFactor, width()*mZoomFactor, height()*mZoomFactor);
|
|
QPixmap pix = mFullImage.copy(rect).scaledToHeight(height(),Qt::SmoothTransformation);
|
|
|
|
return pix;
|
|
}
|
|
|
|
void ZoomableLabel::setPicture(const QPixmap& pix)
|
|
{
|
|
#ifdef DEBUG_GXSCHANNELPOSTTHUMBNAIL
|
|
std::cerr << "Setting new picture of size " << pix.width() << " x " << pix.height() << std::endl;
|
|
#endif
|
|
mFullImage = pix;
|
|
setScaledContents(true);
|
|
|
|
reset();
|
|
updateView();
|
|
}
|
|
void ZoomableLabel::resizeEvent(QResizeEvent *e)
|
|
{
|
|
QLabel::resizeEvent(e);
|
|
|
|
updateView();
|
|
}
|
|
|
|
void ZoomableLabel::updateView()
|
|
{
|
|
// The new image will be cropped from the original image, using the following rules:
|
|
// - first the cropped image size is computed
|
|
// - then center is calculated so that
|
|
// - the original center is preferred
|
|
// - if the crop overlaps the image border, the center is moved.
|
|
|
|
QRect rect(mCenterX - 0.5 * width()*mZoomFactor, mCenterY - 0.5 * height()*mZoomFactor, floor(width()*mZoomFactor), floor(height()*mZoomFactor));
|
|
|
|
#ifdef DEBUG_GXSCHANNELPOSTTHUMBNAIL
|
|
std::cerr << "Updating view: mCenterX=" << mCenterX << ", mCenterY=" << mCenterY << ", mZoomFactor=" << mZoomFactor << std::endl;
|
|
std::cerr << " Image size: " << mFullImage.width() << " x " << mFullImage.height() << ", window size: " << width() << " x " << height() << std::endl;
|
|
std::cerr << " cropped image: " << rect.left() << "," << rect.top() << "+" << rect.width() << "+" << rect.height() << std::endl;
|
|
std::cerr << " saving crop to pix2.png" << std::endl;
|
|
mFullImage.copy(rect).save("pix2.png","PNG");
|
|
#endif
|
|
QLabel::setPixmap(mFullImage.copy(rect));
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|