tdesktop/Telegram/SourceFiles/history/view/media/history_view_theme_document.cpp
2023-04-17 16:36:13 +04:00

495 lines
14 KiB
C++

/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "history/view/media/history_view_theme_document.h"
#include "boxes/background_preview_box.h"
#include "history/history.h"
#include "history/history_item.h"
#include "history/view/history_view_element.h"
#include "history/view/history_view_cursor_state.h"
#include "history/view/media/history_view_sticker_player_abstract.h"
#include "data/data_document.h"
#include "data/data_session.h"
#include "data/data_document_media.h"
#include "data/data_file_click_handler.h"
#include "data/data_file_origin.h"
#include "data/data_wall_paper.h"
#include "base/qthelp_url.h"
#include "core/click_handler_types.h"
#include "core/local_url_handlers.h"
#include "lang/lang_keys.h"
#include "ui/text/format_values.h"
#include "ui/chat/chat_style.h"
#include "ui/chat/chat_theme.h"
#include "ui/cached_round_corners.h"
#include "ui/painter.h"
#include "ui/ui_utility.h"
#include "window/window_session_controller.h"
#include "styles/style_chat.h"
namespace HistoryView {
ThemeDocument::ThemeDocument(
not_null<Element*> parent,
DocumentData *document)
: ThemeDocument(parent, document, std::nullopt, 0) {
}
ThemeDocument::ThemeDocument(
not_null<Element*> parent,
DocumentData *document,
const std::optional<Data::WallPaper> &params,
int serviceWidth)
: File(parent, parent->data())
, _data(document)
, _serviceWidth(serviceWidth) {
Expects(params.has_value() || _data->hasThumbnail() || _data->isTheme());
if (params) {
_background = params->backgroundColors();
_patternOpacity = params->patternOpacity();
_gradientRotation = params->gradientRotation();
}
const auto fullId = _parent->data()->fullId();
if (_data) {
_data->loadThumbnail(fullId);
setDocumentLinks(_data, parent->data());
setStatusSize(Ui::FileStatusSizeReady, _data->size, -1, 0);
} else {
class EmptyFileClickHandler final : public FileClickHandler {
public:
using FileClickHandler::FileClickHandler;
private:
void onClickImpl() const override {
}
};
// We could open BackgroundPreviewBox here, but right now
// WebPage that created ThemeDocument as its attachment does it.
//
// So just provide a non-null click handler for this hack to work.
setLinks(
std::make_shared<EmptyFileClickHandler>(fullId),
nullptr,
nullptr);
}
}
ThemeDocument::~ThemeDocument() {
if (_dataMedia) {
_data->owner().keepAlive(base::take(_dataMedia));
_parent->checkHeavyPart();
}
}
std::optional<Data::WallPaper> ThemeDocument::ParamsFromUrl(
const QString &url) {
const auto local = Core::TryConvertUrlToLocal(url);
const auto paramsPosition = local.indexOf('?');
if (paramsPosition < 0) {
return std::nullopt;
}
const auto paramsString = local.mid(paramsPosition + 1);
const auto params = qthelp::url_parse_params(
paramsString,
qthelp::UrlParamNameTransform::ToLower);
auto paper = Data::DefaultWallPaper().withUrlParams(params);
return paper.backgroundColors().empty()
? std::nullopt
: std::make_optional(std::move(paper));
}
QSize ThemeDocument::countOptimalSize() {
if (_serviceWidth > 0) {
return { _serviceWidth, _serviceWidth };
}
if (!_data) {
return { st::maxWallPaperWidth, st::maxWallPaperHeight };
} else if (_data->isTheme()) {
return st::historyThemeSize;
}
const auto &location = _data->thumbnailLocation();
auto tw = style::ConvertScale(location.width());
auto th = style::ConvertScale(location.height());
if (!tw || !th) {
tw = th = 1;
}
th = (st::maxWallPaperWidth * th) / tw;
tw = st::maxWallPaperWidth;
const auto maxWidth = tw;
const auto minHeight = std::clamp(
th,
st::minPhotoSize,
st::maxWallPaperHeight);
return { maxWidth, minHeight };
}
QSize ThemeDocument::countCurrentSize(int newWidth) {
if (_serviceWidth) {
_pixw = _pixh = _serviceWidth;
return { _serviceWidth, _serviceWidth };
}
if (!_data) {
_pixw = st::maxWallPaperWidth;
_pixh = st::maxWallPaperHeight;
return { _pixw, _pixh };
} else if (_data->isTheme()) {
_pixw = st::historyThemeSize.width();
_pixh = st::historyThemeSize.height();
return st::historyThemeSize;
}
const auto &location = _data->thumbnailLocation();
auto tw = style::ConvertScale(location.width());
auto th = style::ConvertScale(location.height());
if (!tw || !th) {
tw = th = 1;
}
// We use pix() for image copies, because we rely that backgrounds
// are always displayed with the same dimensions (not pixSingle()).
_pixw = maxWidth();// std::min(newWidth, maxWidth());
_pixh = minHeight();// (_pixw * th / tw);
newWidth = _pixw;
const auto newHeight = _pixh; /*std::clamp(
_pixh,
st::minPhotoSize,
st::maxWallPaperHeight);*/
return { newWidth, newHeight };
}
void ThemeDocument::draw(Painter &p, const PaintContext &context) const {
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
ensureDataMediaCreated();
if (_data) {
_dataMedia->automaticLoad(_realParent->fullId(), _parent->data());
}
const auto st = context.st;
const auto sti = context.imageStyle();
auto loaded = dataLoaded();
auto displayLoading = _data && _data->displayLoading();
auto paintx = 0, painty = 0, paintw = width(), painth = height();
if (displayLoading) {
ensureAnimation();
if (!_animation->radial.animating()) {
_animation->radial.start(dataProgress());
}
}
const auto radial = isRadialAnimation();
auto rthumb = style::rtlrect(paintx, painty, paintw, painth, width());
validateThumbnail();
p.drawPixmap(rthumb.topLeft(), _thumbnail);
if (context.selected()) {
Ui::FillComplexOverlayRect(
p,
rthumb,
st->msgSelectOverlay(),
st->msgSelectOverlayCorners(Ui::CachedCornerRadius::Small));
}
if (_data) {
if (!_serviceWidth) {
auto statusX = paintx + st::msgDateImgDelta + st::msgDateImgPadding.x();
auto statusY = painty + st::msgDateImgDelta + st::msgDateImgPadding.y();
auto statusW = st::normalFont->width(_statusText) + 2 * st::msgDateImgPadding.x();
auto statusH = st::normalFont->height + 2 * st::msgDateImgPadding.y();
Ui::FillRoundRect(p, style::rtlrect(statusX - st::msgDateImgPadding.x(), statusY - st::msgDateImgPadding.y(), statusW, statusH, width()), sti->msgDateImgBg, sti->msgDateImgBgCorners);
p.setFont(st::normalFont);
p.setPen(st->msgDateImgFg());
p.drawTextLeft(statusX, statusY, width(), _statusText, statusW - 2 * st::msgDateImgPadding.x());
}
if (radial || (!loaded && !_data->loading())) {
const auto radialOpacity = (radial && loaded && !_data->uploading())
? _animation->radial.opacity() :
1.;
const auto innerSize = st::msgFileLayout.thumbSize;
QRect inner(rthumb.x() + (rthumb.width() - innerSize) / 2, rthumb.y() + (rthumb.height() - innerSize) / 2, innerSize, innerSize);
p.setPen(Qt::NoPen);
if (context.selected()) {
p.setBrush(st->msgDateImgBgSelected());
} else if (isThumbAnimation()) {
auto over = _animation->a_thumbOver.value(1.);
p.setBrush(anim::brush(st->msgDateImgBg(), st->msgDateImgBgOver(), over));
} else {
auto over = ClickHandler::showAsActive(_data->loading() ? _cancell : _openl);
p.setBrush(over ? st->msgDateImgBgOver() : st->msgDateImgBg());
}
p.setOpacity(radialOpacity * p.opacity());
{
PainterHighQualityEnabler hq(p);
p.drawEllipse(inner);
}
p.setOpacity(radialOpacity);
const auto &icon = (radial || _data->loading())
? sti->historyFileThumbCancel
: sti->historyFileThumbDownload;
icon.paintInCenter(p, inner);
p.setOpacity(1);
if (radial) {
QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine)));
_animation->radial.draw(p, rinner, st::msgFileRadialLine, sti->historyFileThumbRadialFg);
}
}
}
}
void ThemeDocument::ensureDataMediaCreated() const {
if (_dataMedia || !_data) {
return;
}
_dataMedia = _data->createMediaView();
if (checkGoodThumbnail()) {
_dataMedia->goodThumbnailWanted();
}
_dataMedia->thumbnailWanted(_realParent->fullId());
_parent->history()->owner().registerHeavyViewPart(_parent);
}
bool ThemeDocument::checkGoodThumbnail() const {
return _data && (!_data->hasThumbnail() || !_data->isPatternWallPaper());
}
void ThemeDocument::validateThumbnail() const {
if (checkGoodThumbnail()) {
if (_thumbnailGood > 0) {
return;
}
ensureDataMediaCreated();
if (const auto good = _dataMedia->goodThumbnail()) {
prepareThumbnailFrom(good, 1);
return;
}
}
if (_thumbnailGood >= 0) {
return;
}
if (!_data) {
generateThumbnail();
return;
}
ensureDataMediaCreated();
if (const auto normal = _dataMedia->thumbnail()) {
prepareThumbnailFrom(normal, 0);
} else if (_thumbnail.isNull()) {
if (const auto blurred = _dataMedia->thumbnailInline()) {
prepareThumbnailFrom(blurred, -1);
}
}
}
void ThemeDocument::generateThumbnail() const {
auto image = Ui::GenerateBackgroundImage(
QSize(_pixw, _pixh) * cIntRetinaFactor(),
_background,
_gradientRotation,
_patternOpacity);
if (_serviceWidth) {
image = Images::Circle(std::move(image));
}
_thumbnail = Ui::PixmapFromImage(std::move(image));
_thumbnail.setDevicePixelRatio(cRetinaFactor());
_thumbnailGood = 1;
}
void ThemeDocument::prepareThumbnailFrom(
not_null<Image*> image,
int good) const {
Expects(_data != nullptr);
Expects(_thumbnailGood <= good);
const auto isTheme = _data->isTheme();
const auto isPattern = _data->isPatternWallPaper();
auto options = (good >= 0 ? Images::Option(0) : Images::Option::Blur)
| (_serviceWidth ? Images::Option::RoundCircle : Images::Option(0))
| (isPattern
? Images::Option::TransparentBackground
: Images::Option(0));
auto original = image->original();
const auto &location = _data->thumbnailLocation();
auto tw = isTheme ? _pixw : style::ConvertScale(location.width());
auto th = isTheme ? _pixh : style::ConvertScale(location.height());
if (!tw || !th) {
tw = th = 1;
}
const auto ratio = style::DevicePixelRatio();
original = Images::Prepare(
std::move(original),
QSize(_pixw, (_pixw * th) / tw) * ratio,
{ .options = options, .outer = { _pixw, _pixh } });
if (isPattern) {
original = Ui::PreparePatternImage(
std::move(original),
_background,
_gradientRotation,
_patternOpacity);
original.setDevicePixelRatio(ratio);
}
_thumbnail = Ui::PixmapFromImage(std::move(original));
_thumbnailGood = good;
}
TextState ThemeDocument::textState(QPoint point, StateRequest request) const {
auto result = TextState(_parent);
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) {
return result;
}
auto paintx = 0, painty = 0, paintw = width(), painth = height();
if (QRect(paintx, painty, paintw, painth).contains(point)) {
if (!_data) {
result.link = _openl;
} else if (_data->uploading()) {
result.link = _cancell;
} else if (dataLoaded()) {
result.link = _openl;
} else if (_data->loading()) {
result.link = _cancell;
} else {
result.link = _openl;
}
}
return result;
}
float64 ThemeDocument::dataProgress() const {
ensureDataMediaCreated();
return _data ? _dataMedia->progress() : 1.;
}
bool ThemeDocument::dataFinished() const {
return !_data
|| (!_data->loading()
&& (!_data->uploading() || _data->waitingForAlbum()));
}
bool ThemeDocument::dataLoaded() const {
ensureDataMediaCreated();
return !_data || _dataMedia->loaded();
}
bool ThemeDocument::isReadyForOpen() const {
ensureDataMediaCreated();
return !_data || _dataMedia->loaded();
}
QString ThemeDocument::additionalInfoString() const {
// This will force message info (time) to be displayed below
// this attachment in WebPage media.
static auto result = QString(" ");
return result;
}
bool ThemeDocument::hasHeavyPart() const {
return (_dataMedia != nullptr);
}
void ThemeDocument::unloadHeavyPart() {
_dataMedia = nullptr;
}
ThemeDocumentBox::ThemeDocumentBox(
not_null<Element*> parent,
const Data::WallPaper &paper)
: _parent(parent)
, _preview(
parent,
paper.document(),
paper,
st::msgServicePhotoWidth) {
_preview.initDimensions();
_preview.resizeGetHeight(_preview.maxWidth());
}
ThemeDocumentBox::~ThemeDocumentBox() = default;
int ThemeDocumentBox::top() {
return st::msgServiceGiftBoxButtonMargins.top();
}
QSize ThemeDocumentBox::size() {
return { _preview.maxWidth(), _preview.minHeight() };
}
QString ThemeDocumentBox::title() {
return QString();
}
QString ThemeDocumentBox::subtitle() {
return _parent->data()->notificationText().text;
}
QString ThemeDocumentBox::button() {
return _parent->data()->out()
? QString()
: tr::lng_action_set_wallpaper_button(tr::now);
}
ClickHandlerPtr ThemeDocumentBox::createViewLink() {
const auto out = _parent->data()->out();
const auto to = _parent->history()->peer;
const auto media = _parent->data()->media();
const auto paper = media ? media->paper() : nullptr;
const auto maybe = paper ? *paper : std::optional<Data::WallPaper>();
const auto itemId = _parent->data()->fullId();
return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
const auto my = context.other.value<ClickHandlerContext>();
if (const auto controller = my.sessionWindow.get()) {
if (out) {
controller->toggleChooseChatTheme(to);
} else if (maybe) {
controller->show(
Box<BackgroundPreviewBox>(
controller,
*maybe,
BackgroundPreviewArgs{ to, itemId }),
Ui::LayerOption::KeepOther);
}
}
});
}
void ThemeDocumentBox::draw(
Painter &p,
const PaintContext &context,
const QRect &geometry) {
p.translate(geometry.topLeft());
_preview.draw(p, context);
p.translate(-geometry.topLeft());
}
void ThemeDocumentBox::stickerClearLoopPlayed() {
}
std::unique_ptr<StickerPlayer> ThemeDocumentBox::stickerTakePlayer(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements) {
return nullptr;
}
bool ThemeDocumentBox::hasHeavyPart() {
return _preview.hasHeavyPart();
}
void ThemeDocumentBox::unloadHeavyPart() {
_preview.unloadHeavyPart();
}
} // namespace HistoryView