history_view_message.cpp 141 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781
  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 "history/view/history_view_message.h"
  8. #include "core/click_handler_types.h" // ClickHandlerContext
  9. #include "core/ui_integration.h"
  10. #include "history/view/history_view_cursor_state.h"
  11. #include "history/history_item_components.h"
  12. #include "history/history_item_helpers.h"
  13. #include "history/view/media/history_view_web_page.h"
  14. #include "history/view/reactions/history_view_reactions.h"
  15. #include "history/view/reactions/history_view_reactions_button.h"
  16. #include "history/view/history_view_group_call_bar.h" // UserpicInRow.
  17. #include "history/view/history_view_reply.h"
  18. #include "history/view/history_view_view_button.h" // ViewButton.
  19. #include "history/history.h"
  20. #include "boxes/premium_preview_box.h"
  21. #include "boxes/share_box.h"
  22. #include "ui/effects/glare.h"
  23. #include "ui/effects/reaction_fly_animation.h"
  24. #include "ui/rect.h"
  25. #include "ui/round_rect.h"
  26. #include "ui/text/text_utilities.h"
  27. #include "ui/text/text_extended_data.h"
  28. #include "ui/power_saving.h"
  29. #include "data/components/factchecks.h"
  30. #include "data/components/sponsored_messages.h"
  31. #include "data/data_session.h"
  32. #include "data/data_user.h"
  33. #include "data/data_channel.h"
  34. #include "data/data_forum_topic.h"
  35. #include "data/data_message_reactions.h"
  36. #include "lang/lang_keys.h"
  37. #include "mainwidget.h"
  38. #include "main/main_session.h"
  39. #include "settings/settings_premium.h"
  40. #include "ui/text/text_options.h"
  41. #include "ui/painter.h"
  42. #include "window/themes/window_theme.h" // IsNightMode.
  43. #include "window/window_session_controller.h"
  44. #include "apiwrap.h"
  45. #include "styles/style_chat.h"
  46. #include "styles/style_chat_helpers.h"
  47. #include "styles/style_dialogs.h"
  48. namespace HistoryView {
  49. namespace {
  50. constexpr auto kPlayStatusLimit = 2;
  51. const auto kPsaTooltipPrefix = "cloud_lng_tooltip_psa_";
  52. class KeyboardStyle : public ReplyKeyboard::Style {
  53. public:
  54. KeyboardStyle(const style::BotKeyboardButton &st, Fn<void()> repaint);
  55. Images::CornersMaskRef buttonRounding(
  56. Ui::BubbleRounding outer,
  57. RectParts sides) const override;
  58. void startPaint(
  59. QPainter &p,
  60. const Ui::ChatStyle *st) const override;
  61. const style::TextStyle &textStyle() const override;
  62. void repaint(not_null<const HistoryItem*> item) const override;
  63. protected:
  64. void paintButtonBg(
  65. QPainter &p,
  66. const Ui::ChatStyle *st,
  67. const QRect &rect,
  68. Ui::BubbleRounding rounding,
  69. float64 howMuchOver) const override;
  70. void paintButtonIcon(
  71. QPainter &p,
  72. const Ui::ChatStyle *st,
  73. const QRect &rect,
  74. int outerWidth,
  75. HistoryMessageMarkupButton::Type type) const override;
  76. void paintButtonLoading(
  77. QPainter &p,
  78. const Ui::ChatStyle *st,
  79. const QRect &rect,
  80. int outerWidth,
  81. Ui::BubbleRounding rounding) const override;
  82. int minButtonWidth(HistoryMessageMarkupButton::Type type) const override;
  83. private:
  84. using BubbleRoundingKey = uchar;
  85. mutable base::flat_map<BubbleRoundingKey, QImage> _cachedBg;
  86. mutable base::flat_map<BubbleRoundingKey, QPainterPath> _cachedOutline;
  87. mutable std::unique_ptr<Ui::GlareEffect> _glare;
  88. Fn<void()> _repaint;
  89. rpl::lifetime _lifetime;
  90. };
  91. KeyboardStyle::KeyboardStyle(
  92. const style::BotKeyboardButton &st,
  93. Fn<void()> repaint)
  94. : ReplyKeyboard::Style(st)
  95. , _repaint(std::move(repaint)) {
  96. style::PaletteChanged(
  97. ) | rpl::start_with_next([=] {
  98. _cachedBg = {};
  99. _cachedOutline = {};
  100. }, _lifetime);
  101. }
  102. void KeyboardStyle::startPaint(
  103. QPainter &p,
  104. const Ui::ChatStyle *st) const {
  105. Expects(st != nullptr);
  106. p.setPen(st->msgServiceFg());
  107. }
  108. const style::TextStyle &KeyboardStyle::textStyle() const {
  109. return st::serviceTextStyle;
  110. }
  111. void KeyboardStyle::repaint(not_null<const HistoryItem*> item) const {
  112. item->history()->owner().requestItemRepaint(item);
  113. }
  114. Images::CornersMaskRef KeyboardStyle::buttonRounding(
  115. Ui::BubbleRounding outer,
  116. RectParts sides) const {
  117. using namespace Images;
  118. using namespace Ui;
  119. using Radius = CachedCornerRadius;
  120. using Corner = BubbleCornerRounding;
  121. auto result = CornersMaskRef(CachedCornersMasks(Radius::BubbleSmall));
  122. if (sides & RectPart::Bottom) {
  123. const auto &large = CachedCornersMasks(Radius::BubbleLarge);
  124. auto round = [&](RectPart side, int index) {
  125. if ((sides & side) && (outer[index] == Corner::Large)) {
  126. result.p[index] = &large[index];
  127. }
  128. };
  129. round(RectPart::Left, kBottomLeft);
  130. round(RectPart::Right, kBottomRight);
  131. }
  132. return result;
  133. }
  134. void KeyboardStyle::paintButtonBg(
  135. QPainter &p,
  136. const Ui::ChatStyle *st,
  137. const QRect &rect,
  138. Ui::BubbleRounding rounding,
  139. float64 howMuchOver) const {
  140. Expects(st != nullptr);
  141. using Corner = Ui::BubbleCornerRounding;
  142. auto &cachedBg = _cachedBg[rounding.key()];
  143. if (cachedBg.isNull()
  144. || cachedBg.width() != (rect.width() * style::DevicePixelRatio())) {
  145. cachedBg = QImage(
  146. rect.size() * style::DevicePixelRatio(),
  147. QImage::Format_ARGB32_Premultiplied);
  148. cachedBg.setDevicePixelRatio(style::DevicePixelRatio());
  149. cachedBg.fill(Qt::transparent);
  150. {
  151. auto painter = QPainter(&cachedBg);
  152. const auto sti = &st->imageStyle(false);
  153. const auto &small = sti->msgServiceBgCornersSmall;
  154. const auto &large = sti->msgServiceBgCornersLarge;
  155. auto corners = Ui::CornersPixmaps();
  156. int radiuses[4];
  157. for (auto i = 0; i != 4; ++i) {
  158. const auto isLarge = (rounding[i] == Corner::Large);
  159. corners.p[i] = (isLarge ? large : small).p[i];
  160. radiuses[i] = Ui::CachedCornerRadiusValue(isLarge
  161. ? Ui::CachedCornerRadius::BubbleLarge
  162. : Ui::CachedCornerRadius::BubbleSmall);
  163. }
  164. const auto r = Rect(rect.size());
  165. _cachedOutline[rounding.key()] = Ui::ComplexRoundedRectPath(
  166. r - Margins(st::lineWidth),
  167. radiuses[0],
  168. radiuses[1],
  169. radiuses[2],
  170. radiuses[3]);
  171. Ui::FillRoundRect(painter, r, sti->msgServiceBg, corners);
  172. }
  173. }
  174. p.drawImage(rect.topLeft(), cachedBg);
  175. if (howMuchOver > 0) {
  176. auto o = p.opacity();
  177. p.setOpacity(o * howMuchOver);
  178. const auto &small = st->msgBotKbOverBgAddCornersSmall();
  179. const auto &large = st->msgBotKbOverBgAddCornersLarge();
  180. auto over = Ui::CornersPixmaps();
  181. for (auto i = 0; i != 4; ++i) {
  182. over.p[i] = (rounding[i] == Corner::Large ? large : small).p[i];
  183. }
  184. Ui::FillRoundRect(p, rect, st->msgBotKbOverBgAdd(), over);
  185. p.setOpacity(o);
  186. }
  187. }
  188. void KeyboardStyle::paintButtonIcon(
  189. QPainter &p,
  190. const Ui::ChatStyle *st,
  191. const QRect &rect,
  192. int outerWidth,
  193. HistoryMessageMarkupButton::Type type) const {
  194. Expects(st != nullptr);
  195. using Type = HistoryMessageMarkupButton::Type;
  196. const auto icon = [&]() -> const style::icon* {
  197. switch (type) {
  198. case Type::Url:
  199. case Type::Auth: return &st->msgBotKbUrlIcon();
  200. case Type::Buy: return &st->msgBotKbPaymentIcon();
  201. case Type::SwitchInlineSame:
  202. case Type::SwitchInline: return &st->msgBotKbSwitchPmIcon();
  203. case Type::WebView:
  204. case Type::SimpleWebView: return &st->msgBotKbWebviewIcon();
  205. case Type::CopyText: return &st->msgBotKbCopyIcon();
  206. }
  207. return nullptr;
  208. }();
  209. if (icon) {
  210. icon->paint(p, rect.x() + rect.width() - icon->width() - st::msgBotKbIconPadding, rect.y() + st::msgBotKbIconPadding, outerWidth);
  211. }
  212. }
  213. void KeyboardStyle::paintButtonLoading(
  214. QPainter &p,
  215. const Ui::ChatStyle *st,
  216. const QRect &rect,
  217. int outerWidth,
  218. Ui::BubbleRounding rounding) const {
  219. Expects(st != nullptr);
  220. if (anim::Disabled()) {
  221. const auto &icon = st->historySendingInvertedIcon();
  222. icon.paint(
  223. p,
  224. rect::right(rect) - icon.width() - st::msgBotKbIconPadding,
  225. rect::bottom(rect) - icon.height() - st::msgBotKbIconPadding,
  226. rect.x() * 2 + rect.width());
  227. return;
  228. }
  229. const auto cacheKey = rounding.key();
  230. auto &cachedBg = _cachedBg[cacheKey];
  231. if (!cachedBg.isNull()) {
  232. if (_glare && _glare->glare.birthTime) {
  233. const auto progress = _glare->progress(crl::now());
  234. const auto w = _glare->width;
  235. const auto h = rect.height();
  236. const auto x = (-w) + (w * 2) * progress;
  237. auto frame = cachedBg;
  238. frame.fill(Qt::transparent);
  239. {
  240. auto painter = QPainter(&frame);
  241. auto hq = PainterHighQualityEnabler(painter);
  242. painter.setPen(Qt::NoPen);
  243. painter.drawTiledPixmap(x, 0, w, h, _glare->pixmap, 0, 0);
  244. auto path = QPainterPath();
  245. path.addRect(Rect(rect.size()));
  246. path -= _cachedOutline[cacheKey];
  247. constexpr auto kBgOutlineAlpha = 0.5;
  248. constexpr auto kFgOutlineAlpha = 0.8;
  249. const auto &c = st::premiumButtonFg->c;
  250. painter.setPen(Qt::NoPen);
  251. painter.setBrush(c);
  252. painter.setOpacity(kBgOutlineAlpha);
  253. painter.drawPath(path);
  254. auto gradient = QLinearGradient(-w, 0, w * 2, 0);
  255. {
  256. constexpr auto kShiftLeft = 0.01;
  257. constexpr auto kShiftRight = 0.99;
  258. auto stops = _glare->computeGradient(c).stops();
  259. stops[1] = {
  260. std::clamp(progress, kShiftLeft, kShiftRight),
  261. QColor(c.red(), c.green(), c.blue(), kFgOutlineAlpha),
  262. };
  263. gradient.setStops(std::move(stops));
  264. }
  265. painter.setBrush(QBrush(gradient));
  266. painter.setOpacity(1);
  267. painter.drawPath(path);
  268. painter.setCompositionMode(
  269. QPainter::CompositionMode_DestinationIn);
  270. painter.drawImage(0, 0, cachedBg);
  271. }
  272. p.drawImage(rect.x(), rect.y(), frame);
  273. } else {
  274. _glare = std::make_unique<Ui::GlareEffect>();
  275. _glare->width = outerWidth;
  276. constexpr auto kTimeout = crl::time(0);
  277. constexpr auto kDuration = crl::time(1100);
  278. const auto color = st::premiumButtonFg->c;
  279. _glare->validate(color, _repaint, kTimeout, kDuration);
  280. }
  281. }
  282. }
  283. int KeyboardStyle::minButtonWidth(
  284. HistoryMessageMarkupButton::Type type) const {
  285. using Type = HistoryMessageMarkupButton::Type;
  286. int result = 2 * buttonPadding(), iconWidth = 0;
  287. switch (type) {
  288. case Type::Url:
  289. case Type::Auth: iconWidth = st::msgBotKbUrlIcon.width(); break;
  290. case Type::Buy: iconWidth = st::msgBotKbPaymentIcon.width(); break;
  291. case Type::SwitchInlineSame:
  292. case Type::SwitchInline: iconWidth = st::msgBotKbSwitchPmIcon.width(); break;
  293. case Type::Callback:
  294. case Type::CallbackWithPassword:
  295. case Type::Game: iconWidth = st::historySendingInvertedIcon.width(); break;
  296. case Type::WebView:
  297. case Type::SimpleWebView: iconWidth = st::msgBotKbWebviewIcon.width(); break;
  298. case Type::CopyText: return st::msgBotKbCopyIcon.width(); break;
  299. }
  300. if (iconWidth > 0) {
  301. result = std::max(result, 2 * iconWidth + 4 * int(st::msgBotKbIconPadding));
  302. }
  303. return result;
  304. }
  305. QString FastForwardText() {
  306. return u"Forward"_q;
  307. }
  308. QString FastReplyText() {
  309. return tr::lng_fast_reply(tr::now);
  310. }
  311. bool ShowFastForwardFor(const QString &username) {
  312. return !username.compare(u"ReviewInsightsBot"_q, Qt::CaseInsensitive)
  313. || !username.compare(u"reviews_bot"_q, Qt::CaseInsensitive);
  314. }
  315. [[nodiscard]] ClickHandlerPtr MakeTopicButtonLink(
  316. not_null<Data::ForumTopic*> topic,
  317. MsgId messageId) {
  318. const auto weak = base::make_weak(topic);
  319. return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
  320. const auto my = context.other.value<ClickHandlerContext>();
  321. if (const auto controller = my.sessionWindow.get()) {
  322. if (const auto strong = weak.get()) {
  323. controller->showTopic(
  324. strong,
  325. messageId,
  326. Window::SectionShow::Way::Forward);
  327. }
  328. }
  329. });
  330. }
  331. struct SecondRightAction final {
  332. std::unique_ptr<Ui::RippleAnimation> ripple;
  333. ClickHandlerPtr link;
  334. };
  335. } // namespace
  336. struct Message::CommentsButton {
  337. std::unique_ptr<Ui::RippleAnimation> ripple;
  338. std::vector<UserpicInRow> userpics;
  339. QImage cachedUserpics;
  340. ClickHandlerPtr link;
  341. QPoint lastPoint;
  342. int rippleShift = 0;
  343. };
  344. struct Message::FromNameStatus {
  345. EmojiStatusId id;
  346. std::unique_ptr<Ui::Text::CustomEmoji> custom;
  347. ClickHandlerPtr link;
  348. int skip = 0;
  349. };
  350. struct Message::RightAction {
  351. std::unique_ptr<Ui::RippleAnimation> ripple;
  352. ClickHandlerPtr link;
  353. QPoint lastPoint;
  354. std::unique_ptr<SecondRightAction> second;
  355. };
  356. LogEntryOriginal::LogEntryOriginal() = default;
  357. LogEntryOriginal::LogEntryOriginal(LogEntryOriginal &&other)
  358. : page(std::move(other.page)) {
  359. }
  360. LogEntryOriginal &LogEntryOriginal::operator=(LogEntryOriginal &&other) {
  361. page = std::move(other.page);
  362. return *this;
  363. }
  364. LogEntryOriginal::~LogEntryOriginal() = default;
  365. Message::Message(
  366. not_null<ElementDelegate*> delegate,
  367. not_null<HistoryItem*> data,
  368. Element *replacing)
  369. : Element(delegate, data, replacing, Flag(0))
  370. , _hideReply(delegate->elementHideReply(this))
  371. , _postShowingAuthor(data->isPostShowingAuthor() ? 1 : 0)
  372. , _bottomInfo(
  373. &data->history()->owner().reactions(),
  374. BottomInfoDataFromMessage(this)) {
  375. if (const auto media = data->media()) {
  376. if (media->giveawayResults()) {
  377. _hideReply = 1;
  378. }
  379. }
  380. initLogEntryOriginal();
  381. initPsa();
  382. setupReactions(replacing);
  383. auto animation = replacing ? replacing->takeEffectAnimation() : nullptr;
  384. if (animation) {
  385. _bottomInfo.continueEffectAnimation(std::move(animation));
  386. }
  387. if (data->isSponsored()) {
  388. const auto &session = data->history()->session();
  389. const auto details = session.sponsoredMessages().lookupDetails(
  390. data->fullId());
  391. if (details.canReport) {
  392. _rightAction = std::make_unique<RightAction>();
  393. _rightAction->second = std::make_unique<SecondRightAction>();
  394. _rightAction->second->link = ReportSponsoredClickHandler(data);
  395. }
  396. }
  397. initPaidInformation();
  398. }
  399. Message::~Message() {
  400. if (_comments || (_fromNameStatus && _fromNameStatus->custom)) {
  401. _comments = nullptr;
  402. _fromNameStatus = nullptr;
  403. checkHeavyPart();
  404. }
  405. }
  406. void Message::initPaidInformation() {
  407. const auto item = data();
  408. if (!item->history()->peer->isUser()) {
  409. return;
  410. }
  411. const auto media = this->media();
  412. const auto mine = PaidInformation{
  413. .messages = 1,
  414. .stars = item->starsPaid(),
  415. };
  416. auto info = media ? media->paidInformation().value_or(mine) : mine;
  417. if (!info) {
  418. return;
  419. }
  420. const auto action = [&] {
  421. return (info.messages == 1)
  422. ? tr::lng_action_paid_message_one(
  423. tr::now,
  424. Ui::Text::WithEntities)
  425. : tr::lng_action_paid_message_some(
  426. tr::now,
  427. lt_count,
  428. info.messages,
  429. Ui::Text::WithEntities);
  430. };
  431. auto text = PreparedServiceText{
  432. .text = item->out()
  433. ? tr::lng_action_paid_message_sent(
  434. tr::now,
  435. lt_count,
  436. info.stars,
  437. lt_action,
  438. action(),
  439. Ui::Text::WithEntities)
  440. : tr::lng_action_paid_message_got(
  441. tr::now,
  442. lt_count,
  443. info.stars,
  444. lt_name,
  445. Ui::Text::Link(item->from()->shortName(), 1),
  446. Ui::Text::WithEntities),
  447. };
  448. if (!item->out()) {
  449. text.links.push_back(item ->from()->createOpenLink());
  450. }
  451. setServicePreMessage(std::move(text));
  452. }
  453. void Message::refreshRightBadge() {
  454. const auto item = data();
  455. const auto text = [&] {
  456. if (item->isDiscussionPost()) {
  457. return (delegate()->elementContext() == Context::Replies)
  458. ? QString()
  459. : tr::lng_channel_badge(tr::now);
  460. } else if (item->author()->isMegagroup()) {
  461. if (const auto msgsigned = item->Get<HistoryMessageSigned>()) {
  462. if (!msgsigned->viaBusinessBot) {
  463. Assert(msgsigned->isAnonymousRank);
  464. return msgsigned->author;
  465. }
  466. }
  467. }
  468. const auto channel = item->history()->peer->asMegagroup();
  469. const auto user = item->author()->asUser();
  470. if (!channel || !user) {
  471. return QString();
  472. }
  473. const auto info = channel->mgInfo.get();
  474. const auto i = info->admins.find(peerToUser(user->id));
  475. const auto custom = (i != info->admins.end())
  476. ? i->second
  477. : (info->creator == user)
  478. ? info->creatorRank
  479. : QString();
  480. return !custom.isEmpty()
  481. ? custom
  482. : (info->creator == user)
  483. ? tr::lng_owner_badge(tr::now)
  484. : (i != info->admins.end())
  485. ? tr::lng_admin_badge(tr::now)
  486. : QString();
  487. }();
  488. auto badge = TextWithEntities{
  489. (text.isEmpty()
  490. ? delegate()->elementAuthorRank(this)
  491. : TextUtilities::RemoveEmoji(TextUtilities::SingleLine(text)))
  492. };
  493. _rightBadgeHasBoosts = 0;
  494. if (const auto boosts = item->boostsApplied()) {
  495. _rightBadgeHasBoosts = 1;
  496. const auto many = (boosts > 1);
  497. const auto &icon = many
  498. ? st::boostsMessageIcon
  499. : st::boostMessageIcon;
  500. const auto padding = many
  501. ? st::boostsMessageIconPadding
  502. : st::boostMessageIconPadding;
  503. const auto owner = &item->history()->owner();
  504. auto added = Ui::Text::SingleCustomEmoji(
  505. owner->customEmojiManager().registerInternalEmoji(icon, padding)
  506. ).append(many ? QString::number(boosts) : QString());
  507. badge.append(' ').append(Ui::Text::Colorized(added, 1));
  508. }
  509. if (badge.empty()) {
  510. _rightBadge.clear();
  511. } else {
  512. const auto context = Core::TextContext({
  513. .session = &item->history()->session(),
  514. .customEmojiLoopLimit = 1,
  515. });
  516. _rightBadge.setMarkedText(
  517. st::defaultTextStyle,
  518. badge,
  519. Ui::NameTextOptions(),
  520. context);
  521. }
  522. }
  523. void Message::applyGroupAdminChanges(
  524. const base::flat_set<UserId> &changes) {
  525. if (!data()->out()
  526. && changes.contains(peerToUser(data()->author()->id))) {
  527. history()->owner().requestViewResize(this);
  528. }
  529. }
  530. void Message::animateReaction(Ui::ReactionFlyAnimationArgs &&args) {
  531. const auto item = data();
  532. const auto media = this->media();
  533. auto g = countGeometry();
  534. if (g.width() < 1 || isHidden()) {
  535. return;
  536. }
  537. const auto repainter = [=] { repaint(); };
  538. const auto bubble = drawBubble();
  539. const auto reactionsInBubble = _reactions && embedReactionsInBubble();
  540. const auto mediaDisplayed = media && media->isDisplayed();
  541. const auto keyboard = item->inlineReplyKeyboard();
  542. auto keyboardHeight = 0;
  543. if (keyboard) {
  544. keyboardHeight = keyboard->naturalHeight();
  545. g.setHeight(g.height() - st::msgBotKbButton.margin - keyboardHeight);
  546. }
  547. if (_reactions && !reactionsInBubble) {
  548. const auto reactionsHeight = st::mediaInBubbleSkip + _reactions->height();
  549. const auto reactionsLeft = (!bubble && mediaDisplayed)
  550. ? media->contentRectForReactions().x()
  551. : 0;
  552. g.setHeight(g.height() - reactionsHeight);
  553. const auto reactionsPosition = QPoint(reactionsLeft + g.left(), g.top() + g.height() + st::mediaInBubbleSkip);
  554. _reactions->animate(args.translated(-reactionsPosition), repainter);
  555. return;
  556. }
  557. if (bubble) {
  558. // Entry page is always a bubble bottom.
  559. auto inner = g;
  560. if (_comments) {
  561. inner.setHeight(inner.height() - st::historyCommentsButtonHeight);
  562. }
  563. auto trect = inner.marginsRemoved(st::msgPadding);
  564. const auto reactionsTop = (reactionsInBubble && !_viewButton)
  565. ? st::mediaInBubbleSkip
  566. : 0;
  567. const auto reactionsHeight = reactionsInBubble
  568. ? (reactionsTop + _reactions->height())
  569. : 0;
  570. if (reactionsInBubble) {
  571. trect.setHeight(trect.height() - reactionsHeight);
  572. const auto reactionsPosition = QPoint(trect.left(), trect.top() + trect.height() + reactionsTop);
  573. _reactions->animate(args.translated(-reactionsPosition), repainter);
  574. return;
  575. }
  576. }
  577. }
  578. void Message::animateEffect(Ui::ReactionFlyAnimationArgs &&args) {
  579. const auto item = data();
  580. const auto media = this->media();
  581. auto g = countGeometry();
  582. if (g.width() < 1 || isHidden()) {
  583. return;
  584. }
  585. const auto repainter = [=] { repaint(); };
  586. const auto bubble = drawBubble();
  587. const auto reactionsInBubble = _reactions && embedReactionsInBubble();
  588. const auto mediaDisplayed = media && media->isDisplayed();
  589. const auto keyboard = item->inlineReplyKeyboard();
  590. auto keyboardHeight = 0;
  591. if (keyboard) {
  592. keyboardHeight = keyboard->naturalHeight();
  593. g.setHeight(g.height() - st::msgBotKbButton.margin - keyboardHeight);
  594. }
  595. const auto animateInBottomInfo = [&](QPoint bottomRight) {
  596. _bottomInfo.animateEffect(args.translated(-bottomRight), repainter);
  597. };
  598. if (bubble) {
  599. const auto entry = logEntryOriginal();
  600. const auto check = factcheckBlock();
  601. // Entry page is always a bubble bottom.
  602. auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || check || (entry/* && entry->isBubbleBottom()*/);
  603. auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop());
  604. auto inner = g;
  605. if (_comments) {
  606. inner.setHeight(inner.height() - st::historyCommentsButtonHeight);
  607. }
  608. auto trect = inner.marginsRemoved(st::msgPadding);
  609. const auto reactionsTop = (reactionsInBubble && !_viewButton)
  610. ? st::mediaInBubbleSkip
  611. : 0;
  612. const auto reactionsHeight = reactionsInBubble
  613. ? (reactionsTop + _reactions->height())
  614. : 0;
  615. if (_viewButton) {
  616. const auto belowInfo = _viewButton->belowMessageInfo();
  617. const auto infoHeight = reactionsInBubble
  618. ? (reactionsHeight + 2 * st::mediaInBubbleSkip)
  619. : _bottomInfo.height();
  620. const auto heightMargins = QMargins(0, 0, 0, infoHeight);
  621. if (belowInfo) {
  622. inner -= heightMargins;
  623. }
  624. trect.setHeight(trect.height() - _viewButton->height());
  625. if (reactionsInBubble) {
  626. trect.setHeight(trect.height() - st::mediaInBubbleSkip + st::msgPadding.bottom());
  627. } else if (mediaDisplayed) {
  628. trect.setHeight(trect.height() - st::mediaInBubbleSkip);
  629. }
  630. }
  631. if (mediaOnBottom) {
  632. trect.setHeight(trect.height()
  633. + st::msgPadding.bottom()
  634. - viewButtonHeight());
  635. }
  636. if (mediaOnTop) {
  637. trect.setY(trect.y() - st::msgPadding.top());
  638. }
  639. if (mediaDisplayed && mediaOnBottom && media->customInfoLayout()) {
  640. auto mediaHeight = media->height();
  641. auto mediaLeft = trect.x() - st::msgPadding.left();
  642. auto mediaTop = (trect.y() + trect.height() - mediaHeight);
  643. animateInBottomInfo(QPoint(mediaLeft, mediaTop) + media->resolveCustomInfoRightBottom());
  644. } else {
  645. animateInBottomInfo({
  646. inner.left() + inner.width() - (st::msgPadding.right() - st::msgDateDelta.x()),
  647. inner.top() + inner.height() - (st::msgPadding.bottom() - st::msgDateDelta.y()),
  648. });
  649. }
  650. } else if (mediaDisplayed) {
  651. animateInBottomInfo(g.topLeft() + media->resolveCustomInfoRightBottom());
  652. }
  653. }
  654. auto Message::takeEffectAnimation()
  655. -> std::unique_ptr<Ui::ReactionFlyAnimation> {
  656. return _bottomInfo.takeEffectAnimation();
  657. }
  658. QRect Message::effectIconGeometry() const {
  659. const auto item = data();
  660. const auto media = this->media();
  661. auto g = countGeometry();
  662. if (g.width() < 1 || isHidden()) {
  663. return {};
  664. }
  665. const auto bubble = drawBubble();
  666. const auto reactionsInBubble = _reactions && embedReactionsInBubble();
  667. const auto mediaDisplayed = media && media->isDisplayed();
  668. const auto keyboard = item->inlineReplyKeyboard();
  669. auto keyboardHeight = 0;
  670. if (keyboard) {
  671. keyboardHeight = keyboard->naturalHeight();
  672. g.setHeight(g.height() - st::msgBotKbButton.margin - keyboardHeight);
  673. }
  674. const auto fromBottomInfo = [&](QPoint bottomRight) {
  675. const auto size = _bottomInfo.currentSize();
  676. return _bottomInfo.effectIconGeometry().translated(
  677. bottomRight - QPoint(size.width(), size.height()));
  678. };
  679. if (bubble) {
  680. const auto entry = logEntryOriginal();
  681. const auto check = factcheckBlock();
  682. // Entry page is always a bubble bottom.
  683. auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || check || (entry/* && entry->isBubbleBottom()*/);
  684. auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop());
  685. auto inner = g;
  686. if (_comments) {
  687. inner.setHeight(inner.height() - st::historyCommentsButtonHeight);
  688. }
  689. auto trect = inner.marginsRemoved(st::msgPadding);
  690. const auto reactionsTop = (reactionsInBubble && !_viewButton)
  691. ? st::mediaInBubbleSkip
  692. : 0;
  693. const auto reactionsHeight = reactionsInBubble
  694. ? (reactionsTop + _reactions->height())
  695. : 0;
  696. if (_viewButton) {
  697. const auto belowInfo = _viewButton->belowMessageInfo();
  698. const auto infoHeight = reactionsInBubble
  699. ? (reactionsHeight + 2 * st::mediaInBubbleSkip)
  700. : _bottomInfo.height();
  701. const auto heightMargins = QMargins(0, 0, 0, infoHeight);
  702. if (belowInfo) {
  703. inner -= heightMargins;
  704. }
  705. trect.setHeight(trect.height() - _viewButton->height());
  706. if (reactionsInBubble) {
  707. trect.setHeight(trect.height() - st::mediaInBubbleSkip + st::msgPadding.bottom());
  708. } else if (mediaDisplayed) {
  709. trect.setHeight(trect.height() - st::mediaInBubbleSkip);
  710. }
  711. }
  712. if (mediaOnBottom) {
  713. trect.setHeight(trect.height()
  714. + st::msgPadding.bottom()
  715. - viewButtonHeight());
  716. }
  717. if (mediaOnTop) {
  718. trect.setY(trect.y() - st::msgPadding.top());
  719. }
  720. if (mediaDisplayed && mediaOnBottom && media->customInfoLayout()) {
  721. auto mediaHeight = media->height();
  722. auto mediaLeft = trect.x() - st::msgPadding.left();
  723. auto mediaTop = (trect.y() + trect.height() - mediaHeight);
  724. return fromBottomInfo(QPoint(mediaLeft, mediaTop) + media->resolveCustomInfoRightBottom());
  725. } else {
  726. return fromBottomInfo({
  727. inner.left() + inner.width() - (st::msgPadding.right() - st::msgDateDelta.x()),
  728. inner.top() + inner.height() - (st::msgPadding.bottom() - st::msgDateDelta.y()),
  729. });
  730. }
  731. } else if (mediaDisplayed) {
  732. return fromBottomInfo(g.topLeft() + media->resolveCustomInfoRightBottom());
  733. }
  734. return {};
  735. }
  736. QSize Message::performCountOptimalSize() {
  737. const auto item = data();
  738. const auto replyData = item->Get<HistoryMessageReply>();
  739. if (replyData && !_hideReply) {
  740. AddComponents(Reply::Bit());
  741. } else {
  742. RemoveComponents(Reply::Bit());
  743. }
  744. const auto factcheck = item->Get<HistoryMessageFactcheck>();
  745. if (factcheck && !factcheck->data.text.empty()) {
  746. AddComponents(Factcheck::Bit());
  747. Get<Factcheck>()->page = history()->session().factchecks().makeMedia(
  748. this,
  749. factcheck);
  750. } else {
  751. RemoveComponents(Factcheck::Bit());
  752. }
  753. const auto markup = item->inlineReplyMarkup();
  754. const auto reactionsKey = [&] {
  755. return embedReactionsInBubble() ? 0 : 1;
  756. };
  757. const auto oldKey = reactionsKey();
  758. validateText();
  759. validateInlineKeyboard(markup);
  760. updateViewButtonExistence();
  761. refreshTopicButton();
  762. const auto media = this->media();
  763. const auto textItem = this->textItem();
  764. const auto defaultInvert = media && media->aboveTextByDefault();
  765. const auto invertDefault = textItem
  766. && textItem->invertMedia()
  767. && !textItem->emptyText();
  768. _invertMedia = invertDefault ? !defaultInvert : defaultInvert;
  769. updateMediaInBubbleState();
  770. if (oldKey != reactionsKey()) {
  771. refreshReactions();
  772. }
  773. refreshRightBadge();
  774. refreshInfoSkipBlock(textItem);
  775. const auto botTop = item->isFakeAboutView()
  776. ? Get<FakeBotAboutTop>()
  777. : nullptr;
  778. if (botTop) {
  779. botTop->init();
  780. }
  781. auto maxWidth = 0;
  782. auto minHeight = 0;
  783. const auto reactionsInBubble = _reactions && embedReactionsInBubble();
  784. if (_reactions) {
  785. _reactions->initDimensions();
  786. }
  787. const auto reply = Get<Reply>();
  788. if (reply) {
  789. reply->update(this, replyData);
  790. }
  791. if (drawBubble()) {
  792. const auto forwarded = item->Get<HistoryMessageForwarded>();
  793. const auto via = item->Get<HistoryMessageVia>();
  794. const auto entry = logEntryOriginal();
  795. const auto check = factcheckBlock();
  796. if (forwarded) {
  797. forwarded->create(via, item);
  798. }
  799. auto mediaDisplayed = false;
  800. if (media) {
  801. mediaDisplayed = media->isDisplayed();
  802. media->initDimensions();
  803. }
  804. if (check) {
  805. check->initDimensions();
  806. }
  807. if (entry) {
  808. entry->initDimensions();
  809. }
  810. // Entry page is always a bubble bottom.
  811. const auto withVisibleText = hasVisibleText();
  812. const auto textualWidth = textualMaxWidth();
  813. auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || check || (entry/* && entry->isBubbleBottom()*/);
  814. auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop());
  815. maxWidth = textualWidth;
  816. if (context() == Context::Replies && item->isDiscussionPost()) {
  817. maxWidth = std::max(maxWidth, st::msgMaxWidth);
  818. }
  819. minHeight = withVisibleText ? text().minHeight() : 0;
  820. if (reactionsInBubble) {
  821. const auto reactionsMaxWidth = st::msgPadding.left()
  822. + _reactions->maxWidth()
  823. + st::msgPadding.right();
  824. accumulate_max(
  825. maxWidth,
  826. std::min(st::msgMaxWidth, reactionsMaxWidth));
  827. if (mediaDisplayed
  828. && !media->additionalInfoString().isEmpty()) {
  829. // In round videos in a web page status text is painted
  830. // in the bottom left corner, reactions should be below.
  831. minHeight += st::msgDateFont->height;
  832. } else {
  833. minHeight += st::mediaInBubbleSkip;
  834. }
  835. if (maxWidth >= reactionsMaxWidth) {
  836. minHeight += _reactions->minHeight();
  837. } else {
  838. const auto widthForReactions = maxWidth
  839. - st::msgPadding.left()
  840. - st::msgPadding.right();
  841. minHeight += _reactions->resizeGetHeight(widthForReactions);
  842. }
  843. }
  844. if (!mediaOnBottom && (!_viewButton || !reactionsInBubble)) {
  845. minHeight += st::msgPadding.bottom();
  846. if (mediaDisplayed) {
  847. minHeight += st::mediaInBubbleSkip;
  848. }
  849. }
  850. if (!mediaOnTop) {
  851. minHeight += st::msgPadding.top();
  852. if (mediaDisplayed) minHeight += st::mediaInBubbleSkip;
  853. if (entry) minHeight += st::mediaInBubbleSkip;
  854. }
  855. if (check) minHeight += st::mediaInBubbleSkip;
  856. if (mediaDisplayed) {
  857. // Parts don't participate in maxWidth() in case of media message.
  858. if (media->enforceBubbleWidth()) {
  859. maxWidth = media->maxWidth();
  860. const auto innerWidth = maxWidth
  861. - st::msgPadding.left()
  862. - st::msgPadding.right();
  863. if (withVisibleText) {
  864. if (maxWidth < textualWidth) {
  865. minHeight -= text().minHeight();
  866. minHeight += text().countHeight(innerWidth);
  867. }
  868. }
  869. if (reactionsInBubble) {
  870. minHeight -= _reactions->minHeight();
  871. minHeight
  872. += _reactions->countCurrentSize(innerWidth).height();
  873. }
  874. } else {
  875. accumulate_max(maxWidth, media->maxWidth());
  876. }
  877. minHeight += media->minHeight();
  878. } else {
  879. // Count parts in maxWidth(), don't count them in minHeight().
  880. // They will be added in resizeGetHeight() anyway.
  881. if (displayFromName()) {
  882. const auto from = item->displayFrom();
  883. validateFromNameText(from);
  884. const auto &name = from
  885. ? _fromName
  886. : item->displayHiddenSenderInfo()->nameText();
  887. auto namew = st::msgPadding.left()
  888. + name.maxWidth()
  889. + (_fromNameStatus
  890. ? st::dialogsPremiumIcon.icon.width()
  891. : 0)
  892. + st::msgPadding.right();
  893. if (via && !displayForwardedFrom()) {
  894. namew += st::msgServiceFont->spacew + via->maxWidth
  895. + (_fromNameStatus ? st::msgServiceFont->spacew : 0);
  896. }
  897. const auto replyWidth = hasFastForward()
  898. ? st::msgFont->width(FastForwardText())
  899. : hasFastReply()
  900. ? st::msgFont->width(FastReplyText())
  901. : 0;
  902. if (!_rightBadge.isEmpty()) {
  903. const auto badgeWidth = _rightBadge.maxWidth();
  904. namew += st::msgPadding.right()
  905. + std::max(badgeWidth, replyWidth);
  906. } else if (replyWidth) {
  907. namew += st::msgPadding.right() + replyWidth;
  908. }
  909. accumulate_max(maxWidth, namew);
  910. } else if (via && !displayForwardedFrom()) {
  911. accumulate_max(maxWidth, st::msgPadding.left() + via->maxWidth + st::msgPadding.right());
  912. }
  913. if (displayedTopicButton()) {
  914. const auto padding = st::msgPadding + st::topicButtonPadding;
  915. accumulate_max(
  916. maxWidth,
  917. (padding.left()
  918. + _topicButton->name.maxWidth()
  919. + st::topicButtonArrowSkip
  920. + padding.right()));
  921. }
  922. if (displayForwardedFrom()) {
  923. const auto skip1 = forwarded->psaType.isEmpty()
  924. ? 0
  925. : st::historyPsaIconSkip1;
  926. auto namew = st::msgPadding.left() + forwarded->text.maxWidth() + skip1 + st::msgPadding.right();
  927. if (via) {
  928. namew += st::msgServiceFont->spacew + via->maxWidth;
  929. }
  930. accumulate_max(maxWidth, namew);
  931. }
  932. if (reply) {
  933. const auto replyw = st::msgPadding.left()
  934. + reply->maxWidth()
  935. + st::msgPadding.right();
  936. accumulate_max(maxWidth, replyw);
  937. }
  938. if (check) {
  939. accumulate_max(maxWidth, check->maxWidth());
  940. minHeight += check->minHeight();
  941. }
  942. if (entry) {
  943. accumulate_max(maxWidth, entry->maxWidth());
  944. minHeight += entry->minHeight();
  945. }
  946. }
  947. if (withVisibleText && botTop) {
  948. accumulate_max(maxWidth, botTop->maxWidth);
  949. minHeight += botTop->height;
  950. }
  951. accumulate_max(maxWidth, minWidthForMedia());
  952. } else if (media) {
  953. media->initDimensions();
  954. maxWidth = media->maxWidth();
  955. minHeight = media->isDisplayed() ? media->minHeight() : 0;
  956. } else {
  957. maxWidth = st::msgMinWidth;
  958. minHeight = 0;
  959. }
  960. // if we have a text bubble we can resize it to fit the keyboard
  961. // but if we have only media we don't do that
  962. if (markup && markup->inlineKeyboard && hasVisibleText()) {
  963. accumulate_max(maxWidth, markup->inlineKeyboard->naturalWidth());
  964. }
  965. return QSize(maxWidth, minHeight);
  966. }
  967. void Message::refreshTopicButton() {
  968. const auto item = data();
  969. if (isAttachedToPrevious()
  970. || delegate()->elementHideTopicButton(this)) {
  971. _topicButton = nullptr;
  972. } else if (const auto topic = item->topic()) {
  973. if (!_topicButton) {
  974. _topicButton = std::make_unique<TopicButton>();
  975. }
  976. const auto jumpToId = IsServerMsgId(item->id) ? item->id : MsgId();
  977. _topicButton->link = MakeTopicButtonLink(topic, jumpToId);
  978. if (_topicButton->nameVersion != topic->titleVersion()) {
  979. _topicButton->nameVersion = topic->titleVersion();
  980. const auto context = Core::TextContext({
  981. .session = &history()->session(),
  982. .repaint = [=] { customEmojiRepaint(); },
  983. .customEmojiLoopLimit = 1,
  984. });
  985. _topicButton->name.setMarkedText(
  986. st::fwdTextStyle,
  987. topic->titleWithIcon(),
  988. kMarkupTextOptions,
  989. context);
  990. }
  991. } else {
  992. _topicButton = nullptr;
  993. }
  994. }
  995. int Message::marginTop() const {
  996. auto result = 0;
  997. if (!isHidden()) {
  998. if (isAttachedToPrevious()) {
  999. result += st::msgMarginTopAttached;
  1000. } else {
  1001. result += st::msgMargin.top();
  1002. }
  1003. }
  1004. result += displayedDateHeight();
  1005. if (const auto bar = Get<UnreadBar>()) {
  1006. result += bar->height();
  1007. }
  1008. if (const auto service = Get<ServicePreMessage>()) {
  1009. result += service->height;
  1010. }
  1011. return result;
  1012. }
  1013. int Message::marginBottom() const {
  1014. return isHidden() ? 0 : st::msgMargin.bottom();
  1015. }
  1016. void Message::draw(Painter &p, const PaintContext &context) const {
  1017. auto g = countGeometry();
  1018. if (g.width() < 1) {
  1019. return;
  1020. }
  1021. const auto item = data();
  1022. const auto media = this->media();
  1023. const auto hasGesture = context.gestureHorizontal.translation
  1024. && (context.gestureHorizontal.msgBareId == item->fullId().msg.bare);
  1025. if (hasGesture) {
  1026. p.translate(context.gestureHorizontal.translation, 0);
  1027. }
  1028. const auto selectionModeResult = delegate()->elementInSelectionMode(this);
  1029. const auto selectionTranslation = (selectionModeResult.progress > 0)
  1030. ? (selectionModeResult.progress
  1031. * AdditionalSpaceForSelectionCheckbox(this, g))
  1032. : 0;
  1033. if (selectionTranslation) {
  1034. p.translate(selectionTranslation, 0);
  1035. }
  1036. if (item->hasUnrequestedFactcheck()) {
  1037. item->history()->session().factchecks().requestFor(item);
  1038. }
  1039. const auto stm = context.messageStyle();
  1040. const auto bubble = drawBubble();
  1041. if (const auto bar = Get<UnreadBar>()) {
  1042. auto unreadbarh = bar->height();
  1043. auto dateh = 0;
  1044. if (const auto date = Get<DateBadge>()) {
  1045. dateh = date->height();
  1046. }
  1047. if (context.clip.intersects(QRect(0, dateh, width(), unreadbarh))) {
  1048. p.translate(0, dateh);
  1049. bar->paint(
  1050. p,
  1051. context,
  1052. 0,
  1053. width(),
  1054. delegate()->elementIsChatWide());
  1055. p.translate(0, -dateh);
  1056. }
  1057. }
  1058. if (const auto service = Get<ServicePreMessage>()) {
  1059. service->paint(p, context, g, delegate()->elementIsChatWide());
  1060. }
  1061. if (isHidden()) {
  1062. return;
  1063. }
  1064. const auto entry = logEntryOriginal();
  1065. const auto check = factcheckBlock();
  1066. auto mediaDisplayed = media && media->isDisplayed();
  1067. // Entry page is always a bubble bottom.
  1068. auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || check || (entry/* && entry->isBubbleBottom()*/);
  1069. auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop());
  1070. const auto displayInfo = needInfoDisplay();
  1071. const auto reactionsInBubble = _reactions && embedReactionsInBubble();
  1072. const auto keyboard = item->inlineReplyKeyboard();
  1073. const auto fullGeometry = g;
  1074. if (keyboard) {
  1075. // We need to count geometry without keyboard for bubble selection
  1076. // intervals counting below.
  1077. const auto keyboardHeight = st::msgBotKbButton.margin + keyboard->naturalHeight();
  1078. g.setHeight(g.height() - keyboardHeight);
  1079. }
  1080. auto mediaSelectionIntervals = (!context.selected() && mediaDisplayed)
  1081. ? media->getBubbleSelectionIntervals(context.selection)
  1082. : std::vector<Ui::BubbleSelectionInterval>();
  1083. auto localMediaTop = 0;
  1084. const auto customHighlight = mediaDisplayed && media->customHighlight();
  1085. if (!mediaSelectionIntervals.empty() || customHighlight) {
  1086. auto localMediaBottom = g.top() + g.height();
  1087. if (data()->repliesAreComments() || data()->externalReply()) {
  1088. localMediaBottom -= st::historyCommentsButtonHeight;
  1089. }
  1090. if (_viewButton) {
  1091. localMediaBottom -= st::mediaInBubbleSkip + _viewButton->height();
  1092. }
  1093. if (reactionsInBubble) {
  1094. localMediaBottom -= st::mediaInBubbleSkip + _reactions->height();
  1095. }
  1096. if (!mediaOnBottom && (!_viewButton || !reactionsInBubble)) {
  1097. localMediaBottom -= st::msgPadding.bottom();
  1098. }
  1099. if (check) {
  1100. localMediaBottom -= check->height();
  1101. }
  1102. if (entry) {
  1103. localMediaBottom -= entry->height();
  1104. }
  1105. localMediaTop = localMediaBottom - media->height();
  1106. for (auto &[top, height] : mediaSelectionIntervals) {
  1107. top += localMediaTop;
  1108. }
  1109. }
  1110. {
  1111. if (selectionTranslation) {
  1112. p.translate(-selectionTranslation, 0);
  1113. }
  1114. if (customHighlight) {
  1115. media->drawHighlight(p, context, localMediaTop);
  1116. } else {
  1117. paintHighlight(p, context, fullGeometry.height());
  1118. }
  1119. if (selectionTranslation) {
  1120. p.translate(selectionTranslation, 0);
  1121. }
  1122. }
  1123. const auto roll = media ? media->bubbleRoll() : Media::BubbleRoll();
  1124. if (roll) {
  1125. p.save();
  1126. p.translate(fullGeometry.center());
  1127. p.rotate(roll.rotate);
  1128. p.scale(roll.scale, roll.scale);
  1129. p.translate(-fullGeometry.center());
  1130. }
  1131. p.setTextPalette(stm->textPalette);
  1132. const auto messageRounding = countMessageRounding();
  1133. if (keyboard) {
  1134. const auto keyboardPosition = QPoint(g.left(), g.top() + g.height() + st::msgBotKbButton.margin);
  1135. p.translate(keyboardPosition);
  1136. keyboard->paint(
  1137. p,
  1138. context.st,
  1139. messageRounding,
  1140. g.width(),
  1141. context.clip.translated(-keyboardPosition));
  1142. p.translate(-keyboardPosition);
  1143. }
  1144. if (_reactions && !reactionsInBubble) {
  1145. const auto reactionsHeight = st::mediaInBubbleSkip + _reactions->height();
  1146. const auto reactionsLeft = (!bubble && mediaDisplayed)
  1147. ? media->contentRectForReactions().x()
  1148. : 0;
  1149. g.setHeight(g.height() - reactionsHeight);
  1150. const auto reactionsPosition = QPoint(reactionsLeft + g.left(), g.top() + g.height() + st::mediaInBubbleSkip);
  1151. p.translate(reactionsPosition);
  1152. prepareCustomEmojiPaint(p, context, *_reactions);
  1153. _reactions->paint(p, context, g.width(), context.clip.translated(-reactionsPosition));
  1154. if (context.reactionInfo) {
  1155. context.reactionInfo->position = reactionsPosition;
  1156. }
  1157. p.translate(-reactionsPosition);
  1158. }
  1159. if (context.highlightPathCache) {
  1160. context.highlightInterpolateTo = g;
  1161. context.highlightPathCache->clear();
  1162. }
  1163. if (bubble) {
  1164. if (displayFromName()
  1165. && item->displayFrom()
  1166. && (_fromNameVersion < item->displayFrom()->nameVersion())) {
  1167. fromNameUpdated(g.width());
  1168. }
  1169. Ui::PaintBubble(
  1170. p,
  1171. Ui::ComplexBubble{
  1172. .simple = Ui::SimpleBubble{
  1173. .st = context.st,
  1174. .geometry = g,
  1175. .pattern = context.bubblesPattern,
  1176. .patternViewport = context.viewport,
  1177. .outerWidth = width(),
  1178. .selected = context.selected(),
  1179. .outbg = context.outbg,
  1180. .rounding = countBubbleRounding(messageRounding),
  1181. },
  1182. .selection = mediaSelectionIntervals,
  1183. });
  1184. auto inner = g;
  1185. paintCommentsButton(p, inner, context);
  1186. auto trect = inner.marginsRemoved(st::msgPadding);
  1187. const auto additionalInfoSkip = (mediaDisplayed
  1188. && !media->additionalInfoString().isEmpty())
  1189. ? st::msgDateFont->height
  1190. : 0;
  1191. const auto reactionsTop = (reactionsInBubble && !_viewButton)
  1192. ? (additionalInfoSkip + st::mediaInBubbleSkip)
  1193. : additionalInfoSkip;
  1194. const auto reactionsHeight = reactionsInBubble
  1195. ? (reactionsTop + _reactions->height())
  1196. : 0;
  1197. if (reactionsInBubble) {
  1198. trect.setHeight(trect.height() - reactionsHeight);
  1199. const auto reactionsPosition = QPoint(trect.left(), trect.top() + trect.height() + reactionsTop);
  1200. p.translate(reactionsPosition);
  1201. prepareCustomEmojiPaint(p, context, *_reactions);
  1202. _reactions->paint(p, context, g.width(), context.clip.translated(-reactionsPosition));
  1203. if (context.reactionInfo) {
  1204. context.reactionInfo->position = reactionsPosition;
  1205. }
  1206. p.translate(-reactionsPosition);
  1207. }
  1208. if (_viewButton) {
  1209. const auto belowInfo = _viewButton->belowMessageInfo();
  1210. const auto infoHeight = reactionsInBubble
  1211. ? (reactionsHeight + 2 * st::mediaInBubbleSkip)
  1212. : _bottomInfo.height();
  1213. const auto heightMargins = QMargins(0, 0, 0, infoHeight);
  1214. _viewButton->draw(
  1215. p,
  1216. _viewButton->countRect(belowInfo
  1217. ? inner
  1218. : inner - heightMargins),
  1219. context);
  1220. if (belowInfo) {
  1221. inner.setHeight(inner.height() - _viewButton->height());
  1222. }
  1223. trect.setHeight(trect.height() - _viewButton->height());
  1224. if (reactionsInBubble) {
  1225. trect.setHeight(trect.height() - st::mediaInBubbleSkip + st::msgPadding.bottom());
  1226. } else if (mediaDisplayed) {
  1227. trect.setHeight(trect.height() - st::mediaInBubbleSkip);
  1228. }
  1229. }
  1230. if (mediaOnBottom) {
  1231. trect.setHeight(trect.height() + st::msgPadding.bottom());
  1232. }
  1233. if (mediaOnTop) {
  1234. trect.setY(trect.y() - st::msgPadding.top());
  1235. } else {
  1236. paintFromName(p, trect, context);
  1237. paintTopicButton(p, trect, context);
  1238. paintForwardedInfo(p, trect, context);
  1239. paintViaBotIdInfo(p, trect, context);
  1240. paintReplyInfo(p, trect, context);
  1241. }
  1242. if (entry) {
  1243. trect.setHeight(trect.height() - entry->height());
  1244. }
  1245. if (check) {
  1246. trect.setHeight(trect.height() - check->height() - st::mediaInBubbleSkip);
  1247. }
  1248. if (displayInfo) {
  1249. trect.setHeight(trect.height()
  1250. - (_bottomInfo.height() - st::msgDateFont->height));
  1251. }
  1252. auto textSelection = context.selection;
  1253. auto highlightRange = context.highlight.range;
  1254. const auto mediaHeight = mediaDisplayed ? media->height() : 0;
  1255. const auto paintMedia = [&](int top) {
  1256. if (!mediaDisplayed) {
  1257. return;
  1258. }
  1259. const auto mediaSelection = _invertMedia
  1260. ? context.selection
  1261. : skipTextSelection(context.selection);
  1262. const auto maybeMediaHighlight = context.highlightPathCache
  1263. && context.highlightPathCache->isEmpty();
  1264. auto mediaPosition = QPoint(inner.left(), top);
  1265. p.translate(mediaPosition);
  1266. media->draw(p, context.translated(
  1267. -mediaPosition
  1268. ).withSelection(mediaSelection));
  1269. if (context.reactionInfo && !displayInfo && !_reactions) {
  1270. const auto add = QPoint(0, mediaHeight);
  1271. context.reactionInfo->position = mediaPosition + add;
  1272. if (context.reactionInfo->effectPaint) {
  1273. context.reactionInfo->effectOffset -= add;
  1274. }
  1275. }
  1276. if (maybeMediaHighlight
  1277. && !context.highlightPathCache->isEmpty()) {
  1278. context.highlightPathCache->translate(mediaPosition);
  1279. }
  1280. p.translate(-mediaPosition);
  1281. };
  1282. if (mediaDisplayed && _invertMedia) {
  1283. if (!mediaOnTop) {
  1284. trect.setY(trect.y() + st::mediaInBubbleSkip);
  1285. }
  1286. paintMedia(trect.y());
  1287. trect.setY(trect.y()
  1288. + mediaHeight
  1289. + (mediaOnBottom ? 0 : st::mediaInBubbleSkip));
  1290. textSelection = media->skipSelection(textSelection);
  1291. highlightRange = media->skipSelection(highlightRange);
  1292. }
  1293. auto copy = context;
  1294. copy.selection = textSelection;
  1295. copy.highlight.range = highlightRange;
  1296. paintText(p, trect, copy);
  1297. if (mediaDisplayed && !_invertMedia) {
  1298. paintMedia(trect.y() + trect.height() - mediaHeight);
  1299. if (context.reactionInfo && !displayInfo && !_reactions) {
  1300. context.reactionInfo->position
  1301. = QPoint(inner.left(), trect.y() + trect.height());
  1302. if (context.reactionInfo->effectPaint) {
  1303. context.reactionInfo->effectOffset -= QPoint(0, mediaHeight);
  1304. }
  1305. }
  1306. }
  1307. if (check) {
  1308. auto checkLeft = inner.left();
  1309. auto checkTop = trect.y() + trect.height() + st::mediaInBubbleSkip;
  1310. p.translate(checkLeft, checkTop);
  1311. auto checkContext = context.translated(checkLeft, -checkTop);
  1312. checkContext.selection = skipTextSelection(context.selection);
  1313. if (mediaDisplayed) {
  1314. checkContext.selection = media->skipSelection(
  1315. checkContext.selection);
  1316. }
  1317. check->draw(p, checkContext);
  1318. p.translate(-checkLeft, -checkTop);
  1319. }
  1320. if (entry) {
  1321. auto entryLeft = inner.left();
  1322. auto entryTop = trect.y() + trect.height();
  1323. p.translate(entryLeft, entryTop);
  1324. auto entryContext = context.translated(-entryLeft, -entryTop);
  1325. entryContext.selection = skipTextSelection(context.selection);
  1326. if (mediaDisplayed) {
  1327. entryContext.selection = media->skipSelection(
  1328. entryContext.selection);
  1329. }
  1330. entry->draw(p, entryContext);
  1331. p.translate(-entryLeft, -entryTop);
  1332. }
  1333. if (displayInfo) {
  1334. const auto bottomSelected = context.selected()
  1335. || (!mediaSelectionIntervals.empty()
  1336. && (mediaSelectionIntervals.back().top
  1337. + mediaSelectionIntervals.back().height
  1338. >= inner.y() + inner.height()));
  1339. drawInfo(
  1340. p,
  1341. context.withSelection(
  1342. bottomSelected ? FullSelection : TextSelection()),
  1343. inner.left() + inner.width(),
  1344. inner.top() + inner.height(),
  1345. 2 * inner.left() + inner.width(),
  1346. InfoDisplayType::Default);
  1347. if (context.reactionInfo && !_reactions) {
  1348. const auto add = QPoint(0, inner.top() + inner.height());
  1349. context.reactionInfo->position = add;
  1350. if (context.reactionInfo->effectPaint) {
  1351. context.reactionInfo->effectOffset -= add;
  1352. }
  1353. }
  1354. if (_comments) {
  1355. const auto o = p.opacity();
  1356. p.setOpacity(0.3);
  1357. p.fillRect(g.left(), g.top() + g.height() - st::historyCommentsButtonHeight - st::lineWidth, g.width(), st::lineWidth, stm->msgDateFg);
  1358. p.setOpacity(o);
  1359. }
  1360. }
  1361. if (const auto size = rightActionSize()) {
  1362. const auto fastShareSkip = std::clamp(
  1363. (g.height() - size->height()) / 2,
  1364. 0,
  1365. st::historyFastShareBottom);
  1366. const auto fastShareLeft = hasRightLayout()
  1367. ? (g.left() - size->width() - st::historyFastShareLeft)
  1368. : (g.left() + g.width() + st::historyFastShareLeft);
  1369. const auto fastShareTop = data()->isSponsored()
  1370. ? g.top() + fastShareSkip
  1371. : g.top() + g.height() - fastShareSkip - size->height();
  1372. const auto o = p.opacity();
  1373. if (selectionModeResult.progress > 0) {
  1374. p.setOpacity(1. - selectionModeResult.progress);
  1375. }
  1376. drawRightAction(p, context, fastShareLeft, fastShareTop, width());
  1377. if (selectionModeResult.progress > 0) {
  1378. p.setOpacity(o);
  1379. }
  1380. }
  1381. if (media) {
  1382. media->paintBubbleFireworks(p, g, context.now);
  1383. }
  1384. } else if (media && media->isDisplayed()) {
  1385. p.translate(g.topLeft());
  1386. media->draw(p, context.translated(
  1387. -g.topLeft()
  1388. ).withSelection(skipTextSelection(context.selection)));
  1389. if (context.reactionInfo && !_reactions) {
  1390. const auto add = QPoint(0, g.height());
  1391. context.reactionInfo->position = g.topLeft() + add;
  1392. if (context.reactionInfo->effectPaint) {
  1393. context.reactionInfo->effectOffset -= add;
  1394. }
  1395. }
  1396. p.translate(-g.topLeft());
  1397. }
  1398. p.restoreTextPalette();
  1399. if (context.highlightPathCache
  1400. && !context.highlightPathCache->isEmpty()) {
  1401. const auto alpha = int(0.25
  1402. * context.highlight.collapsion
  1403. * context.highlight.opacity
  1404. * 255);
  1405. if (alpha > 0) {
  1406. context.highlightPathCache->setFillRule(Qt::WindingFill);
  1407. auto color = context.messageStyle()->textPalette.linkFg->c;
  1408. color.setAlpha(alpha);
  1409. p.fillPath(*context.highlightPathCache, color);
  1410. }
  1411. }
  1412. if (roll) {
  1413. p.restore();
  1414. }
  1415. if (const auto reply = Get<Reply>()) {
  1416. if (const auto replyData = item->Get<HistoryMessageReply>()) {
  1417. if (reply->isNameUpdated(this, replyData)) {
  1418. const_cast<Message*>(this)->setPendingResize();
  1419. }
  1420. }
  1421. }
  1422. if (hasGesture) {
  1423. p.translate(-context.gestureHorizontal.translation, 0);
  1424. constexpr auto kShiftRatio = 1.5;
  1425. constexpr auto kBouncePart = 0.25;
  1426. constexpr auto kMaxHeightRatio = 3.5;
  1427. constexpr auto kStrokeWidth = 2.;
  1428. constexpr auto kWaveWidth = 10.;
  1429. const auto isLeftSize = (!context.outbg)
  1430. || delegate()->elementIsChatWide();
  1431. const auto ratio = std::min(context.gestureHorizontal.ratio, 1.);
  1432. const auto reachRatio = context.gestureHorizontal.reachRatio;
  1433. const auto size = st::historyFastShareSize;
  1434. const auto outerWidth = st::historySwipeIconSkip
  1435. + (isLeftSize ? rect::right(g) : width())
  1436. + ((g.height() < size * kMaxHeightRatio)
  1437. ? rightActionSize().value_or(QSize()).width()
  1438. : 0);
  1439. const auto shift = std::min(
  1440. (size * kShiftRatio * context.gestureHorizontal.ratio),
  1441. -1. * context.gestureHorizontal.translation
  1442. ) + (st::historySwipeIconSkip * ratio * (isLeftSize ? .7 : 1.));
  1443. const auto rect = QRectF(
  1444. outerWidth - shift,
  1445. g.y() + (g.height() - size) / 2,
  1446. size,
  1447. size);
  1448. const auto center = rect::center(rect);
  1449. const auto spanAngle = ratio * arc::kFullLength;
  1450. const auto strokeWidth = style::ConvertFloatScale(kStrokeWidth);
  1451. const auto reachScale = std::clamp(
  1452. (reachRatio > kBouncePart)
  1453. ? (kBouncePart * 2 - reachRatio)
  1454. : reachRatio,
  1455. 0.,
  1456. 1.);
  1457. auto pen = Window::Theme::IsNightMode()
  1458. ? QPen(anim::with_alpha(context.st->msgServiceFg()->c, 0.3))
  1459. : QPen(context.st->msgServiceBg());
  1460. pen.setWidthF(strokeWidth - (1. * (reachScale / kBouncePart)));
  1461. const auto arcRect = rect - Margins(strokeWidth);
  1462. p.save();
  1463. {
  1464. auto hq = PainterHighQualityEnabler(p);
  1465. p.setPen(Qt::NoPen);
  1466. p.setBrush(context.st->msgServiceBg());
  1467. p.setOpacity(ratio);
  1468. p.translate(center);
  1469. if (reachScale) {
  1470. p.scale(-(1. + 1. * reachScale), (1. + 1. * reachScale));
  1471. } else {
  1472. p.scale(-1., 1.);
  1473. }
  1474. p.translate(-center);
  1475. // All the next draws are mirrored.
  1476. p.drawEllipse(rect);
  1477. context.st->historyFastShareIcon().paintInCenter(p, rect);
  1478. p.setPen(pen);
  1479. p.setBrush(Qt::NoBrush);
  1480. p.drawArc(arcRect, arc::kQuarterLength, spanAngle);
  1481. // p.drawArc(arcRect, arc::kQuarterLength, spanAngle);
  1482. if (reachRatio) {
  1483. const auto w = style::ConvertFloatScale(kWaveWidth);
  1484. p.setOpacity(ratio - reachRatio);
  1485. p.drawArc(
  1486. arcRect + Margins(reachRatio * reachRatio * w),
  1487. arc::kQuarterLength,
  1488. spanAngle);
  1489. }
  1490. }
  1491. p.restore();
  1492. }
  1493. if (selectionTranslation) {
  1494. p.translate(-selectionTranslation, 0);
  1495. }
  1496. if (selectionModeResult.progress) {
  1497. const auto progress = selectionModeResult.progress;
  1498. if (progress <= 1.) {
  1499. if (context.selected()) {
  1500. if (!_selectionRoundCheckbox) {
  1501. _selectionRoundCheckbox
  1502. = std::make_unique<Ui::RoundCheckbox>(
  1503. st::msgSelectionCheck,
  1504. [this] { repaint(); });
  1505. }
  1506. }
  1507. if (_selectionRoundCheckbox) {
  1508. _selectionRoundCheckbox->setChecked(
  1509. context.selected(),
  1510. anim::type::normal);
  1511. }
  1512. const auto o = ScopedPainterOpacity(p, progress);
  1513. const auto &st = st::msgSelectionCheck;
  1514. const auto right = delegate()->elementIsChatWide()
  1515. ? std::min(
  1516. int(_bubbleWidthLimit
  1517. + st::msgPhotoSkip
  1518. + st::msgSelectionOffset
  1519. + st::msgPadding.left()
  1520. + st.size),
  1521. width())
  1522. : width();
  1523. const auto pos = QPoint(
  1524. (right
  1525. - (st::msgSelectionOffset * progress - st.size) / 2
  1526. - st::msgPadding.right() / 2
  1527. - st.size
  1528. - st::historyScroll.deltax),
  1529. rect::bottom(g) - st.size - st::msgSelectionBottomSkip);
  1530. {
  1531. p.setPen(QPen(st.border, st.width));
  1532. p.setBrush(context.st->msgServiceBg());
  1533. auto hq = PainterHighQualityEnabler(p);
  1534. p.drawEllipse(QRect(pos, Size(st.size)));
  1535. }
  1536. if (_selectionRoundCheckbox) {
  1537. _selectionRoundCheckbox->paint(p, pos.x(), pos.y(), width());
  1538. }
  1539. } else {
  1540. _selectionRoundCheckbox = nullptr;
  1541. }
  1542. } else {
  1543. _selectionRoundCheckbox = nullptr;
  1544. }
  1545. }
  1546. void Message::paintCommentsButton(
  1547. Painter &p,
  1548. QRect &g,
  1549. const PaintContext &context) const {
  1550. if (!data()->repliesAreComments() && !data()->externalReply()) {
  1551. return;
  1552. }
  1553. if (!_comments) {
  1554. _comments = std::make_unique<CommentsButton>();
  1555. history()->owner().registerHeavyViewPart(const_cast<Message*>(this));
  1556. }
  1557. const auto stm = context.messageStyle();
  1558. const auto views = data()->Get<HistoryMessageViews>();
  1559. g.setHeight(g.height() - st::historyCommentsButtonHeight);
  1560. const auto top = g.top() + g.height();
  1561. auto left = g.left();
  1562. auto width = g.width();
  1563. if (_comments->ripple) {
  1564. p.setOpacity(st::historyPollRippleOpacity);
  1565. const auto colorOverride = &stm->msgWaveformInactive->c;
  1566. _comments->ripple->paint(
  1567. p,
  1568. left - _comments->rippleShift,
  1569. top,
  1570. width,
  1571. colorOverride);
  1572. if (_comments->ripple->empty()) {
  1573. _comments->ripple.reset();
  1574. }
  1575. p.setOpacity(1.);
  1576. }
  1577. left += st::historyCommentsSkipLeft;
  1578. width -= st::historyCommentsSkipLeft
  1579. + st::historyCommentsSkipRight;
  1580. const auto &open = stm->historyCommentsOpen;
  1581. open.paint(p,
  1582. left + width - open.width(),
  1583. top + (st::historyCommentsButtonHeight - open.height()) / 2,
  1584. width);
  1585. if (!views || views->recentRepliers.empty()) {
  1586. const auto &icon = stm->historyComments;
  1587. icon.paint(
  1588. p,
  1589. left,
  1590. top + (st::historyCommentsButtonHeight - icon.height()) / 2,
  1591. width);
  1592. left += icon.width();
  1593. } else {
  1594. auto &list = _comments->userpics;
  1595. const auto limit = HistoryMessageViews::kMaxRecentRepliers;
  1596. const auto count = std::min(int(views->recentRepliers.size()), limit);
  1597. const auto single = st::historyCommentsUserpics.size;
  1598. const auto shift = st::historyCommentsUserpics.shift;
  1599. const auto regenerate = [&] {
  1600. if (list.size() != count) {
  1601. return true;
  1602. }
  1603. for (auto i = 0; i != count; ++i) {
  1604. auto &entry = list[i];
  1605. const auto peer = entry.peer;
  1606. auto &view = entry.view;
  1607. const auto wasView = view.cloud.get();
  1608. if (views->recentRepliers[i] != peer->id
  1609. || peer->userpicUniqueKey(view) != entry.uniqueKey
  1610. || view.cloud.get() != wasView) {
  1611. return true;
  1612. }
  1613. }
  1614. return false;
  1615. }();
  1616. if (regenerate) {
  1617. for (auto i = 0; i != count; ++i) {
  1618. const auto peerId = views->recentRepliers[i];
  1619. if (i == list.size()) {
  1620. list.push_back(UserpicInRow{
  1621. history()->owner().peer(peerId)
  1622. });
  1623. } else if (list[i].peer->id != peerId) {
  1624. list[i].peer = history()->owner().peer(peerId);
  1625. }
  1626. }
  1627. while (list.size() > count) {
  1628. list.pop_back();
  1629. }
  1630. GenerateUserpicsInRow(
  1631. _comments->cachedUserpics,
  1632. list,
  1633. st::historyCommentsUserpics,
  1634. limit);
  1635. }
  1636. p.drawImage(
  1637. left,
  1638. top + (st::historyCommentsButtonHeight - single) / 2,
  1639. _comments->cachedUserpics);
  1640. left += single + (count - 1) * (single - shift);
  1641. }
  1642. left += st::historyCommentsSkipText;
  1643. p.setPen(stm->msgFileThumbLinkFg);
  1644. p.setFont(st::semiboldFont);
  1645. const auto textTop = top + (st::historyCommentsButtonHeight - st::semiboldFont->height) / 2;
  1646. p.drawTextLeft(
  1647. left,
  1648. textTop,
  1649. width,
  1650. views ? views->replies.text : tr::lng_replies_view_original(tr::now),
  1651. views ? views->replies.textWidth : -1);
  1652. if (views && data()->areCommentsUnread()) {
  1653. p.setPen(Qt::NoPen);
  1654. p.setBrush(stm->msgFileBg);
  1655. {
  1656. PainterHighQualityEnabler hq(p);
  1657. p.drawEllipse(style::rtlrect(left + views->replies.textWidth + st::mediaUnreadSkip, textTop + st::mediaUnreadTop, st::mediaUnreadSize, st::mediaUnreadSize, width));
  1658. }
  1659. }
  1660. }
  1661. void Message::paintFromName(
  1662. Painter &p,
  1663. QRect &trect,
  1664. const PaintContext &context) const {
  1665. const auto item = data();
  1666. if (!displayFromName()) {
  1667. return;
  1668. }
  1669. const auto badgeWidth = _rightBadge.isEmpty() ? 0 : _rightBadge.maxWidth();
  1670. const auto replyWidth = [&] {
  1671. if (isUnderCursor()) {
  1672. if (displayFastForward()) {
  1673. return st::msgFont->width(FastForwardText());
  1674. } else if (displayFastReply()) {
  1675. return st::msgFont->width(FastReplyText());
  1676. }
  1677. }
  1678. return 0;
  1679. }();
  1680. const auto rightWidth = replyWidth ? replyWidth : badgeWidth;
  1681. auto availableLeft = trect.left();
  1682. auto availableWidth = trect.width();
  1683. if (rightWidth) {
  1684. availableWidth -= st::msgPadding.right() + rightWidth;
  1685. }
  1686. const auto stm = context.messageStyle();
  1687. const auto from = item->displayFrom();
  1688. const auto info = from ? nullptr : item->displayHiddenSenderInfo();
  1689. Assert(from || info);
  1690. const auto nameFg = !context.outbg
  1691. ? FromNameFg(context, colorIndex())
  1692. : stm->msgServiceFg->c;
  1693. const auto nameText = [&] {
  1694. if (from) {
  1695. validateFromNameText(from);
  1696. return static_cast<const Ui::Text::String*>(&_fromName);
  1697. }
  1698. return &info->nameText();
  1699. }();
  1700. const auto statusWidth = _fromNameStatus
  1701. ? st::dialogsPremiumIcon.icon.width()
  1702. : 0;
  1703. if (statusWidth && availableWidth > statusWidth) {
  1704. const auto x = availableLeft
  1705. + std::min(availableWidth - statusWidth, nameText->maxWidth());
  1706. const auto y = trect.top();
  1707. auto color = nameFg;
  1708. color.setAlpha(115);
  1709. const auto id = from ? from->emojiStatusId() : EmojiStatusId();
  1710. if (_fromNameStatus->id != id) {
  1711. const auto that = const_cast<Message*>(this);
  1712. _fromNameStatus->custom = id
  1713. ? std::make_unique<Ui::Text::LimitedLoopsEmoji>(
  1714. history()->owner().customEmojiManager().create(
  1715. Data::EmojiStatusCustomId(id),
  1716. [=] { that->customEmojiRepaint(); }),
  1717. kPlayStatusLimit)
  1718. : nullptr;
  1719. if (id && !_fromNameStatus->id) {
  1720. history()->owner().registerHeavyViewPart(that);
  1721. } else if (!id && _fromNameStatus->id) {
  1722. that->checkHeavyPart();
  1723. }
  1724. _fromNameStatus->id = id;
  1725. }
  1726. if (_fromNameStatus->custom) {
  1727. clearCustomEmojiRepaint();
  1728. _fromNameStatus->custom->paint(p, {
  1729. .textColor = color,
  1730. .now = context.now,
  1731. .position = QPoint(
  1732. x - 2 * _fromNameStatus->skip,
  1733. y + _fromNameStatus->skip),
  1734. .paused = context.paused || On(PowerSaving::kEmojiStatus),
  1735. });
  1736. } else {
  1737. st::dialogsPremiumIcon.icon.paint(p, x, y, width(), color);
  1738. }
  1739. availableWidth -= statusWidth;
  1740. }
  1741. p.setFont(st::msgNameFont);
  1742. p.setPen(nameFg);
  1743. nameText->draw(p, {
  1744. .position = { availableLeft, trect.top() },
  1745. .availableWidth = availableWidth,
  1746. .elisionLines = 1,
  1747. });
  1748. const auto skipWidth = nameText->maxWidth()
  1749. + (_fromNameStatus
  1750. ? (st::dialogsPremiumIcon.icon.width()
  1751. + st::msgServiceFont->spacew)
  1752. : 0)
  1753. + st::msgServiceFont->spacew;
  1754. availableLeft += skipWidth;
  1755. availableWidth -= skipWidth;
  1756. auto via = item->Get<HistoryMessageVia>();
  1757. if (via && !displayForwardedFrom() && availableWidth > 0) {
  1758. p.setPen(stm->msgServiceFg);
  1759. p.drawText(availableLeft, trect.top() + st::msgServiceFont->ascent, via->text);
  1760. auto skipWidth = via->width + st::msgServiceFont->spacew;
  1761. availableLeft += skipWidth;
  1762. availableWidth -= skipWidth;
  1763. }
  1764. if (rightWidth) {
  1765. p.setPen(stm->msgDateFg);
  1766. if (replyWidth) {
  1767. p.setFont(ClickHandler::showAsActive(_fastReplyLink)
  1768. ? st::msgFont->underline()
  1769. : st::msgFont);
  1770. p.drawText(
  1771. trect.left() + trect.width() - rightWidth,
  1772. trect.top() + st::msgFont->ascent,
  1773. hasFastForward() ? FastForwardText() : FastReplyText());
  1774. } else {
  1775. const auto shift = QPoint(trect.width() - rightWidth, 0);
  1776. const auto pen = !_rightBadgeHasBoosts
  1777. ? QPen()
  1778. : !context.outbg
  1779. ? QPen(FromNameFg(context, colorIndex()))
  1780. : stm->msgServiceFg->p;
  1781. auto colored = std::array<Ui::Text::SpecialColor, 1>{
  1782. { { &pen, &pen } },
  1783. };
  1784. _rightBadge.draw(p, {
  1785. .position = trect.topLeft() + shift,
  1786. .availableWidth = rightWidth,
  1787. .colors = colored,
  1788. .now = context.now,
  1789. });
  1790. }
  1791. }
  1792. trect.setY(trect.y() + st::msgNameFont->height);
  1793. }
  1794. void Message::paintTopicButton(
  1795. Painter &p,
  1796. QRect &trect,
  1797. const PaintContext &context) const {
  1798. const auto button = displayedTopicButton();
  1799. if (!button) {
  1800. return;
  1801. }
  1802. trect.setTop(trect.top() + st::topicButtonSkip);
  1803. const auto padding = st::topicButtonPadding;
  1804. const auto availableWidth = trect.width();
  1805. const auto height = padding.top()
  1806. + st::msgNameFont->height
  1807. + padding.bottom();
  1808. const auto width = std::max(
  1809. std::min(
  1810. availableWidth,
  1811. (padding.left()
  1812. + button->name.maxWidth()
  1813. + st::topicButtonArrowSkip
  1814. + padding.right())),
  1815. height);
  1816. const auto rect = QRect(trect.x(), trect.y(), width, height);
  1817. const auto stm = context.messageStyle();
  1818. const auto skip = padding.right() + st::topicButtonArrowSkip;
  1819. auto color = stm->msgServiceFg->c;
  1820. color.setAlpha(color.alpha() / 8);
  1821. p.setPen(Qt::NoPen);
  1822. p.setBrush(color);
  1823. {
  1824. auto hq = PainterHighQualityEnabler(p);
  1825. p.drawRoundedRect(rect, height / 2, height / 2);
  1826. }
  1827. if (button->ripple) {
  1828. button->ripple->paint(
  1829. p,
  1830. rect.x(),
  1831. rect.y(),
  1832. this->width(),
  1833. &color);
  1834. if (button->ripple->empty()) {
  1835. button->ripple.reset();
  1836. }
  1837. }
  1838. clearCustomEmojiRepaint();
  1839. p.setPen(stm->msgServiceFg);
  1840. p.setTextPalette(stm->fwdTextPalette);
  1841. button->name.drawElided(
  1842. p,
  1843. trect.x() + padding.left(),
  1844. trect.y() + padding.top(),
  1845. width - padding.left() - skip);
  1846. const auto &icon = st::topicButtonArrow;
  1847. icon.paint(
  1848. p,
  1849. rect.x() + rect.width() - skip + st::topicButtonArrowPosition.x(),
  1850. rect.y() + padding.top() + st::topicButtonArrowPosition.y(),
  1851. this->width(),
  1852. stm->msgServiceFg->c);
  1853. trect.setY(trect.y() + height + st::topicButtonSkip);
  1854. }
  1855. void Message::paintForwardedInfo(
  1856. Painter &p,
  1857. QRect &trect,
  1858. const PaintContext &context) const {
  1859. if (displayForwardedFrom()) {
  1860. const auto item = data();
  1861. const auto st = context.st;
  1862. const auto stm = context.messageStyle();
  1863. const auto forwarded = item->Get<HistoryMessageForwarded>();
  1864. const auto &serviceFont = st::msgServiceFont;
  1865. const auto skip1 = forwarded->psaType.isEmpty()
  1866. ? 0
  1867. : st::historyPsaIconSkip1;
  1868. const auto skip2 = forwarded->psaType.isEmpty()
  1869. ? 0
  1870. : st::historyPsaIconSkip2;
  1871. const auto fits = (forwarded->text.maxWidth() + skip1 <= trect.width());
  1872. const auto skip = fits ? skip1 : skip2;
  1873. const auto useWidth = trect.width() - skip;
  1874. const auto countedHeight = forwarded->text.countHeight(useWidth);
  1875. const auto breakEverywhere = (countedHeight > 2 * serviceFont->height);
  1876. p.setPen(!forwarded->psaType.isEmpty()
  1877. ? st->boxTextFgGood()
  1878. : stm->msgServiceFg);
  1879. p.setFont(serviceFont);
  1880. p.setTextPalette(!forwarded->psaType.isEmpty()
  1881. ? st->historyPsaForwardPalette()
  1882. : stm->fwdTextPalette);
  1883. forwarded->text.drawElided(p, trect.x(), trect.y(), useWidth, 2, style::al_left, 0, -1, 0, breakEverywhere);
  1884. p.setTextPalette(stm->textPalette);
  1885. if (!forwarded->psaType.isEmpty()) {
  1886. const auto entry = Get<PsaTooltipState>();
  1887. Assert(entry != nullptr);
  1888. const auto shown = entry->buttonVisibleAnimation.value(
  1889. entry->buttonVisible ? 1. : 0.);
  1890. if (shown > 0) {
  1891. const auto &icon = stm->historyPsaIcon;
  1892. const auto position = fits
  1893. ? st::historyPsaIconPosition1
  1894. : st::historyPsaIconPosition2;
  1895. const auto x = trect.x() + trect.width() - position.x() - icon.width();
  1896. const auto y = trect.y() + position.y();
  1897. if (shown == 1) {
  1898. icon.paint(p, x, y, trect.width());
  1899. } else {
  1900. p.save();
  1901. p.translate(x + icon.width() / 2, y + icon.height() / 2);
  1902. p.scale(shown, shown);
  1903. p.setOpacity(shown);
  1904. icon.paint(p, -icon.width() / 2, -icon.height() / 2, width());
  1905. p.restore();
  1906. }
  1907. }
  1908. }
  1909. trect.setY(trect.y() + ((fits ? 1 : 2) * serviceFont->height));
  1910. }
  1911. }
  1912. void Message::paintReplyInfo(
  1913. Painter &p,
  1914. QRect &trect,
  1915. const PaintContext &context) const {
  1916. if (const auto reply = Get<Reply>()) {
  1917. reply->paint(
  1918. p,
  1919. this,
  1920. context,
  1921. trect.x(),
  1922. trect.y(),
  1923. trect.width(),
  1924. true);
  1925. trect.setY(trect.y() + reply->height());
  1926. }
  1927. }
  1928. void Message::paintViaBotIdInfo(
  1929. Painter &p,
  1930. QRect &trect,
  1931. const PaintContext &context) const {
  1932. const auto item = data();
  1933. if (!displayFromName() && !displayForwardedFrom()) {
  1934. if (auto via = item->Get<HistoryMessageVia>()) {
  1935. const auto stm = context.messageStyle();
  1936. p.setFont(st::msgServiceNameFont);
  1937. p.setPen(stm->msgServiceFg);
  1938. p.drawTextLeft(trect.left(), trect.top(), width(), via->text);
  1939. trect.setY(trect.y() + st::msgServiceNameFont->height);
  1940. }
  1941. }
  1942. }
  1943. void Message::paintText(
  1944. Painter &p,
  1945. QRect &trect,
  1946. const PaintContext &context) const {
  1947. if (!hasVisibleText()) {
  1948. return;
  1949. }
  1950. const auto stm = context.messageStyle();
  1951. p.setPen(stm->historyTextFg);
  1952. p.setFont(st::msgFont);
  1953. prepareCustomEmojiPaint(p, context, text());
  1954. if (const auto botTop = Get<FakeBotAboutTop>()) {
  1955. botTop->text.drawLeftElided(
  1956. p,
  1957. trect.x(),
  1958. trect.y(),
  1959. trect.width(),
  1960. width());
  1961. trect.setY(trect.y() + botTop->height);
  1962. }
  1963. auto highlightRequest = context.computeHighlightCache();
  1964. text().draw(p, {
  1965. .position = trect.topLeft(),
  1966. .availableWidth = trect.width(),
  1967. .palette = &stm->textPalette,
  1968. .pre = stm->preCache.get(),
  1969. .blockquote = context.quoteCache(contentColorIndex()),
  1970. .colors = context.st->highlightColors(),
  1971. .spoiler = Ui::Text::DefaultSpoilerCache(),
  1972. .now = context.now,
  1973. .pausedEmoji = context.paused || On(PowerSaving::kEmojiChat),
  1974. .pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler),
  1975. .selection = context.selection,
  1976. .highlight = highlightRequest ? &*highlightRequest : nullptr,
  1977. .useFullWidth = true,
  1978. });
  1979. }
  1980. PointState Message::pointState(QPoint point) const {
  1981. auto g = countGeometry();
  1982. if (g.width() < 1 || isHidden()) {
  1983. return PointState::Outside;
  1984. }
  1985. const auto media = this->media();
  1986. const auto item = data();
  1987. const auto reactionsInBubble = _reactions && embedReactionsInBubble();
  1988. if (drawBubble()) {
  1989. if (!g.contains(point)) {
  1990. return PointState::Outside;
  1991. }
  1992. if (const auto mediaDisplayed = media && media->isDisplayed()) {
  1993. // Hack for grouped media point state.
  1994. const auto entry = logEntryOriginal();
  1995. const auto check = factcheckBlock();
  1996. // Entry page is always a bubble bottom.
  1997. auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || check || (entry/* && entry->isBubbleBottom()*/);
  1998. auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop());
  1999. if (item->repliesAreComments() || item->externalReply()) {
  2000. g.setHeight(g.height() - st::historyCommentsButtonHeight);
  2001. }
  2002. auto trect = g.marginsRemoved(st::msgPadding);
  2003. if (reactionsInBubble) {
  2004. const auto reactionsHeight = (_viewButton ? 0 : st::mediaInBubbleSkip)
  2005. + _reactions->height();
  2006. trect.setHeight(trect.height() - reactionsHeight);
  2007. }
  2008. if (_viewButton) {
  2009. trect.setHeight(trect.height() - _viewButton->height());
  2010. if (reactionsInBubble) {
  2011. trect.setHeight(trect.height() + st::msgPadding.bottom());
  2012. } else if (mediaDisplayed) {
  2013. trect.setHeight(trect.height() - st::mediaInBubbleSkip);
  2014. }
  2015. }
  2016. if (mediaOnBottom) {
  2017. trect.setHeight(trect.height() + st::msgPadding.bottom());
  2018. }
  2019. //if (mediaOnTop) {
  2020. // trect.setY(trect.y() - st::msgPadding.top());
  2021. //} else {
  2022. // if (getStateFromName(point, trect, &result)) return result;
  2023. // if (getStateTopicButton(point, trect, &result)) return result;
  2024. // if (getStateForwardedInfo(point, trect, &result, request)) return result;
  2025. // if (getStateReplyInfo(point, trect, &result)) return result;
  2026. // if (getStateViaBotIdInfo(point, trect, &result)) return result;
  2027. //}
  2028. if (check) {
  2029. auto checkHeight = check->height();
  2030. trect.setHeight(trect.height() - checkHeight - st::mediaInBubbleSkip);
  2031. }
  2032. if (entry) {
  2033. auto entryHeight = entry->height();
  2034. trect.setHeight(trect.height() - entryHeight);
  2035. }
  2036. const auto mediaHeight = mediaDisplayed ? media->height() : 0;
  2037. const auto mediaLeft = trect.x() - st::msgPadding.left();
  2038. const auto mediaTop = (!mediaDisplayed || _invertMedia)
  2039. ? (trect.y() + (mediaOnTop ? 0 : st::mediaInBubbleSkip))
  2040. : (trect.y() + trect.height() - mediaHeight);
  2041. if (mediaDisplayed && _invertMedia) {
  2042. trect.setY(mediaTop
  2043. + mediaHeight
  2044. + (mediaOnBottom ? 0 : st::mediaInBubbleSkip));
  2045. }
  2046. if (point.y() >= mediaTop
  2047. && point.y() < mediaTop + mediaHeight) {
  2048. return media->pointState(point - QPoint(mediaLeft, mediaTop));
  2049. }
  2050. }
  2051. return PointState::Inside;
  2052. } else if (media) {
  2053. return media->pointState(point - g.topLeft());
  2054. }
  2055. return PointState::Outside;
  2056. }
  2057. bool Message::displayFromPhoto() const {
  2058. return hasFromPhoto() && !isAttachedToNext();
  2059. }
  2060. void Message::clickHandlerPressedChanged(
  2061. const ClickHandlerPtr &handler,
  2062. bool pressed) {
  2063. if (const auto markup = data()->Get<HistoryMessageReplyMarkup>()) {
  2064. if (const auto keyboard = markup->inlineKeyboard.get()) {
  2065. keyboard->clickHandlerPressedChanged(
  2066. handler,
  2067. pressed,
  2068. countMessageRounding());
  2069. }
  2070. }
  2071. Element::clickHandlerPressedChanged(handler, pressed);
  2072. if (const auto check = factcheckBlock()) {
  2073. check->clickHandlerPressedChanged(handler, pressed);
  2074. }
  2075. if (!handler) {
  2076. return;
  2077. } else if (_rightAction && (handler == _rightAction->link)) {
  2078. toggleRightActionRipple(pressed);
  2079. } else if (_rightAction
  2080. && _rightAction->second
  2081. && (handler == _rightAction->second->link)) {
  2082. const auto rightSize = rightActionSize();
  2083. Assert(rightSize != std::nullopt);
  2084. if (pressed) {
  2085. if (!_rightAction->second->ripple) {
  2086. // Create a ripple.
  2087. _rightAction->second->ripple
  2088. = std::make_unique<Ui::RippleAnimation>(
  2089. st::defaultRippleAnimation,
  2090. Ui::RippleAnimation::RoundRectMask(
  2091. Size(rightSize->width()),
  2092. rightSize->width() / 2),
  2093. [=] { repaint(); });
  2094. }
  2095. _rightAction->second->ripple->add(_rightAction->lastPoint);
  2096. } else if (_rightAction->second->ripple) {
  2097. _rightAction->second->ripple->lastStop();
  2098. }
  2099. } else if (_comments && (handler == _comments->link)) {
  2100. toggleCommentsButtonRipple(pressed);
  2101. } else if (_topicButton && (handler == _topicButton->link)) {
  2102. toggleTopicButtonRipple(pressed);
  2103. } else if (_viewButton) {
  2104. _viewButton->checkLink(handler, pressed);
  2105. } else if (const auto reply = Get<Reply>()
  2106. ; reply && (handler == reply->link())) {
  2107. toggleReplyRipple(pressed);
  2108. }
  2109. }
  2110. void Message::toggleCommentsButtonRipple(bool pressed) {
  2111. Expects(_comments != nullptr);
  2112. if (!drawBubble()) {
  2113. return;
  2114. } else if (pressed) {
  2115. if (!_comments->ripple) {
  2116. createCommentsButtonRipple();
  2117. }
  2118. _comments->ripple->add(_comments->lastPoint
  2119. + QPoint(_comments->rippleShift, 0));
  2120. } else if (_comments->ripple) {
  2121. _comments->ripple->lastStop();
  2122. }
  2123. }
  2124. void Message::toggleRightActionRipple(bool pressed) {
  2125. Expects(_rightAction != nullptr);
  2126. const auto rightSize = rightActionSize();
  2127. Assert(rightSize != std::nullopt);
  2128. if (pressed) {
  2129. if (!_rightAction->ripple) {
  2130. // Create a ripple.
  2131. const auto size = _rightAction->second
  2132. ? Size(rightSize->width())
  2133. : *rightSize;
  2134. _rightAction->ripple = std::make_unique<Ui::RippleAnimation>(
  2135. st::defaultRippleAnimation,
  2136. Ui::RippleAnimation::RoundRectMask(size, size.width() / 2),
  2137. [=] { repaint(); });
  2138. }
  2139. _rightAction->ripple->add(_rightAction->lastPoint);
  2140. } else if (_rightAction->ripple) {
  2141. _rightAction->ripple->lastStop();
  2142. }
  2143. }
  2144. void Message::toggleReplyRipple(bool pressed) {
  2145. const auto reply = Get<Reply>();
  2146. if (!reply) {
  2147. return;
  2148. }
  2149. if (pressed) {
  2150. if (!unwrapped()) {
  2151. const auto &padding = st::msgPadding;
  2152. const auto geometry = countGeometry();
  2153. const auto margins = reply->margins();
  2154. const auto size = QSize(
  2155. geometry.width() - padding.left() - padding.right(),
  2156. reply->height() - margins.top() - margins.bottom());
  2157. reply->createRippleAnimation(this, size);
  2158. }
  2159. reply->addRipple();
  2160. } else {
  2161. reply->stopLastRipple();
  2162. }
  2163. }
  2164. BottomRippleMask Message::bottomRippleMask(int buttonHeight) const {
  2165. using namespace Ui;
  2166. using namespace Images;
  2167. using Radius = CachedCornerRadius;
  2168. using Corner = BubbleCornerRounding;
  2169. const auto g = countGeometry();
  2170. const auto buttonWidth = g.width();
  2171. const auto &large = CachedCornersMasks(Radius::BubbleLarge);
  2172. const auto &small = CachedCornersMasks(Radius::BubbleSmall);
  2173. const auto rounding = countBubbleRounding();
  2174. const auto icon = (rounding.bottomLeft == Corner::Tail)
  2175. ? &st::historyBubbleTailInLeft
  2176. : (rounding.bottomRight == Corner::Tail)
  2177. ? &st::historyBubbleTailInRight
  2178. : nullptr;
  2179. const auto shift = (rounding.bottomLeft == Corner::Tail)
  2180. ? icon->width()
  2181. : 0;
  2182. const auto added = shift ? shift : icon ? icon->width() : 0;
  2183. auto corners = CornersMaskRef();
  2184. const auto set = [&](int index) {
  2185. corners.p[index] = (rounding[index] == Corner::Large)
  2186. ? &large[index]
  2187. : (rounding[index] == Corner::Small)
  2188. ? &small[index]
  2189. : nullptr;
  2190. };
  2191. set(kBottomLeft);
  2192. set(kBottomRight);
  2193. const auto drawer = [&](QPainter &p) {
  2194. p.setCompositionMode(QPainter::CompositionMode_Source);
  2195. const auto ratio = style::DevicePixelRatio();
  2196. const auto corner = [&](int index, bool right) {
  2197. if (const auto image = corners.p[index]) {
  2198. const auto width = image->width() / ratio;
  2199. const auto height = image->height() / ratio;
  2200. p.drawImage(
  2201. QRect(
  2202. shift + (right ? (buttonWidth - width) : 0),
  2203. buttonHeight - height,
  2204. width,
  2205. height),
  2206. *image);
  2207. }
  2208. };
  2209. corner(kBottomLeft, false);
  2210. corner(kBottomRight, true);
  2211. if (icon) {
  2212. const auto left = shift ? 0 : buttonWidth;
  2213. p.fillRect(
  2214. QRect{ left, 0, added, buttonHeight },
  2215. Qt::transparent);
  2216. icon->paint(
  2217. p,
  2218. left,
  2219. buttonHeight - icon->height(),
  2220. buttonWidth + shift,
  2221. Qt::white);
  2222. }
  2223. };
  2224. return {
  2225. RippleAnimation::MaskByDrawer(
  2226. QSize(buttonWidth + added, buttonHeight),
  2227. true,
  2228. drawer),
  2229. shift,
  2230. };
  2231. }
  2232. void Message::createCommentsButtonRipple() {
  2233. auto mask = bottomRippleMask(st::historyCommentsButtonHeight);
  2234. _comments->ripple = std::make_unique<Ui::RippleAnimation>(
  2235. st::defaultRippleAnimation,
  2236. std::move(mask.image),
  2237. [=] { repaint(); });
  2238. _comments->rippleShift = mask.shift;
  2239. }
  2240. void Message::toggleTopicButtonRipple(bool pressed) {
  2241. Expects(_topicButton != nullptr);
  2242. if (!drawBubble()) {
  2243. return;
  2244. } else if (pressed) {
  2245. if (!_topicButton->ripple) {
  2246. createTopicButtonRipple();
  2247. }
  2248. _topicButton->ripple->add(_topicButton->lastPoint);
  2249. } else if (_topicButton->ripple) {
  2250. _topicButton->ripple->lastStop();
  2251. }
  2252. }
  2253. void Message::createTopicButtonRipple() {
  2254. const auto geometry = countGeometry().marginsRemoved(st::msgPadding);
  2255. const auto availableWidth = geometry.width();
  2256. const auto padding = st::topicButtonPadding;
  2257. const auto height = padding.top()
  2258. + st::msgNameFont->height
  2259. + padding.bottom();
  2260. const auto width = std::max(
  2261. std::min(
  2262. availableWidth,
  2263. (padding.left()
  2264. + _topicButton->name.maxWidth()
  2265. + st::topicButtonArrowSkip
  2266. + padding.right())),
  2267. height);
  2268. auto mask = Ui::RippleAnimation::RoundRectMask(
  2269. { width, height },
  2270. height / 2);
  2271. _topicButton->ripple = std::make_unique<Ui::RippleAnimation>(
  2272. st::defaultRippleAnimation,
  2273. std::move(mask),
  2274. [=] { repaint(); });
  2275. }
  2276. bool Message::hasHeavyPart() const {
  2277. return _comments
  2278. || (_fromNameStatus && _fromNameStatus->custom)
  2279. || Element::hasHeavyPart();
  2280. }
  2281. void Message::unloadHeavyPart() {
  2282. Element::unloadHeavyPart();
  2283. _comments = nullptr;
  2284. if (_fromNameStatus) {
  2285. _fromNameStatus->custom = nullptr;
  2286. _fromNameStatus->id = EmojiStatusId();
  2287. }
  2288. }
  2289. bool Message::hasFromPhoto() const {
  2290. if (isHidden()) {
  2291. return false;
  2292. }
  2293. switch (context()) {
  2294. case Context::AdminLog:
  2295. return true;
  2296. case Context::History:
  2297. case Context::ChatPreview:
  2298. case Context::TTLViewer:
  2299. case Context::Pinned:
  2300. case Context::Replies:
  2301. case Context::SavedSublist:
  2302. case Context::ScheduledTopic: {
  2303. const auto item = data();
  2304. if (item->isSponsored()) {
  2305. return false;
  2306. } else if (item->isPostHidingAuthor()) {
  2307. return false;
  2308. } else if (item->isPost()) {
  2309. return true;
  2310. } else if (item->isEmpty()
  2311. || item->isFakeAboutView()
  2312. || (context() == Context::Replies && item->isDiscussionPost())) {
  2313. return false;
  2314. } else if (delegate()->elementIsChatWide()) {
  2315. return true;
  2316. } else if (item->history()->peer->isVerifyCodes()) {
  2317. return !hasOutLayout();
  2318. } else if (const auto forwarded = item->Get<HistoryMessageForwarded>()) {
  2319. const auto peer = item->history()->peer;
  2320. if (peer->isSelf() || peer->isRepliesChat()) {
  2321. return !hasOutLayout();
  2322. }
  2323. }
  2324. return !item->out() && !item->history()->peer->isUser();
  2325. } break;
  2326. case Context::ContactPreview:
  2327. case Context::ShortcutMessages:
  2328. return false;
  2329. }
  2330. Unexpected("Context in Message::hasFromPhoto.");
  2331. }
  2332. TextState Message::textState(
  2333. QPoint point,
  2334. StateRequest request) const {
  2335. const auto item = data();
  2336. const auto media = this->media();
  2337. auto result = TextState(item);
  2338. const auto visibleMediaTextLen = visibleMediaTextLength();
  2339. const auto visibleTextLen = visibleTextLength();
  2340. const auto minSymbol = (_invertMedia && request.onlyMessageText)
  2341. ? visibleMediaTextLen
  2342. : 0;
  2343. result.symbol = minSymbol;
  2344. auto g = countGeometry();
  2345. if (g.width() < 1 || isHidden()) {
  2346. return result;
  2347. }
  2348. if (const auto service = Get<ServicePreMessage>()) {
  2349. result.link = service->textState(point, request, g);
  2350. if (result.link) {
  2351. return result;
  2352. }
  2353. }
  2354. const auto bubble = drawBubble();
  2355. const auto reactionsInBubble = _reactions && embedReactionsInBubble();
  2356. const auto mediaDisplayed = media && media->isDisplayed();
  2357. auto keyboard = item->inlineReplyKeyboard();
  2358. auto keyboardHeight = 0;
  2359. if (keyboard) {
  2360. keyboardHeight = keyboard->naturalHeight();
  2361. g.setHeight(g.height() - st::msgBotKbButton.margin - keyboardHeight);
  2362. }
  2363. if (_reactions && !reactionsInBubble) {
  2364. const auto reactionsHeight = st::mediaInBubbleSkip + _reactions->height();
  2365. const auto reactionsLeft = (!bubble && mediaDisplayed)
  2366. ? media->contentRectForReactions().x()
  2367. : 0;
  2368. g.setHeight(g.height() - reactionsHeight);
  2369. const auto reactionsPosition = QPoint(reactionsLeft + g.left(), g.top() + g.height() + st::mediaInBubbleSkip);
  2370. if (_reactions->getState(point - reactionsPosition, &result)) {
  2371. result.symbol += visibleMediaTextLen + visibleTextLen;
  2372. return result;
  2373. }
  2374. }
  2375. if (bubble) {
  2376. const auto inBubble = g.contains(point);
  2377. const auto check = factcheckBlock();
  2378. const auto entry = logEntryOriginal();
  2379. // Entry page is always a bubble bottom.
  2380. auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || check || (entry/* && entry->isBubbleBottom()*/);
  2381. auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop());
  2382. auto inner = g;
  2383. if (getStateCommentsButton(point, inner, &result)) {
  2384. result.symbol += visibleMediaTextLen + visibleTextLen;
  2385. return result;
  2386. }
  2387. auto trect = inner.marginsRemoved(st::msgPadding);
  2388. const auto additionalInfoSkip = (mediaDisplayed
  2389. && !media->additionalInfoString().isEmpty())
  2390. ? st::msgDateFont->height
  2391. : 0;
  2392. const auto reactionsTop = (reactionsInBubble && !_viewButton)
  2393. ? (additionalInfoSkip + st::mediaInBubbleSkip)
  2394. : additionalInfoSkip;
  2395. const auto reactionsHeight = reactionsInBubble
  2396. ? (reactionsTop + _reactions->height())
  2397. : 0;
  2398. if (reactionsInBubble) {
  2399. trect.setHeight(trect.height() - reactionsHeight);
  2400. const auto reactionsPosition = QPoint(trect.left(), trect.top() + trect.height() + reactionsTop);
  2401. if (_reactions->getState(point - reactionsPosition, &result)) {
  2402. result.symbol += visibleMediaTextLen + visibleTextLen;
  2403. return result;
  2404. }
  2405. }
  2406. if (_viewButton) {
  2407. const auto belowInfo = _viewButton->belowMessageInfo();
  2408. const auto infoHeight = reactionsInBubble
  2409. ? (reactionsHeight + 2 * st::mediaInBubbleSkip)
  2410. : _bottomInfo.height();
  2411. const auto heightMargins = QMargins(0, 0, 0, infoHeight);
  2412. if (_viewButton->getState(
  2413. point,
  2414. _viewButton->countRect(belowInfo
  2415. ? inner
  2416. : inner - heightMargins),
  2417. &result)) {
  2418. result.symbol += visibleMediaTextLen + visibleTextLen;
  2419. return result;
  2420. }
  2421. if (belowInfo) {
  2422. inner.setHeight(inner.height() - _viewButton->height());
  2423. }
  2424. trect.setHeight(trect.height() - _viewButton->height());
  2425. if (reactionsInBubble) {
  2426. trect.setHeight(trect.height() - st::mediaInBubbleSkip + st::msgPadding.bottom());
  2427. } else if (mediaDisplayed) {
  2428. trect.setHeight(trect.height() - st::mediaInBubbleSkip);
  2429. }
  2430. }
  2431. if (mediaOnBottom) {
  2432. trect.setHeight(trect.height() + st::msgPadding.bottom());
  2433. }
  2434. if (mediaOnTop) {
  2435. trect.setY(trect.y() - st::msgPadding.top());
  2436. } else if (inBubble) {
  2437. if (getStateFromName(point, trect, &result)) {
  2438. return result;
  2439. }
  2440. if (getStateTopicButton(point, trect, &result)) {
  2441. return result;
  2442. }
  2443. if (getStateForwardedInfo(point, trect, &result, request)) {
  2444. return result;
  2445. }
  2446. if (getStateViaBotIdInfo(point, trect, &result)) {
  2447. return result;
  2448. }
  2449. if (getStateReplyInfo(point, trect, &result)) {
  2450. return result;
  2451. }
  2452. }
  2453. if (entry) {
  2454. auto entryHeight = entry->height();
  2455. trect.setHeight(trect.height() - entryHeight);
  2456. auto entryLeft = inner.left();
  2457. auto entryTop = trect.y() + trect.height();
  2458. if (point.y() >= entryTop && point.y() < entryTop + entryHeight) {
  2459. result = entry->textState(
  2460. point - QPoint(entryLeft, entryTop),
  2461. request);
  2462. result.symbol += visibleTextLength()
  2463. + visibleMediaTextLength();
  2464. }
  2465. }
  2466. if (check) {
  2467. auto checkHeight = check->height();
  2468. trect.setHeight(trect.height() - checkHeight - st::mediaInBubbleSkip);
  2469. auto checkLeft = inner.left();
  2470. auto checkTop = trect.y() + trect.height() + st::mediaInBubbleSkip;
  2471. if (point.y() >= checkTop && point.y() < checkTop + checkHeight) {
  2472. result = check->textState(
  2473. point - QPoint(checkLeft, checkTop),
  2474. request);
  2475. result.symbol += visibleTextLength()
  2476. + visibleMediaTextLength();
  2477. }
  2478. }
  2479. auto checkBottomInfoState = [&] {
  2480. if (mediaOnBottom
  2481. && (check || entry || media->customInfoLayout())) {
  2482. return;
  2483. }
  2484. const auto bottomInfoResult = bottomInfoTextState(
  2485. inner.left() + inner.width(),
  2486. inner.top() + inner.height(),
  2487. point,
  2488. InfoDisplayType::Default);
  2489. if (bottomInfoResult.link
  2490. || bottomInfoResult.cursor != CursorState::None
  2491. || bottomInfoResult.customTooltip) {
  2492. result = bottomInfoResult;
  2493. }
  2494. };
  2495. if (!inBubble) {
  2496. if (point.y() >= g.y() + g.height()) {
  2497. result.symbol += visibleTextLen + visibleMediaTextLen;
  2498. }
  2499. } else if (result.symbol <= minSymbol) {
  2500. const auto mediaHeight = mediaDisplayed ? media->height() : 0;
  2501. const auto mediaLeft = trect.x() - st::msgPadding.left();
  2502. const auto mediaTop = (!mediaDisplayed || _invertMedia)
  2503. ? (trect.y() + (mediaOnTop ? 0 : st::mediaInBubbleSkip))
  2504. : (trect.y() + trect.height() - mediaHeight);
  2505. if (mediaDisplayed && _invertMedia) {
  2506. trect.setY(mediaTop
  2507. + mediaHeight
  2508. + (mediaOnBottom ? 0 : st::mediaInBubbleSkip));
  2509. }
  2510. if (point.y() >= mediaTop
  2511. && point.y() < mediaTop + mediaHeight) {
  2512. result = media->textState(
  2513. point - QPoint(mediaLeft, mediaTop),
  2514. request);
  2515. if (_invertMedia) {
  2516. if (request.onlyMessageText) {
  2517. result.symbol = minSymbol;
  2518. result.afterSymbol = false;
  2519. result.cursor = CursorState::None;
  2520. }
  2521. } else if (request.onlyMessageText) {
  2522. result.symbol = visibleTextLen;
  2523. result.afterSymbol = false;
  2524. result.cursor = CursorState::None;
  2525. } else {
  2526. result.symbol += visibleTextLen;
  2527. }
  2528. } else if (getStateText(point, trect, &result, request)) {
  2529. if (_invertMedia) {
  2530. result.symbol += visibleMediaTextLen;
  2531. }
  2532. result.overMessageText = true;
  2533. checkBottomInfoState();
  2534. return result;
  2535. } else if (point.y() >= trect.y() + trect.height()) {
  2536. result.symbol = visibleTextLen + visibleMediaTextLen;
  2537. }
  2538. }
  2539. checkBottomInfoState();
  2540. if (const auto size = rightActionSize(); size && _rightAction) {
  2541. const auto fastShareSkip = std::clamp(
  2542. (g.height() - size->height()) / 2,
  2543. 0,
  2544. st::historyFastShareBottom);
  2545. const auto fastShareLeft = hasRightLayout()
  2546. ? (g.left() - size->width() - st::historyFastShareLeft)
  2547. : (g.left() + g.width() + st::historyFastShareLeft);
  2548. const auto fastShareTop = data()->isSponsored()
  2549. ? g.top() + fastShareSkip
  2550. : g.top() + g.height() - fastShareSkip - size->height();
  2551. if (QRect(
  2552. fastShareLeft,
  2553. fastShareTop,
  2554. size->width(),
  2555. size->height()
  2556. ).contains(point)) {
  2557. result.link = rightActionLink(point
  2558. - QPoint(fastShareLeft, fastShareTop));
  2559. }
  2560. }
  2561. } else if (media && media->isDisplayed()) {
  2562. result = media->textState(point - g.topLeft(), request);
  2563. if (request.onlyMessageText) {
  2564. result.symbol = 0;
  2565. result.afterSymbol = false;
  2566. result.cursor = CursorState::None;
  2567. }
  2568. result.symbol += visibleTextLength();
  2569. }
  2570. if (keyboard && item->isHistoryEntry()) {
  2571. const auto keyboardTop = g.top()
  2572. + g.height()
  2573. + st::msgBotKbButton.margin
  2574. + ((_reactions && !reactionsInBubble)
  2575. ? (st::mediaInBubbleSkip + _reactions->height())
  2576. : 0);
  2577. if (QRect(g.left(), keyboardTop, g.width(), keyboardHeight).contains(point)) {
  2578. result.link = keyboard->getLink(point - QPoint(g.left(), keyboardTop));
  2579. }
  2580. }
  2581. return result;
  2582. }
  2583. bool Message::getStateCommentsButton(
  2584. QPoint point,
  2585. QRect &g,
  2586. not_null<TextState*> outResult) const {
  2587. if (!_comments) {
  2588. return false;
  2589. }
  2590. g.setHeight(g.height() - st::historyCommentsButtonHeight);
  2591. if (data()->isSending()
  2592. || !QRect(
  2593. g.left(),
  2594. g.top() + g.height(),
  2595. g.width(),
  2596. st::historyCommentsButtonHeight).contains(point)) {
  2597. return false;
  2598. }
  2599. if (!_comments->link && data()->repliesAreComments()) {
  2600. _comments->link = createGoToCommentsLink();
  2601. } else if (!_comments->link && data()->externalReply()) {
  2602. _comments->link = prepareRightActionLink();
  2603. }
  2604. outResult->link = _comments->link;
  2605. _comments->lastPoint = point - QPoint(g.left(), g.top() + g.height());
  2606. return true;
  2607. }
  2608. ClickHandlerPtr Message::createGoToCommentsLink() const {
  2609. const auto fullId = data()->fullId();
  2610. const auto sessionId = data()->history()->session().uniqueId();
  2611. return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
  2612. const auto controller = ExtractController(context);
  2613. if (!controller || controller->session().uniqueId() != sessionId) {
  2614. return;
  2615. }
  2616. if (const auto item = controller->session().data().message(fullId)) {
  2617. const auto history = item->history();
  2618. if (const auto channel = history->peer->asChannel()) {
  2619. if (channel->invitePeekExpires()) {
  2620. controller->showToast(
  2621. tr::lng_channel_invite_private(tr::now));
  2622. return;
  2623. }
  2624. }
  2625. controller->showRepliesForMessage(history, item->id);
  2626. }
  2627. });
  2628. }
  2629. bool Message::getStateFromName(
  2630. QPoint point,
  2631. QRect &trect,
  2632. not_null<TextState*> outResult) const {
  2633. if (!displayFromName()) {
  2634. return false;
  2635. }
  2636. const auto replyWidth = [&] {
  2637. if (isUnderCursor()) {
  2638. if (displayFastForward()) {
  2639. return st::msgFont->width(FastForwardText());
  2640. } else if (displayFastReply()) {
  2641. return st::msgFont->width(FastReplyText());
  2642. }
  2643. }
  2644. return 0;
  2645. }();
  2646. if (replyWidth
  2647. && point.x() >= trect.left() + trect.width() - replyWidth
  2648. && point.x() < trect.left() + trect.width() + st::msgPadding.right()
  2649. && point.y() >= trect.top() - st::msgPadding.top()
  2650. && point.y() < trect.top() + st::msgServiceFont->height) {
  2651. outResult->link = fastReplyLink();
  2652. return true;
  2653. }
  2654. if (point.y() >= trect.top() && point.y() < trect.top() + st::msgNameFont->height) {
  2655. auto availableLeft = trect.left();
  2656. auto availableWidth = trect.width();
  2657. if (replyWidth) {
  2658. availableWidth -= st::msgPadding.right() + replyWidth;
  2659. }
  2660. const auto item = data();
  2661. const auto from = item->displayFrom();
  2662. const auto nameText = [&]() -> const Ui::Text::String * {
  2663. if (from) {
  2664. validateFromNameText(from);
  2665. return &_fromName;
  2666. } else if (const auto info = item->displayHiddenSenderInfo()) {
  2667. return &info->nameText();
  2668. } else {
  2669. Unexpected("Corrupt forwarded information in message.");
  2670. }
  2671. }();
  2672. const auto statusWidth = (from && _fromNameStatus)
  2673. ? st::dialogsPremiumIcon.icon.width()
  2674. : 0;
  2675. if (statusWidth && availableWidth > statusWidth) {
  2676. const auto x = availableLeft + std::min(
  2677. availableWidth - statusWidth,
  2678. nameText->maxWidth()
  2679. ) - (_fromNameStatus->custom ? (2 * _fromNameStatus->skip) : 0);
  2680. const auto checkWidth = _fromNameStatus->custom
  2681. ? (st::emojiSize - 2 * _fromNameStatus->skip)
  2682. : statusWidth;
  2683. if (point.x() >= x && point.x() < x + checkWidth) {
  2684. ensureFromNameStatusLink(from);
  2685. outResult->link = _fromNameStatus->link;
  2686. return true;
  2687. }
  2688. availableWidth -= statusWidth;
  2689. }
  2690. if (point.x() >= availableLeft
  2691. && point.x() < availableLeft + availableWidth
  2692. && point.x() < availableLeft + nameText->maxWidth()) {
  2693. outResult->link = fromLink();
  2694. return true;
  2695. }
  2696. auto via = item->Get<HistoryMessageVia>();
  2697. if (via
  2698. && !displayForwardedFrom()
  2699. && point.x() >= availableLeft + nameText->maxWidth() + st::msgServiceFont->spacew
  2700. && point.x() < availableLeft + availableWidth
  2701. && point.x() < availableLeft + nameText->maxWidth() + st::msgServiceFont->spacew + via->width) {
  2702. outResult->link = via->link;
  2703. return true;
  2704. }
  2705. }
  2706. trect.setTop(trect.top() + st::msgNameFont->height);
  2707. return false;
  2708. }
  2709. void Message::ensureFromNameStatusLink(not_null<PeerData*> peer) const {
  2710. Expects(_fromNameStatus != nullptr);
  2711. if (_fromNameStatus->link) {
  2712. return;
  2713. }
  2714. _fromNameStatus->link = std::make_shared<LambdaClickHandler>([=](
  2715. ClickContext context) {
  2716. const auto controller = ExtractController(context);
  2717. if (controller) {
  2718. Settings::ShowEmojiStatusPremium(controller, peer);
  2719. }
  2720. });
  2721. }
  2722. bool Message::getStateTopicButton(
  2723. QPoint point,
  2724. QRect &trect,
  2725. not_null<TextState*> outResult) const {
  2726. if (!displayedTopicButton()) {
  2727. return false;
  2728. }
  2729. trect.setTop(trect.top() + st::topicButtonSkip);
  2730. const auto padding = st::topicButtonPadding;
  2731. const auto availableWidth = trect.width();
  2732. const auto height = padding.top()
  2733. + st::msgNameFont->height
  2734. + padding.bottom();
  2735. const auto width = std::max(
  2736. std::min(
  2737. availableWidth,
  2738. (padding.left()
  2739. + _topicButton->name.maxWidth()
  2740. + st::topicButtonArrowSkip
  2741. + padding.right())),
  2742. height);
  2743. const auto rect = QRect(trect.x(), trect.y(), width, height);
  2744. if (rect.contains(point)) {
  2745. outResult->link = _topicButton->link;
  2746. _topicButton->lastPoint = point - rect.topLeft();
  2747. return true;
  2748. }
  2749. trect.setY(trect.y() + height + st::topicButtonSkip);
  2750. return false;
  2751. }
  2752. bool Message::getStateForwardedInfo(
  2753. QPoint point,
  2754. QRect &trect,
  2755. not_null<TextState*> outResult,
  2756. StateRequest request) const {
  2757. if (!displayForwardedFrom()) {
  2758. return false;
  2759. }
  2760. const auto item = data();
  2761. const auto forwarded = item->Get<HistoryMessageForwarded>();
  2762. const auto skip1 = forwarded->psaType.isEmpty()
  2763. ? 0
  2764. : st::historyPsaIconSkip1;
  2765. const auto skip2 = forwarded->psaType.isEmpty()
  2766. ? 0
  2767. : st::historyPsaIconSkip2;
  2768. const auto fits = (forwarded->text.maxWidth() <= (trect.width() - skip1));
  2769. const auto fwdheight = (fits ? 1 : 2) * st::semiboldFont->height;
  2770. if (point.y() >= trect.top() && point.y() < trect.top() + fwdheight) {
  2771. if (skip1) {
  2772. const auto &icon = st::historyPsaIconIn;
  2773. const auto position = fits
  2774. ? st::historyPsaIconPosition1
  2775. : st::historyPsaIconPosition2;
  2776. const auto iconRect = QRect(
  2777. trect.x() + trect.width() - position.x() - icon.width(),
  2778. trect.y() + position.y(),
  2779. icon.width(),
  2780. icon.height());
  2781. if (iconRect.contains(point)) {
  2782. if (const auto link = psaTooltipLink()) {
  2783. outResult->link = link;
  2784. return true;
  2785. }
  2786. }
  2787. }
  2788. const auto useWidth = trect.width() - (fits ? skip1 : skip2);
  2789. const auto breakEverywhere = (forwarded->text.countHeight(useWidth) > 2 * st::semiboldFont->height);
  2790. auto textRequest = request.forText();
  2791. if (breakEverywhere) {
  2792. textRequest.flags |= Ui::Text::StateRequest::Flag::BreakEverywhere;
  2793. }
  2794. *outResult = TextState(item, forwarded->text.getState(
  2795. point - trect.topLeft(),
  2796. useWidth,
  2797. textRequest));
  2798. outResult->symbol = 0;
  2799. outResult->afterSymbol = false;
  2800. if (breakEverywhere) {
  2801. outResult->cursor = CursorState::Forwarded;
  2802. } else {
  2803. outResult->cursor = CursorState::None;
  2804. }
  2805. return true;
  2806. }
  2807. trect.setTop(trect.top() + fwdheight);
  2808. return false;
  2809. }
  2810. ClickHandlerPtr Message::psaTooltipLink() const {
  2811. const auto state = Get<PsaTooltipState>();
  2812. if (!state || !state->buttonVisible) {
  2813. return nullptr;
  2814. } else if (state->link) {
  2815. return state->link;
  2816. }
  2817. const auto type = state->type;
  2818. const auto handler = [=] {
  2819. const auto custom = type.isEmpty()
  2820. ? QString()
  2821. : Lang::GetNonDefaultValue(kPsaTooltipPrefix + type.toUtf8());
  2822. auto text = Ui::Text::RichLangValue(
  2823. (custom.isEmpty()
  2824. ? tr::lng_tooltip_psa_default(tr::now)
  2825. : custom));
  2826. TextUtilities::ParseEntities(text, 0);
  2827. psaTooltipToggled(true);
  2828. delegate()->elementShowTooltip(text, crl::guard(this, [=] {
  2829. psaTooltipToggled(false);
  2830. }));
  2831. };
  2832. state->link = std::make_shared<LambdaClickHandler>(
  2833. crl::guard(this, handler));
  2834. return state->link;
  2835. }
  2836. void Message::psaTooltipToggled(bool tooltipShown) const {
  2837. const auto visible = !tooltipShown;
  2838. const auto state = Get<PsaTooltipState>();
  2839. if (state->buttonVisible == visible) {
  2840. return;
  2841. }
  2842. state->buttonVisible = visible;
  2843. history()->owner().notifyViewLayoutChange(this);
  2844. state->buttonVisibleAnimation.start(
  2845. [=] { repaint(); },
  2846. visible ? 0. : 1.,
  2847. visible ? 1. : 0.,
  2848. st::fadeWrapDuration);
  2849. }
  2850. bool Message::getStateReplyInfo(
  2851. QPoint point,
  2852. QRect &trect,
  2853. not_null<TextState*> outResult) const {
  2854. if (const auto reply = Get<Reply>()) {
  2855. const auto margins = reply->margins();
  2856. const auto height = reply->height();
  2857. if (point.y() >= trect.top() && point.y() < trect.top() + height) {
  2858. const auto g = QRect(
  2859. trect.x(),
  2860. trect.y() + margins.top(),
  2861. trect.width(),
  2862. height - margins.top() - margins.bottom());
  2863. if (g.contains(point)) {
  2864. if (const auto link = reply->link()) {
  2865. outResult->link = reply->link();
  2866. reply->saveRipplePoint(point - g.topLeft());
  2867. }
  2868. }
  2869. return true;
  2870. }
  2871. trect.setTop(trect.top() + height);
  2872. }
  2873. return false;
  2874. }
  2875. bool Message::getStateViaBotIdInfo(
  2876. QPoint point,
  2877. QRect &trect,
  2878. not_null<TextState*> outResult) const {
  2879. const auto item = data();
  2880. if (const auto via = item->Get<HistoryMessageVia>()) {
  2881. if (!displayFromName() && !displayForwardedFrom()) {
  2882. if (QRect(trect.x(), trect.y(), via->width, st::msgNameFont->height).contains(point)) {
  2883. outResult->link = via->link;
  2884. return true;
  2885. }
  2886. trect.setTop(trect.top() + st::msgNameFont->height);
  2887. }
  2888. }
  2889. return false;
  2890. }
  2891. bool Message::getStateText(
  2892. QPoint point,
  2893. QRect &trect,
  2894. not_null<TextState*> outResult,
  2895. StateRequest request) const {
  2896. if (!hasVisibleText()) {
  2897. return false;
  2898. } else if (const auto botTop = Get<FakeBotAboutTop>()) {
  2899. trect.setY(trect.y() + botTop->height);
  2900. }
  2901. const auto item = this->textItem();
  2902. if (base::in_range(point.y(), trect.y(), trect.y() + trect.height())) {
  2903. *outResult = TextState(item, text().getState(
  2904. point - trect.topLeft(),
  2905. trect.width(),
  2906. request.forText()));
  2907. return true;
  2908. }
  2909. return false;
  2910. }
  2911. // Forward to media.
  2912. void Message::updatePressed(QPoint point) {
  2913. const auto item = data();
  2914. const auto media = this->media();
  2915. if (!media) {
  2916. return;
  2917. }
  2918. auto g = countGeometry();
  2919. auto keyboard = item->inlineReplyKeyboard();
  2920. if (keyboard) {
  2921. auto keyboardHeight = st::msgBotKbButton.margin + keyboard->naturalHeight();
  2922. g.setHeight(g.height() - keyboardHeight);
  2923. }
  2924. if (drawBubble()) {
  2925. auto mediaDisplayed = media && media->isDisplayed();
  2926. auto trect = g.marginsAdded(-st::msgPadding);
  2927. if (mediaDisplayed && media->isBubbleTop()) {
  2928. trect.setY(trect.y() - st::msgPadding.top());
  2929. } else {
  2930. if (displayFromName()) {
  2931. trect.setTop(trect.top() + st::msgNameFont->height);
  2932. }
  2933. if (displayedTopicButton()) {
  2934. trect.setTop(trect.top()
  2935. + st::topicButtonSkip
  2936. + st::topicButtonPadding.top()
  2937. + st::msgNameFont->height
  2938. + st::topicButtonPadding.bottom()
  2939. + st::topicButtonSkip);
  2940. }
  2941. if (displayForwardedFrom()) {
  2942. auto forwarded = item->Get<HistoryMessageForwarded>();
  2943. auto fwdheight = ((forwarded->text.maxWidth() > trect.width()) ? 2 : 1) * st::semiboldFont->height;
  2944. trect.setTop(trect.top() + fwdheight);
  2945. }
  2946. if (const auto reply = Get<Reply>()) {
  2947. trect.setTop(trect.top() + reply->height());
  2948. }
  2949. if (const auto via = item->Get<HistoryMessageVia>()) {
  2950. if (!displayFromName() && !displayForwardedFrom()) {
  2951. trect.setTop(trect.top() + st::msgNameFont->height);
  2952. }
  2953. }
  2954. }
  2955. if (mediaDisplayed && media->isBubbleBottom()) {
  2956. trect.setHeight(trect.height() + st::msgPadding.bottom());
  2957. }
  2958. if (mediaDisplayed) {
  2959. auto mediaHeight = media->height();
  2960. auto mediaLeft = trect.x() - st::msgPadding.left();
  2961. auto mediaTop = (trect.y() + trect.height() - mediaHeight);
  2962. media->updatePressed(point - QPoint(mediaLeft, mediaTop));
  2963. }
  2964. } else {
  2965. media->updatePressed(point - g.topLeft());
  2966. }
  2967. }
  2968. TextForMimeData Message::selectedText(TextSelection selection) const {
  2969. const auto media = this->media();
  2970. auto logEntryOriginalResult = TextForMimeData();
  2971. auto factcheckResult = TextForMimeData();
  2972. const auto mediaDisplayed = (media && media->isDisplayed());
  2973. const auto mediaBefore = mediaDisplayed && invertMedia();
  2974. const auto textSelection = mediaBefore
  2975. ? media->skipSelection(selection)
  2976. : selection;
  2977. const auto mediaSelection = !invertMedia()
  2978. ? skipTextSelection(selection)
  2979. : selection;
  2980. auto textResult = hasVisibleText()
  2981. ? text().toTextForMimeData(textSelection)
  2982. : TextForMimeData();
  2983. auto mediaResult = (mediaDisplayed || isHiddenByGroup())
  2984. ? media->selectedText(mediaSelection)
  2985. : TextForMimeData();
  2986. if (const auto check = factcheckBlock()) {
  2987. const auto checkSelection = mediaBefore
  2988. ? skipTextSelection(textSelection)
  2989. : mediaDisplayed
  2990. ? media->skipSelection(mediaSelection)
  2991. : skipTextSelection(selection);
  2992. factcheckResult = check->selectedText(checkSelection);
  2993. }
  2994. if (const auto entry = logEntryOriginal()) {
  2995. const auto originalSelection = mediaBefore
  2996. ? skipTextSelection(textSelection)
  2997. : mediaDisplayed
  2998. ? media->skipSelection(mediaSelection)
  2999. : skipTextSelection(selection);
  3000. logEntryOriginalResult = entry->selectedText(originalSelection);
  3001. }
  3002. auto &first = mediaBefore ? mediaResult : textResult;
  3003. auto &second = mediaBefore ? textResult : mediaResult;
  3004. auto result = first;
  3005. if (result.empty()) {
  3006. result = std::move(second);
  3007. } else if (!second.empty()) {
  3008. result.append(u"\n\n"_q).append(std::move(second));
  3009. }
  3010. if (result.empty()) {
  3011. result = std::move(factcheckResult);
  3012. } else if (!factcheckResult.empty()) {
  3013. result.append(u"\n\n"_q).append(std::move(factcheckResult));
  3014. }
  3015. if (result.empty()) {
  3016. result = std::move(logEntryOriginalResult);
  3017. } else if (!logEntryOriginalResult.empty()) {
  3018. result.append(u"\n\n"_q).append(std::move(logEntryOriginalResult));
  3019. }
  3020. return result;
  3021. }
  3022. SelectedQuote Message::selectedQuote(TextSelection selection) const {
  3023. const auto textItem = this->textItem();
  3024. const auto item = textItem ? textItem : data().get();
  3025. const auto &translated = item->translatedText();
  3026. const auto &original = item->originalText();
  3027. if (&translated != &original
  3028. || selection.empty()
  3029. || selection == FullSelection) {
  3030. return {};
  3031. } else if (hasVisibleText()) {
  3032. const auto media = this->media();
  3033. const auto mediaDisplayed = media && media->isDisplayed();
  3034. const auto mediaBefore = mediaDisplayed && invertMedia();
  3035. const auto textSelection = mediaBefore
  3036. ? media->skipSelection(selection)
  3037. : selection;
  3038. return FindSelectedQuote(text(), textSelection, item);
  3039. } else if (const auto media = this->media()) {
  3040. if (media->isDisplayed() || isHiddenByGroup()) {
  3041. return media->selectedQuote(selection);
  3042. }
  3043. }
  3044. return {};
  3045. }
  3046. TextSelection Message::selectionFromQuote(
  3047. const SelectedQuote &quote) const {
  3048. Expects(quote.item != nullptr);
  3049. if (quote.text.empty()) {
  3050. return {};
  3051. }
  3052. const auto item = quote.item;
  3053. const auto &translated = item->translatedText();
  3054. const auto &original = item->originalText();
  3055. if (&translated != &original) {
  3056. return {};
  3057. } else if (hasVisibleText()) {
  3058. const auto media = this->media();
  3059. const auto mediaDisplayed = media && media->isDisplayed();
  3060. const auto mediaBefore = mediaDisplayed && invertMedia();
  3061. const auto result = FindSelectionFromQuote(text(), quote);
  3062. return mediaBefore ? media->unskipSelection(result) : result;
  3063. } else if (const auto media = this->media()) {
  3064. if (media->isDisplayed() || isHiddenByGroup()) {
  3065. return media->selectionFromQuote(quote);
  3066. }
  3067. }
  3068. return {};
  3069. }
  3070. TextSelection Message::adjustSelection(
  3071. TextSelection selection,
  3072. TextSelectType type) const {
  3073. const auto media = this->media();
  3074. const auto mediaDisplayed = media && media->isDisplayed();
  3075. const auto mediaBefore = mediaDisplayed && invertMedia();
  3076. const auto textSelection = mediaBefore
  3077. ? media->skipSelection(selection)
  3078. : selection;
  3079. const auto useSelection = [](TextSelection selection, bool skipped) {
  3080. return !skipped || (selection != TextSelection(uint16(), uint16()));
  3081. };
  3082. auto textAdjusted = (hasVisibleText()
  3083. && useSelection(textSelection, mediaBefore))
  3084. ? text().adjustSelection(textSelection, type)
  3085. : textSelection;
  3086. auto textResult = mediaBefore
  3087. ? media->unskipSelection(textAdjusted)
  3088. : textAdjusted;
  3089. auto mediaResult = TextSelection();
  3090. auto mediaSelection = mediaBefore
  3091. ? selection
  3092. : skipTextSelection(selection);
  3093. if (mediaDisplayed) {
  3094. auto mediaAdjusted = useSelection(mediaSelection, !mediaBefore)
  3095. ? media->adjustSelection(mediaSelection, type)
  3096. : mediaSelection;
  3097. mediaResult = mediaBefore
  3098. ? mediaAdjusted
  3099. : unskipTextSelection(mediaAdjusted);
  3100. }
  3101. auto checkResult = TextSelection();
  3102. if (const auto check = factcheckBlock()) {
  3103. auto checkSelection = !mediaDisplayed
  3104. ? skipTextSelection(selection)
  3105. : mediaBefore
  3106. ? skipTextSelection(textSelection)
  3107. : media->skipSelection(mediaSelection);
  3108. auto checkAdjusted = useSelection(checkSelection, true)
  3109. ? check->adjustSelection(checkSelection, type)
  3110. : checkSelection;
  3111. checkResult = unskipTextSelection(checkAdjusted);
  3112. if (mediaDisplayed) {
  3113. checkResult = media->unskipSelection(checkResult);
  3114. }
  3115. }
  3116. auto entryResult = TextSelection();
  3117. if (const auto entry = logEntryOriginal()) {
  3118. auto entrySelection = !mediaDisplayed
  3119. ? skipTextSelection(selection)
  3120. : mediaBefore
  3121. ? skipTextSelection(textSelection)
  3122. : media->skipSelection(mediaSelection);
  3123. auto entryAdjusted = useSelection(entrySelection, true)
  3124. ? entry->adjustSelection(entrySelection, type)
  3125. : entrySelection;
  3126. entryResult = unskipTextSelection(entryAdjusted);
  3127. if (mediaDisplayed) {
  3128. entryResult = media->unskipSelection(entryResult);
  3129. }
  3130. }
  3131. auto result = textResult;
  3132. if (!mediaResult.empty()) {
  3133. result = result.empty() ? mediaResult : TextSelection{
  3134. std::min(result.from, mediaResult.from),
  3135. std::max(result.to, mediaResult.to),
  3136. };
  3137. }
  3138. if (!checkResult.empty()) {
  3139. result = result.empty() ? checkResult : TextSelection{
  3140. std::min(result.from, checkResult.from),
  3141. std::max(result.to, checkResult.to),
  3142. };
  3143. }
  3144. if (!entryResult.empty()) {
  3145. result = result.empty() ? entryResult : TextSelection{
  3146. std::min(result.from, entryResult.from),
  3147. std::max(result.to, entryResult.to),
  3148. };
  3149. }
  3150. return result;
  3151. }
  3152. Reactions::ButtonParameters Message::reactionButtonParameters(
  3153. QPoint position,
  3154. const TextState &reactionState) const {
  3155. using namespace Reactions;
  3156. auto result = ButtonParameters{ .context = data()->fullId() };
  3157. const auto outsideBubble = (!_comments && !embedReactionsInBubble());
  3158. const auto geometry = countGeometry();
  3159. result.pointer = position;
  3160. const auto onTheLeft = hasRightLayout();
  3161. const auto keyboard = data()->inlineReplyKeyboard();
  3162. const auto keyboardHeight = keyboard
  3163. ? (st::msgBotKbButton.margin + keyboard->naturalHeight())
  3164. : 0;
  3165. const auto reactionsHeight = (_reactions && !embedReactionsInBubble())
  3166. ? (st::mediaInBubbleSkip + _reactions->height())
  3167. : 0;
  3168. const auto innerHeight = geometry.height()
  3169. - keyboardHeight
  3170. - reactionsHeight;
  3171. const auto maybeRelativeCenter = outsideBubble
  3172. ? media()->reactionButtonCenterOverride()
  3173. : std::nullopt;
  3174. const auto addOnTheRight = [&] {
  3175. return (maybeRelativeCenter
  3176. || !(displayFastShare() || displayGoToOriginal()))
  3177. ? st::reactionCornerCenter.x()
  3178. : 0;
  3179. };
  3180. const auto relativeCenter = QPoint(
  3181. maybeRelativeCenter.value_or(onTheLeft
  3182. ? -st::reactionCornerCenter.x()
  3183. : (geometry.width() + addOnTheRight())),
  3184. innerHeight + st::reactionCornerCenter.y());
  3185. result.center = geometry.topLeft() + relativeCenter;
  3186. if (reactionState.itemId != result.context
  3187. && !geometry.contains(position)) {
  3188. result.outside = true;
  3189. }
  3190. const auto minSkip = (st::reactionCornerShadow.left()
  3191. + st::reactionCornerSize.width()
  3192. + st::reactionCornerShadow.right()) / 2;
  3193. result.center = QPoint(
  3194. std::min(std::max(result.center.x(), minSkip), width() - minSkip),
  3195. result.center.y());
  3196. return result;
  3197. }
  3198. int Message::reactionsOptimalWidth() const {
  3199. return _reactions ? _reactions->countNiceWidth() : 0;
  3200. }
  3201. void Message::drawInfo(
  3202. Painter &p,
  3203. const PaintContext &context,
  3204. int right,
  3205. int bottom,
  3206. int width,
  3207. InfoDisplayType type) const {
  3208. p.setFont(st::msgDateFont);
  3209. const auto st = context.st;
  3210. const auto sti = context.imageStyle();
  3211. const auto stm = context.messageStyle();
  3212. bool invertedsprites = (type == InfoDisplayType::Image)
  3213. || (type == InfoDisplayType::Background);
  3214. int32 infoRight = right, infoBottom = bottom;
  3215. switch (type) {
  3216. case InfoDisplayType::Default:
  3217. infoRight -= st::msgPadding.right() - st::msgDateDelta.x();
  3218. infoBottom -= st::msgPadding.bottom() - st::msgDateDelta.y();
  3219. p.setPen(stm->msgDateFg);
  3220. break;
  3221. case InfoDisplayType::Image:
  3222. infoRight -= st::msgDateImgDelta + st::msgDateImgPadding.x();
  3223. infoBottom -= st::msgDateImgDelta + st::msgDateImgPadding.y();
  3224. p.setPen(st->msgDateImgFg());
  3225. break;
  3226. case InfoDisplayType::Background:
  3227. infoRight -= st::msgDateImgPadding.x();
  3228. infoBottom -= st::msgDateImgPadding.y();
  3229. p.setPen(st->msgServiceFg());
  3230. break;
  3231. }
  3232. const auto size = _bottomInfo.currentSize();
  3233. const auto dateX = infoRight - size.width();
  3234. const auto dateY = infoBottom - size.height();
  3235. if (type == InfoDisplayType::Image) {
  3236. const auto dateW = size.width() + 2 * st::msgDateImgPadding.x();
  3237. const auto dateH = size.height() + 2 * st::msgDateImgPadding.y();
  3238. Ui::FillRoundRect(p, dateX - st::msgDateImgPadding.x(), dateY - st::msgDateImgPadding.y(), dateW, dateH, sti->msgDateImgBg, sti->msgDateImgBgCorners);
  3239. } else if (type == InfoDisplayType::Background) {
  3240. const auto dateW = size.width() + 2 * st::msgDateImgPadding.x();
  3241. const auto dateH = size.height() + 2 * st::msgDateImgPadding.y();
  3242. Ui::FillRoundRect(p, dateX - st::msgDateImgPadding.x(), dateY - st::msgDateImgPadding.y(), dateW, dateH, sti->msgServiceBg, sti->msgServiceBgCornersSmall);
  3243. }
  3244. _bottomInfo.paint(
  3245. p,
  3246. { dateX, dateY },
  3247. width,
  3248. delegate()->elementShownUnread(this),
  3249. invertedsprites,
  3250. context);
  3251. }
  3252. TextState Message::bottomInfoTextState(
  3253. int right,
  3254. int bottom,
  3255. QPoint point,
  3256. InfoDisplayType type) const {
  3257. auto infoRight = right;
  3258. auto infoBottom = bottom;
  3259. switch (type) {
  3260. case InfoDisplayType::Default:
  3261. infoRight -= st::msgPadding.right() - st::msgDateDelta.x();
  3262. infoBottom -= st::msgPadding.bottom() - st::msgDateDelta.y();
  3263. break;
  3264. case InfoDisplayType::Image:
  3265. infoRight -= st::msgDateImgDelta + st::msgDateImgPadding.x();
  3266. infoBottom -= st::msgDateImgDelta + st::msgDateImgPadding.y();
  3267. break;
  3268. case InfoDisplayType::Background:
  3269. infoRight -= st::msgDateImgPadding.x();
  3270. infoBottom -= st::msgDateImgPadding.y();
  3271. break;
  3272. }
  3273. const auto size = _bottomInfo.currentSize();
  3274. const auto infoLeft = infoRight - size.width();
  3275. const auto infoTop = infoBottom - size.height();
  3276. return _bottomInfo.textState(
  3277. this,
  3278. point - QPoint{ infoLeft, infoTop });
  3279. }
  3280. int Message::infoWidth() const {
  3281. return _bottomInfo.optimalSize().width();
  3282. }
  3283. int Message::bottomInfoFirstLineWidth() const {
  3284. return _bottomInfo.firstLineWidth();
  3285. }
  3286. bool Message::bottomInfoIsWide() const {
  3287. if (_reactions && embedReactionsInBubble()) {
  3288. return false;
  3289. }
  3290. return _bottomInfo.isWide();
  3291. }
  3292. bool Message::isSignedAuthorElided() const {
  3293. return _bottomInfo.isSignedAuthorElided();
  3294. }
  3295. bool Message::embedReactionsInBubble() const {
  3296. return needInfoDisplay();
  3297. }
  3298. void Message::validateInlineKeyboard(HistoryMessageReplyMarkup *markup) {
  3299. if (!markup
  3300. || markup->inlineKeyboard
  3301. || markup->hiddenBy(data()->media())) {
  3302. return;
  3303. }
  3304. const auto item = data();
  3305. markup->inlineKeyboard = std::make_unique<ReplyKeyboard>(
  3306. item,
  3307. std::make_unique<KeyboardStyle>(
  3308. st::msgBotKbButton,
  3309. [=] { item->history()->owner().requestItemRepaint(item); }));
  3310. }
  3311. void Message::validateFromNameText(PeerData *from) const {
  3312. if (!from) {
  3313. if (_fromNameStatus) {
  3314. _fromNameStatus = nullptr;
  3315. }
  3316. return;
  3317. }
  3318. const auto version = from->nameVersion();
  3319. if (_fromNameVersion < version) {
  3320. _fromNameVersion = version;
  3321. _fromName.setText(
  3322. st::msgNameStyle,
  3323. from->name(),
  3324. Ui::NameTextOptions());
  3325. }
  3326. if (from->isPremium()
  3327. || (from->isChannel()
  3328. && from->emojiStatusId()
  3329. && from != history()->peer)) {
  3330. if (!_fromNameStatus) {
  3331. _fromNameStatus = std::make_unique<FromNameStatus>();
  3332. const auto size = st::emojiSize;
  3333. const auto emoji = Ui::Text::AdjustCustomEmojiSize(size);
  3334. _fromNameStatus->skip = (size - emoji) / 2;
  3335. }
  3336. } else if (_fromNameStatus) {
  3337. _fromNameStatus = nullptr;
  3338. }
  3339. }
  3340. bool Message::updateBottomInfo() {
  3341. const auto wasInfo = _bottomInfo.currentSize();
  3342. _bottomInfo.update(BottomInfoDataFromMessage(this), width());
  3343. return (_bottomInfo.currentSize() != wasInfo);
  3344. }
  3345. void Message::itemDataChanged() {
  3346. const auto infoChanged = updateBottomInfo();
  3347. const auto reactionsChanged = updateReactions();
  3348. if (infoChanged || reactionsChanged) {
  3349. history()->owner().requestViewResize(this);
  3350. } else {
  3351. repaint();
  3352. }
  3353. }
  3354. auto Message::verticalRepaintRange() const -> VerticalRepaintRange {
  3355. const auto media = this->media();
  3356. const auto add = media ? media->bubbleRollRepaintMargins() : QMargins();
  3357. return {
  3358. .top = -add.top(),
  3359. .height = height() + add.top() + add.bottom()
  3360. };
  3361. }
  3362. void Message::refreshDataIdHook() {
  3363. if (_rightAction && base::take(_rightAction->link)) {
  3364. _rightAction->link = rightActionLink(_rightAction->lastPoint);
  3365. }
  3366. if (base::take(_fastReplyLink)) {
  3367. _fastReplyLink = fastReplyLink();
  3368. }
  3369. if (_viewButton) {
  3370. _viewButton = nullptr;
  3371. updateViewButtonExistence();
  3372. }
  3373. if (_comments) {
  3374. _comments->link = nullptr;
  3375. }
  3376. }
  3377. int Message::monospaceMaxWidth() const {
  3378. return st::msgPadding.left()
  3379. + (hasVisibleText() ? text().countMaxMonospaceWidth() : 0)
  3380. + st::msgPadding.right();
  3381. }
  3382. int Message::viewButtonHeight() const {
  3383. return _viewButton ? _viewButton->height() : 0;
  3384. }
  3385. void Message::updateViewButtonExistence() {
  3386. const auto item = data();
  3387. const auto media = item->media();
  3388. const auto has = (media && ViewButton::MediaHasViewButton(media));
  3389. if (!has) {
  3390. _viewButton = nullptr;
  3391. return;
  3392. } else if (_viewButton) {
  3393. return;
  3394. }
  3395. auto make = [=](auto &&from) {
  3396. return std::make_unique<ViewButton>(
  3397. std::forward<decltype(from)>(from),
  3398. colorIndex(),
  3399. [=] { repaint(); });
  3400. };
  3401. _viewButton = make(media);
  3402. }
  3403. void Message::initLogEntryOriginal() {
  3404. if (const auto log = data()->Get<HistoryMessageLogEntryOriginal>()) {
  3405. AddComponents(LogEntryOriginal::Bit());
  3406. const auto entry = Get<LogEntryOriginal>();
  3407. using Flags = MediaWebPageFlags;
  3408. entry->page = std::make_unique<WebPage>(this, log->page, Flags());
  3409. }
  3410. }
  3411. void Message::initPsa() {
  3412. if (const auto forwarded = data()->Get<HistoryMessageForwarded>()) {
  3413. if (!forwarded->psaType.isEmpty()) {
  3414. AddComponents(PsaTooltipState::Bit());
  3415. Get<PsaTooltipState>()->type = forwarded->psaType;
  3416. }
  3417. }
  3418. }
  3419. WebPage *Message::logEntryOriginal() const {
  3420. if (const auto entry = Get<LogEntryOriginal>()) {
  3421. return entry->page.get();
  3422. }
  3423. return nullptr;
  3424. }
  3425. WebPage *Message::factcheckBlock() const {
  3426. if (const auto entry = Get<Factcheck>()) {
  3427. return entry->page.get();
  3428. }
  3429. return nullptr;
  3430. }
  3431. bool Message::toggleSelectionByHandlerClick(
  3432. const ClickHandlerPtr &handler) const {
  3433. if (_comments && _comments->link == handler) {
  3434. return true;
  3435. } else if (_viewButton && _viewButton->link() == handler) {
  3436. return true;
  3437. } else if (const auto media = this->media()) {
  3438. if (media->toggleSelectionByHandlerClick(handler)) {
  3439. return true;
  3440. }
  3441. }
  3442. return false;
  3443. }
  3444. bool Message::allowTextSelectionByHandler(
  3445. const ClickHandlerPtr &handler) const {
  3446. if (const auto media = this->media()) {
  3447. if (media->allowTextSelectionByHandler(handler)) {
  3448. return true;
  3449. }
  3450. }
  3451. if (dynamic_cast<Ui::Text::BlockquoteClickHandler*>(handler.get())) {
  3452. return true;
  3453. }
  3454. return false;
  3455. }
  3456. bool Message::hasFromName() const {
  3457. switch (context()) {
  3458. case Context::AdminLog:
  3459. return true;
  3460. case Context::History:
  3461. case Context::ChatPreview:
  3462. case Context::TTLViewer:
  3463. case Context::Pinned:
  3464. case Context::Replies:
  3465. case Context::SavedSublist:
  3466. case Context::ScheduledTopic: {
  3467. const auto item = data();
  3468. const auto peer = item->history()->peer;
  3469. if (hasOutLayout() && !item->from()->isChannel()) {
  3470. if (peer->isSelf()) {
  3471. if (const auto forwarded = item->Get<HistoryMessageForwarded>()) {
  3472. return forwarded->savedFromSender
  3473. && forwarded->savedFromSender->isChannel();
  3474. }
  3475. }
  3476. return false;
  3477. } else if (!peer->isUser()) {
  3478. if (const auto media = this->media()) {
  3479. return !media->hideFromName();
  3480. }
  3481. return true;
  3482. }
  3483. if (const auto forwarded = item->Get<HistoryMessageForwarded>()) {
  3484. if (forwarded->imported
  3485. && peer.get() == forwarded->originalSender) {
  3486. return false;
  3487. } else if (item->showForwardsFromSender(forwarded)) {
  3488. return true;
  3489. }
  3490. }
  3491. return false;
  3492. } break;
  3493. case Context::ContactPreview:
  3494. case Context::ShortcutMessages:
  3495. return false;
  3496. }
  3497. Unexpected("Context in Message::hasFromName.");
  3498. }
  3499. bool Message::displayFromName() const {
  3500. if (!hasFromName() || isAttachedToPrevious() || data()->isSponsored()) {
  3501. return false;
  3502. }
  3503. return !Has<PsaTooltipState>();
  3504. }
  3505. bool Message::displayForwardedFrom() const {
  3506. const auto item = data();
  3507. if (const auto forwarded = item->Get<HistoryMessageForwarded>()) {
  3508. if (forwarded->story) {
  3509. return true;
  3510. } else if (item->showForwardsFromSender(forwarded)) {
  3511. return forwarded->savedFromHiddenSenderInfo
  3512. || (forwarded->savedFromSender
  3513. && (forwarded->savedFromSender
  3514. != forwarded->originalSender));
  3515. }
  3516. if (const auto sender = item->discussionPostOriginalSender()) {
  3517. if (sender == forwarded->originalSender) {
  3518. return false;
  3519. }
  3520. }
  3521. const auto media = item->media();
  3522. return !media || !media->dropForwardedInfo();
  3523. }
  3524. return false;
  3525. }
  3526. bool Message::hasOutLayout() const {
  3527. const auto item = data();
  3528. if (item->history()->peer->isSelf()) {
  3529. if (const auto forwarded = item->Get<HistoryMessageForwarded>()) {
  3530. if (context() == Context::ShortcutMessages) {
  3531. return true;
  3532. }
  3533. return (context() == Context::SavedSublist)
  3534. && (!forwarded->forwardOfForward()
  3535. ? (forwarded->originalSender
  3536. && forwarded->originalSender->isSelf())
  3537. : ((forwarded->savedFromSender
  3538. && forwarded->savedFromSender->isSelf())
  3539. || forwarded->savedFromOutgoing));
  3540. }
  3541. return true;
  3542. } else if (const auto forwarded = item->Get<HistoryMessageForwarded>()) {
  3543. if (!forwarded->imported
  3544. || !forwarded->originalSender
  3545. || !forwarded->originalSender->isSelf()) {
  3546. if (item->showForwardsFromSender(forwarded)) {
  3547. return false;
  3548. }
  3549. }
  3550. }
  3551. return item->out() && !item->isPost();
  3552. }
  3553. bool Message::drawBubble() const {
  3554. const auto item = data();
  3555. if (isHidden()) {
  3556. return false;
  3557. } else if (logEntryOriginal()
  3558. || factcheckBlock()
  3559. || item->isFakeAboutView()) {
  3560. return true;
  3561. }
  3562. const auto media = this->media();
  3563. return media
  3564. ? (hasVisibleText() || media->needsBubble())
  3565. : !item->isEmpty();
  3566. }
  3567. bool Message::hasBubble() const {
  3568. return drawBubble();
  3569. }
  3570. TopicButton *Message::displayedTopicButton() const {
  3571. return _topicButton.get();
  3572. }
  3573. bool Message::unwrapped() const {
  3574. const auto item = data();
  3575. if (isHidden()) {
  3576. return true;
  3577. } else if (logEntryOriginal() || factcheckBlock()) {
  3578. return false;
  3579. }
  3580. const auto media = this->media();
  3581. return media
  3582. ? (!hasVisibleText() && media->unwrapped())
  3583. : item->isEmpty();
  3584. }
  3585. int Message::minWidthForMedia() const {
  3586. auto result = infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x());
  3587. const auto views = data()->Get<HistoryMessageViews>();
  3588. if (data()->repliesAreComments() && !views->replies.text.isEmpty()) {
  3589. const auto limit = HistoryMessageViews::kMaxRecentRepliers;
  3590. const auto single = st::historyCommentsUserpics.size;
  3591. const auto shift = st::historyCommentsUserpics.shift;
  3592. const auto added = single
  3593. + (limit - 1) * (single - shift)
  3594. + st::historyCommentsSkipLeft
  3595. + st::historyCommentsSkipRight
  3596. + st::historyCommentsSkipText
  3597. + st::historyCommentsOpenOutSelected.width()
  3598. + st::historyCommentsSkipRight
  3599. + st::mediaUnreadSkip
  3600. + st::mediaUnreadSize;
  3601. accumulate_max(result, added + views->replies.textWidth);
  3602. } else if (data()->externalReply()) {
  3603. const auto added = st::historyCommentsIn.width()
  3604. + st::historyCommentsSkipLeft
  3605. + st::historyCommentsSkipRight
  3606. + st::historyCommentsSkipText
  3607. + st::historyCommentsOpenOutSelected.width()
  3608. + st::historyCommentsSkipRight;
  3609. accumulate_max(result, added + st::semiboldFont->width(
  3610. tr::lng_replies_view_original(tr::now)));
  3611. }
  3612. return result;
  3613. }
  3614. bool Message::hasFastReply() const {
  3615. if (context() == Context::Replies) {
  3616. if (data()->isDiscussionPost()) {
  3617. return false;
  3618. }
  3619. } else if (context() != Context::History) {
  3620. return false;
  3621. }
  3622. const auto peer = data()->history()->peer;
  3623. return !hasOutLayout() && (peer->isChat() || peer->isMegagroup());
  3624. }
  3625. bool Message::hasFastForward() const {
  3626. if (context() != Context::History) {
  3627. return false;
  3628. }
  3629. const auto item = data();
  3630. const auto from = item->from()->asUser();
  3631. if (!from || !from->isBot() || !ShowFastForwardFor(from->username())) {
  3632. return false;
  3633. }
  3634. const auto peer = item->history()->peer;
  3635. if (!peer->isChat() && !peer->isMegagroup()) {
  3636. return false;
  3637. }
  3638. return !hasOutLayout();
  3639. }
  3640. bool Message::displayFastReply() const {
  3641. const auto canSendAnything = [&] {
  3642. const auto item = data();
  3643. const auto peer = item->history()->peer;
  3644. const auto topic = item->topic();
  3645. return topic
  3646. ? Data::CanSendAnything(topic)
  3647. : Data::CanSendAnything(peer);
  3648. };
  3649. return hasFastReply()
  3650. && data()->isRegular()
  3651. && canSendAnything()
  3652. && !delegate()->elementInSelectionMode(this).inSelectionMode;
  3653. }
  3654. bool Message::displayFastForward() const {
  3655. return hasFastForward()
  3656. && data()->allowsForward()
  3657. && !delegate()->elementInSelectionMode(this).inSelectionMode;
  3658. }
  3659. bool Message::displayRightActionComments() const {
  3660. return !isPinnedContext()
  3661. && (context() != Context::SavedSublist)
  3662. && data()->repliesAreComments()
  3663. && media()
  3664. && media()->isDisplayed()
  3665. && !hasBubble();
  3666. }
  3667. std::optional<QSize> Message::rightActionSize() const {
  3668. if (displayRightActionComments()) {
  3669. const auto views = data()->Get<HistoryMessageViews>();
  3670. Assert(views != nullptr);
  3671. return (views->repliesSmall.textWidth > 0)
  3672. ? QSize(
  3673. std::max(
  3674. st::historyFastShareSize,
  3675. 2 * st::historyFastShareBottom + views->repliesSmall.textWidth),
  3676. st::historyFastShareSize + st::historyFastShareBottom + st::semiboldFont->height)
  3677. : QSize(st::historyFastShareSize, st::historyFastShareSize);
  3678. }
  3679. return data()->isSponsored()
  3680. ? ((_rightAction && _rightAction->second)
  3681. ? QSize(st::historyFastCloseSize, st::historyFastCloseSize * 2)
  3682. : QSize(st::historyFastCloseSize, st::historyFastCloseSize))
  3683. : (displayFastShare() || displayGoToOriginal())
  3684. ? QSize(st::historyFastShareSize, st::historyFastShareSize)
  3685. : std::optional<QSize>();
  3686. }
  3687. bool Message::displayFastShare() const {
  3688. const auto item = data();
  3689. const auto peer = item->history()->peer;
  3690. if (!item->allowsForward()) {
  3691. return false;
  3692. } else if (peer->isChannel()) {
  3693. return !peer->isMegagroup();
  3694. } else if (const auto user = peer->asUser()) {
  3695. if (const auto forwarded = item->Get<HistoryMessageForwarded>()) {
  3696. return !item->out()
  3697. && forwarded->originalSender
  3698. && forwarded->originalSender->isBroadcast()
  3699. && !item->showForwardsFromSender(forwarded);
  3700. } else if (user->isBot() && !item->out()) {
  3701. if (const auto media = this->media()) {
  3702. return media->allowsFastShare();
  3703. }
  3704. }
  3705. }
  3706. return false;
  3707. }
  3708. bool Message::displayGoToOriginal() const {
  3709. if (isPinnedContext()) {
  3710. return !hasOutLayout();
  3711. }
  3712. const auto item = data();
  3713. if (const auto forwarded = item->Get<HistoryMessageForwarded>()) {
  3714. return forwarded->savedFromPeer
  3715. && forwarded->savedFromMsgId
  3716. && (!item->externalReply() || !hasBubble())
  3717. && (context() != Context::Replies);
  3718. }
  3719. return false;
  3720. }
  3721. void Message::drawRightAction(
  3722. Painter &p,
  3723. const PaintContext &context,
  3724. int left,
  3725. int top,
  3726. int outerWidth) const {
  3727. ensureRightAction();
  3728. const auto size = rightActionSize();
  3729. const auto st = context.st;
  3730. if (_rightAction->ripple) {
  3731. const auto &stm = context.messageStyle();
  3732. const auto colorOverride = &stm->msgWaveformInactive->c;
  3733. _rightAction->ripple->paint(
  3734. p,
  3735. left,
  3736. top,
  3737. size->width(),
  3738. colorOverride);
  3739. if (_rightAction->ripple->empty()) {
  3740. _rightAction->ripple.reset();
  3741. }
  3742. }
  3743. if (_rightAction->second && _rightAction->second->ripple) {
  3744. const auto &stm = context.messageStyle();
  3745. const auto colorOverride = &stm->msgWaveformInactive->c;
  3746. _rightAction->second->ripple->paint(
  3747. p,
  3748. left,
  3749. top + st::historyFastCloseSize,
  3750. size->width(),
  3751. colorOverride);
  3752. if (_rightAction->second->ripple->empty()) {
  3753. _rightAction->second->ripple.reset();
  3754. }
  3755. }
  3756. p.setPen(Qt::NoPen);
  3757. p.setBrush(st->msgServiceBg());
  3758. {
  3759. PainterHighQualityEnabler hq(p);
  3760. const auto rect = style::rtlrect(
  3761. left,
  3762. top,
  3763. size->width(),
  3764. size->height(),
  3765. outerWidth);
  3766. const auto usual = st::historyFastShareSize;
  3767. if (size->width() == size->height() && size->width() == usual) {
  3768. p.drawEllipse(rect);
  3769. } else {
  3770. p.drawRoundedRect(rect, usual / 2, usual / 2);
  3771. }
  3772. }
  3773. if (displayRightActionComments()) {
  3774. const auto &icon = st->historyFastCommentsIcon();
  3775. icon.paint(
  3776. p,
  3777. left + (size->width() - icon.width()) / 2,
  3778. top + (st::historyFastShareSize - icon.height()) / 2,
  3779. outerWidth);
  3780. const auto views = data()->Get<HistoryMessageViews>();
  3781. Assert(views != nullptr);
  3782. if (views->repliesSmall.textWidth > 0) {
  3783. p.setPen(st->msgServiceFg());
  3784. p.setFont(st::semiboldFont);
  3785. p.drawTextLeft(
  3786. left + (size->width() - views->repliesSmall.textWidth) / 2,
  3787. top + st::historyFastShareSize,
  3788. outerWidth,
  3789. views->repliesSmall.text,
  3790. views->repliesSmall.textWidth);
  3791. }
  3792. } else if (_rightAction->second) {
  3793. st->historyFastCloseIcon().paintInCenter(
  3794. p,
  3795. QRect(left, top, size->width(), size->width()));
  3796. st->historyFastMoreIcon().paintInCenter(
  3797. p,
  3798. QRect(left, size->width() + top, size->width(), size->width()));
  3799. } else {
  3800. const auto &icon = data()->isSponsored()
  3801. ? st->historyFastCloseIcon()
  3802. : (displayFastShare()
  3803. && !isPinnedContext()
  3804. && this->context() != Context::SavedSublist)
  3805. ? st->historyFastShareIcon()
  3806. : st->historyGoToOriginalIcon();
  3807. icon.paintInCenter(p, Rect(left, top, *size));
  3808. }
  3809. }
  3810. ClickHandlerPtr Message::rightActionLink(
  3811. std::optional<QPoint> pressPoint) const {
  3812. if (delegate()->elementInSelectionMode(this).progress > 0) {
  3813. return nullptr;
  3814. }
  3815. ensureRightAction();
  3816. if (!_rightAction->link) {
  3817. _rightAction->link = prepareRightActionLink();
  3818. }
  3819. if (pressPoint) {
  3820. _rightAction->lastPoint = *pressPoint;
  3821. }
  3822. if (_rightAction->second
  3823. && (_rightAction->lastPoint.y() > st::historyFastCloseSize)) {
  3824. return _rightAction->second->link;
  3825. }
  3826. return _rightAction->link;
  3827. }
  3828. void Message::ensureRightAction() const {
  3829. if (_rightAction) {
  3830. return;
  3831. }
  3832. Assert(rightActionSize().has_value());
  3833. _rightAction = std::make_unique<RightAction>();
  3834. }
  3835. ClickHandlerPtr Message::prepareRightActionLink() const {
  3836. if (data()->isSponsored()) {
  3837. return HideSponsoredClickHandler();
  3838. } else if (isPinnedContext()) {
  3839. return JumpToMessageClickHandler(data());
  3840. } else if ((context() != Context::SavedSublist)
  3841. && displayRightActionComments()) {
  3842. return createGoToCommentsLink();
  3843. }
  3844. const auto sessionId = data()->history()->session().uniqueId();
  3845. const auto owner = &data()->history()->owner();
  3846. const auto itemId = data()->fullId();
  3847. const auto forwarded = data()->Get<HistoryMessageForwarded>();
  3848. const auto savedFromPeer = forwarded
  3849. ? forwarded->savedFromPeer
  3850. : nullptr;
  3851. const auto savedFromMsgId = forwarded ? forwarded->savedFromMsgId : 0;
  3852. using Callback = FnMut<void(not_null<Window::SessionController*>)>;
  3853. const auto showByThread = std::make_shared<Callback>();
  3854. const auto showByThreadWeak = std::weak_ptr<Callback>(showByThread);
  3855. if (data()->externalReply()) {
  3856. *showByThread = [=, requested = 0](
  3857. not_null<Window::SessionController*> controller) mutable {
  3858. const auto original = savedFromPeer->owner().message(
  3859. savedFromPeer,
  3860. savedFromMsgId);
  3861. if (original && original->replyToTop()) {
  3862. controller->showRepliesForMessage(
  3863. original->history(),
  3864. original->replyToTop(),
  3865. original->id,
  3866. Window::SectionShow::Way::Forward);
  3867. } else if (!requested) {
  3868. const auto prequested = &requested;
  3869. requested = 1;
  3870. savedFromPeer->session().api().requestMessageData(
  3871. savedFromPeer,
  3872. savedFromMsgId,
  3873. [=, weak = base::make_weak(controller)] {
  3874. if (const auto strong = showByThreadWeak.lock()) {
  3875. if (const auto strongController = weak.get()) {
  3876. *prequested = 2;
  3877. (*strong)(strongController);
  3878. }
  3879. }
  3880. });
  3881. } else if (requested == 2) {
  3882. controller->showPeerHistory(
  3883. savedFromPeer,
  3884. Window::SectionShow::Way::Forward,
  3885. savedFromMsgId);
  3886. }
  3887. };
  3888. };
  3889. return std::make_shared<LambdaClickHandler>([=](
  3890. ClickContext context) {
  3891. const auto controller = ExtractController(context);
  3892. if (!controller || controller->session().uniqueId() != sessionId) {
  3893. return;
  3894. }
  3895. if (const auto item = owner->message(itemId)) {
  3896. if (*showByThread) {
  3897. (*showByThread)(controller);
  3898. } else if (savedFromPeer && savedFromMsgId) {
  3899. controller->showPeerHistory(
  3900. savedFromPeer,
  3901. Window::SectionShow::Way::Forward,
  3902. savedFromMsgId);
  3903. } else {
  3904. FastShareMessage(controller, item);
  3905. }
  3906. }
  3907. });
  3908. }
  3909. ClickHandlerPtr Message::fastReplyLink() const {
  3910. if (_fastReplyLink) {
  3911. return _fastReplyLink;
  3912. }
  3913. const auto itemId = data()->fullId();
  3914. const auto sessionId = data()->history()->session().uniqueId();
  3915. _fastReplyLink = hasFastForward()
  3916. ? std::make_shared<LambdaClickHandler>([=](ClickContext context) {
  3917. const auto controller = ExtractController(context);
  3918. const auto session = controller
  3919. ? &controller->session()
  3920. : nullptr;
  3921. if (!session || session->uniqueId() != sessionId) {
  3922. return;
  3923. } else if (const auto item = session->data().message(itemId)) {
  3924. FastShareMessage(controller, item);
  3925. }
  3926. })
  3927. : std::make_shared<LambdaClickHandler>(crl::guard(this, [=] {
  3928. delegate()->elementReplyTo({ itemId });
  3929. }));
  3930. return _fastReplyLink;
  3931. }
  3932. bool Message::isPinnedContext() const {
  3933. return context() == Context::Pinned;
  3934. }
  3935. void Message::updateMediaInBubbleState() {
  3936. const auto item = data();
  3937. const auto media = this->media();
  3938. if (media) {
  3939. media->updateNeedBubbleState();
  3940. }
  3941. const auto reactionsInBubble = (_reactions && embedReactionsInBubble());
  3942. auto mediaHasSomethingBelow = (_viewButton != nullptr)
  3943. || reactionsInBubble
  3944. || (invertMedia() && hasVisibleText());
  3945. auto mediaHasSomethingAbove = false;
  3946. auto getMediaHasSomethingAbove = [&] {
  3947. return displayFromName()
  3948. || displayedTopicButton()
  3949. || displayForwardedFrom()
  3950. || Has<Reply>()
  3951. || item->Has<HistoryMessageVia>();
  3952. };
  3953. const auto entry = logEntryOriginal();
  3954. const auto check = factcheckBlock();
  3955. if (check) {
  3956. mediaHasSomethingBelow = true;
  3957. mediaHasSomethingAbove = getMediaHasSomethingAbove();
  3958. auto checkState = (mediaHasSomethingAbove
  3959. || hasVisibleText()
  3960. || (media && media->isDisplayed()))
  3961. ? MediaInBubbleState::Bottom
  3962. : MediaInBubbleState::None;
  3963. check->setInBubbleState(checkState);
  3964. if (!media) {
  3965. check->setBubbleRounding(countBubbleRounding());
  3966. return;
  3967. }
  3968. } else if (entry) {
  3969. mediaHasSomethingBelow = true;
  3970. mediaHasSomethingAbove = getMediaHasSomethingAbove();
  3971. auto entryState = (mediaHasSomethingAbove
  3972. || hasVisibleText()
  3973. || (media && media->isDisplayed()))
  3974. ? MediaInBubbleState::Bottom
  3975. : MediaInBubbleState::None;
  3976. entry->setInBubbleState(entryState);
  3977. if (!media) {
  3978. entry->setBubbleRounding(countBubbleRounding());
  3979. return;
  3980. }
  3981. } else if (!media) {
  3982. return;
  3983. }
  3984. const auto guard = gsl::finally([&] {
  3985. media->setBubbleRounding(countBubbleRounding());
  3986. });
  3987. if (!drawBubble()) {
  3988. media->setInBubbleState(MediaInBubbleState::None);
  3989. return;
  3990. }
  3991. if (!check && !entry) {
  3992. mediaHasSomethingAbove = getMediaHasSomethingAbove();
  3993. }
  3994. if (!invertMedia() && hasVisibleText()) {
  3995. mediaHasSomethingAbove = true;
  3996. }
  3997. const auto state = [&] {
  3998. if (mediaHasSomethingAbove) {
  3999. if (mediaHasSomethingBelow) {
  4000. return MediaInBubbleState::Middle;
  4001. }
  4002. return MediaInBubbleState::Bottom;
  4003. } else if (mediaHasSomethingBelow) {
  4004. return MediaInBubbleState::Top;
  4005. }
  4006. return MediaInBubbleState::None;
  4007. }();
  4008. media->setInBubbleState(state);
  4009. }
  4010. void Message::fromNameUpdated(int width) const {
  4011. const auto item = data();
  4012. const auto replyWidth = hasFastForward()
  4013. ? st::msgFont->width(FastForwardText())
  4014. : hasFastReply()
  4015. ? st::msgFont->width(FastReplyText())
  4016. : 0;
  4017. if (!_rightBadge.isEmpty()) {
  4018. const auto badgeWidth = _rightBadge.maxWidth();
  4019. width -= st::msgPadding.right() + std::max(badgeWidth, replyWidth);
  4020. } else if (replyWidth) {
  4021. width -= st::msgPadding.right() + replyWidth;
  4022. }
  4023. const auto from = item->displayFrom();
  4024. validateFromNameText(from);
  4025. if (const auto via = item->Get<HistoryMessageVia>()) {
  4026. if (!displayForwardedFrom()) {
  4027. const auto nameText = [&]() -> const Ui::Text::String * {
  4028. if (from) {
  4029. return &_fromName;
  4030. } else if (const auto info = item->originalHiddenSenderInfo()) {
  4031. return &info->nameText();
  4032. } else {
  4033. Unexpected("Corrupted forwarded information in message.");
  4034. }
  4035. }();
  4036. via->resize(width
  4037. - st::msgPadding.left()
  4038. - st::msgPadding.right()
  4039. - nameText->maxWidth()
  4040. + (_fromNameStatus
  4041. ? (st::dialogsPremiumIcon.icon.width()
  4042. + st::msgServiceFont->spacew)
  4043. : 0)
  4044. - st::msgServiceFont->spacew);
  4045. }
  4046. }
  4047. }
  4048. TextSelection Message::skipTextSelection(TextSelection selection) const {
  4049. if (selection.from == 0xFFFF || !hasVisibleText()) {
  4050. return selection;
  4051. }
  4052. return HistoryView::UnshiftItemSelection(selection, text());
  4053. }
  4054. TextSelection Message::unskipTextSelection(TextSelection selection) const {
  4055. if (!hasVisibleText()) {
  4056. return selection;
  4057. }
  4058. return HistoryView::ShiftItemSelection(selection, text());
  4059. }
  4060. QRect Message::innerGeometry() const {
  4061. auto result = countGeometry();
  4062. if (!hasOutLayout()) {
  4063. const auto w = std::max(
  4064. (media() ? media()->resolveCustomInfoRightBottom().x() : 0),
  4065. result.width());
  4066. result.setWidth(std::min(
  4067. w + rightActionSize().value_or(QSize(0, 0)).width() * 2,
  4068. width()));
  4069. }
  4070. if (hasBubble()) {
  4071. const auto cut = [&](int amount) {
  4072. amount = std::min(amount, result.height());
  4073. result.setTop(result.top() + amount);
  4074. };
  4075. cut(st::msgPadding.top() + st::mediaInBubbleSkip);
  4076. if (displayFromName()) {
  4077. // See paintFromName().
  4078. cut(st::msgNameFont->height);
  4079. }
  4080. if (displayedTopicButton()) {
  4081. cut(st::topicButtonSkip
  4082. + st::topicButtonPadding.top()
  4083. + st::msgNameFont->height
  4084. + st::topicButtonPadding.bottom()
  4085. + st::topicButtonSkip);
  4086. }
  4087. if (!displayFromName() && !displayForwardedFrom()) {
  4088. // See paintViaBotIdInfo().
  4089. if (data()->Has<HistoryMessageVia>()) {
  4090. cut(st::msgServiceNameFont->height);
  4091. }
  4092. }
  4093. // Skip displayForwardedFrom() until there are no animations for it.
  4094. if (const auto reply = Get<Reply>()) {
  4095. // See paintReplyInfo().
  4096. cut(reply->height());
  4097. }
  4098. }
  4099. return result;
  4100. }
  4101. QRect Message::countGeometry() const {
  4102. const auto item = data();
  4103. const auto centeredView = item->isFakeAboutView()
  4104. || (context() == Context::Replies && item->isDiscussionPost());
  4105. const auto media = this->media();
  4106. const auto mediaWidth = (media && media->isDisplayed())
  4107. ? media->width()
  4108. : width();
  4109. const auto outbg = hasOutLayout();
  4110. const auto availableWidth = width()
  4111. - st::msgMargin.left()
  4112. - (centeredView ? st::msgMargin.left() : st::msgMargin.right());
  4113. auto contentLeft = hasRightLayout()
  4114. ? st::msgMargin.right()
  4115. : st::msgMargin.left();
  4116. auto contentWidth = availableWidth;
  4117. if (hasFromPhoto()) {
  4118. contentLeft += st::msgPhotoSkip;
  4119. if (const auto size = rightActionSize()) {
  4120. contentWidth -= size->width() + (st::msgPhotoSkip - st::historyFastShareSize);
  4121. }
  4122. //} else if (!Adaptive::Wide() && !out() && !fromChannel() && st::msgPhotoSkip - (hmaxwidth - hwidth) > 0) {
  4123. // contentLeft += st::msgPhotoSkip - (hmaxwidth - hwidth);
  4124. }
  4125. accumulate_min(contentWidth, maxWidth());
  4126. accumulate_min(contentWidth, int(_bubbleWidthLimit));
  4127. if (mediaWidth < contentWidth) {
  4128. const auto textualWidth = textualMaxWidth();
  4129. if (mediaWidth < textualWidth
  4130. && (!media || !media->enforceBubbleWidth())) {
  4131. accumulate_min(contentWidth, textualWidth);
  4132. } else {
  4133. contentWidth = mediaWidth;
  4134. }
  4135. }
  4136. if (contentWidth < availableWidth && !delegate()->elementIsChatWide()) {
  4137. if (outbg) {
  4138. contentLeft += availableWidth - contentWidth;
  4139. } else if (centeredView) {
  4140. contentLeft += (availableWidth - contentWidth) / 2;
  4141. }
  4142. } else if (contentWidth < availableWidth && centeredView) {
  4143. contentLeft += std::max(
  4144. ((st::msgMaxWidth + 2 * st::msgPhotoSkip) - contentWidth) / 2,
  4145. 0);
  4146. }
  4147. const auto contentTop = marginTop();
  4148. return QRect(
  4149. contentLeft,
  4150. contentTop,
  4151. contentWidth,
  4152. height() - contentTop - marginBottom());
  4153. }
  4154. Ui::BubbleRounding Message::countMessageRounding() const {
  4155. const auto smallTop = isBubbleAttachedToPrevious();
  4156. const auto smallBottom = isBubbleAttachedToNext();
  4157. const auto media = smallBottom ? nullptr : this->media();
  4158. const auto item = data();
  4159. const auto keyboard = item->inlineReplyKeyboard();
  4160. const auto skipTail = smallBottom
  4161. || (media && media->skipBubbleTail())
  4162. || (keyboard != nullptr)
  4163. || item->isFakeAboutView()
  4164. || (context() == Context::Replies && item->isDiscussionPost());
  4165. const auto right = hasRightLayout();
  4166. using Corner = Ui::BubbleCornerRounding;
  4167. return Ui::BubbleRounding{
  4168. .topLeft = (smallTop && !right) ? Corner::Small : Corner::Large,
  4169. .topRight = (smallTop && right) ? Corner::Small : Corner::Large,
  4170. .bottomLeft = ((smallBottom && !right)
  4171. ? Corner::Small
  4172. : (!skipTail && !right)
  4173. ? Corner::Tail
  4174. : Corner::Large),
  4175. .bottomRight = ((smallBottom && right)
  4176. ? Corner::Small
  4177. : (!skipTail && right)
  4178. ? Corner::Tail
  4179. : Corner::Large),
  4180. };
  4181. }
  4182. Ui::BubbleRounding Message::countBubbleRounding(
  4183. Ui::BubbleRounding messageRounding) const {
  4184. if (const auto keyboard = data()->inlineReplyKeyboard()) {
  4185. messageRounding.bottomLeft
  4186. = messageRounding.bottomRight
  4187. = Ui::BubbleCornerRounding::Small;
  4188. }
  4189. return messageRounding;
  4190. }
  4191. Ui::BubbleRounding Message::countBubbleRounding() const {
  4192. return countBubbleRounding(countMessageRounding());
  4193. }
  4194. int Message::resizeContentGetHeight(int newWidth) {
  4195. if (isHidden()) {
  4196. return marginTop() + marginBottom();
  4197. } else if (newWidth < st::msgMinWidth) {
  4198. return height();
  4199. }
  4200. const auto item = data();
  4201. const auto postShowingAuthor = item->isPostShowingAuthor() ? 1 : 0;
  4202. if (_postShowingAuthor != postShowingAuthor) {
  4203. _postShowingAuthor = postShowingAuthor;
  4204. _fromNameVersion = -1;
  4205. previousInBlocksChanged();
  4206. const auto size = _bottomInfo.currentSize();
  4207. _bottomInfo.update(BottomInfoDataFromMessage(this), newWidth);
  4208. if (size != _bottomInfo.currentSize()) {
  4209. // maxWidth may have changed, full recount required.
  4210. setPendingResize();
  4211. return resizeGetHeight(newWidth);
  4212. }
  4213. }
  4214. auto newHeight = minHeight();
  4215. if (const auto service = Get<ServicePreMessage>()) {
  4216. service->resizeToWidth(newWidth, delegate()->elementIsChatWide());
  4217. }
  4218. const auto botTop = item->isFakeAboutView()
  4219. ? Get<FakeBotAboutTop>()
  4220. : nullptr;
  4221. const auto media = this->media();
  4222. const auto mediaDisplayed = media ? media->isDisplayed() : false;
  4223. const auto bubble = drawBubble();
  4224. item->resolveDependent();
  4225. // This code duplicates countGeometry() but also resizes media.
  4226. const auto centeredView = item->isFakeAboutView()
  4227. || (context() == Context::Replies && item->isDiscussionPost());
  4228. auto contentWidth = newWidth
  4229. - st::msgMargin.left()
  4230. - (centeredView ? st::msgMargin.left() : st::msgMargin.right());
  4231. if (hasFromPhoto()) {
  4232. if (const auto size = rightActionSize()) {
  4233. contentWidth -= size->width() + (st::msgPhotoSkip - st::historyFastShareSize);
  4234. }
  4235. }
  4236. accumulate_min(contentWidth, maxWidth());
  4237. _bubbleWidthLimit = std::max(st::msgMaxWidth, monospaceMaxWidth());
  4238. accumulate_min(contentWidth, int(_bubbleWidthLimit));
  4239. if (mediaDisplayed) {
  4240. media->resizeGetHeight(contentWidth);
  4241. if (media->width() < contentWidth) {
  4242. const auto textualWidth = textualMaxWidth();
  4243. if (media->width() < textualWidth
  4244. && !media->enforceBubbleWidth()) {
  4245. accumulate_min(contentWidth, textualWidth);
  4246. } else {
  4247. contentWidth = media->width();
  4248. }
  4249. }
  4250. }
  4251. const auto textWidth = qMax(contentWidth - st::msgPadding.left() - st::msgPadding.right(), 1);
  4252. const auto reactionsInBubble = _reactions && embedReactionsInBubble();
  4253. const auto bottomInfoHeight = _bottomInfo.resizeGetHeight(
  4254. std::min(
  4255. _bottomInfo.optimalSize().width(),
  4256. textWidth - 2 * st::msgDateDelta.x()));
  4257. if (bubble) {
  4258. auto reply = Get<Reply>();
  4259. auto via = item->Get<HistoryMessageVia>();
  4260. const auto check = factcheckBlock();
  4261. const auto entry = logEntryOriginal();
  4262. // Entry page is always a bubble bottom.
  4263. auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || check || (entry/* && entry->isBubbleBottom()*/);
  4264. auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop());
  4265. if (reactionsInBubble) {
  4266. _reactions->resizeGetHeight(textWidth);
  4267. }
  4268. if (contentWidth == maxWidth()) {
  4269. if (mediaDisplayed) {
  4270. if (check) {
  4271. newHeight += check->resizeGetHeight(contentWidth) + st::mediaInBubbleSkip;
  4272. }
  4273. if (entry) {
  4274. newHeight += entry->resizeGetHeight(contentWidth);
  4275. }
  4276. } else {
  4277. if (check) {
  4278. check->resizeGetHeight(contentWidth);
  4279. }
  4280. if (entry) {
  4281. // In case of text-only message it is counted in minHeight already.
  4282. entry->resizeGetHeight(contentWidth);
  4283. }
  4284. }
  4285. } else {
  4286. const auto withVisibleText = hasVisibleText();
  4287. newHeight = 0;
  4288. if (withVisibleText) {
  4289. if (botTop) {
  4290. newHeight += botTop->height;
  4291. }
  4292. newHeight += textHeightFor(textWidth);
  4293. }
  4294. if (!mediaOnBottom && (!_viewButton || !reactionsInBubble)) {
  4295. newHeight += st::msgPadding.bottom();
  4296. if (mediaDisplayed) {
  4297. newHeight += st::mediaInBubbleSkip;
  4298. }
  4299. }
  4300. if (!mediaOnTop) {
  4301. newHeight += st::msgPadding.top();
  4302. if (mediaDisplayed) newHeight += st::mediaInBubbleSkip;
  4303. if (entry) newHeight += st::mediaInBubbleSkip;
  4304. }
  4305. if (mediaDisplayed) {
  4306. newHeight += media->height();
  4307. }
  4308. if (check) {
  4309. newHeight += check->resizeGetHeight(contentWidth) + st::mediaInBubbleSkip;
  4310. }
  4311. if (entry) {
  4312. newHeight += entry->resizeGetHeight(contentWidth);
  4313. }
  4314. if (reactionsInBubble) {
  4315. if (mediaDisplayed
  4316. && !media->additionalInfoString().isEmpty()) {
  4317. // In round videos in a web page status text is painted
  4318. // in the bottom left corner, reactions should be below.
  4319. newHeight += st::msgDateFont->height;
  4320. } else {
  4321. newHeight += st::mediaInBubbleSkip;
  4322. }
  4323. newHeight += _reactions->height();
  4324. }
  4325. }
  4326. if (displayFromName()) {
  4327. fromNameUpdated(contentWidth);
  4328. newHeight += st::msgNameFont->height;
  4329. } else if (via && !displayForwardedFrom()) {
  4330. via->resize(contentWidth - st::msgPadding.left() - st::msgPadding.right());
  4331. newHeight += st::msgNameFont->height;
  4332. }
  4333. if (displayedTopicButton()) {
  4334. newHeight += st::topicButtonSkip
  4335. + st::topicButtonPadding.top()
  4336. + st::msgNameFont->height
  4337. + st::topicButtonPadding.bottom()
  4338. + st::topicButtonSkip;
  4339. }
  4340. if (displayForwardedFrom()) {
  4341. const auto forwarded = item->Get<HistoryMessageForwarded>();
  4342. const auto skip1 = forwarded->psaType.isEmpty()
  4343. ? 0
  4344. : st::historyPsaIconSkip1;
  4345. const auto fwdheight = ((forwarded->text.maxWidth() > (contentWidth - st::msgPadding.left() - st::msgPadding.right() - skip1)) ? 2 : 1) * st::semiboldFont->height;
  4346. newHeight += fwdheight;
  4347. }
  4348. if (reply) {
  4349. newHeight += reply->resizeToWidth(contentWidth
  4350. - st::msgPadding.left()
  4351. - st::msgPadding.right());
  4352. }
  4353. if (needInfoDisplay()) {
  4354. newHeight += (bottomInfoHeight - st::msgDateFont->height);
  4355. }
  4356. if (item->repliesAreComments() || item->externalReply()) {
  4357. newHeight += st::historyCommentsButtonHeight;
  4358. } else if (_comments) {
  4359. _comments = nullptr;
  4360. checkHeavyPart();
  4361. }
  4362. newHeight += viewButtonHeight();
  4363. } else if (mediaDisplayed) {
  4364. newHeight = media->height();
  4365. } else {
  4366. newHeight = 0;
  4367. }
  4368. if (_reactions && !reactionsInBubble) {
  4369. const auto reactionsWidth = (!bubble && mediaDisplayed)
  4370. ? media->contentRectForReactions().width()
  4371. : contentWidth;
  4372. newHeight += st::mediaInBubbleSkip
  4373. + _reactions->resizeGetHeight(reactionsWidth);
  4374. if (hasRightLayout()) {
  4375. _reactions->flipToRight();
  4376. }
  4377. }
  4378. if (const auto keyboard = item->inlineReplyKeyboard()) {
  4379. const auto keyboardHeight = st::msgBotKbButton.margin + keyboard->naturalHeight();
  4380. newHeight += keyboardHeight;
  4381. keyboard->resize(contentWidth, keyboardHeight - st::msgBotKbButton.margin);
  4382. }
  4383. newHeight += marginTop() + marginBottom();
  4384. return newHeight;
  4385. }
  4386. bool Message::needInfoDisplay() const {
  4387. const auto media = this->media();
  4388. const auto mediaDisplayed = media ? media->isDisplayed() : false;
  4389. const auto check = factcheckBlock();
  4390. const auto entry = logEntryOriginal();
  4391. return entry
  4392. ? !entry->customInfoLayout()
  4393. : check
  4394. ? !check->customInfoLayout()
  4395. : ((mediaDisplayed && media->isBubbleBottom())
  4396. ? !media->customInfoLayout()
  4397. : true);
  4398. }
  4399. bool Message::invertMedia() const {
  4400. return _invertMedia;
  4401. }
  4402. bool Message::hasVisibleText() const {
  4403. const auto textItem = this->textItem();
  4404. if (!textItem) {
  4405. return false;
  4406. } else if (textItem->emptyText()) {
  4407. if (const auto media = textItem->media()) {
  4408. return media->storyExpired();
  4409. }
  4410. return false;
  4411. }
  4412. const auto media = this->media();
  4413. return !media || !media->hideMessageText();
  4414. }
  4415. int Message::visibleTextLength() const {
  4416. return hasVisibleText() ? text().length() : 0;
  4417. }
  4418. int Message::visibleMediaTextLength() const {
  4419. const auto media = this->media();
  4420. return (media && media->isDisplayed())
  4421. ? media->fullSelectionLength()
  4422. : 0;
  4423. }
  4424. QSize Message::performCountCurrentSize(int newWidth) {
  4425. const auto newHeight = resizeContentGetHeight(newWidth);
  4426. return { newWidth, newHeight };
  4427. }
  4428. void Message::refreshInfoSkipBlock(HistoryItem *textItem) {
  4429. const auto media = this->media();
  4430. const auto hasTextSkipBlock = [&] {
  4431. if (!textItem || textItem->_text.empty()) {
  4432. if (const auto media = data()->media()) {
  4433. return media->storyExpired();
  4434. }
  4435. return false;
  4436. } else if (factcheckBlock()
  4437. || data()->Has<HistoryMessageLogEntryOriginal>()) {
  4438. return false;
  4439. } else if (media && media->isDisplayed() && !_invertMedia) {
  4440. return false;
  4441. } else if (_reactions) {
  4442. return false;
  4443. }
  4444. return true;
  4445. }();
  4446. const auto skipWidth = skipBlockWidth();
  4447. const auto skipHeight = skipBlockHeight();
  4448. if (_reactions) {
  4449. if (needInfoDisplay()) {
  4450. _reactions->updateSkipBlock(skipWidth, skipHeight);
  4451. } else {
  4452. _reactions->removeSkipBlock();
  4453. }
  4454. }
  4455. validateTextSkipBlock(hasTextSkipBlock, skipWidth, skipHeight);
  4456. }
  4457. TimeId Message::displayedEditDate() const {
  4458. const auto item = data();
  4459. const auto overrided = media() && media()->overrideEditedDate();
  4460. if (item->hideEditedBadge() && !overrided) {
  4461. return TimeId(0);
  4462. } else if (const auto edited = displayedEditBadge()) {
  4463. return edited->date;
  4464. }
  4465. return TimeId(0);
  4466. }
  4467. HistoryMessageEdited *Message::displayedEditBadge() {
  4468. if (const auto media = this->media()) {
  4469. if (media->overrideEditedDate()) {
  4470. return media->displayedEditBadge();
  4471. }
  4472. }
  4473. return data()->Get<HistoryMessageEdited>();
  4474. }
  4475. const HistoryMessageEdited *Message::displayedEditBadge() const {
  4476. if (const auto media = this->media()) {
  4477. if (media->overrideEditedDate()) {
  4478. return media->displayedEditBadge();
  4479. }
  4480. }
  4481. return data()->Get<HistoryMessageEdited>();
  4482. }
  4483. } // namespace HistoryView