overview_layout.cpp 68 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "overview/overview_layout.h"
  8. #include "overview/overview_layout_delegate.h"
  9. #include "core/ui_integration.h" // TextContext
  10. #include "data/data_document.h"
  11. #include "data/data_document_resolver.h"
  12. #include "data/data_session.h"
  13. #include "data/data_web_page.h"
  14. #include "data/data_peer.h"
  15. #include "data/data_photo_media.h"
  16. #include "data/data_document_media.h"
  17. #include "data/data_file_click_handler.h"
  18. #include "ui/boxes/confirm_box.h"
  19. #include "lang/lang_keys.h"
  20. #include "layout/layout_selection.h"
  21. #include "storage/file_upload.h"
  22. #include "main/main_session.h"
  23. #include "media/audio/media_audio.h"
  24. #include "media/player/media_player_instance.h"
  25. #include "storage/localstorage.h"
  26. #include "history/history.h"
  27. #include "history/history_item.h"
  28. #include "history/history_item_components.h"
  29. #include "history/history_item_helpers.h"
  30. #include "history/view/history_view_cursor_state.h"
  31. #include "history/view/media/history_view_document.h" // DrawThumbnailAsSongCover
  32. #include "base/unixtime.h"
  33. #include "ui/effects/round_checkbox.h"
  34. #include "ui/effects/spoiler_mess.h"
  35. #include "ui/image/image.h"
  36. #include "ui/text/format_song_document_name.h"
  37. #include "ui/text/format_values.h"
  38. #include "ui/text/text_options.h"
  39. #include "ui/text/text_utilities.h"
  40. #include "ui/cached_round_corners.h"
  41. #include "ui/painter.h"
  42. #include "ui/power_saving.h"
  43. #include "ui/ui_utility.h"
  44. #include "styles/style_chat.h"
  45. #include "styles/style_chat_helpers.h"
  46. #include "styles/style_overview.h"
  47. namespace Overview {
  48. namespace Layout {
  49. namespace {
  50. using TextState = HistoryView::TextState;
  51. TextParseOptions _documentNameOptions = {
  52. TextParseMultiline | TextParseLinks | TextParseMarkdown, // flags
  53. 0, // maxw
  54. 0, // maxh
  55. Qt::LayoutDirectionAuto, // dir
  56. };
  57. constexpr auto kMaxInlineArea = 1280 * 720;
  58. constexpr auto kStoryRatio = 1.46;
  59. [[nodiscard]] bool CanPlayInline(not_null<DocumentData*> document) {
  60. const auto dimensions = document->dimensions;
  61. return dimensions.width() * dimensions.height() <= kMaxInlineArea;
  62. }
  63. [[nodiscard]] QImage CropMediaFrame(QImage image, int width, int height) {
  64. const auto ratio = style::DevicePixelRatio();
  65. width *= ratio;
  66. height *= ratio;
  67. const auto finalize = [&](QImage result) {
  68. result = result.scaled(
  69. width,
  70. height,
  71. Qt::IgnoreAspectRatio,
  72. Qt::SmoothTransformation);
  73. result.setDevicePixelRatio(ratio);
  74. return result;
  75. };
  76. if (image.width() * height == image.height() * width) {
  77. if (image.width() != width) {
  78. return finalize(std::move(image));
  79. }
  80. image.setDevicePixelRatio(ratio);
  81. return image;
  82. } else if (image.width() * height > image.height() * width) {
  83. const auto use = (image.height() * width) / height;
  84. const auto skip = (image.width() - use) / 2;
  85. return finalize(image.copy(skip, 0, use, image.height()));
  86. } else {
  87. const auto use = (image.width() * height) / width;
  88. const auto skip = (image.height() - use) / 2;
  89. return finalize(image.copy(0, skip, image.width(), use));
  90. }
  91. }
  92. } // namespace
  93. class Checkbox {
  94. public:
  95. template <typename UpdateCallback>
  96. Checkbox(UpdateCallback callback, const style::RoundCheckbox &st)
  97. : _updateCallback(callback)
  98. , _check(st, _updateCallback) {
  99. }
  100. void paint(Painter &p, QPoint position, int outerWidth, bool selected, bool selecting);
  101. void setActive(bool active);
  102. void setPressed(bool pressed);
  103. void invalidateCache() {
  104. _check.invalidateCache();
  105. }
  106. private:
  107. void startAnimation();
  108. Fn<void()> _updateCallback;
  109. Ui::RoundCheckbox _check;
  110. Ui::Animations::Simple _pression;
  111. bool _active = false;
  112. bool _pressed = false;
  113. };
  114. void Checkbox::paint(Painter &p, QPoint position, int outerWidth, bool selected, bool selecting) {
  115. _check.setDisplayInactive(selecting);
  116. _check.setChecked(selected);
  117. const auto pression = _pression.value((_active && _pressed) ? 1. : 0.);
  118. const auto masterScale = 1. - (1. - st::overviewCheckPressedSize) * pression;
  119. _check.paint(p, position.x(), position.y(), outerWidth, masterScale);
  120. }
  121. void Checkbox::setActive(bool active) {
  122. _active = active;
  123. if (_pressed) {
  124. startAnimation();
  125. }
  126. }
  127. void Checkbox::setPressed(bool pressed) {
  128. _pressed = pressed;
  129. if (_active) {
  130. startAnimation();
  131. }
  132. }
  133. void Checkbox::startAnimation() {
  134. auto showPressed = (_pressed && _active);
  135. _pression.start(_updateCallback, showPressed ? 0. : 1., showPressed ? 1. : 0., st::overviewCheck.duration);
  136. }
  137. ItemBase::ItemBase(
  138. not_null<Delegate*> delegate,
  139. not_null<HistoryItem*> parent)
  140. : _delegate(delegate)
  141. , _parent(parent)
  142. , _dateTime(ItemDateTime(parent)) {
  143. }
  144. ItemBase::~ItemBase() = default;
  145. QDateTime ItemBase::dateTime() const {
  146. return _dateTime;
  147. }
  148. void ItemBase::clickHandlerActiveChanged(
  149. const ClickHandlerPtr &action,
  150. bool active) {
  151. _parent->history()->session().data().requestItemRepaint(_parent);
  152. if (_check) {
  153. _check->setActive(active);
  154. }
  155. }
  156. void ItemBase::clickHandlerPressedChanged(
  157. const ClickHandlerPtr &action,
  158. bool pressed) {
  159. _parent->history()->session().data().requestItemRepaint(_parent);
  160. if (_check) {
  161. _check->setPressed(pressed);
  162. }
  163. }
  164. void ItemBase::invalidateCache() {
  165. if (_check) {
  166. _check->invalidateCache();
  167. }
  168. }
  169. void ItemBase::paintCheckbox(
  170. Painter &p,
  171. QPoint position,
  172. bool selected,
  173. const PaintContext *context) {
  174. if (selected || context->selecting) {
  175. ensureCheckboxCreated();
  176. }
  177. if (_check) {
  178. _check->paint(p, position, _width, selected, context->selecting);
  179. }
  180. }
  181. const style::RoundCheckbox &ItemBase::checkboxStyle() const {
  182. return st::overviewCheck;
  183. }
  184. void ItemBase::ensureCheckboxCreated() {
  185. if (_check) {
  186. return;
  187. }
  188. const auto repaint = [=] {
  189. _parent->history()->session().data().requestItemRepaint(_parent);
  190. };
  191. _check = std::make_unique<Checkbox>(repaint, checkboxStyle());
  192. }
  193. void RadialProgressItem::setDocumentLinks(
  194. not_null<DocumentData*> document,
  195. bool forceOpen) {
  196. const auto context = parent()->fullId();
  197. setLinks(
  198. std::make_shared<DocumentOpenClickHandler>(
  199. document,
  200. crl::guard(this, [=](FullMsgId id) {
  201. clearSpoiler();
  202. delegate()->openDocument(document, id, forceOpen);
  203. }),
  204. context),
  205. std::make_shared<DocumentSaveClickHandler>(document, context),
  206. std::make_shared<DocumentCancelClickHandler>(
  207. document,
  208. nullptr,
  209. context));
  210. }
  211. void RadialProgressItem::clickHandlerActiveChanged(
  212. const ClickHandlerPtr &action,
  213. bool active) {
  214. ItemBase::clickHandlerActiveChanged(action, active);
  215. if (action == _openl || action == _savel || action == _cancell) {
  216. if (iconAnimated()) {
  217. const auto repaint = [=] {
  218. parent()->history()->session().data().requestItemRepaint(
  219. parent());
  220. };
  221. _a_iconOver.start(
  222. repaint,
  223. active ? 0. : 1.,
  224. active ? 1. : 0.,
  225. st::msgFileOverDuration);
  226. }
  227. }
  228. }
  229. void RadialProgressItem::setLinks(
  230. ClickHandlerPtr &&openl,
  231. ClickHandlerPtr &&savel,
  232. ClickHandlerPtr &&cancell) {
  233. _openl = std::move(openl);
  234. _savel = std::move(savel);
  235. _cancell = std::move(cancell);
  236. }
  237. void RadialProgressItem::radialAnimationCallback(crl::time now) const {
  238. const auto updated = [&] {
  239. return _radial->update(dataProgress(), dataFinished(), now);
  240. }();
  241. if (!anim::Disabled() || updated) {
  242. parent()->history()->session().data().requestItemRepaint(parent());
  243. }
  244. if (!_radial->animating()) {
  245. checkRadialFinished();
  246. }
  247. }
  248. void RadialProgressItem::ensureRadial() {
  249. if (_radial) {
  250. return;
  251. }
  252. _radial = std::make_unique<Ui::RadialAnimation>([=](crl::time now) {
  253. radialAnimationCallback(now);
  254. });
  255. }
  256. void RadialProgressItem::checkRadialFinished() const {
  257. if (_radial && !_radial->animating() && dataLoaded()) {
  258. _radial.reset();
  259. }
  260. }
  261. RadialProgressItem::~RadialProgressItem() = default;
  262. void StatusText::update(
  263. int64 newSize,
  264. int64 fullSize,
  265. TimeId duration,
  266. TimeId realDuration) {
  267. setSize(newSize);
  268. if (_size == Ui::FileStatusSizeReady) {
  269. _text = (duration >= 0) ? Ui::FormatDurationAndSizeText(duration, fullSize) : (duration < -1 ? Ui::FormatGifAndSizeText(fullSize) : Ui::FormatSizeText(fullSize));
  270. } else if (_size == Ui::FileStatusSizeLoaded) {
  271. _text = (duration >= 0) ? Ui::FormatDurationText(duration) : (duration < -1 ? u"GIF"_q : Ui::FormatSizeText(fullSize));
  272. } else if (_size == Ui::FileStatusSizeFailed) {
  273. _text = tr::lng_attach_failed(tr::now);
  274. } else if (_size >= 0) {
  275. _text = Ui::FormatDownloadText(_size, fullSize);
  276. } else {
  277. _text = Ui::FormatPlayedText(-_size - 1, realDuration);
  278. }
  279. }
  280. void StatusText::setSize(int64 newSize) {
  281. _size = newSize;
  282. }
  283. Photo::Photo(
  284. not_null<Delegate*> delegate,
  285. not_null<HistoryItem*> parent,
  286. not_null<PhotoData*> photo,
  287. MediaOptions options)
  288. : ItemBase(delegate, parent)
  289. , _data(photo)
  290. , _link(std::make_shared<PhotoOpenClickHandler>(
  291. photo,
  292. crl::guard(this, [=](FullMsgId id) {
  293. clearSpoiler();
  294. delegate->openPhoto(photo, id);
  295. }),
  296. parent->fullId()))
  297. , _spoiler(options.spoiler ? std::make_unique<Ui::SpoilerAnimation>([=] {
  298. delegate->repaintItem(this);
  299. }) : nullptr)
  300. , _pinned(options.pinned)
  301. , _story(options.story) {
  302. if (_data->inlineThumbnailBytes().isEmpty()
  303. && (_data->hasExact(Data::PhotoSize::Small)
  304. || _data->hasExact(Data::PhotoSize::Thumbnail))) {
  305. _data->load(Data::PhotoSize::Small, parent->fullId());
  306. }
  307. }
  308. Photo::~Photo() = default;
  309. void Photo::initDimensions() {
  310. _maxw = 2 * st::overviewPhotoMinSize;
  311. _minh = _story ? qRound(_maxw * kStoryRatio) : _maxw;
  312. }
  313. int32 Photo::resizeGetHeight(int32 width) {
  314. width = qMin(width, _maxw);
  315. if (_width != width) {
  316. _width = width;
  317. _height = _story ? qRound(_width * kStoryRatio) : _width;
  318. }
  319. return _height;
  320. }
  321. void Photo::paint(Painter &p, const QRect &clip, TextSelection selection, const PaintContext *context) {
  322. const auto selected = (selection == FullSelection);
  323. const auto widthChanged = (_pix.width()
  324. != (_width * style::DevicePixelRatio()));
  325. if (!_goodLoaded || widthChanged) {
  326. ensureDataMediaCreated();
  327. const auto good = !_spoiler
  328. && (_dataMedia->loaded()
  329. || _dataMedia->image(Data::PhotoSize::Thumbnail));
  330. if ((good && !_goodLoaded) || widthChanged) {
  331. _goodLoaded = good;
  332. _pix = QPixmap();
  333. if (_goodLoaded) {
  334. setPixFrom(_dataMedia->image(Data::PhotoSize::Large)
  335. ? _dataMedia->image(Data::PhotoSize::Large)
  336. : _dataMedia->image(Data::PhotoSize::Thumbnail));
  337. } else if (const auto small = _spoiler
  338. ? nullptr
  339. : _dataMedia->image(Data::PhotoSize::Small)) {
  340. setPixFrom(small);
  341. } else if (const auto blurred = _dataMedia->thumbnailInline()) {
  342. setPixFrom(blurred);
  343. }
  344. }
  345. }
  346. if (_pix.isNull()) {
  347. p.fillRect(0, 0, _width, _height, st::overviewPhotoBg);
  348. } else {
  349. p.drawPixmap(0, 0, _pix);
  350. }
  351. if (_spoiler) {
  352. const auto paused = context->paused || On(PowerSaving::kChatSpoiler);
  353. Ui::FillSpoilerRect(
  354. p,
  355. QRect(0, 0, _width, _height),
  356. Ui::DefaultImageSpoiler().frame(
  357. _spoiler->index(context->ms, paused)));
  358. }
  359. if (selected) {
  360. p.fillRect(0, 0, _width, _height, st::overviewPhotoSelectOverlay);
  361. }
  362. if (_pinned) {
  363. const auto &icon = selected
  364. ? st::storyPinnedIconSelected
  365. : st::storyPinnedIcon;
  366. icon.paint(p, _width - icon.width(), 0, _width);
  367. }
  368. const auto checkDelta = st::overviewCheckSkip + st::overviewCheck.size;
  369. const auto checkLeft = _width - checkDelta;
  370. const auto checkTop = _height - checkDelta;
  371. paintCheckbox(p, { checkLeft, checkTop }, selected, context);
  372. }
  373. void Photo::setPixFrom(not_null<Image*> image) {
  374. Expects(_width > 0 && _height > 0);
  375. auto img = image->original();
  376. if (!_goodLoaded) {
  377. img = Images::Blur(std::move(img));
  378. }
  379. _pix = Ui::PixmapFromImage(
  380. CropMediaFrame(std::move(img), _width, _height));
  381. // In case we have inline thumbnail we can unload all images and we still
  382. // won't get a blank image in the media viewer when the photo is opened.
  383. if (!_data->inlineThumbnailBytes().isEmpty()) {
  384. _dataMedia = nullptr;
  385. delegate()->unregisterHeavyItem(this);
  386. }
  387. }
  388. void Photo::ensureDataMediaCreated() const {
  389. if (_dataMedia) {
  390. return;
  391. }
  392. _dataMedia = _data->createMediaView();
  393. if (_data->inlineThumbnailBytes().isEmpty()) {
  394. _dataMedia->wanted(Data::PhotoSize::Small, parent()->fullId());
  395. }
  396. _dataMedia->wanted(Data::PhotoSize::Thumbnail, parent()->fullId());
  397. delegate()->registerHeavyItem(this);
  398. }
  399. void Photo::clearSpoiler() {
  400. if (_spoiler) {
  401. _spoiler = nullptr;
  402. _pix = QPixmap();
  403. delegate()->repaintItem(this);
  404. }
  405. }
  406. void Photo::itemDataChanged() {
  407. const auto pinned = parent()->isPinned();
  408. if (_pinned != pinned) {
  409. _pinned = pinned;
  410. delegate()->repaintItem(this);
  411. }
  412. }
  413. void Photo::clearHeavyPart() {
  414. _dataMedia = nullptr;
  415. }
  416. TextState Photo::getState(
  417. QPoint point,
  418. StateRequest request) const {
  419. if (hasPoint(point)) {
  420. return { parent(), _link };
  421. }
  422. return {};
  423. }
  424. Video::Video(
  425. not_null<Delegate*> delegate,
  426. not_null<HistoryItem*> parent,
  427. not_null<DocumentData*> video,
  428. MediaOptions options)
  429. : RadialProgressItem(delegate, parent)
  430. , _data(video)
  431. , _videoCover(LookupVideoCover(video, parent))
  432. , _duration(Ui::FormatDurationText(_data->duration() / 1000))
  433. , _spoiler(options.spoiler ? std::make_unique<Ui::SpoilerAnimation>([=] {
  434. delegate->repaintItem(this);
  435. }) : nullptr)
  436. , _pinned(options.pinned)
  437. , _story(options.story) {
  438. setDocumentLinks(_data);
  439. if (!_videoCover) {
  440. _data->loadThumbnail(parent->fullId());
  441. } else if (_videoCover->inlineThumbnailBytes().isEmpty()
  442. && (_videoCover->hasExact(Data::PhotoSize::Small)
  443. || _videoCover->hasExact(Data::PhotoSize::Thumbnail))) {
  444. _videoCover->load(Data::PhotoSize::Small, parent->fullId());
  445. }
  446. }
  447. Video::~Video() = default;
  448. void Video::initDimensions() {
  449. _maxw = 2 * st::overviewPhotoMinSize;
  450. _minh = _story ? qRound(_maxw * kStoryRatio) : _maxw;
  451. }
  452. int32 Video::resizeGetHeight(int32 width) {
  453. width = qMin(width, _maxw);
  454. if (_width != width) {
  455. _width = width;
  456. _height = _story ? qRound(_width * kStoryRatio) : _width;
  457. }
  458. return _height;
  459. }
  460. void Video::paint(Painter &p, const QRect &clip, TextSelection selection, const PaintContext *context) {
  461. ensureDataMediaCreated();
  462. const auto selected = (selection == FullSelection);
  463. const auto blurred = _videoCover
  464. ? _videoCoverMedia->thumbnailInline()
  465. : _dataMedia->thumbnailInline();
  466. const auto thumbnail = _spoiler
  467. ? nullptr
  468. : _videoCover
  469. ? _videoCoverMedia->image(Data::PhotoSize::Small)
  470. : _dataMedia->thumbnail();
  471. const auto good = _spoiler
  472. ? nullptr
  473. : _videoCover
  474. ? _videoCoverMedia->image(Data::PhotoSize::Large)
  475. : _dataMedia->goodThumbnail();
  476. bool loaded = dataLoaded(), displayLoading = _data->displayLoading();
  477. if (displayLoading) {
  478. ensureRadial();
  479. if (!_radial->animating()) {
  480. _radial->start(dataProgress());
  481. }
  482. }
  483. updateStatusText();
  484. const auto radial = isRadialAnimation();
  485. const auto radialOpacity = radial ? _radial->opacity() : 0.;
  486. if ((blurred || thumbnail || good)
  487. && ((_pix.width() != _width * style::DevicePixelRatio())
  488. || (_pixBlurred && (thumbnail || good)))) {
  489. auto img = good
  490. ? good->original()
  491. : thumbnail
  492. ? thumbnail->original()
  493. : Images::Blur(blurred->original());
  494. _pix = Ui::PixmapFromImage(
  495. CropMediaFrame(std::move(img), _width, _height));
  496. _pixBlurred = !(thumbnail || good);
  497. }
  498. if (_pix.isNull()) {
  499. p.fillRect(0, 0, _width, _height, st::overviewPhotoBg);
  500. } else {
  501. p.drawPixmap(0, 0, _pix);
  502. }
  503. if (_spoiler) {
  504. const auto paused = context->paused || On(PowerSaving::kChatSpoiler);
  505. Ui::FillSpoilerRect(
  506. p,
  507. QRect(0, 0, _width, _height),
  508. Ui::DefaultImageSpoiler().frame(
  509. _spoiler->index(context->ms, paused)));
  510. }
  511. if (selected) {
  512. p.fillRect(QRect(0, 0, _width, _height), st::overviewPhotoSelectOverlay);
  513. }
  514. if (_pinned) {
  515. const auto &icon = selected
  516. ? st::storyPinnedIconSelected
  517. : st::storyPinnedIcon;
  518. icon.paint(p, _width - icon.width(), 0, _width);
  519. }
  520. if (!selected && !context->selecting && radialOpacity < 1.) {
  521. if (clip.intersects(QRect(0, _height - st::normalFont->height, _width, st::normalFont->height))) {
  522. const auto download = !loaded && !_dataMedia->canBePlayed(parent());
  523. const auto &icon = download
  524. ? (selected ? st::overviewVideoDownloadSelected : st::overviewVideoDownload)
  525. : (selected ? st::overviewVideoPlaySelected : st::overviewVideoPlay);
  526. const auto text = download ? _status.text() : _duration;
  527. const auto margin = st::overviewVideoStatusMargin;
  528. const auto padding = st::overviewVideoStatusPadding;
  529. const auto statusX = margin + padding.x(), statusY = _height - margin - padding.y() - st::normalFont->height;
  530. const auto statusW = icon.width() + padding.x() + st::normalFont->width(text) + 2 * padding.x();
  531. const auto statusH = st::normalFont->height + 2 * padding.y();
  532. p.setOpacity(1. - radialOpacity);
  533. Ui::FillRoundRect(p, statusX - padding.x(), statusY - padding.y(), statusW, statusH, selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? Ui::OverviewVideoSelectedCorners : Ui::OverviewVideoCorners);
  534. p.setFont(st::normalFont);
  535. p.setPen(st::msgDateImgFg);
  536. icon.paint(p, statusX, statusY + (st::normalFont->height - icon.height()) / 2, _width);
  537. p.drawTextLeft(statusX + icon.width() + padding.x(), statusY, _width, text, statusW - 2 * padding.x());
  538. }
  539. }
  540. QRect inner((_width - st::overviewVideoRadialSize) / 2, (_height - st::overviewVideoRadialSize) / 2, st::overviewVideoRadialSize, st::overviewVideoRadialSize);
  541. if (radial && clip.intersects(inner)) {
  542. p.setOpacity(radialOpacity);
  543. p.setPen(Qt::NoPen);
  544. if (selected) {
  545. p.setBrush(st::msgDateImgBgSelected);
  546. } else {
  547. auto over = ClickHandler::showAsActive((_data->loading() || _data->uploading()) ? _cancell : (loaded || _dataMedia->canBePlayed(parent())) ? _openl : _savel);
  548. p.setBrush(anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, _a_iconOver.value(over ? 1. : 0.)));
  549. }
  550. {
  551. PainterHighQualityEnabler hq(p);
  552. p.drawEllipse(inner);
  553. }
  554. const auto icon = [&] {
  555. return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel);
  556. }();
  557. icon->paintInCenter(p, inner);
  558. if (radial) {
  559. p.setOpacity(1);
  560. QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine)));
  561. _radial->draw(p, rinner, st::msgFileRadialLine, selected ? st::historyFileThumbRadialFgSelected : st::historyFileThumbRadialFg);
  562. }
  563. }
  564. p.setOpacity(1);
  565. const auto checkDelta = st::overviewCheckSkip + st::overviewCheck.size;
  566. const auto checkLeft = _width - checkDelta;
  567. const auto checkTop = _height - checkDelta;
  568. paintCheckbox(p, { checkLeft, checkTop }, selected, context);
  569. }
  570. void Video::ensureDataMediaCreated() const {
  571. if (_dataMedia && (!_videoCover || _videoCoverMedia)) {
  572. return;
  573. }
  574. _dataMedia = _data->createMediaView();
  575. if (_videoCover) {
  576. _videoCoverMedia = _videoCover->createMediaView();
  577. _videoCover->load(Data::PhotoSize::Large, parent()->fullId());
  578. } else {
  579. _dataMedia->goodThumbnailWanted();
  580. _dataMedia->thumbnailWanted(parent()->fullId());
  581. }
  582. delegate()->registerHeavyItem(this);
  583. }
  584. void Video::clearSpoiler() {
  585. if (_spoiler) {
  586. _spoiler = nullptr;
  587. _pix = QPixmap();
  588. delegate()->repaintItem(this);
  589. }
  590. }
  591. void Video::itemDataChanged() {
  592. const auto pinned = parent()->isPinned();
  593. if (_pinned != pinned) {
  594. _pinned = pinned;
  595. delegate()->repaintItem(this);
  596. }
  597. }
  598. void Video::clearHeavyPart() {
  599. _dataMedia = nullptr;
  600. }
  601. float64 Video::dataProgress() const {
  602. ensureDataMediaCreated();
  603. return _dataMedia->progress();
  604. }
  605. bool Video::dataFinished() const {
  606. return !_data->loading();
  607. }
  608. bool Video::dataLoaded() const {
  609. ensureDataMediaCreated();
  610. return _dataMedia->loaded();
  611. }
  612. bool Video::iconAnimated() const {
  613. return true;
  614. }
  615. TextState Video::getState(
  616. QPoint point,
  617. StateRequest request) const {
  618. if (hasPoint(point)) {
  619. ensureDataMediaCreated();
  620. const auto link = (_data->loading() || _data->uploading())
  621. ? _cancell
  622. : (dataLoaded() || _dataMedia->canBePlayed(parent()))
  623. ? _openl
  624. : _savel;
  625. return { parent(), link };
  626. }
  627. return {};
  628. }
  629. void Video::updateStatusText() {
  630. auto statusSize = int64();
  631. if (_data->status == FileDownloadFailed || _data->status == FileUploadFailed) {
  632. statusSize = Ui::FileStatusSizeFailed;
  633. } else if (_data->uploading()) {
  634. statusSize = _data->uploadingData->offset;
  635. } else if (dataLoaded()) {
  636. statusSize = Ui::FileStatusSizeLoaded;
  637. } else {
  638. statusSize = Ui::FileStatusSizeReady;
  639. }
  640. if (statusSize != _status.size()) {
  641. auto status = statusSize;
  642. auto size = _data->size;
  643. if (statusSize >= 0 && statusSize < 0xFF000000LL) {
  644. size = status;
  645. status = Ui::FileStatusSizeReady;
  646. }
  647. _status.update(status, size, -1, 0);
  648. _status.setSize(statusSize);
  649. }
  650. }
  651. Voice::Voice(
  652. not_null<Delegate*> delegate,
  653. not_null<HistoryItem*> parent,
  654. not_null<DocumentData*> voice,
  655. const style::OverviewFileLayout &st)
  656. : RadialProgressItem(delegate, parent)
  657. , _data(voice)
  658. , _namel(std::make_shared<DocumentOpenClickHandler>(
  659. _data,
  660. crl::guard(this, [=](FullMsgId id) {
  661. delegate->openDocument(_data, id);
  662. }),
  663. parent->fullId()))
  664. , _st(st) {
  665. AddComponents(Info::Bit());
  666. setDocumentLinks(_data);
  667. _data->loadThumbnail(parent->fullId());
  668. updateName();
  669. const auto dateText = Ui::Text::Link(
  670. langDateTime(base::unixtime::parse(parent->date()))); // Link 1.
  671. _details.setMarkedText(
  672. st::defaultTextStyle,
  673. tr::lng_date_and_duration(
  674. tr::now,
  675. lt_date,
  676. dateText,
  677. lt_duration,
  678. { .text = Ui::FormatDurationText(_data->duration() / 1000) },
  679. Ui::Text::WithEntities));
  680. _details.setLink(1, JumpToMessageClickHandler(parent));
  681. }
  682. void Voice::initDimensions() {
  683. _maxw = _st.maxWidth;
  684. _minh = _st.songPadding.top() + _st.songThumbSize + _st.songPadding.bottom() + st::lineWidth;
  685. }
  686. void Voice::paint(Painter &p, const QRect &clip, TextSelection selection, const PaintContext *context) {
  687. ensureDataMediaCreated();
  688. bool selected = (selection == FullSelection);
  689. bool loaded = dataLoaded(), displayLoading = _data->displayLoading();
  690. if (displayLoading) {
  691. ensureRadial();
  692. if (!_radial->animating()) {
  693. _radial->start(dataProgress());
  694. }
  695. }
  696. const auto showPause = updateStatusText();
  697. const auto nameVersion = parent()->fromOriginal()->nameVersion();
  698. if (_nameVersion < nameVersion) {
  699. updateName();
  700. }
  701. const auto radial = isRadialAnimation();
  702. const auto nameleft = _st.songPadding.left()
  703. + _st.songThumbSize
  704. + _st.songPadding.right();
  705. const auto nameright = _st.songPadding.left();
  706. const auto nametop = _st.songNameTop;
  707. const auto statustop = _st.songStatusTop;
  708. const auto namewidth = _width - nameleft - nameright;
  709. const auto inner = style::rtlrect(
  710. _st.songPadding.left(),
  711. _st.songPadding.top(),
  712. _st.songThumbSize,
  713. _st.songThumbSize,
  714. _width);
  715. if (clip.intersects(inner)) {
  716. if (_data->hasThumbnail()) {
  717. ensureDataMediaCreated();
  718. }
  719. const auto thumbnail = _dataMedia
  720. ? _dataMedia->thumbnail()
  721. : nullptr;
  722. const auto blurred = _dataMedia
  723. ? _dataMedia->thumbnailInline()
  724. : nullptr;
  725. p.setPen(Qt::NoPen);
  726. if (thumbnail || blurred) {
  727. const auto options = Images::Option::RoundCircle
  728. | (blurred ? Images::Option::Blur : Images::Option());
  729. const auto thumb = (thumbnail ? thumbnail : blurred)->pix(
  730. inner.size(),
  731. { .options = options });
  732. p.drawPixmap(inner.topLeft(), thumb);
  733. } else if (_data->hasThumbnail()) {
  734. PainterHighQualityEnabler hq(p);
  735. p.setBrush(st::imageBg);
  736. p.drawEllipse(inner);
  737. }
  738. const auto &checkLink = (_data->loading() || _data->uploading())
  739. ? _cancell
  740. : (_dataMedia->canBePlayed(parent()) || loaded)
  741. ? _openl
  742. : _savel;
  743. if (selected) {
  744. p.setBrush((thumbnail || blurred) ? st::msgDateImgBgSelected : st::msgFileInBgSelected);
  745. } else if (_data->hasThumbnail()) {
  746. auto over = ClickHandler::showAsActive(checkLink);
  747. p.setBrush(anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, _a_iconOver.value(over ? 1. : 0.)));
  748. } else {
  749. auto over = ClickHandler::showAsActive(checkLink);
  750. p.setBrush(anim::brush(st::msgFileInBg, st::msgFileInBgOver, _a_iconOver.value(over ? 1. : 0.)));
  751. }
  752. {
  753. PainterHighQualityEnabler hq(p);
  754. p.drawEllipse(inner);
  755. }
  756. if (radial) {
  757. QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine)));
  758. auto &bg = selected ? st::historyFileInRadialFgSelected : st::historyFileInRadialFg;
  759. _radial->draw(p, rinner, st::msgFileRadialLine, bg);
  760. }
  761. const auto icon = [&] {
  762. if (_data->loading() || _data->uploading()) {
  763. return &(selected ? _st.voiceCancelSelected : _st.voiceCancel);
  764. } else if (showPause) {
  765. return &(selected ? _st.voicePauseSelected : _st.voicePause);
  766. } else if (_dataMedia->canBePlayed(parent())) {
  767. return &(selected ? _st.voicePlaySelected : _st.voicePlay);
  768. }
  769. return &(selected
  770. ? _st.voiceDownloadSelected
  771. : _st.voiceDownload);
  772. }();
  773. icon->paintInCenter(p, inner);
  774. }
  775. if (clip.intersects(style::rtlrect(nameleft, nametop, namewidth, st::semiboldFont->height, _width))) {
  776. p.setPen(st::historyFileNameInFg);
  777. _name.drawLeftElided(p, nameleft, nametop, namewidth, _width);
  778. }
  779. if (clip.intersects(style::rtlrect(nameleft, statustop, namewidth, st::normalFont->height, _width))) {
  780. p.setFont(st::normalFont);
  781. p.setPen(selected ? st::mediaInFgSelected : st::mediaInFg);
  782. int32 unreadx = nameleft;
  783. if (_status.size() == Ui::FileStatusSizeLoaded || _status.size() == Ui::FileStatusSizeReady) {
  784. p.setTextPalette(selected ? st::mediaInPaletteSelected : st::mediaInPalette);
  785. _details.drawLeftElided(p, nameleft, statustop, namewidth, _width);
  786. p.restoreTextPalette();
  787. unreadx += _details.maxWidth();
  788. } else {
  789. int32 statusw = st::normalFont->width(_status.text());
  790. p.drawTextLeft(nameleft, statustop, _width, _status.text(), statusw);
  791. unreadx += statusw;
  792. }
  793. auto captionLeft = unreadx + st::mediaUnreadSkip;
  794. if (parent()->hasUnreadMediaFlag() && unreadx + st::mediaUnreadSkip + st::mediaUnreadSize <= _width) {
  795. p.setPen(Qt::NoPen);
  796. p.setBrush(selected ? st::msgFileInBgSelected : st::msgFileInBg);
  797. {
  798. PainterHighQualityEnabler hq(p);
  799. p.drawEllipse(style::rtlrect(unreadx + st::mediaUnreadSkip, statustop + st::mediaUnreadTop, st::mediaUnreadSize, st::mediaUnreadSize, _width));
  800. }
  801. captionLeft += st::mediaUnreadSkip + st::mediaUnreadSize;
  802. }
  803. if (!_caption.isEmpty()) {
  804. p.setPen(st::historyFileNameInFg);
  805. const auto w = _width - captionLeft - st::defaultScrollArea.width;
  806. _caption.draw(p, Ui::Text::PaintContext{
  807. .position = QPoint(captionLeft, statustop),
  808. .availableWidth = w,
  809. .spoiler = Ui::Text::DefaultSpoilerCache(),
  810. .paused = context
  811. ? context->paused
  812. : On(PowerSaving::kEmojiChat),
  813. .pausedEmoji = On(PowerSaving::kEmojiChat),
  814. .pausedSpoiler = On(PowerSaving::kChatSpoiler),
  815. .elisionLines = 1,
  816. });
  817. }
  818. }
  819. const auto checkDelta = _st.songThumbSize
  820. + st::overviewCheckSkip
  821. - st::overviewSmallCheck.size;
  822. const auto checkLeft = _st.songPadding.left() + checkDelta;
  823. const auto checkTop = _st.songPadding.top() + checkDelta;
  824. paintCheckbox(p, { checkLeft, checkTop }, selected, context);
  825. }
  826. TextState Voice::getState(
  827. QPoint point,
  828. StateRequest request) const {
  829. ensureDataMediaCreated();
  830. const auto loaded = dataLoaded();
  831. const auto nameleft = _st.songPadding.left()
  832. + _st.songThumbSize
  833. + _st.songPadding.right();
  834. const auto nameright = _st.songPadding.left();
  835. const auto nametop = _st.songNameTop;
  836. const auto statustop = _st.songStatusTop;
  837. const auto inner = style::rtlrect(
  838. _st.songPadding.left(),
  839. _st.songPadding.top(),
  840. _st.songThumbSize,
  841. _st.songThumbSize,
  842. _width);
  843. if (inner.contains(point)) {
  844. const auto link = (_data->loading() || _data->uploading())
  845. ? _cancell
  846. : (_dataMedia->canBePlayed(parent()) || loaded)
  847. ? _openl
  848. : _savel;
  849. return { parent(), link };
  850. }
  851. auto result = TextState(parent());
  852. const auto statusmaxwidth = _width - nameleft - nameright;
  853. const auto statusrect = style::rtlrect(
  854. nameleft,
  855. statustop,
  856. statusmaxwidth,
  857. st::normalFont->height,
  858. _width);
  859. if (statusrect.contains(point)) {
  860. if (_status.size() == Ui::FileStatusSizeLoaded || _status.size() == Ui::FileStatusSizeReady) {
  861. auto textState = _details.getStateLeft(point - QPoint(nameleft, statustop), _width, _width);
  862. result.link = textState.link;
  863. result.cursor = textState.uponSymbol
  864. ? HistoryView::CursorState::Text
  865. : HistoryView::CursorState::None;
  866. }
  867. }
  868. const auto namewidth = std::min(
  869. _width - nameleft - nameright,
  870. _name.maxWidth());
  871. const auto namerect = style::rtlrect(
  872. nameleft,
  873. nametop,
  874. namewidth,
  875. st::normalFont->height,
  876. _width);
  877. if (namerect.contains(point) && !result.link && !_data->loading()) {
  878. return { parent(), _namel };
  879. }
  880. return result;
  881. }
  882. void Voice::ensureDataMediaCreated() const {
  883. if (_dataMedia) {
  884. return;
  885. }
  886. _dataMedia = _data->createMediaView();
  887. delegate()->registerHeavyItem(this);
  888. }
  889. void Voice::clearHeavyPart() {
  890. _dataMedia = nullptr;
  891. }
  892. float64 Voice::dataProgress() const {
  893. ensureDataMediaCreated();
  894. return _dataMedia->progress();
  895. }
  896. bool Voice::dataFinished() const {
  897. return !_data->loading();
  898. }
  899. bool Voice::dataLoaded() const {
  900. ensureDataMediaCreated();
  901. return _dataMedia->loaded();
  902. }
  903. bool Voice::iconAnimated() const {
  904. return true;
  905. }
  906. const style::RoundCheckbox &Voice::checkboxStyle() const {
  907. return st::overviewSmallCheck;
  908. }
  909. void Voice::updateName() {
  910. if (const auto forwarded = parent()->Get<HistoryMessageForwarded>()) {
  911. const auto info = parent()->originalHiddenSenderInfo();
  912. const auto name = info
  913. ? tr::lng_forwarded(tr::now, lt_user, info->nameText().toString())
  914. : parent()->fromOriginal()->isChannel()
  915. ? tr::lng_forwarded_channel(
  916. tr::now,
  917. lt_channel,
  918. parent()->fromOriginal()->name())
  919. : tr::lng_forwarded(
  920. tr::now,
  921. lt_user,
  922. parent()->fromOriginal()->name());
  923. _name.setText(st::semiboldTextStyle, name, Ui::NameTextOptions());
  924. } else {
  925. _name.setText(
  926. st::semiboldTextStyle,
  927. parent()->from()->name(),
  928. Ui::NameTextOptions());
  929. }
  930. _nameVersion = parent()->fromOriginal()->nameVersion();
  931. _caption.setMarkedText(
  932. st::defaultTextStyle,
  933. parent()->originalText(),
  934. Ui::DialogTextOptions(),
  935. Core::TextContext({
  936. .session = &parent()->history()->session(),
  937. .repaint = [=] { delegate()->repaintItem(this); },
  938. }));
  939. }
  940. bool Voice::updateStatusText() {
  941. auto showPause = false;
  942. auto statusSize = int64();
  943. auto realDuration = TimeId();
  944. if (_data->status == FileDownloadFailed || _data->status == FileUploadFailed) {
  945. statusSize = Ui::FileStatusSizeFailed;
  946. } else if (dataLoaded()) {
  947. statusSize = Ui::FileStatusSizeLoaded;
  948. } else {
  949. statusSize = Ui::FileStatusSizeReady;
  950. }
  951. const auto state = Media::Player::instance()->getState(AudioMsgId::Type::Voice);
  952. if (state.id == AudioMsgId(_data, parent()->fullId(), state.id.externalPlayId())
  953. && !Media::Player::IsStoppedOrStopping(state.state)) {
  954. statusSize = -1 - (state.position / state.frequency);
  955. realDuration = (state.length / state.frequency);
  956. showPause = Media::Player::ShowPauseIcon(state.state);
  957. }
  958. if (statusSize != _status.size()) {
  959. _status.update(statusSize, _data->size, _data->duration() / 1000, realDuration);
  960. }
  961. return showPause;
  962. }
  963. Document::Document(
  964. not_null<Delegate*> delegate,
  965. not_null<HistoryItem*> parent,
  966. DocumentFields fields,
  967. const style::OverviewFileLayout &st)
  968. : RadialProgressItem(delegate, parent)
  969. , _data(fields.document)
  970. , _msgl(parent->isHistoryEntry()
  971. ? JumpToMessageClickHandler(parent)
  972. : nullptr)
  973. , _namel(std::make_shared<DocumentOpenClickHandler>(
  974. _data,
  975. crl::guard(this, [=](FullMsgId id) {
  976. delegate->openDocument(_data, id);
  977. }),
  978. parent->fullId()))
  979. , _st(st)
  980. , _generic(::Layout::DocumentGenericPreview::Create(_data))
  981. , _forceFileLayout(fields.forceFileLayout)
  982. , _date(langDateTime(base::unixtime::parse(fields.dateOverride
  983. ? fields.dateOverride
  984. : parent->date())))
  985. , _ext(_generic.ext)
  986. , _datew(st::normalFont->width(_date)) {
  987. _name.setMarkedText(
  988. st::defaultTextStyle,
  989. (!_forceFileLayout
  990. ? Ui::Text::FormatSongNameFor(_data).textWithEntities()
  991. : Ui::Text::FormatDownloadsName(_data)),
  992. _documentNameOptions);
  993. AddComponents(Info::Bit());
  994. setDocumentLinks(_data);
  995. _status.update(
  996. Ui::FileStatusSizeReady,
  997. _data->size,
  998. songLayout() ? (_data->duration() / 1000) : -1,
  999. 0);
  1000. if (withThumb()) {
  1001. _data->loadThumbnail(parent->fullId());
  1002. auto tw = style::ConvertScale(_data->thumbnailLocation().width());
  1003. auto th = style::ConvertScale(_data->thumbnailLocation().height());
  1004. if (tw > th) {
  1005. _thumbw = (tw * _st.fileThumbSize) / th;
  1006. } else {
  1007. _thumbw = _st.fileThumbSize;
  1008. }
  1009. } else {
  1010. _thumbw = 0;
  1011. }
  1012. _extw = st::overviewFileExtFont->width(_ext);
  1013. if (_extw > _st.fileThumbSize - st::overviewFileExtPadding * 2) {
  1014. _ext = st::overviewFileExtFont->elided(_ext, _st.fileThumbSize - st::overviewFileExtPadding * 2, Qt::ElideMiddle);
  1015. _extw = st::overviewFileExtFont->width(_ext);
  1016. }
  1017. }
  1018. bool Document::downloadInCorner() const {
  1019. return _data->isAudioFile()
  1020. && parent()->allowsForward()
  1021. && _data->canBeStreamed(parent())
  1022. && !_data->inappPlaybackFailed();
  1023. }
  1024. void Document::initDimensions() {
  1025. _maxw = _st.maxWidth;
  1026. if (songLayout()) {
  1027. _minh = _st.songPadding.top() + _st.songThumbSize + _st.songPadding.bottom();
  1028. } else {
  1029. _minh = _st.filePadding.top() + _st.fileThumbSize + _st.filePadding.bottom() + st::lineWidth;
  1030. }
  1031. }
  1032. void Document::paint(Painter &p, const QRect &clip, TextSelection selection, const PaintContext *context) {
  1033. ensureDataMediaCreated();
  1034. const auto selected = (selection == FullSelection);
  1035. const auto cornerDownload = downloadInCorner();
  1036. _dataMedia->automaticLoad(parent()->fullId(), parent());
  1037. const auto loaded = dataLoaded();
  1038. const auto displayLoading = _data->displayLoading();
  1039. if (displayLoading) {
  1040. ensureRadial();
  1041. if (!_radial->animating()) {
  1042. _radial->start(dataProgress());
  1043. }
  1044. }
  1045. const auto showPause = updateStatusText();
  1046. const auto radial = isRadialAnimation();
  1047. int32 nameleft = 0, nametop = 0, nameright = 0, statustop = 0, datetop = -1;
  1048. const auto wthumb = withThumb();
  1049. const auto isSong = songLayout();
  1050. if (isSong) {
  1051. nameleft = _st.songPadding.left() + _st.songThumbSize + _st.songPadding.right();
  1052. nameright = _st.songPadding.left();
  1053. nametop = _st.songNameTop;
  1054. statustop = _st.songStatusTop;
  1055. auto inner = style::rtlrect(_st.songPadding.left(), _st.songPadding.top(), _st.songThumbSize, _st.songThumbSize, _width);
  1056. if (clip.intersects(inner)) {
  1057. const auto isLoading = (!cornerDownload
  1058. && (_data->loading() || _data->uploading()));
  1059. p.setPen(Qt::NoPen);
  1060. using namespace HistoryView;
  1061. const auto coverDrawn = _data->isSongWithCover()
  1062. && DrawThumbnailAsSongCover(
  1063. p,
  1064. st::songCoverOverlayFg,
  1065. _dataMedia,
  1066. inner,
  1067. selected);
  1068. if (!coverDrawn) {
  1069. if (selected) {
  1070. p.setBrush(st::msgFileInBgSelected);
  1071. } else {
  1072. const auto over = ClickHandler::showAsActive(isLoading
  1073. ? _cancell
  1074. : (loaded || _dataMedia->canBePlayed(parent()))
  1075. ? _openl
  1076. : _savel);
  1077. p.setBrush(anim::brush(
  1078. _st.songIconBg,
  1079. _st.songOverBg,
  1080. _a_iconOver.value(over ? 1. : 0.)));
  1081. }
  1082. PainterHighQualityEnabler hq(p);
  1083. p.drawEllipse(inner);
  1084. }
  1085. const auto icon = [&] {
  1086. if (!coverDrawn) {
  1087. if (isLoading) {
  1088. return &(selected
  1089. ? _st.voiceCancelSelected
  1090. : _st.voiceCancel);
  1091. } else if (showPause) {
  1092. return &(selected
  1093. ? _st.voicePauseSelected
  1094. : _st.voicePause);
  1095. } else if (loaded || _dataMedia->canBePlayed(parent())) {
  1096. return &(selected
  1097. ? _st.voicePlaySelected
  1098. : _st.voicePlay);
  1099. }
  1100. return &(selected
  1101. ? _st.voiceDownloadSelected
  1102. : _st.voiceDownload);
  1103. }
  1104. if (isLoading) {
  1105. return &(selected ? _st.songCancelSelected : _st.songCancel);
  1106. } else if (showPause) {
  1107. return &(selected ? _st.songPauseSelected : _st.songPause);
  1108. } else if (loaded || _dataMedia->canBePlayed(parent())) {
  1109. return &(selected ? _st.songPlaySelected : _st.songPlay);
  1110. }
  1111. return &(selected ? _st.songDownloadSelected : _st.songDownload);
  1112. }();
  1113. icon->paintInCenter(p, inner);
  1114. if (radial && !cornerDownload) {
  1115. auto rinner = inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine));
  1116. auto &bg = selected ? st::historyFileInRadialFgSelected : st::historyFileInRadialFg;
  1117. _radial->draw(p, rinner, st::msgFileRadialLine, bg);
  1118. }
  1119. drawCornerDownload(p, selected, context);
  1120. }
  1121. } else {
  1122. nameleft = _st.fileThumbSize + _st.filePadding.right();
  1123. nametop = st::linksBorder + _st.fileNameTop;
  1124. statustop = st::linksBorder + _st.fileStatusTop;
  1125. datetop = st::linksBorder + _st.fileDateTop;
  1126. QRect border(style::rtlrect(nameleft, 0, _width - nameleft, st::linksBorder, _width));
  1127. if (!context->skipBorder && clip.intersects(border)) {
  1128. p.fillRect(clip.intersected(border), st::linksBorderFg);
  1129. }
  1130. QRect rthumb(style::rtlrect(0, st::linksBorder + _st.filePadding.top(), _st.fileThumbSize, _st.fileThumbSize, _width));
  1131. if (clip.intersects(rthumb)) {
  1132. if (wthumb) {
  1133. ensureDataMediaCreated();
  1134. const auto thumbnail = _dataMedia->thumbnail();
  1135. const auto blurred = _dataMedia->thumbnailInline();
  1136. if (thumbnail || blurred) {
  1137. if (_thumb.isNull() || (thumbnail && !_thumbLoaded)) {
  1138. _thumbLoaded = (thumbnail != nullptr);
  1139. const auto options = Images::Option::RoundSmall
  1140. | (_thumbLoaded
  1141. ? Images::Option()
  1142. : Images::Option::Blur);
  1143. const auto image = thumbnail ? thumbnail : blurred;
  1144. _thumb = image->pixNoCache(
  1145. _thumbw * style::DevicePixelRatio(),
  1146. {
  1147. .options = options,
  1148. .outer = QSize(
  1149. _st.fileThumbSize,
  1150. _st.fileThumbSize),
  1151. });
  1152. }
  1153. p.drawPixmap(rthumb.topLeft(), _thumb);
  1154. } else {
  1155. p.setPen(Qt::NoPen);
  1156. p.setBrush(st::overviewFileThumbBg);
  1157. p.drawRoundedRect(
  1158. rthumb,
  1159. st::roundRadiusSmall,
  1160. st::roundRadiusSmall);
  1161. }
  1162. } else {
  1163. p.setPen(Qt::NoPen);
  1164. p.setBrush(_generic.color);
  1165. p.drawRoundedRect(
  1166. rthumb,
  1167. st::roundRadiusSmall,
  1168. st::roundRadiusSmall);
  1169. if (!radial && loaded && !_ext.isEmpty()) {
  1170. p.setFont(st::overviewFileExtFont);
  1171. p.setPen(st::overviewFileExtFg);
  1172. p.drawText(rthumb.left() + (rthumb.width() - _extw) / 2, rthumb.top() + st::overviewFileExtTop + st::overviewFileExtFont->ascent, _ext);
  1173. }
  1174. }
  1175. if (selected) {
  1176. p.setPen(Qt::NoPen);
  1177. p.setBrush(st::defaultTextPalette.selectOverlay);
  1178. p.drawRoundedRect(
  1179. rthumb,
  1180. st::roundRadiusSmall,
  1181. st::roundRadiusSmall);
  1182. }
  1183. if (radial || (!loaded && !_data->loading())) {
  1184. QRect inner(rthumb.x() + (rthumb.width() - _st.songThumbSize) / 2, rthumb.y() + (rthumb.height() - _st.songThumbSize) / 2, _st.songThumbSize, _st.songThumbSize);
  1185. if (clip.intersects(inner)) {
  1186. auto radialOpacity = (radial && loaded && !_data->uploading()) ? _radial->opacity() : 1;
  1187. p.setPen(Qt::NoPen);
  1188. if (selected) {
  1189. p.setBrush(wthumb
  1190. ? st::msgDateImgBgSelected
  1191. : _generic.selected);
  1192. } else {
  1193. auto over = ClickHandler::showAsActive(_data->loading() ? _cancell : _savel);
  1194. p.setBrush(anim::brush(
  1195. wthumb ? st::msgDateImgBg : _generic.dark,
  1196. wthumb ? st::msgDateImgBgOver : _generic.over,
  1197. _a_iconOver.value(over ? 1. : 0.)));
  1198. }
  1199. p.setOpacity(radialOpacity * p.opacity());
  1200. {
  1201. PainterHighQualityEnabler hq(p);
  1202. p.drawEllipse(inner);
  1203. }
  1204. p.setOpacity(radialOpacity);
  1205. auto icon = ([loaded, this, selected] {
  1206. if (loaded || _data->loading()) {
  1207. return &(selected ? st::historyFileThumbCancelSelected : st::historyFileThumbCancel);
  1208. }
  1209. return &(selected ? st::historyFileThumbDownloadSelected : st::historyFileThumbDownload);
  1210. })();
  1211. icon->paintInCenter(p, inner);
  1212. if (radial) {
  1213. p.setOpacity(1);
  1214. QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine)));
  1215. _radial->draw(p, rinner, st::msgFileRadialLine, selected ? st::historyFileThumbRadialFgSelected : st::historyFileThumbRadialFg);
  1216. }
  1217. }
  1218. }
  1219. }
  1220. }
  1221. const auto availwidth = _width - nameleft - nameright;
  1222. const auto namewidth = std::min(availwidth, _name.maxWidth());
  1223. if (clip.intersects(style::rtlrect(nameleft, nametop, namewidth, st::semiboldFont->height, _width))) {
  1224. p.setPen(st::historyFileNameInFg);
  1225. _name.drawLeftElided(p, nameleft, nametop, namewidth, _width);
  1226. }
  1227. if (clip.intersects(style::rtlrect(nameleft, statustop, availwidth, st::normalFont->height, _width))) {
  1228. p.setFont(st::normalFont);
  1229. p.setPen((isSong && selected) ? st::mediaInFgSelected : st::mediaInFg);
  1230. p.drawTextLeft(nameleft, statustop, _width, _status.text());
  1231. }
  1232. if (datetop >= 0 && clip.intersects(style::rtlrect(nameleft, datetop, _datew, st::normalFont->height, _width))) {
  1233. p.setFont((_msgl && ClickHandler::showAsActive(_msgl))
  1234. ? st::normalFont->underline()
  1235. : st::normalFont);
  1236. p.setPen(st::mediaInFg);
  1237. p.drawTextLeft(nameleft, datetop, _width, _date, _datew);
  1238. }
  1239. const auto checkDelta = (isSong ? _st.songThumbSize : _st.fileThumbSize)
  1240. + (isSong ? st::overviewCheckSkip : -st::overviewCheckSkip)
  1241. - st::overviewSmallCheck.size;
  1242. const auto checkLeft = (isSong
  1243. ? _st.songPadding.left()
  1244. : 0) + checkDelta;
  1245. const auto checkTop = (isSong
  1246. ? _st.songPadding.top()
  1247. : (st::linksBorder + _st.filePadding.top())) + checkDelta;
  1248. paintCheckbox(p, { checkLeft, checkTop }, selected, context);
  1249. }
  1250. void Document::drawCornerDownload(QPainter &p, bool selected, const PaintContext *context) const {
  1251. if (dataLoaded()
  1252. || _data->loadedInMediaCache()
  1253. || !downloadInCorner()) {
  1254. return;
  1255. }
  1256. const auto size = st::overviewSmallCheck.size;
  1257. const auto shift = _st.songThumbSize + st::overviewCheckSkip - size;
  1258. const auto inner = style::rtlrect(_st.songPadding.left() + shift, _st.songPadding.top() + shift, size, size, _width);
  1259. auto pen = st::windowBg->p;
  1260. pen.setWidth(st::lineWidth);
  1261. p.setPen(pen);
  1262. if (selected) {
  1263. p.setBrush(st::msgFileInBgSelected);
  1264. } else {
  1265. p.setBrush(_st.songIconBg);
  1266. }
  1267. {
  1268. PainterHighQualityEnabler hq(p);
  1269. p.drawEllipse(inner);
  1270. }
  1271. const auto icon = [&] {
  1272. if (_data->loading()) {
  1273. return &(selected ? st::overviewSmallCancelSelected : st::overviewSmallCancel);
  1274. }
  1275. return &(selected ? st::overviewSmallDownloadSelected : st::overviewSmallDownload);
  1276. }();
  1277. icon->paintInCenter(p, inner);
  1278. if (_radial && _radial->animating()) {
  1279. const auto rinner = inner.marginsRemoved(QMargins(st::historyAudioRadialLine, st::historyAudioRadialLine, st::historyAudioRadialLine, st::historyAudioRadialLine));
  1280. const auto &fg = selected
  1281. ? st::historyFileInIconFgSelected
  1282. : st::historyFileInIconFg;
  1283. _radial->draw(p, rinner, st::historyAudioRadialLine, fg);
  1284. }
  1285. }
  1286. TextState Document::cornerDownloadTextState(
  1287. QPoint point,
  1288. StateRequest request) const {
  1289. auto result = TextState(parent());
  1290. if (!downloadInCorner()
  1291. || dataLoaded()
  1292. || _data->loadedInMediaCache()) {
  1293. return result;
  1294. }
  1295. const auto size = st::overviewSmallCheck.size;
  1296. const auto shift = _st.songThumbSize + st::overviewCheckSkip - size;
  1297. const auto inner = style::rtlrect(_st.songPadding.left() + shift, _st.songPadding.top() + shift, size, size, _width);
  1298. if (inner.contains(point)) {
  1299. result.link = _data->loading() ? _cancell : _savel;
  1300. }
  1301. return result;
  1302. }
  1303. TextState Document::getState(
  1304. QPoint point,
  1305. StateRequest request) const {
  1306. ensureDataMediaCreated();
  1307. const auto loaded = dataLoaded();
  1308. if (songLayout()) {
  1309. const auto nameleft = _st.songPadding.left() + _st.songThumbSize + _st.songPadding.right();
  1310. const auto nameright = _st.songPadding.left();
  1311. const auto namewidth = std::min(
  1312. _width - nameleft - nameright,
  1313. _name.maxWidth());
  1314. const auto nametop = _st.songNameTop;
  1315. if (const auto state = cornerDownloadTextState(point, request); state.link) {
  1316. return state;
  1317. }
  1318. const auto inner = style::rtlrect(
  1319. _st.songPadding.left(),
  1320. _st.songPadding.top(),
  1321. _st.songThumbSize,
  1322. _st.songThumbSize,
  1323. _width);
  1324. if (inner.contains(point)) {
  1325. const auto link = (!downloadInCorner()
  1326. && (_data->loading() || _data->uploading()))
  1327. ? _cancell
  1328. : (loaded || _dataMedia->canBePlayed(parent()))
  1329. ? _openl
  1330. : _savel;
  1331. return { parent(), link };
  1332. }
  1333. const auto namerect = style::rtlrect(
  1334. nameleft,
  1335. nametop,
  1336. namewidth,
  1337. st::semiboldFont->height,
  1338. _width);
  1339. if (namerect.contains(point) && !_data->loading()) {
  1340. return { parent(), _namel };
  1341. }
  1342. } else {
  1343. const auto nameleft = _st.fileThumbSize + _st.filePadding.right();
  1344. const auto nameright = 0;
  1345. const auto nametop = st::linksBorder + _st.fileNameTop;
  1346. const auto namewidth = std::min(
  1347. _width - nameleft - nameright,
  1348. _name.maxWidth());
  1349. const auto datetop = st::linksBorder + _st.fileDateTop;
  1350. const auto rthumb = style::rtlrect(
  1351. 0,
  1352. st::linksBorder + _st.filePadding.top(),
  1353. _st.fileThumbSize,
  1354. _st.fileThumbSize,
  1355. _width);
  1356. if (rthumb.contains(point)) {
  1357. const auto link = (_data->loading() || _data->uploading())
  1358. ? _cancell
  1359. : loaded
  1360. ? _openl
  1361. : _savel;
  1362. return { parent(), link };
  1363. }
  1364. if (_data->status != FileUploadFailed) {
  1365. auto daterect = style::rtlrect(
  1366. nameleft,
  1367. datetop,
  1368. _datew,
  1369. st::normalFont->height,
  1370. _width);
  1371. if (daterect.contains(point)) {
  1372. return { parent(), _msgl };
  1373. }
  1374. }
  1375. if (!_data->loading() && !_data->isNull()) {
  1376. auto leftofnamerect = style::rtlrect(
  1377. 0,
  1378. st::linksBorder,
  1379. nameleft,
  1380. _height - st::linksBorder,
  1381. _width);
  1382. if (loaded && leftofnamerect.contains(point)) {
  1383. return { parent(), _namel };
  1384. }
  1385. const auto namerect = style::rtlrect(
  1386. nameleft,
  1387. nametop,
  1388. namewidth,
  1389. st::semiboldFont->height,
  1390. _width);
  1391. if (namerect.contains(point)) {
  1392. return { parent(), _namel };
  1393. }
  1394. }
  1395. }
  1396. return {};
  1397. }
  1398. const style::RoundCheckbox &Document::checkboxStyle() const {
  1399. return st::overviewSmallCheck;
  1400. }
  1401. bool Document::songLayout() const {
  1402. return !_forceFileLayout && _data->isSong();
  1403. }
  1404. void Document::ensureDataMediaCreated() const {
  1405. if (_dataMedia) {
  1406. return;
  1407. }
  1408. _dataMedia = _data->createMediaView();
  1409. _dataMedia->thumbnailWanted(parent()->fullId());
  1410. delegate()->registerHeavyItem(this);
  1411. }
  1412. void Document::clearHeavyPart() {
  1413. _dataMedia = nullptr;
  1414. }
  1415. float64 Document::dataProgress() const {
  1416. ensureDataMediaCreated();
  1417. return _dataMedia->progress();
  1418. }
  1419. bool Document::dataFinished() const {
  1420. return !_data->loading();
  1421. }
  1422. bool Document::dataLoaded() const {
  1423. ensureDataMediaCreated();
  1424. return _dataMedia->loaded();
  1425. }
  1426. bool Document::iconAnimated() const {
  1427. return songLayout()
  1428. || !dataLoaded()
  1429. || (_radial && _radial->animating());
  1430. }
  1431. bool Document::withThumb() const {
  1432. return !songLayout() && _data->hasThumbnail();
  1433. }
  1434. bool Document::updateStatusText() {
  1435. auto showPause = false;
  1436. auto statusSize = int64();
  1437. auto realDuration = TimeId();
  1438. if (_data->status == FileDownloadFailed
  1439. || _data->status == FileUploadFailed) {
  1440. statusSize = Ui::FileStatusSizeFailed;
  1441. } else if (_data->uploading()) {
  1442. statusSize = _data->uploadingData->offset;
  1443. } else if (_data->loading()) {
  1444. statusSize = _data->loadOffset();
  1445. } else if (dataLoaded()) {
  1446. statusSize = Ui::FileStatusSizeLoaded;
  1447. } else {
  1448. statusSize = Ui::FileStatusSizeReady;
  1449. }
  1450. const auto isSong = songLayout();
  1451. if (isSong) {
  1452. const auto state = Media::Player::instance()->getState(AudioMsgId::Type::Song);
  1453. if (state.id == AudioMsgId(_data, parent()->fullId(), state.id.externalPlayId()) && !Media::Player::IsStoppedOrStopping(state.state)) {
  1454. statusSize = -1 - (state.position / state.frequency);
  1455. realDuration = (state.length / state.frequency);
  1456. showPause = Media::Player::ShowPauseIcon(state.state);
  1457. }
  1458. if (!showPause && (state.id == AudioMsgId(_data, parent()->fullId(), state.id.externalPlayId())) && Media::Player::instance()->isSeeking(AudioMsgId::Type::Song)) {
  1459. showPause = true;
  1460. }
  1461. }
  1462. if (statusSize != _status.size()) {
  1463. _status.update(
  1464. statusSize,
  1465. _data->size,
  1466. isSong ? (_data->duration() / 1000) : -1,
  1467. realDuration);
  1468. }
  1469. return showPause;
  1470. }
  1471. Link::Link(
  1472. not_null<Delegate*> delegate,
  1473. not_null<HistoryItem*> parent,
  1474. Data::Media *media)
  1475. : ItemBase(delegate, parent)
  1476. , _text(st::msgMinWidth) {
  1477. AddComponents(Info::Bit());
  1478. auto textWithEntities = parent->originalText();
  1479. QString mainUrl;
  1480. auto text = textWithEntities.text;
  1481. const auto &entities = textWithEntities.entities;
  1482. int32 from = 0, till = text.size(), lnk = entities.size();
  1483. for (const auto &entity : entities) {
  1484. auto type = entity.type();
  1485. if (type != EntityType::Url && type != EntityType::CustomUrl && type != EntityType::Email) {
  1486. continue;
  1487. }
  1488. const auto customUrl = entity.data();
  1489. const auto entityText = text.mid(entity.offset(), entity.length());
  1490. const auto url = customUrl.isEmpty() ? entityText : customUrl;
  1491. if (_links.isEmpty()) {
  1492. mainUrl = url;
  1493. }
  1494. _links.push_back(LinkEntry(url, entityText));
  1495. }
  1496. if (_links.empty()) {
  1497. if (const auto media = parent->media()) {
  1498. if (const auto webpage = media->webpage()) {
  1499. if (!webpage->displayUrl.isEmpty()
  1500. && !webpage->url.isEmpty()) {
  1501. _links.push_back(
  1502. LinkEntry(webpage->displayUrl, webpage->url));
  1503. }
  1504. }
  1505. }
  1506. }
  1507. while (lnk > 0 && till > from) {
  1508. --lnk;
  1509. auto &entity = entities.at(lnk);
  1510. auto type = entity.type();
  1511. if (type != EntityType::Url && type != EntityType::CustomUrl && type != EntityType::Email) {
  1512. ++lnk;
  1513. break;
  1514. }
  1515. int32 afterLinkStart = entity.offset() + entity.length();
  1516. if (till > afterLinkStart) {
  1517. if (!QRegularExpression(u"^[,.\\s_=+\\-;:`'\"\\(\\)\\[\\]\\{\\}<>*&^%\\$#@!\\\\/]+$"_q).match(text.mid(afterLinkStart, till - afterLinkStart)).hasMatch()) {
  1518. ++lnk;
  1519. break;
  1520. }
  1521. }
  1522. till = entity.offset();
  1523. }
  1524. if (!lnk) {
  1525. if (QRegularExpression(u"^[,.\\s\\-;:`'\"\\(\\)\\[\\]\\{\\}<>*&^%\\$#@!\\\\/]+$"_q).match(text.mid(from, till - from)).hasMatch()) {
  1526. till = from;
  1527. }
  1528. }
  1529. const auto createHandler = [](const QString &url) {
  1530. return UrlClickHandler::IsSuspicious(url)
  1531. ? std::make_shared<HiddenUrlClickHandler>(url)
  1532. : std::make_shared<UrlClickHandler>(url, false);
  1533. };
  1534. _page = media ? media->webpage() : nullptr;
  1535. if (_page) {
  1536. mainUrl = _page->url;
  1537. if (_page->document) {
  1538. _photol = std::make_shared<DocumentOpenClickHandler>(
  1539. _page->document,
  1540. crl::guard(this, [=](FullMsgId id) {
  1541. delegate->openDocument(_page->document, id);
  1542. }),
  1543. parent->fullId());
  1544. } else if (_page->photo) {
  1545. if (_page->type == WebPageType::Profile
  1546. || _page->type == WebPageType::Video) {
  1547. _photol = createHandler(_page->url);
  1548. } else if (_page->type == WebPageType::Photo
  1549. || _page->type == WebPageType::Document
  1550. || _page->siteName == u"Twitter"_q
  1551. || _page->siteName == u"Facebook"_q) {
  1552. _photol = std::make_shared<PhotoOpenClickHandler>(
  1553. _page->photo,
  1554. crl::guard(this, [=](FullMsgId id) {
  1555. delegate->openPhoto(_page->photo, id);
  1556. }),
  1557. parent->fullId());
  1558. } else {
  1559. _photol = createHandler(_page->url);
  1560. }
  1561. } else {
  1562. _photol = createHandler(_page->url);
  1563. }
  1564. } else if (!mainUrl.isEmpty()) {
  1565. _photol = createHandler(mainUrl);
  1566. }
  1567. if (from >= till && _page) {
  1568. text = _page->description.text;
  1569. from = 0;
  1570. till = text.size();
  1571. }
  1572. if (till > from) {
  1573. TextParseOptions opts = { TextParseMultiline, int32(st::linksMaxWidth), 3 * st::normalFont->height, Qt::LayoutDirectionAuto };
  1574. _text.setText(st::defaultTextStyle, text.mid(from, till - from), opts);
  1575. }
  1576. int32 tw = 0, th = 0;
  1577. if (_page && _page->photo) {
  1578. const auto photo = _page->photo;
  1579. if (photo->hasExact(Data::PhotoSize::Small)
  1580. || photo->hasExact(Data::PhotoSize::Thumbnail)) {
  1581. photo->load(Data::PhotoSize::Small, parent->fullId());
  1582. }
  1583. tw = style::ConvertScale(photo->width());
  1584. th = style::ConvertScale(photo->height());
  1585. } else if (_page && _page->document && _page->document->hasThumbnail()) {
  1586. _page->document->loadThumbnail(parent->fullId());
  1587. const auto &location = _page->document->thumbnailLocation();
  1588. tw = style::ConvertScale(location.width());
  1589. th = style::ConvertScale(location.height());
  1590. }
  1591. if (tw > st::linksPhotoSize) {
  1592. if (th > tw) {
  1593. th = th * st::linksPhotoSize / tw;
  1594. tw = st::linksPhotoSize;
  1595. } else if (th > st::linksPhotoSize) {
  1596. tw = tw * st::linksPhotoSize / th;
  1597. th = st::linksPhotoSize;
  1598. }
  1599. }
  1600. _pixw = qMax(tw, 1);
  1601. _pixh = qMax(th, 1);
  1602. if (_page) {
  1603. _title = _page->title;
  1604. }
  1605. auto parts = QStringView(mainUrl).split('/');
  1606. if (!parts.isEmpty()) {
  1607. auto domain = parts.at(0);
  1608. if (parts.size() > 2 && domain.endsWith(':') && parts.at(1).isEmpty()) { // http:// and others
  1609. domain = parts.at(2);
  1610. }
  1611. parts = domain.split('@').constLast().split('.', Qt::SkipEmptyParts);
  1612. if (parts.size() > 1) {
  1613. _letter = parts.at(parts.size() - 2).at(0).toUpper();
  1614. if (_title.isEmpty()) {
  1615. _title.reserve(parts.at(parts.size() - 2).size());
  1616. _title.append(_letter).append(parts.at(parts.size() - 2).mid(1));
  1617. }
  1618. }
  1619. }
  1620. _titlew = st::semiboldFont->width(_title);
  1621. }
  1622. void Link::initDimensions() {
  1623. _maxw = st::linksMaxWidth;
  1624. _minh = 0;
  1625. if (!_title.isEmpty()) {
  1626. _minh += st::semiboldFont->height;
  1627. }
  1628. if (!_text.isEmpty()) {
  1629. _minh += qMin(3 * st::normalFont->height, _text.countHeight(_maxw - st::linksPhotoSize - st::linksPhotoPadding));
  1630. }
  1631. _minh += _links.size() * st::normalFont->height;
  1632. _minh = qMax(_minh, int32(st::linksPhotoSize)) + st::linksMargin.top() + st::linksMargin.bottom() + st::linksBorder;
  1633. }
  1634. int32 Link::resizeGetHeight(int32 width) {
  1635. _width = qMin(width, _maxw);
  1636. int32 w = _width - st::linksPhotoSize - st::linksPhotoPadding;
  1637. for (const auto &link : std::as_const(_links)) {
  1638. link.lnk->setFullDisplayed(w >= link.width);
  1639. }
  1640. _height = 0;
  1641. if (!_title.isEmpty()) {
  1642. _height += st::semiboldFont->height;
  1643. }
  1644. if (!_text.isEmpty()) {
  1645. _height += qMin(3 * st::normalFont->height, _text.countHeight(_width - st::linksPhotoSize - st::linksPhotoPadding));
  1646. }
  1647. _height += _links.size() * st::normalFont->height;
  1648. _height = qMax(_height, int32(st::linksPhotoSize)) + st::linksMargin.top() + st::linksMargin.bottom() + st::linksBorder;
  1649. return _height;
  1650. }
  1651. void Link::paint(Painter &p, const QRect &clip, TextSelection selection, const PaintContext *context) {
  1652. auto selected = (selection == FullSelection);
  1653. const auto pixLeft = 0;
  1654. const auto pixTop = st::linksMargin.top() + st::linksBorder;
  1655. if (clip.intersects(style::rtlrect(0, pixTop, st::linksPhotoSize, st::linksPhotoSize, _width))) {
  1656. validateThumbnail();
  1657. if (!_thumbnail.isNull()) {
  1658. p.drawPixmap(pixLeft, pixTop, _thumbnail);
  1659. }
  1660. }
  1661. const auto left = st::linksPhotoSize + st::linksPhotoPadding;
  1662. const auto w = _width - left;
  1663. auto top = [&] {
  1664. if (!_title.isEmpty() && _text.isEmpty() && _links.size() == 1) {
  1665. return pixTop + (st::linksPhotoSize - st::semiboldFont->height - st::normalFont->height) / 2;
  1666. }
  1667. return st::linksTextTop;
  1668. }();
  1669. p.setPen(st::linksTextFg);
  1670. p.setFont(st::semiboldFont);
  1671. if (!_title.isEmpty()) {
  1672. if (clip.intersects(style::rtlrect(left, top, qMin(w, _titlew), st::semiboldFont->height, _width))) {
  1673. p.drawTextLeft(left, top, _width, (w < _titlew) ? st::semiboldFont->elided(_title, w) : _title);
  1674. }
  1675. top += st::semiboldFont->height;
  1676. }
  1677. p.setFont(st::msgFont);
  1678. if (!_text.isEmpty()) {
  1679. int32 h = qMin(st::normalFont->height * 3, _text.countHeight(w));
  1680. if (clip.intersects(style::rtlrect(left, top, w, h, _width))) {
  1681. _text.drawLeftElided(p, left, top, w, _width, 3);
  1682. }
  1683. top += h;
  1684. }
  1685. p.setPen(st::windowActiveTextFg);
  1686. for (const auto &link : std::as_const(_links)) {
  1687. if (clip.intersects(style::rtlrect(left, top, qMin(w, link.width), st::normalFont->height, _width))) {
  1688. p.setFont(ClickHandler::showAsActive(link.lnk) ? st::normalFont->underline() : st::normalFont);
  1689. p.drawTextLeft(left, top, _width, (w < link.width) ? st::normalFont->elided(link.text, w) : link.text);
  1690. }
  1691. top += st::normalFont->height;
  1692. }
  1693. QRect border(style::rtlrect(left, 0, w, st::linksBorder, _width));
  1694. if (!context->skipBorder && clip.intersects(border)) {
  1695. p.fillRect(clip.intersected(border), st::linksBorderFg);
  1696. }
  1697. const auto checkDelta = st::linksPhotoSize + st::overviewCheckSkip
  1698. - st::overviewSmallCheck.size;
  1699. const auto checkLeft = pixLeft + checkDelta;
  1700. const auto checkTop = pixTop + checkDelta;
  1701. paintCheckbox(p, { checkLeft, checkTop }, selected, context);
  1702. }
  1703. void Link::validateThumbnail() {
  1704. if (!_thumbnail.isNull() && !_thumbnailBlurred) {
  1705. return;
  1706. }
  1707. const auto size = QSize(_pixw, _pixh);
  1708. const auto outer = QSize(st::linksPhotoSize, st::linksPhotoSize);
  1709. if (_page && _page->photo) {
  1710. using Data::PhotoSize;
  1711. ensurePhotoMediaCreated();
  1712. const auto args = Images::PrepareArgs{
  1713. .options = Images::Option::RoundSmall,
  1714. .outer = outer,
  1715. };
  1716. if (const auto thumbnail = _photoMedia->image(PhotoSize::Thumbnail)) {
  1717. _thumbnail = thumbnail->pixSingle(size, args);
  1718. _thumbnailBlurred = false;
  1719. } else if (const auto large = _photoMedia->image(PhotoSize::Large)) {
  1720. _thumbnail = large->pixSingle(size, args);
  1721. _thumbnailBlurred = false;
  1722. } else if (const auto small = _photoMedia->image(PhotoSize::Small)) {
  1723. _thumbnail = small->pixSingle(size, args);
  1724. _thumbnailBlurred = false;
  1725. } else if (const auto blurred = _photoMedia->thumbnailInline()) {
  1726. _thumbnail = blurred->pixSingle(size, args.blurred());
  1727. return;
  1728. } else {
  1729. return;
  1730. }
  1731. _photoMedia = nullptr;
  1732. delegate()->unregisterHeavyItem(this);
  1733. } else if (_page && _page->document && _page->document->hasThumbnail()) {
  1734. ensureDocumentMediaCreated();
  1735. const auto args = Images::PrepareArgs{
  1736. .options = (_page->document->isVideoMessage()
  1737. ? Images::Option::RoundCircle
  1738. : Images::Option::RoundSmall),
  1739. .outer = outer,
  1740. };
  1741. if (const auto thumbnail = _documentMedia->thumbnail()) {
  1742. _thumbnail = thumbnail->pixSingle(size, args);
  1743. _thumbnailBlurred = false;
  1744. } else if (const auto blurred = _documentMedia->thumbnailInline()) {
  1745. _thumbnail = blurred->pixSingle(size, args.blurred());
  1746. return;
  1747. } else {
  1748. return;
  1749. }
  1750. _documentMedia = nullptr;
  1751. delegate()->unregisterHeavyItem(this);
  1752. } else {
  1753. const auto size = QSize(st::linksPhotoSize, st::linksPhotoSize);
  1754. _thumbnail = QPixmap(size * style::DevicePixelRatio());
  1755. _thumbnail.fill(Qt::transparent);
  1756. auto p = Painter(&_thumbnail);
  1757. const auto index = _letter.isEmpty()
  1758. ? 0
  1759. : (_letter[0].unicode() % 4);
  1760. const auto fill = [&](style::color color, Ui::CachedRoundCorners corners) {
  1761. auto pixRect = QRect(
  1762. 0,
  1763. 0,
  1764. st::linksPhotoSize,
  1765. st::linksPhotoSize);
  1766. Ui::FillRoundRect(p, pixRect, color, corners);
  1767. };
  1768. switch (index) {
  1769. case 0: fill(st::msgFile1Bg, Ui::Doc1Corners); break;
  1770. case 1: fill(st::msgFile2Bg, Ui::Doc2Corners); break;
  1771. case 2: fill(st::msgFile3Bg, Ui::Doc3Corners); break;
  1772. case 3: fill(st::msgFile4Bg, Ui::Doc4Corners); break;
  1773. }
  1774. if (!_letter.isEmpty()) {
  1775. p.setFont(st::linksLetterFont);
  1776. p.setPen(st::linksLetterFg);
  1777. p.drawText(
  1778. QRect(0, 0, st::linksPhotoSize, st::linksPhotoSize),
  1779. _letter,
  1780. style::al_center);
  1781. }
  1782. _thumbnailBlurred = false;
  1783. }
  1784. }
  1785. void Link::ensurePhotoMediaCreated() {
  1786. if (_photoMedia) {
  1787. return;
  1788. }
  1789. _photoMedia = _page->photo->createMediaView();
  1790. _photoMedia->wanted(Data::PhotoSize::Small, parent()->fullId());
  1791. delegate()->registerHeavyItem(this);
  1792. }
  1793. void Link::ensureDocumentMediaCreated() {
  1794. if (_documentMedia) {
  1795. return;
  1796. }
  1797. _documentMedia = _page->document->createMediaView();
  1798. _documentMedia->thumbnailWanted(parent()->fullId());
  1799. delegate()->registerHeavyItem(this);
  1800. }
  1801. void Link::clearHeavyPart() {
  1802. _photoMedia = nullptr;
  1803. _documentMedia = nullptr;
  1804. }
  1805. TextState Link::getState(
  1806. QPoint point,
  1807. StateRequest request) const {
  1808. int32 left = st::linksPhotoSize + st::linksPhotoPadding, top = st::linksMargin.top() + st::linksBorder, w = _width - left;
  1809. if (style::rtlrect(0, top, st::linksPhotoSize, st::linksPhotoSize, _width).contains(point)) {
  1810. return { parent(), _photol };
  1811. }
  1812. if (!_title.isEmpty() && _text.isEmpty() && _links.size() == 1) {
  1813. top += (st::linksPhotoSize - st::semiboldFont->height - st::normalFont->height) / 2;
  1814. }
  1815. if (!_title.isEmpty()) {
  1816. if (style::rtlrect(left, top, qMin(w, _titlew), st::semiboldFont->height, _width).contains(point)) {
  1817. return { parent(), _photol };
  1818. }
  1819. top += st::webPageTitleFont->height;
  1820. }
  1821. if (!_text.isEmpty()) {
  1822. top += qMin(st::normalFont->height * 3, _text.countHeight(w));
  1823. }
  1824. for (const auto &link : _links) {
  1825. if (style::rtlrect(left, top, qMin(w, link.width), st::normalFont->height, _width).contains(point)) {
  1826. return { parent(), ClickHandlerPtr(link.lnk) };
  1827. }
  1828. top += st::normalFont->height;
  1829. }
  1830. return {};
  1831. }
  1832. const style::RoundCheckbox &Link::checkboxStyle() const {
  1833. return st::overviewSmallCheck;
  1834. }
  1835. Link::LinkEntry::LinkEntry(const QString &url, const QString &text)
  1836. : text(text)
  1837. , width(st::normalFont->width(text))
  1838. , lnk(UrlClickHandler::IsSuspicious(url)
  1839. ? std::make_shared<HiddenUrlClickHandler>(url)
  1840. : std::make_shared<UrlClickHandler>(url)) {
  1841. }
  1842. // Copied from inline_bot_layout_internal.
  1843. Gif::Gif(
  1844. not_null<Delegate*> delegate,
  1845. not_null<HistoryItem*> parent,
  1846. not_null<DocumentData*> gif)
  1847. : RadialProgressItem(delegate, parent)
  1848. , _data(gif) {
  1849. setDocumentLinks(_data, true);
  1850. _data->loadThumbnail(parent->fullId());
  1851. }
  1852. Gif::~Gif() = default;
  1853. int Gif::contentWidth() const {
  1854. if (_data->dimensions.width() > 0) {
  1855. return _data->dimensions.width();
  1856. }
  1857. return style::ConvertScale(_data->thumbnailLocation().width());
  1858. }
  1859. int Gif::contentHeight() const {
  1860. if (_data->dimensions.height() > 0) {
  1861. return _data->dimensions.height();
  1862. }
  1863. return style::ConvertScale(_data->thumbnailLocation().height());
  1864. }
  1865. void Gif::initDimensions() {
  1866. int32 w = contentWidth(), h = contentHeight();
  1867. if (w <= 0 || h <= 0) {
  1868. _maxw = 0;
  1869. } else {
  1870. w = w * st::inlineMediaHeight / h;
  1871. _maxw = qMax(w, int32(st::inlineResultsMinWidth));
  1872. }
  1873. _minh = st::inlineMediaHeight + st::inlineResultsSkip;
  1874. }
  1875. int32 Gif::resizeGetHeight(int32 width) {
  1876. _width = width;
  1877. _height = _minh;
  1878. return _height;
  1879. }
  1880. QSize Gif::countFrameSize() const {
  1881. const auto animating = (_gif && _gif->ready());
  1882. auto framew = animating ? _gif->width() : contentWidth();
  1883. auto frameh = animating ? _gif->height() : contentHeight();
  1884. const auto height = st::inlineMediaHeight;
  1885. const auto maxSize = st::maxStickerSize;
  1886. if (framew * height > frameh * _width) {
  1887. if (framew < maxSize || frameh > height) {
  1888. if (frameh > height || (framew * height / frameh) <= maxSize) {
  1889. framew = framew * height / frameh;
  1890. frameh = height;
  1891. } else {
  1892. frameh = int32(frameh * maxSize) / framew;
  1893. framew = maxSize;
  1894. }
  1895. }
  1896. } else {
  1897. if (frameh < maxSize || framew > _width) {
  1898. if (framew > _width || (frameh * _width / framew) <= maxSize) {
  1899. frameh = frameh * _width / framew;
  1900. framew = _width;
  1901. } else {
  1902. framew = int32(framew * maxSize) / frameh;
  1903. frameh = maxSize;
  1904. }
  1905. }
  1906. }
  1907. return QSize(framew, frameh);
  1908. }
  1909. void Gif::clipCallback(Media::Clip::Notification notification) {
  1910. using namespace Media::Clip;
  1911. switch (notification) {
  1912. case Notification::Reinit: {
  1913. if (_gif) {
  1914. if (_gif->state() == State::Error) {
  1915. _gif.setBad();
  1916. } else if (_gif->ready() && !_gif->started()) {
  1917. if (_gif->width() * _gif->height() > kMaxInlineArea) {
  1918. _data->dimensions = QSize(
  1919. _gif->width(),
  1920. _gif->height());
  1921. _gif.reset();
  1922. } else {
  1923. _gif->start({
  1924. .frame = countFrameSize(),
  1925. .outer = { _width, st::inlineMediaHeight },
  1926. });
  1927. }
  1928. } else if (_gif->autoPausedGif()
  1929. && !delegate()->itemVisible(this)) {
  1930. clearHeavyPart();
  1931. }
  1932. }
  1933. update();
  1934. } break;
  1935. case Notification::Repaint: {
  1936. if (_gif && !_gif->currentDisplayed()) {
  1937. update();
  1938. }
  1939. } break;
  1940. }
  1941. }
  1942. void Gif::validateThumbnail(
  1943. Image *image,
  1944. QSize size,
  1945. QSize frame,
  1946. bool good) {
  1947. if (!image || (_thumbGood && !good)) {
  1948. return;
  1949. } else if ((_thumb.size() == size * style::DevicePixelRatio())
  1950. && (_thumbGood || !good)) {
  1951. return;
  1952. }
  1953. _thumbGood = good;
  1954. _thumb = image->pixNoCache(
  1955. frame * style::DevicePixelRatio(),
  1956. {
  1957. .options = (good ? Images::Option() : Images::Option::Blur),
  1958. .outer = size,
  1959. }).toImage();
  1960. }
  1961. void Gif::prepareThumbnail(QSize size, QSize frame) {
  1962. const auto document = _data;
  1963. Assert(document != nullptr);
  1964. ensureDataMediaCreated();
  1965. validateThumbnail(_dataMedia->thumbnail(), size, frame, true);
  1966. validateThumbnail(_dataMedia->thumbnailInline(), size, frame, false);
  1967. }
  1968. void Gif::paint(
  1969. Painter &p,
  1970. const QRect &clip,
  1971. TextSelection selection,
  1972. const PaintContext *context) {
  1973. const auto document = _data;
  1974. ensureDataMediaCreated();
  1975. const auto preview = Data::VideoPreviewState(_dataMedia.get());
  1976. preview.automaticLoad(getItem()->fullId());
  1977. const auto displayLoading = !preview.usingThumbnail()
  1978. && document->displayLoading();
  1979. const auto loaded = preview.loaded();
  1980. const auto loading = preview.loading();
  1981. if (loaded
  1982. && !_gif
  1983. && !_gif.isBad()
  1984. && CanPlayInline(document)) {
  1985. auto that = const_cast<Gif*>(this);
  1986. that->_gif = preview.makeAnimation([=](
  1987. Media::Clip::Notification notification) {
  1988. that->clipCallback(notification);
  1989. });
  1990. }
  1991. const auto animating = (_gif && _gif->started());
  1992. if (displayLoading) {
  1993. ensureRadial();
  1994. if (!_radial->animating()) {
  1995. _radial->start(dataProgress());
  1996. }
  1997. }
  1998. const auto radial = isRadialAnimation();
  1999. const auto frame = countFrameSize();
  2000. const auto r = QRect(0, 0, _width, st::inlineMediaHeight);
  2001. if (animating) {
  2002. const auto pixmap = _gif->current({
  2003. .frame = frame,
  2004. .outer = r.size(),
  2005. }, context->paused ? 0 : context->ms);
  2006. if (_thumb.isNull()) {
  2007. _thumb = pixmap;
  2008. _thumbGood = true;
  2009. }
  2010. p.drawImage(r.topLeft(), pixmap);
  2011. } else {
  2012. prepareThumbnail(r.size(), frame);
  2013. if (_thumb.isNull()) {
  2014. p.fillRect(r, st::overviewPhotoBg);
  2015. } else {
  2016. p.drawImage(r.topLeft(), _thumb);
  2017. }
  2018. }
  2019. const auto selected = (selection == FullSelection);
  2020. if (radial
  2021. || _gif.isBad()
  2022. || (!_gif && !loaded && !loading && !preview.usingThumbnail())) {
  2023. const auto radialOpacity = (radial && loaded)
  2024. ? _radial->opacity()
  2025. : 1.;
  2026. p.fillRect(r, st::msgDateImgBg);
  2027. p.setOpacity(radialOpacity);
  2028. auto icon = [&] {
  2029. if (radial || loading) {
  2030. return &st::historyFileInCancel;
  2031. } else if (loaded) {
  2032. return &st::historyFileInPlay;
  2033. }
  2034. return &st::historyFileInDownload;
  2035. }();
  2036. const auto size = st::overviewVideoRadialSize;
  2037. QRect inner(
  2038. (r.width() - size) / 2,
  2039. (r.height() - size) / 2,
  2040. size,
  2041. size);
  2042. icon->paintInCenter(p, inner);
  2043. if (radial) {
  2044. p.setOpacity(1);
  2045. const auto margin = st::msgFileRadialLine;
  2046. const auto rinner = inner
  2047. - QMargins(margin, margin, margin, margin);
  2048. auto &bg = selected
  2049. ? st::historyFileInRadialFgSelected
  2050. : st::historyFileInRadialFg;
  2051. _radial->draw(p, rinner, st::msgFileRadialLine, bg);
  2052. }
  2053. }
  2054. const auto checkDelta = st::overviewCheckSkip + st::overviewCheck.size;
  2055. const auto checkLeft = _width - checkDelta;
  2056. const auto checkTop = st::overviewCheckSkip;
  2057. paintCheckbox(p, { checkLeft, checkTop }, selected, context);
  2058. }
  2059. void Gif::update() {
  2060. delegate()->repaintItem(this);
  2061. }
  2062. void Gif::ensureDataMediaCreated() const {
  2063. if (_dataMedia) {
  2064. return;
  2065. }
  2066. _dataMedia = _data->createMediaView();
  2067. _dataMedia->goodThumbnailWanted();
  2068. _dataMedia->thumbnailWanted(parent()->fullId());
  2069. delegate()->registerHeavyItem(this);
  2070. }
  2071. void Gif::clearHeavyPart() {
  2072. _gif.reset();
  2073. _dataMedia = nullptr;
  2074. }
  2075. void Gif::setPosition(int32 position) {
  2076. AbstractLayoutItem::setPosition(position);
  2077. if (position < 0) {
  2078. _gif.reset();
  2079. }
  2080. }
  2081. float64 Gif::dataProgress() const {
  2082. ensureDataMediaCreated();
  2083. return _dataMedia->progress();
  2084. }
  2085. bool Gif::dataFinished() const {
  2086. return !_data->loading();
  2087. }
  2088. bool Gif::dataLoaded() const {
  2089. ensureDataMediaCreated();
  2090. const auto preview = Data::VideoPreviewState(_dataMedia.get());
  2091. return preview.loaded();
  2092. }
  2093. bool Gif::iconAnimated() const {
  2094. return true;
  2095. }
  2096. TextState Gif::getState(
  2097. QPoint point,
  2098. StateRequest request) const {
  2099. if (hasPoint(point)) {
  2100. const auto link = (_data->loading() || _data->uploading())
  2101. ? _cancell
  2102. : dataLoaded()
  2103. ? _openl
  2104. : _savel;
  2105. return { parent(), link };
  2106. }
  2107. return {};
  2108. }
  2109. void Gif::updateStatusText() {
  2110. auto statusSize = int64();
  2111. if (_data->status == FileDownloadFailed || _data->status == FileUploadFailed) {
  2112. statusSize = Ui::FileStatusSizeFailed;
  2113. } else if (_data->uploading()) {
  2114. statusSize = _data->uploadingData->offset;
  2115. } else if (dataLoaded()) {
  2116. statusSize = Ui::FileStatusSizeLoaded;
  2117. } else {
  2118. statusSize = Ui::FileStatusSizeReady;
  2119. }
  2120. if (statusSize != _status.size()) {
  2121. auto status = statusSize;
  2122. auto size = _data->size;
  2123. if (statusSize >= 0 && statusSize < 0xFF000000LL) {
  2124. size = status;
  2125. status = Ui::FileStatusSizeReady;
  2126. }
  2127. _status.update(status, size, -1, 0);
  2128. _status.setSize(statusSize);
  2129. }
  2130. }
  2131. } // namespace Layout
  2132. } // namespace Overview