"Fossies" - the Fresh Open Source Software Archive 
Member "tdesktop-2.6.1/Telegram/SourceFiles/history/view/media/history_view_photo.cpp" (24 Feb 2021, 25879 Bytes) of package /linux/misc/tdesktop-2.6.1.tar.gz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
For more information about "history_view_photo.cpp" see the
Fossies "Dox" file reference documentation and the last
Fossies "Diffs" side-by-side code changes report:
2.5.9_vs_2.6.0.
1 /*
2 This file is part of Telegram Desktop,
3 the official desktop application for the Telegram messaging service.
4
5 For license and copyright information please follow this link:
6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
7 */
8 #include "history/view/media/history_view_photo.h"
9
10 #include "layout.h"
11 #include "history/history_item_components.h"
12 #include "history/history_item.h"
13 #include "history/history.h"
14 #include "history/view/history_view_element.h"
15 #include "history/view/history_view_cursor_state.h"
16 #include "history/view/media/history_view_media_common.h"
17 #include "media/streaming/media_streaming_instance.h"
18 #include "media/streaming/media_streaming_player.h"
19 #include "media/streaming/media_streaming_document.h"
20 #include "main/main_session.h"
21 #include "main/main_session_settings.h"
22 #include "ui/image/image.h"
23 #include "ui/grouped_layout.h"
24 #include "ui/cached_round_corners.h"
25 #include "data/data_session.h"
26 #include "data/data_streaming.h"
27 #include "data/data_photo.h"
28 #include "data/data_photo_media.h"
29 #include "data/data_file_origin.h"
30 #include "data/data_auto_download.h"
31 #include "core/application.h"
32 #include "styles/style_chat.h"
33
34 namespace HistoryView {
35 namespace {
36
37 using Data::PhotoSize;
38
39 } // namespace
40
41 struct Photo::Streamed {
42 explicit Streamed(std::shared_ptr<::Media::Streaming::Document> shared);
43 ::Media::Streaming::Instance instance;
44 QImage frozenFrame;
45 };
46
47 Photo::Streamed::Streamed(
48 std::shared_ptr<::Media::Streaming::Document> shared)
49 : instance(std::move(shared), nullptr) {
50 }
51
52 Photo::Photo(
53 not_null<Element*> parent,
54 not_null<HistoryItem*> realParent,
55 not_null<PhotoData*> photo)
56 : File(parent, realParent)
57 , _data(photo)
58 , _caption(st::minPhotoSize - st::msgPadding.left() - st::msgPadding.right()) {
59 _caption = createCaption(realParent);
60 create(realParent->fullId());
61 }
62
63 Photo::Photo(
64 not_null<Element*> parent,
65 not_null<PeerData*> chat,
66 not_null<PhotoData*> photo,
67 int width)
68 : File(parent, parent->data())
69 , _data(photo)
70 , _serviceWidth(width) {
71 create(parent->data()->fullId(), chat);
72 }
73
74 Photo::~Photo() {
75 if (_streamed || _dataMedia) {
76 if (_streamed) {
77 _data->owner().streaming().keepAlive(_data);
78 stopAnimation();
79 }
80 if (_dataMedia) {
81 _data->owner().keepAlive(base::take(_dataMedia));
82 _parent->checkHeavyPart();
83 }
84 }
85 }
86
87 void Photo::create(FullMsgId contextId, PeerData *chat) {
88 setLinks(
89 std::make_shared<PhotoOpenClickHandler>(_data, contextId, chat),
90 std::make_shared<PhotoSaveClickHandler>(_data, contextId, chat),
91 std::make_shared<PhotoCancelClickHandler>(_data, contextId, chat));
92 if ((_dataMedia = _data->activeMediaView())) {
93 dataMediaCreated();
94 } else if (_data->inlineThumbnailBytes().isEmpty()
95 && (_data->hasExact(PhotoSize::Small)
96 || _data->hasExact(PhotoSize::Thumbnail))) {
97 _data->load(PhotoSize::Small, contextId);
98 }
99 }
100
101 void Photo::ensureDataMediaCreated() const {
102 if (_dataMedia) {
103 return;
104 }
105 _dataMedia = _data->createMediaView();
106 dataMediaCreated();
107 }
108
109 void Photo::dataMediaCreated() const {
110 Expects(_dataMedia != nullptr);
111
112 if (_data->inlineThumbnailBytes().isEmpty()
113 && !_dataMedia->image(PhotoSize::Large)
114 && !_dataMedia->image(PhotoSize::Thumbnail)) {
115 _dataMedia->wanted(PhotoSize::Small, _realParent->fullId());
116 }
117 history()->owner().registerHeavyViewPart(_parent);
118 }
119
120 bool Photo::hasHeavyPart() const {
121 return _streamed || _dataMedia;
122 }
123
124 void Photo::unloadHeavyPart() {
125 stopAnimation();
126 _dataMedia = nullptr;
127 }
128
129 QSize Photo::countOptimalSize() {
130 if (_parent->media() != this) {
131 _caption = Ui::Text::String();
132 } else if (_caption.hasSkipBlock()) {
133 _caption.updateSkipBlock(
134 _parent->skipBlockWidth(),
135 _parent->skipBlockHeight());
136 }
137
138 auto maxWidth = 0;
139 auto minHeight = 0;
140
141 auto tw = style::ConvertScale(_data->width());
142 auto th = style::ConvertScale(_data->height());
143 if (!tw || !th) {
144 tw = th = 1;
145 }
146 if (tw > st::maxMediaSize) {
147 th = (st::maxMediaSize * th) / tw;
148 tw = st::maxMediaSize;
149 }
150 if (th > st::maxMediaSize) {
151 tw = (st::maxMediaSize * tw) / th;
152 th = st::maxMediaSize;
153 }
154
155 if (_serviceWidth > 0) {
156 return { _serviceWidth, _serviceWidth };
157 }
158 const auto minWidth = qMax(
159 (_parent->hasBubble() ? st::historyPhotoBubbleMinWidth : st::minPhotoSize),
160 _parent->minWidthForMedia());
161 const auto maxActualWidth = qMax(tw, minWidth);
162 maxWidth = qMax(maxActualWidth, th);
163 minHeight = qMax(th, st::minPhotoSize);
164 if (_parent->hasBubble() && !_caption.isEmpty()) {
165 auto captionw = maxActualWidth - st::msgPadding.left() - st::msgPadding.right();
166 minHeight += st::mediaCaptionSkip + _caption.countHeight(captionw);
167 if (isBubbleBottom()) {
168 minHeight += st::msgPadding.bottom();
169 }
170 }
171 return { maxWidth, minHeight };
172 }
173
174 QSize Photo::countCurrentSize(int newWidth) {
175 auto tw = style::ConvertScale(_data->width());
176 auto th = style::ConvertScale(_data->height());
177 if (tw > st::maxMediaSize) {
178 th = (st::maxMediaSize * th) / tw;
179 tw = st::maxMediaSize;
180 }
181 if (th > st::maxMediaSize) {
182 tw = (st::maxMediaSize * tw) / th;
183 th = st::maxMediaSize;
184 }
185
186 _pixw = qMin(newWidth, maxWidth());
187 _pixh = th;
188 if (tw > _pixw) {
189 _pixh = (_pixw * _pixh / tw);
190 } else {
191 _pixw = tw;
192 }
193 if (_pixh > newWidth) {
194 _pixw = (_pixw * newWidth) / _pixh;
195 _pixh = newWidth;
196 }
197 if (_pixw < 1) _pixw = 1;
198 if (_pixh < 1) _pixh = 1;
199
200 auto minWidth = qMax(
201 (_parent->hasBubble() ? st::historyPhotoBubbleMinWidth : st::minPhotoSize),
202 _parent->minWidthForMedia());
203 newWidth = qMax(_pixw, minWidth);
204 auto newHeight = qMax(_pixh, st::minPhotoSize);
205 if (_parent->hasBubble() && !_caption.isEmpty()) {
206 const auto captionw = newWidth
207 - st::msgPadding.left()
208 - st::msgPadding.right();
209 newHeight += st::mediaCaptionSkip + _caption.countHeight(captionw);
210 if (isBubbleBottom()) {
211 newHeight += st::msgPadding.bottom();
212 }
213 }
214 return { newWidth, newHeight };
215 }
216
217 void Photo::draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms) const {
218 if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
219
220 ensureDataMediaCreated();
221 _dataMedia->automaticLoad(_realParent->fullId(), _parent->data());
222 auto selected = (selection == FullSelection);
223 auto loaded = _dataMedia->loaded();
224 auto displayLoading = _data->displayLoading();
225
226 auto inWebPage = (_parent->media() != this);
227 auto paintx = 0, painty = 0, paintw = width(), painth = height();
228 auto bubble = _parent->hasBubble();
229
230 auto captionw = paintw - st::msgPadding.left() - st::msgPadding.right();
231
232 if (displayLoading) {
233 ensureAnimation();
234 if (!_animation->radial.animating()) {
235 _animation->radial.start(_dataMedia->progress());
236 }
237 }
238 const auto radial = isRadialAnimation();
239
240 auto rthumb = style::rtlrect(paintx, painty, paintw, painth, width());
241 if (_serviceWidth > 0) {
242 paintUserpicFrame(p, rthumb.topLeft(), selected);
243 } else {
244 if (bubble) {
245 if (!_caption.isEmpty()) {
246 painth -= st::mediaCaptionSkip + _caption.countHeight(captionw);
247 if (isBubbleBottom()) {
248 painth -= st::msgPadding.bottom();
249 }
250 rthumb = style::rtlrect(paintx, painty, paintw, painth, width());
251 }
252 } else {
253 Ui::FillRoundShadow(p, 0, 0, paintw, painth, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? Ui::InSelectedShadowCorners : Ui::InShadowCorners);
254 }
255 auto inWebPage = (_parent->media() != this);
256 auto roundRadius = inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large;
257 auto roundCorners = inWebPage ? RectPart::AllCorners : ((isBubbleTop() ? (RectPart::TopLeft | RectPart::TopRight) : RectPart::None)
258 | ((isRoundedInBubbleBottom() && _caption.isEmpty()) ? (RectPart::BottomLeft | RectPart::BottomRight) : RectPart::None));
259 const auto pix = [&] {
260 if (const auto large = _dataMedia->image(PhotoSize::Large)) {
261 return large->pixSingle(_pixw, _pixh, paintw, painth, roundRadius, roundCorners);
262 } else if (const auto thumbnail = _dataMedia->image(
263 PhotoSize::Thumbnail)) {
264 return thumbnail->pixBlurredSingle(_pixw, _pixh, paintw, painth, roundRadius, roundCorners);
265 } else if (const auto small = _dataMedia->image(
266 PhotoSize::Small)) {
267 return small->pixBlurredSingle(_pixw, _pixh, paintw, painth, roundRadius, roundCorners);
268 } else if (const auto blurred = _dataMedia->thumbnailInline()) {
269 return blurred->pixBlurredSingle(_pixw, _pixh, paintw, painth, roundRadius, roundCorners);
270 } else {
271 return QPixmap();
272 }
273 }();
274 p.drawPixmap(rthumb.topLeft(), pix);
275 if (selected) {
276 Ui::FillComplexOverlayRect(p, rthumb, roundRadius, roundCorners);
277 }
278 }
279 if (radial || (!loaded && !_data->loading())) {
280 const auto radialOpacity = (radial && loaded && !_data->uploading())
281 ? _animation->radial.opacity() :
282 1.;
283 const auto innerSize = st::msgFileLayout.thumbSize;
284 QRect inner(rthumb.x() + (rthumb.width() - innerSize) / 2, rthumb.y() + (rthumb.height() - innerSize) / 2, innerSize, innerSize);
285 p.setPen(Qt::NoPen);
286 if (selected) {
287 p.setBrush(st::msgDateImgBgSelected);
288 } else if (isThumbAnimation()) {
289 auto over = _animation->a_thumbOver.value(1.);
290 p.setBrush(anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, over));
291 } else {
292 auto over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel);
293 p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg);
294 }
295
296 p.setOpacity(radialOpacity * p.opacity());
297
298 {
299 PainterHighQualityEnabler hq(p);
300 p.drawEllipse(inner);
301 }
302
303 p.setOpacity(radialOpacity);
304 auto icon = [&]() -> const style::icon* {
305 if (radial || _data->loading()) {
306 return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel);
307 }
308 return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload);
309 }();
310 if (icon) {
311 icon->paintInCenter(p, inner);
312 }
313 p.setOpacity(1);
314 if (radial) {
315 QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine)));
316 _animation->radial.draw(p, rinner, st::msgFileRadialLine, selected ? st::historyFileThumbRadialFgSelected : st::historyFileThumbRadialFg);
317 }
318 }
319
320 // date
321 if (!_caption.isEmpty()) {
322 auto outbg = _parent->hasOutLayout();
323 p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg));
324 _caption.draw(p, st::msgPadding.left(), painty + painth + st::mediaCaptionSkip, captionw, style::al_left, 0, -1, selection);
325 } else if (!inWebPage) {
326 auto fullRight = paintx + paintw;
327 auto fullBottom = painty + painth;
328 if (needInfoDisplay()) {
329 _parent->drawInfo(p, fullRight, fullBottom, 2 * paintx + paintw, selected, InfoDisplayType::Image);
330 }
331 if (const auto size = bubble ? std::nullopt : _parent->rightActionSize()) {
332 auto fastShareLeft = (fullRight + st::historyFastShareLeft);
333 auto fastShareTop = (fullBottom - st::historyFastShareBottom - size->height());
334 _parent->drawRightAction(p, fastShareLeft, fastShareTop, 2 * paintx + paintw);
335 }
336 }
337 }
338
339 void Photo::paintUserpicFrame(
340 Painter &p,
341 QPoint photoPosition,
342 bool selected) const {
343 const auto autoplay = _data->videoCanBePlayed() && videoAutoplayEnabled();
344 const auto startPlay = autoplay && !_streamed;
345 if (startPlay) {
346 const_cast<Photo*>(this)->playAnimation(true);
347 } else {
348 checkStreamedIsStarted();
349 }
350
351 const auto size = QSize{ _pixw, _pixh };
352 const auto rect = QRect(photoPosition, size);
353
354 if (_streamed
355 && _streamed->instance.player().ready()
356 && !_streamed->instance.player().videoSize().isEmpty()) {
357 const auto paused = _parent->delegate()->elementIsGifPaused();
358 auto request = ::Media::Streaming::FrameRequest();
359 request.outer = size * cIntRetinaFactor();
360 request.resize = size * cIntRetinaFactor();
361 request.radius = ImageRoundRadius::Ellipse;
362 if (_streamed->instance.playerLocked()) {
363 if (_streamed->frozenFrame.isNull()) {
364 _streamed->frozenFrame = _streamed->instance.frame(request);
365 }
366 p.drawImage(rect, _streamed->frozenFrame);
367 } else {
368 _streamed->frozenFrame = QImage();
369 p.drawImage(rect, _streamed->instance.frame(request));
370 if (!paused) {
371 _streamed->instance.markFrameShown();
372 }
373 }
374 return;
375 }
376 const auto pix = [&] {
377 if (const auto large = _dataMedia->image(PhotoSize::Large)) {
378 return large->pixCircled(_pixw, _pixh);
379 } else if (const auto thumbnail = _dataMedia->image(
380 PhotoSize::Thumbnail)) {
381 return thumbnail->pixBlurredCircled(_pixw, _pixh);
382 } else if (const auto small = _dataMedia->image(
383 PhotoSize::Small)) {
384 return small->pixBlurredCircled(_pixw, _pixh);
385 } else if (const auto blurred = _dataMedia->thumbnailInline()) {
386 return blurred->pixBlurredCircled(_pixw, _pixh);
387 } else {
388 return QPixmap();
389 }
390 }();
391 p.drawPixmap(rect, pix);
392
393 if (_data->videoCanBePlayed() && !_streamed) {
394 const auto innerSize = st::msgFileLayout.thumbSize;
395 auto inner = QRect(rect.x() + (rect.width() - innerSize) / 2, rect.y() + (rect.height() - innerSize) / 2, innerSize, innerSize);
396 p.setPen(Qt::NoPen);
397 if (selected) {
398 p.setBrush(st::msgDateImgBgSelected);
399 } else {
400 const auto over = ClickHandler::showAsActive(_openl);
401 p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg);
402 }
403 {
404 PainterHighQualityEnabler hq(p);
405 p.drawEllipse(inner);
406 }
407 const auto icon = [&]() -> const style::icon * {
408 return &(selected ? st::historyFileThumbPlaySelected : st::historyFileThumbPlay);
409 }();
410 if (icon) {
411 icon->paintInCenter(p, inner);
412 }
413 }
414 }
415
416 TextState Photo::textState(QPoint point, StateRequest request) const {
417 auto result = TextState(_parent);
418
419 if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) {
420 return result;
421 }
422 auto paintx = 0, painty = 0, paintw = width(), painth = height();
423 auto bubble = _parent->hasBubble();
424
425 if (bubble && !_caption.isEmpty()) {
426 const auto captionw = paintw
427 - st::msgPadding.left()
428 - st::msgPadding.right();
429 painth -= _caption.countHeight(captionw);
430 if (isBubbleBottom()) {
431 painth -= st::msgPadding.bottom();
432 }
433 if (QRect(st::msgPadding.left(), painth, captionw, height() - painth).contains(point)) {
434 result = TextState(_parent, _caption.getState(
435 point - QPoint(st::msgPadding.left(), painth),
436 captionw,
437 request.forText()));
438 return result;
439 }
440 painth -= st::mediaCaptionSkip;
441 }
442 if (QRect(paintx, painty, paintw, painth).contains(point)) {
443 ensureDataMediaCreated();
444 if (_data->uploading()) {
445 result.link = _cancell;
446 } else if (_dataMedia->loaded()) {
447 result.link = _openl;
448 } else if (_data->loading()) {
449 result.link = _cancell;
450 } else {
451 result.link = _savel;
452 }
453 }
454 if (_caption.isEmpty() && _parent->media() == this) {
455 auto fullRight = paintx + paintw;
456 auto fullBottom = painty + painth;
457 if (_parent->pointInTime(fullRight, fullBottom, point, InfoDisplayType::Image)) {
458 result.cursor = CursorState::Date;
459 }
460 if (const auto size = bubble ? std::nullopt : _parent->rightActionSize()) {
461 auto fastShareLeft = (fullRight + st::historyFastShareLeft);
462 auto fastShareTop = (fullBottom - st::historyFastShareBottom - size->height());
463 if (QRect(fastShareLeft, fastShareTop, size->width(), size->height()).contains(point)) {
464 result.link = _parent->rightActionLink();
465 }
466 }
467 }
468 return result;
469 }
470
471 QSize Photo::sizeForGroupingOptimal(int maxWidth) const {
472 const auto width = _data->width();
473 const auto height = _data->height();
474 return { std::max(width, 1), std::max(height, 1) };
475 }
476
477 QSize Photo::sizeForGrouping(int width) const {
478 return sizeForGroupingOptimal(width);
479 }
480
481 void Photo::drawGrouped(
482 Painter &p,
483 const QRect &clip,
484 TextSelection selection,
485 crl::time ms,
486 const QRect &geometry,
487 RectParts sides,
488 RectParts corners,
489 float64 highlightOpacity,
490 not_null<uint64*> cacheKey,
491 not_null<QPixmap*> cache) const {
492 ensureDataMediaCreated();
493 _dataMedia->automaticLoad(_realParent->fullId(), _parent->data());
494
495 validateGroupedCache(geometry, corners, cacheKey, cache);
496
497 const auto selected = (selection == FullSelection);
498 const auto loaded = _dataMedia->loaded();
499 const auto displayLoading = _data->displayLoading();
500 const auto bubble = _parent->hasBubble();
501
502 if (displayLoading) {
503 ensureAnimation();
504 if (!_animation->radial.animating()) {
505 _animation->radial.start(_dataMedia->progress());
506 }
507 }
508 const auto radial = isRadialAnimation();
509
510 if (!bubble) {
511 // App::roundShadow(p, 0, 0, paintw, painth, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners);
512 }
513 p.drawPixmap(geometry.topLeft(), *cache);
514
515 const auto overlayOpacity = selected
516 ? (1. - highlightOpacity)
517 : highlightOpacity;
518 if (overlayOpacity > 0.) {
519 p.setOpacity(overlayOpacity);
520 const auto roundRadius = ImageRoundRadius::Large;
521 Ui::FillComplexOverlayRect(p, geometry, roundRadius, corners);
522 if (!selected) {
523 Ui::FillComplexOverlayRect(p, geometry, roundRadius, corners);
524 }
525 p.setOpacity(1.);
526 }
527
528 const auto displayState = radial
529 || (!loaded && !_data->loading())
530 || _data->waitingForAlbum();
531 if (displayState) {
532 const auto radialOpacity = radial
533 ? _animation->radial.opacity()
534 : 1.;
535 const auto backOpacity = (loaded && !_data->uploading())
536 ? radialOpacity
537 : 1.;
538 const auto radialSize = st::historyGroupRadialSize;
539 const auto inner = QRect(
540 geometry.x() + (geometry.width() - radialSize) / 2,
541 geometry.y() + (geometry.height() - radialSize) / 2,
542 radialSize,
543 radialSize);
544 p.setPen(Qt::NoPen);
545 if (selected) {
546 p.setBrush(st::msgDateImgBgSelected);
547 } else if (isThumbAnimation()) {
548 auto over = _animation->a_thumbOver.value(1.);
549 p.setBrush(anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, over));
550 } else {
551 auto over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel);
552 p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg);
553 }
554
555 p.setOpacity(backOpacity * p.opacity());
556
557 {
558 PainterHighQualityEnabler hq(p);
559 p.drawEllipse(inner);
560 }
561
562 const auto icon = [&]() -> const style::icon* {
563 if (_data->waitingForAlbum()) {
564 return &(selected ? st::historyFileThumbWaitingSelected : st::historyFileThumbWaiting);
565 } else if (radial || _data->loading()) {
566 return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel);
567 }
568 return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload);
569 }();
570 const auto previous = [&]() -> const style::icon* {
571 if (_data->waitingForAlbum()) {
572 return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel);
573 }
574 return nullptr;
575 }();
576 p.setOpacity(backOpacity);
577 if (icon) {
578 if (previous && radialOpacity > 0. && radialOpacity < 1.) {
579 PaintInterpolatedIcon(p, *icon, *previous, radialOpacity, inner);
580 } else {
581 icon->paintInCenter(p, inner);
582 }
583 }
584 p.setOpacity(1);
585 if (radial) {
586 const auto line = st::historyGroupRadialLine;
587 const auto rinner = inner.marginsRemoved({ line, line, line, line });
588 const auto color = selected
589 ? st::historyFileThumbRadialFgSelected
590 : st::historyFileThumbRadialFg;
591 _animation->radial.draw(p, rinner, line, color);
592 }
593 }
594 }
595
596 TextState Photo::getStateGrouped(
597 const QRect &geometry,
598 RectParts sides,
599 QPoint point,
600 StateRequest request) const {
601 if (!geometry.contains(point)) {
602 return {};
603 }
604 ensureDataMediaCreated();
605 return TextState(_parent, _data->uploading()
606 ? _cancell
607 : _dataMedia->loaded()
608 ? _openl
609 : _data->loading()
610 ? _cancell
611 : _savel);
612 }
613
614 float64 Photo::dataProgress() const {
615 ensureDataMediaCreated();
616 return _dataMedia->progress();
617 }
618
619 bool Photo::dataFinished() const {
620 return !_data->loading()
621 && (!_data->uploading() || _data->waitingForAlbum());
622 }
623
624 bool Photo::dataLoaded() const {
625 ensureDataMediaCreated();
626 return _dataMedia->loaded();
627 }
628
629 bool Photo::needInfoDisplay() const {
630 return (_parent->data()->id < 0
631 || _parent->isUnderCursor()
632 || _parent->isLastAndSelfMessage());
633 }
634
635 void Photo::validateGroupedCache(
636 const QRect &geometry,
637 RectParts corners,
638 not_null<uint64*> cacheKey,
639 not_null<QPixmap*> cache) const {
640 using Option = Images::Option;
641
642 ensureDataMediaCreated();
643
644 const auto loaded = _dataMedia->loaded();
645 const auto loadLevel = loaded
646 ? 2
647 : (_dataMedia->thumbnailInline()
648 || _dataMedia->image(PhotoSize::Small)
649 || _dataMedia->image(PhotoSize::Thumbnail))
650 ? 1
651 : 0;
652 const auto width = geometry.width();
653 const auto height = geometry.height();
654 const auto options = Option::Smooth
655 | Option::RoundedLarge
656 | (loaded ? Option::None : Option::Blurred)
657 | ((corners & RectPart::TopLeft) ? Option::RoundedTopLeft : Option::None)
658 | ((corners & RectPart::TopRight) ? Option::RoundedTopRight : Option::None)
659 | ((corners & RectPart::BottomLeft) ? Option::RoundedBottomLeft : Option::None)
660 | ((corners & RectPart::BottomRight) ? Option::RoundedBottomRight : Option::None);
661 const auto key = (uint64(width) << 48)
662 | (uint64(height) << 32)
663 | (uint64(options) << 16)
664 | (uint64(loadLevel));
665 if (*cacheKey == key) {
666 return;
667 }
668
669 const auto originalWidth = style::ConvertScale(_data->width());
670 const auto originalHeight = style::ConvertScale(_data->height());
671 const auto pixSize = Ui::GetImageScaleSizeForGeometry(
672 { originalWidth, originalHeight },
673 { width, height });
674 const auto pixWidth = pixSize.width() * cIntRetinaFactor();
675 const auto pixHeight = pixSize.height() * cIntRetinaFactor();
676 const auto image = _dataMedia->image(PhotoSize::Large)
677 ? _dataMedia->image(PhotoSize::Large)
678 : _dataMedia->image(PhotoSize::Thumbnail)
679 ? _dataMedia->image(PhotoSize::Thumbnail)
680 : _dataMedia->image(PhotoSize::Small)
681 ? _dataMedia->image(PhotoSize::Small)
682 : _dataMedia->thumbnailInline()
683 ? _dataMedia->thumbnailInline()
684 : Image::BlankMedia().get();
685
686 *cacheKey = key;
687 *cache = image->pixNoCache(pixWidth, pixHeight, options, width, height);
688 }
689
690 bool Photo::createStreamingObjects() {
691 using namespace ::Media::Streaming;
692
693 setStreamed(std::make_unique<Streamed>(
694 history()->owner().streaming().sharedDocument(
695 _data,
696 _realParent->fullId())));
697 _streamed->instance.player().updates(
698 ) | rpl::start_with_next_error([=](Update &&update) {
699 handleStreamingUpdate(std::move(update));
700 }, [=](Error &&error) {
701 handleStreamingError(std::move(error));
702 }, _streamed->instance.lifetime());
703 if (_streamed->instance.ready()) {
704 streamingReady(base::duplicate(_streamed->instance.info()));
705 }
706 if (!_streamed->instance.valid()) {
707 stopAnimation();
708 return false;
709 }
710 checkStreamedIsStarted();
711 return true;
712 }
713
714 void Photo::setStreamed(std::unique_ptr<Streamed> value) {
715 const auto removed = (_streamed && !value);
716 const auto set = (!_streamed && value);
717 _streamed = std::move(value);
718 if (set) {
719 history()->owner().registerHeavyViewPart(_parent);
720 } else if (removed) {
721 _parent->checkHeavyPart();
722 }
723 }
724
725 void Photo::handleStreamingUpdate(::Media::Streaming::Update &&update) {
726 using namespace ::Media::Streaming;
727
728 v::match(update.data, [&](Information &update) {
729 streamingReady(std::move(update));
730 }, [&](const PreloadedVideo &update) {
731 }, [&](const UpdateVideo &update) {
732 repaintStreamedContent();
733 }, [&](const PreloadedAudio &update) {
734 }, [&](const UpdateAudio &update) {
735 }, [&](const WaitingForData &update) {
736 }, [&](MutedByOther) {
737 }, [&](Finished) {
738 });
739 }
740
741 void Photo::handleStreamingError(::Media::Streaming::Error &&error) {
742 _data->setVideoPlaybackFailed();
743 stopAnimation();
744 }
745
746 void Photo::repaintStreamedContent() {
747 if (_streamed && !_streamed->frozenFrame.isNull()) {
748 return;
749 } else if (_parent->delegate()->elementIsGifPaused()) {
750 return;
751 }
752 history()->owner().requestViewRepaint(_parent);
753 }
754
755 void Photo::streamingReady(::Media::Streaming::Information &&info) {
756 history()->owner().requestViewRepaint(_parent);
757 }
758
759 void Photo::checkAnimation() {
760 if (_streamed && !videoAutoplayEnabled()) {
761 stopAnimation();
762 }
763 }
764
765 void Photo::stopAnimation() {
766 setStreamed(nullptr);
767 }
768
769 void Photo::playAnimation(bool autoplay) {
770 ensureDataMediaCreated();
771 if (_streamed && autoplay) {
772 return;
773 } else if (_streamed && videoAutoplayEnabled()) {
774 Core::App().showPhoto(_data, _parent->data());
775 return;
776 }
777 if (_streamed) {
778 stopAnimation();
779 } else if (_data->videoCanBePlayed()) {
780 if (!videoAutoplayEnabled()) {
781 history()->owner().checkPlayingAnimations();
782 }
783 if (!createStreamingObjects()) {
784 _data->setVideoPlaybackFailed();
785 return;
786 }
787 }
788 }
789
790 void Photo::checkStreamedIsStarted() const {
791 if (!_streamed) {
792 return;
793 } else if (_streamed->instance.paused()) {
794 _streamed->instance.resume();
795 }
796 if (_streamed
797 && !_streamed->instance.active()
798 && !_streamed->instance.failed()) {
799 const auto position = _data->videoStartPosition();
800 auto options = ::Media::Streaming::PlaybackOptions();
801 options.position = position;
802 options.mode = ::Media::Streaming::Mode::Video;
803 options.loop = true;
804 _streamed->instance.play(options);
805 }
806 }
807
808 bool Photo::videoAutoplayEnabled() const {
809 return Data::AutoDownload::ShouldAutoPlay(
810 _data->session().settings().autoDownload(),
811 _realParent->history()->peer,
812 _data);
813 }
814
815 TextForMimeData Photo::selectedText(TextSelection selection) const {
816 return _caption.toTextForMimeData(selection);
817 }
818
819 bool Photo::needsBubble() const {
820 if (!_caption.isEmpty()) {
821 return true;
822 }
823 const auto item = _parent->data();
824 if (item->toHistoryMessage()) {
825 return item->repliesAreComments()
826 || item->externalReply()
827 || item->viaBot()
828 || _parent->displayedReply()
829 || _parent->displayForwardedFrom()
830 || _parent->displayFromName();
831 }
832 return false;
833 }
834
835 bool Photo::isReadyForOpen() const {
836 ensureDataMediaCreated();
837 return _dataMedia->loaded();
838 }
839
840 void Photo::parentTextUpdated() {
841 _caption = (_parent->media() == this)
842 ? createCaption(_parent->data())
843 : Ui::Text::String();
844 history()->owner().requestViewResize(_parent);
845 }
846
847 } // namespace HistoryView