media_view_overlay_widget.cpp 184 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737
  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 "media/view/media_view_overlay_widget.h"
  8. #include "apiwrap.h"
  9. #include "api/api_attached_stickers.h"
  10. #include "api/api_peer_photo.h"
  11. #include "base/qt/qt_common_adapters.h"
  12. #include "base/timer_rpl.h"
  13. #include "lang/lang_keys.h"
  14. #include "menu/menu_sponsored.h"
  15. #include "boxes/premium_preview_box.h"
  16. #include "core/application.h"
  17. #include "core/click_handler_types.h"
  18. #include "core/file_utilities.h"
  19. #include "core/mime_type.h"
  20. #include "core/ui_integration.h"
  21. #include "core/crash_reports.h"
  22. #include "core/sandbox.h"
  23. #include "core/shortcuts.h"
  24. #include "ui/widgets/menu/menu_add_action_callback.h"
  25. #include "ui/widgets/menu/menu_add_action_callback_factory.h"
  26. #include "ui/widgets/dropdown_menu.h"
  27. #include "ui/widgets/popup_menu.h"
  28. #include "ui/widgets/buttons.h"
  29. #include "ui/layers/layer_manager.h"
  30. #include "ui/text/text_utilities.h"
  31. #include "ui/platform/ui_platform_window_title.h"
  32. #include "ui/toast/toast.h"
  33. #include "ui/text/format_values.h"
  34. #include "ui/item_text_options.h"
  35. #include "ui/painter.h"
  36. #include "ui/rect.h"
  37. #include "ui/power_saving.h"
  38. #include "ui/cached_round_corners.h"
  39. #include "ui/gl/gl_window.h"
  40. #include "ui/boxes/confirm_box.h"
  41. #include "ui/ui_utility.h"
  42. #include "info/info_memento.h"
  43. #include "info/info_controller.h"
  44. #include "info/statistics/info_statistics_widget.h"
  45. #include "boxes/delete_messages_box.h"
  46. #include "boxes/report_messages_box.h"
  47. #include "media/audio/media_audio.h"
  48. #include "media/view/media_view_group_thumbs.h"
  49. #include "media/view/media_view_pip.h"
  50. #include "media/view/media_view_overlay_raster.h"
  51. #include "media/view/media_view_overlay_opengl.h"
  52. #include "media/stories/media_stories_share.h"
  53. #include "media/stories/media_stories_view.h"
  54. #include "media/streaming/media_streaming_document.h"
  55. #include "media/streaming/media_streaming_player.h"
  56. #include "media/player/media_player_instance.h"
  57. #include "history/history.h"
  58. #include "history/history_item_helpers.h"
  59. #include "history/view/media/history_view_media.h"
  60. #include "history/view/reactions/history_view_reactions_selector.h"
  61. #include "data/components/sponsored_messages.h"
  62. #include "data/data_session.h"
  63. #include "data/data_changes.h"
  64. #include "data/data_channel.h"
  65. #include "data/data_chat.h"
  66. #include "data/data_user.h"
  67. #include "data/data_media_rotation.h"
  68. #include "data/data_photo_media.h"
  69. #include "data/data_document_media.h"
  70. #include "data/data_document_resolver.h"
  71. #include "data/data_file_click_handler.h"
  72. #include "data/data_download_manager.h"
  73. #include "window/themes/window_theme_preview.h"
  74. #include "window/window_peer_menu.h"
  75. #include "window/window_controller.h"
  76. #include "base/platform/base_platform_info.h"
  77. #include "base/power_save_blocker.h"
  78. #include "base/random.h"
  79. #include "base/unixtime.h"
  80. #include "base/qt_signal_producer.h"
  81. #include "base/event_filter.h"
  82. #include "main/main_account.h"
  83. #include "main/main_domain.h" // Domain::activeSessionValue.
  84. #include "main/main_session.h"
  85. #include "main/main_session_settings.h"
  86. #include "layout/layout_document_generic_preview.h"
  87. #include "platform/platform_overlay_widget.h"
  88. #include "storage/file_download.h"
  89. #include "storage/storage_account.h"
  90. #include "calls/calls_instance.h"
  91. #include "styles/style_media_view.h"
  92. #include "styles/style_calls.h"
  93. #include "styles/style_chat.h"
  94. #include "styles/style_menu_icons.h"
  95. #ifdef Q_OS_MAC
  96. #include "platform/mac/touchbar/mac_touchbar_media_view.h"
  97. #endif // Q_OS_MAC
  98. #include <QtWidgets/QApplication>
  99. #include <QtCore/QBuffer>
  100. #include <QtGui/QGuiApplication>
  101. #include <QtGui/QWindow>
  102. #include <QtGui/QScreen>
  103. #include <kurlmimedata.h>
  104. namespace Media {
  105. namespace View {
  106. namespace {
  107. constexpr auto kPreloadCount = 3;
  108. constexpr auto kMaxZoomLevel = 7; // x8
  109. constexpr auto kZoomToScreenLevel = 1024;
  110. constexpr auto kOverlayLoaderPriority = 2;
  111. constexpr auto kSeekTimeMs = 5 * crl::time(1000);
  112. // macOS OpenGL renderer fails to render larger texture
  113. // even though it reports that max texture size is 16384.
  114. constexpr auto kMaxDisplayImageSize = 4096;
  115. // Preload X message ids before and after current.
  116. constexpr auto kIdsLimit = 48;
  117. // Preload next messages if we went further from current than that.
  118. constexpr auto kIdsPreloadAfter = 28;
  119. constexpr auto kLeftSiblingTextureIndex = 1;
  120. constexpr auto kRightSiblingTextureIndex = 2;
  121. constexpr auto kStoriesControlsOpacity = 1.;
  122. constexpr auto kStorySavePromoDuration = 3 * crl::time(1000);
  123. class PipDelegate final : public Pip::Delegate {
  124. public:
  125. PipDelegate(QWidget *parent, not_null<Main::Session*> session);
  126. void pipSaveGeometry(QByteArray geometry) override;
  127. QByteArray pipLoadGeometry() override;
  128. float64 pipPlaybackSpeed() override;
  129. QWidget *pipParentWidget() override;
  130. private:
  131. QWidget *_parent = nullptr;
  132. not_null<Main::Session*> _session;
  133. };
  134. [[nodiscard]] Core::WindowPosition DefaultPosition() {
  135. const auto moncrc = [&] {
  136. if (const auto active = Core::App().activeWindow()) {
  137. const auto widget = active->widget();
  138. if (const auto screen = widget->screen()) {
  139. return Platform::ScreenNameChecksum(screen->name());
  140. }
  141. }
  142. return Core::App().settings().windowPosition().moncrc;
  143. }();
  144. return {
  145. .moncrc = moncrc,
  146. .scale = cScale(),
  147. .x = st::mediaviewDefaultLeft,
  148. .y = st::mediaviewDefaultTop,
  149. .w = st::mediaviewDefaultWidth,
  150. .h = st::mediaviewDefaultHeight,
  151. };
  152. }
  153. PipDelegate::PipDelegate(QWidget *parent, not_null<Main::Session*> session)
  154. : _parent(parent)
  155. , _session(session) {
  156. }
  157. void PipDelegate::pipSaveGeometry(QByteArray geometry) {
  158. Core::App().settings().setVideoPipGeometry(geometry);
  159. Core::App().saveSettingsDelayed();
  160. }
  161. QByteArray PipDelegate::pipLoadGeometry() {
  162. return Core::App().settings().videoPipGeometry();
  163. }
  164. float64 PipDelegate::pipPlaybackSpeed() {
  165. return Core::App().settings().videoPlaybackSpeed();
  166. }
  167. QWidget *PipDelegate::pipParentWidget() {
  168. return _parent;
  169. }
  170. [[nodiscard]] Images::Options VideoThumbOptions(DocumentData *document) {
  171. const auto result = Images::Option::Blur;
  172. return (document && document->isVideoMessage())
  173. ? (result | Images::Option::RoundCircle)
  174. : result;
  175. }
  176. [[nodiscard]] QImage PrepareStaticImage(Images::ReadArgs &&args) {
  177. auto read = Images::Read(std::move(args));
  178. return (read.image.width() > kMaxDisplayImageSize
  179. || read.image.height() > kMaxDisplayImageSize)
  180. ? read.image.scaled(
  181. kMaxDisplayImageSize,
  182. kMaxDisplayImageSize,
  183. Qt::KeepAspectRatio,
  184. Qt::SmoothTransformation)
  185. : read.image;
  186. }
  187. [[nodiscard]] bool IsSemitransparent(const QImage &image) {
  188. if (image.isNull()) {
  189. return true;
  190. } else if (!image.hasAlphaChannel()) {
  191. return false;
  192. }
  193. Assert(image.format() == QImage::Format_ARGB32_Premultiplied);
  194. constexpr auto kAlphaMask = 0xFF000000;
  195. auto ints = reinterpret_cast<const uint32*>(image.bits());
  196. const auto add = (image.bytesPerLine() / 4) - image.width();
  197. for (auto y = 0; y != image.height(); ++y) {
  198. for (auto till = ints + image.width(); ints != till; ++ints) {
  199. if ((*ints & kAlphaMask) != kAlphaMask) {
  200. return true;
  201. }
  202. }
  203. ints += add;
  204. }
  205. return false;
  206. }
  207. } // namespace
  208. class OverlayWidget::SponsoredButton : public Ui::RippleButton {
  209. public:
  210. SponsoredButton(QWidget *parent)
  211. : Ui::RippleButton(parent, st::mediaviewSponsoredButton.ripple) {
  212. }
  213. void setText(QString text) {
  214. _text = Ui::Text::String(
  215. st::mediaviewSponsoredButton.style,
  216. std::move(text),
  217. kDefaultTextOptions,
  218. width());
  219. resize(width(), _text.minHeight() * 2);
  220. }
  221. void setOpacity(float opacity) {
  222. _opacity = opacity;
  223. }
  224. protected:
  225. void paintEvent(QPaintEvent *e) override {
  226. auto p = QPainter(this);
  227. const auto &st = st::mediaviewSponsoredButton;
  228. p.setOpacity(_opacity);
  229. const auto over = Ui::AbstractButton::isOver();
  230. const auto down = Ui::AbstractButton::isDown();
  231. {
  232. auto hq = PainterHighQualityEnabler(p);
  233. p.setPen(Qt::NoPen);
  234. p.setBrush((over || down) ? st.textBgOver : st.textBg);
  235. p.drawRoundedRect(
  236. rect(),
  237. st::mediaviewCaptionRadius,
  238. st::mediaviewCaptionRadius);
  239. }
  240. Ui::RippleButton::paintRipple(p, 0, 0);
  241. p.setPen(st.textFg);
  242. p.setBrush(Qt::NoBrush);
  243. _text.draw(p, {
  244. .position = QPoint(
  245. (width() - _text.maxWidth()) / 2,
  246. (height() - _text.minHeight()) / 2),
  247. .outerWidth = width(),
  248. .availableWidth = width(),
  249. });
  250. }
  251. QImage prepareRippleMask() const override {
  252. return Ui::RippleAnimation::RoundRectMask(
  253. size(),
  254. st::mediaviewCaptionRadius);
  255. }
  256. QPoint prepareRippleStartPosition() const override {
  257. return mapFromGlobal(QCursor::pos())
  258. - rect::m::pos::tl(st::mediaviewSponsoredButton.padding);
  259. }
  260. private:
  261. Ui::Text::String _text;
  262. float64 _opacity = 1.;
  263. };
  264. struct OverlayWidget::SharedMedia {
  265. SharedMedia(SharedMediaKey key) : key(key) {
  266. }
  267. SharedMediaKey key;
  268. rpl::lifetime lifetime;
  269. };
  270. struct OverlayWidget::UserPhotos {
  271. UserPhotos(UserPhotosKey key) : key(key) {
  272. }
  273. UserPhotosKey key;
  274. rpl::lifetime lifetime;
  275. };
  276. struct OverlayWidget::Collage {
  277. Collage(CollageKey key) : key(key) {
  278. }
  279. CollageKey key;
  280. };
  281. struct OverlayWidget::Streamed {
  282. Streamed(
  283. not_null<DocumentData*> quality,
  284. not_null<DocumentData*> original,
  285. HistoryItem *context,
  286. Data::FileOrigin origin,
  287. Fn<void()> waitingCallback);
  288. Streamed(
  289. not_null<PhotoData*> photo,
  290. Data::FileOrigin origin,
  291. Fn<void()> waitingCallback);
  292. Streaming::Instance instance;
  293. std::unique_ptr<PlaybackControls> controls;
  294. std::unique_ptr<base::PowerSaveBlocker> powerSaveBlocker;
  295. bool ready = false;
  296. bool withSound = false;
  297. bool pausedBySeek = false;
  298. bool resumeOnCallEnd = false;
  299. };
  300. struct OverlayWidget::PipWrap {
  301. PipWrap(
  302. QWidget *parent,
  303. not_null<DocumentData*> document,
  304. Data::FileOrigin origin,
  305. not_null<DocumentData*> chosenQuality,
  306. HistoryItem *context,
  307. VideoQuality quality,
  308. std::shared_ptr<Streaming::Document> shared,
  309. FnMut<void()> closeAndContinue,
  310. FnMut<void()> destroy);
  311. PipWrap(const PipWrap &other) = delete;
  312. PipWrap &operator=(const PipWrap &other) = delete;
  313. PipDelegate delegate;
  314. Pip wrapped;
  315. rpl::lifetime lifetime;
  316. };
  317. struct OverlayWidget::ItemContext {
  318. not_null<HistoryItem*> item;
  319. MsgId topicRootId = 0;
  320. };
  321. struct OverlayWidget::StoriesContext {
  322. not_null<PeerData*> peer;
  323. StoryId id = 0;
  324. Data::StoriesContext within;
  325. };
  326. class OverlayWidget::Show final : public ChatHelpers::Show {
  327. public:
  328. explicit Show(not_null<OverlayWidget*> widget) : _widget(widget) {
  329. }
  330. void activate() override {
  331. if (!_widget->isHidden()) {
  332. _widget->activate();
  333. }
  334. }
  335. void showOrHideBoxOrLayer(
  336. std::variant<
  337. v::null_t,
  338. object_ptr<Ui::BoxContent>,
  339. std::unique_ptr<Ui::LayerWidget>> &&layer,
  340. Ui::LayerOptions options,
  341. anim::type animated) const override {
  342. _widget->_layerBg->uiShow()->showOrHideBoxOrLayer(
  343. std::move(layer),
  344. options,
  345. anim::type::normal);
  346. }
  347. not_null<QWidget*> toastParent() const override {
  348. return _widget->_body;
  349. }
  350. bool valid() const override {
  351. return _widget->_session || _widget->_storiesSession;
  352. }
  353. operator bool() const override {
  354. return valid();
  355. }
  356. Main::Session &session() const override {
  357. Expects(_widget->_session || _widget->_storiesSession);
  358. return _widget->_session
  359. ? *_widget->_session
  360. : *_widget->_storiesSession;
  361. }
  362. bool paused(ChatHelpers::PauseReason reason) const override {
  363. if (_widget->isHidden()
  364. || (!_widget->_fullscreen
  365. && !_widget->_window->isActiveWindow())) {
  366. return true;
  367. } else if (reason < ChatHelpers::PauseReason::Layer
  368. && _widget->_layerBg->topShownLayer() != nullptr) {
  369. return true;
  370. }
  371. return false;
  372. }
  373. rpl::producer<> pauseChanged() const override {
  374. return rpl::never<>();
  375. }
  376. rpl::producer<bool> adjustShadowLeft() const override {
  377. return rpl::single(false);
  378. }
  379. SendMenu::Details sendMenuDetails() const override {
  380. return { SendMenu::Type::SilentOnly };
  381. }
  382. bool showMediaPreview(
  383. Data::FileOrigin origin,
  384. not_null<DocumentData*> document) const override {
  385. return false; // #TODO stories
  386. }
  387. bool showMediaPreview(
  388. Data::FileOrigin origin,
  389. not_null<PhotoData*> photo) const override {
  390. return false; // #TODO stories
  391. }
  392. void processChosenSticker(
  393. ChatHelpers::FileChosen &&chosen) const override {
  394. _widget->_storiesStickerOrEmojiChosen.fire(std::move(chosen));
  395. }
  396. private:
  397. not_null<OverlayWidget*> _widget;
  398. };
  399. OverlayWidget::Streamed::Streamed(
  400. not_null<DocumentData*> quality,
  401. not_null<DocumentData*> original,
  402. HistoryItem *context,
  403. Data::FileOrigin origin,
  404. Fn<void()> waitingCallback)
  405. : instance(quality, original, context, origin, std::move(waitingCallback)) {
  406. }
  407. OverlayWidget::Streamed::Streamed(
  408. not_null<PhotoData*> photo,
  409. Data::FileOrigin origin,
  410. Fn<void()> waitingCallback)
  411. : instance(photo, origin, std::move(waitingCallback)) {
  412. }
  413. OverlayWidget::PipWrap::PipWrap(
  414. QWidget *parent,
  415. not_null<DocumentData*> document,
  416. Data::FileOrigin origin,
  417. not_null<DocumentData*> chosenQuality,
  418. HistoryItem *context,
  419. VideoQuality quality,
  420. std::shared_ptr<Streaming::Document> shared,
  421. FnMut<void()> closeAndContinue,
  422. FnMut<void()> destroy)
  423. : delegate(parent, &document->session())
  424. , wrapped(
  425. &delegate,
  426. document,
  427. origin,
  428. chosenQuality,
  429. context,
  430. quality,
  431. std::move(shared),
  432. std::move(closeAndContinue),
  433. std::move(destroy)) {
  434. }
  435. OverlayWidget::OverlayWidget()
  436. : _wrap(std::make_unique<Ui::GL::Window>())
  437. , _window(_wrap->window())
  438. , _helper(Platform::CreateOverlayWidgetHelper(_window.get(), [=](bool maximized) {
  439. toggleFullScreen(maximized);
  440. }))
  441. , _body(_wrap->widget())
  442. , _titleBugWorkaround(std::make_unique<Ui::RpWidget>(_body))
  443. , _surface(
  444. Ui::GL::CreateSurface(_body, chooseRenderer(_wrap->backend())))
  445. , _widget(_surface->rpWidget())
  446. , _fullscreen(Core::App().settings().mediaViewPosition().maximized == 2)
  447. , _windowed(Core::App().settings().mediaViewPosition().maximized == 0)
  448. , _quality(Core::App().settings().videoQuality())
  449. , _layerBg(std::make_unique<Ui::LayerManager>(_body))
  450. , _docDownload(_body, tr::lng_media_download(tr::now), st::mediaviewFileLink)
  451. , _docSaveAs(_body, tr::lng_mediaview_save_as(tr::now), st::mediaviewFileLink)
  452. , _docCancel(_body, tr::lng_cancel(tr::now), st::mediaviewFileLink)
  453. , _radial([=](crl::time now) { return radialAnimationCallback(now); })
  454. , _lastAction(-st::mediaviewDeltaFromLastAction, -st::mediaviewDeltaFromLastAction)
  455. , _stateAnimation([=](crl::time now) { return stateAnimationCallback(now); })
  456. , _dropdown(_body, st::mediaviewDropdownMenu) {
  457. _layerBg->setStyleOverrides(&st::groupCallBox, &st::groupCallLayerBox);
  458. _layerBg->setHideByBackgroundClick(true);
  459. CrashReports::SetAnnotation("OpenGL Renderer", "[not-initialized]");
  460. Lang::Updated(
  461. ) | rpl::start_with_next([=] {
  462. refreshLang();
  463. }, lifetime());
  464. _lastPositiveVolume = (Core::App().settings().videoVolume() > 0.)
  465. ? Core::App().settings().videoVolume()
  466. : Core::Settings::kDefaultVolume;
  467. _saveMsgTimer.setCallback([=, delay = st::mediaviewSaveMsgHiding] {
  468. _saveMsgAnimation.start([=] { updateSaveMsg(); }, 1., 0., delay);
  469. });
  470. _docRectImage = QImage(
  471. st::mediaviewFileSize * style::DevicePixelRatio(),
  472. QImage::Format_ARGB32_Premultiplied);
  473. _docRectImage.setDevicePixelRatio(style::DevicePixelRatio());
  474. Shortcuts::Requests(
  475. ) | rpl::start_with_next([=](not_null<Shortcuts::Request*> request) {
  476. request->check(
  477. Shortcuts::Command::MediaViewerFullscreen
  478. ) && request->handle([=] {
  479. if (_streamed) {
  480. playbackToggleFullScreen();
  481. return true;
  482. }
  483. return false;
  484. });
  485. }, lifetime());
  486. setupWindow();
  487. const auto mousePosition = [](not_null<QEvent*> e) {
  488. return static_cast<QMouseEvent*>(e.get())->pos();
  489. };
  490. const auto mouseButton = [](not_null<QEvent*> e) {
  491. return static_cast<QMouseEvent*>(e.get())->button();
  492. };
  493. base::install_event_filter(_window, [=](not_null<QEvent*> e) {
  494. const auto type = e->type();
  495. if (type == QEvent::Move) {
  496. const auto position = static_cast<QMoveEvent*>(e.get())->pos();
  497. DEBUG_LOG(("Viewer Pos: Moved to %1, %2")
  498. .arg(position.x())
  499. .arg(position.y()));
  500. if (_windowed) {
  501. savePosition();
  502. } else {
  503. moveToScreen(true);
  504. }
  505. } else if (type == QEvent::Resize) {
  506. const auto size = static_cast<QResizeEvent*>(e.get())->size();
  507. DEBUG_LOG(("Viewer Pos: Resized to %1, %2")
  508. .arg(size.width())
  509. .arg(size.height()));
  510. if (_windowed) {
  511. savePosition();
  512. }
  513. } else if (type == QEvent::Close
  514. && !Core::Sandbox::Instance().isSavingSession()
  515. && !Core::Quitting()) {
  516. e->ignore();
  517. close();
  518. return base::EventFilterResult::Cancel;
  519. } else if (type == QEvent::ThemeChange && Platform::IsLinux()) {
  520. _window->setWindowIcon(Window::CreateIcon(_session));
  521. } else if (type == QEvent::ContextMenu) {
  522. const auto event = static_cast<QContextMenuEvent*>(e.get());
  523. const auto mouse = (event->reason() == QContextMenuEvent::Mouse);
  524. const auto position = mouse
  525. ? std::make_optional(event->pos())
  526. : std::nullopt;
  527. if (handleContextMenu(position)) {
  528. return base::EventFilterResult::Cancel;
  529. }
  530. } else if (e->type() == QEvent::WindowStateChange) {
  531. const auto state = window()->windowState();
  532. if (state == Qt::WindowMinimized || Platform::IsMac()) {
  533. } else if (state == Qt::WindowMaximized) {
  534. if (_fullscreen || _windowed) {
  535. _fullscreen = _windowed = false;
  536. savePosition();
  537. }
  538. } else if (_fullscreen || _windowed) {
  539. } else if (state == Qt::WindowFullScreen) {
  540. _fullscreen = true;
  541. savePosition();
  542. } else {
  543. _windowed = true;
  544. savePosition();
  545. }
  546. }
  547. return base::EventFilterResult::Continue;
  548. });
  549. base::install_event_filter(_body, [=](not_null<QEvent*> e) {
  550. const auto type = e->type();
  551. if (type == QEvent::Resize) {
  552. const auto size = static_cast<QResizeEvent*>(e.get())->size();
  553. // Somehow Windows 11 knows the geometry of first widget below
  554. // the semi-native title control widgets and it uses
  555. // it's geometry to show the snap grid popup around it when
  556. // you put the mouse over the Maximize button. In the 4.6.4 beta
  557. // the first widget was `_widget`, so the popup was shown
  558. // either above the window or, if not enough space above, below
  559. // the whole window, you couldn't even put the mouse on it.
  560. //
  561. // So now here is this weird workaround that places our
  562. // `_titleBugWorkaround` widget as the first one under the title
  563. // controls and the system shows the popup around its geometry,
  564. // so we set it's height to the title controls height
  565. // and everything works as expected.
  566. //
  567. // This doesn't make sense. But it works. :shrug:
  568. _titleBugWorkaround->setGeometry(
  569. { 0, 0, size.width(), st::mediaviewTitleButton.height });
  570. _widget->setGeometry({ QPoint(), size });
  571. updateControlsGeometry();
  572. } else if (type == QEvent::KeyPress) {
  573. handleKeyPress(static_cast<QKeyEvent*>(e.get()));
  574. }
  575. return base::EventFilterResult::Continue;
  576. });
  577. base::install_event_filter(_widget, [=](not_null<QEvent*> e) {
  578. const auto type = e->type();
  579. if (type == QEvent::Leave) {
  580. if (_over != Over::None) {
  581. updateOverState(Over::None);
  582. }
  583. } else if (type == QEvent::MouseButtonPress) {
  584. handleMousePress(mousePosition(e), mouseButton(e));
  585. } else if (type == QEvent::MouseButtonRelease) {
  586. handleMouseRelease(mousePosition(e), mouseButton(e));
  587. } else if (type == QEvent::MouseMove) {
  588. handleMouseMove(mousePosition(e));
  589. } else if (type == QEvent::MouseButtonDblClick) {
  590. if (handleDoubleClick(mousePosition(e), mouseButton(e))) {
  591. return base::EventFilterResult::Cancel;
  592. } else {
  593. handleMousePress(mousePosition(e), mouseButton(e));
  594. }
  595. } else if (type == QEvent::TouchBegin
  596. || type == QEvent::TouchUpdate
  597. || type == QEvent::TouchEnd
  598. || type == QEvent::TouchCancel) {
  599. if (handleTouchEvent(static_cast<QTouchEvent*>(e.get()))) {
  600. return base::EventFilterResult::Cancel;
  601. }
  602. } else if (type == QEvent::Wheel) {
  603. handleWheelEvent(static_cast<QWheelEvent*>(e.get()));
  604. }
  605. return base::EventFilterResult::Continue;
  606. });
  607. _helper->mouseEvents(
  608. ) | rpl::start_with_next([=](not_null<QMouseEvent*> e) {
  609. if (_helper->skipTitleHitTest(e->windowPos().toPoint())) {
  610. return;
  611. }
  612. const auto type = e->type();
  613. const auto position = e->pos();
  614. if (type == QEvent::MouseButtonPress) {
  615. handleMousePress(position, e->button());
  616. } else if (type == QEvent::MouseButtonRelease) {
  617. handleMouseRelease(position, e->button());
  618. } else if (type == QEvent::MouseMove) {
  619. handleMouseMove(position);
  620. } else if (type == QEvent::MouseButtonDblClick) {
  621. if (!handleDoubleClick(position, e->button())) {
  622. handleMousePress(position, e->button());
  623. }
  624. }
  625. }, lifetime());
  626. _topShadowRight = _helper->controlsSideRightValue();
  627. _topShadowRight.changes(
  628. ) | rpl::start_with_next([=] {
  629. updateControlsGeometry();
  630. update();
  631. }, lifetime());
  632. _helper->topNotchSkipValue(
  633. ) | rpl::start_with_next([=](int notch) {
  634. if (_topNotchSize != notch) {
  635. _topNotchSize = notch;
  636. if (_fullscreen) {
  637. updateControlsGeometry();
  638. }
  639. }
  640. }, lifetime());
  641. _window->setTitle(tr::lng_mediaview_title(tr::now));
  642. _window->setTitleStyle(st::mediaviewTitle);
  643. if constexpr (Platform::IsMac()) {
  644. // Without Qt::Tool starting with Qt 5.15.1 this widget
  645. // when being opened from a fullscreen main window was
  646. // opening not as overlay over the main window, but as
  647. // a separate fullscreen window with a separate space.
  648. _window->setWindowFlags(Qt::FramelessWindowHint | Qt::Tool);
  649. }
  650. _widget->setMouseTracking(true);
  651. _window->screenValue(
  652. ) | rpl::skip(1) | rpl::start_with_next([=](not_null<QScreen*> screen) {
  653. handleScreenChanged(screen);
  654. }, lifetime());
  655. subscribeToScreenGeometry();
  656. updateGeometry();
  657. updateControlsGeometry();
  658. #ifdef Q_OS_MAC
  659. TouchBar::SetupMediaViewTouchBar(
  660. _window->winId(),
  661. static_cast<PlaybackControls::Delegate*>(this),
  662. _touchbarTrackState.events(),
  663. _touchbarDisplay.events(),
  664. _touchbarFullscreenToggled.events());
  665. #endif // Q_OS_MAC
  666. using namespace rpl::mappers;
  667. rpl::combine(
  668. Core::App().calls().currentCallValue(),
  669. Core::App().calls().currentGroupCallValue(),
  670. _1 || _2
  671. ) | rpl::start_with_next([=](bool call) {
  672. if (!_streamed
  673. || !_document
  674. || (_document->isAnimation() && !_document->isVideoMessage())) {
  675. return;
  676. } else if (call) {
  677. playbackPauseOnCall();
  678. } else {
  679. playbackResumeOnCall();
  680. }
  681. }, lifetime());
  682. _widget->setAttribute(Qt::WA_AcceptTouchEvents);
  683. _touchTimer.setCallback([=] { handleTouchTimer(); });
  684. _controlsHideTimer.setCallback([=] { hideControls(); });
  685. _helper->controlsActivations(
  686. ) | rpl::start_with_next([=] {
  687. activateControls();
  688. }, lifetime());
  689. _docDownload->addClickHandler([=] { downloadMedia(); });
  690. _docSaveAs->addClickHandler([=] { saveAs(); });
  691. _docCancel->addClickHandler([=] { saveCancel(); });
  692. _dropdown->setHiddenCallback([this] { dropdownHidden(); });
  693. _dropdownShowTimer.setCallback([=] { showDropdown(); });
  694. orderWidgets();
  695. }
  696. void OverlayWidget::showSaveMsgToast(const QString &path, auto phrase) {
  697. showSaveMsgToastWith(path, phrase(
  698. tr::now,
  699. lt_downloads,
  700. Ui::Text::Link(
  701. tr::lng_mediaview_downloads(tr::now),
  702. "internal:show_saved_message"),
  703. Ui::Text::WithEntities));
  704. }
  705. void OverlayWidget::showSaveMsgToastWith(
  706. const QString &path,
  707. const TextWithEntities &text) {
  708. _saveMsgFilename = path;
  709. _saveMsgText.setMarkedText(st::mediaviewSaveMsgStyle, text);
  710. const auto w = _saveMsgText.maxWidth()
  711. + st::mediaviewSaveMsgPadding.left()
  712. + st::mediaviewSaveMsgPadding.right();
  713. const auto h = st::mediaviewSaveMsgStyle.font->height
  714. + st::mediaviewSaveMsgPadding.top()
  715. + st::mediaviewSaveMsgPadding.bottom();
  716. _saveMsg = QRect(
  717. (width() - w) / 2,
  718. _minUsedTop + (_maxUsedHeight - h) / 2,
  719. w,
  720. h);
  721. const auto callback = [=](float64 value) {
  722. updateSaveMsg();
  723. if (!_saveMsgAnimation.animating()) {
  724. _saveMsgTimer.callOnce(st::mediaviewSaveMsgShown);
  725. }
  726. };
  727. const auto duration = st::mediaviewSaveMsgShowing;
  728. _saveMsgAnimation.start(callback, 0., 1., duration);
  729. updateSaveMsg();
  730. }
  731. void OverlayWidget::orderWidgets() {
  732. _helper->orderWidgets();
  733. }
  734. void OverlayWidget::setupWindow() {
  735. _window->setBodyTitleArea([=](QPoint widgetPoint) {
  736. using Flag = Ui::WindowTitleHitTestFlag;
  737. Ui::WindowTitleHitTestFlags result;
  738. if (!_widget->rect().contains(widgetPoint)
  739. || _helper->skipTitleHitTest(_widget->mapTo(_window, widgetPoint))) {
  740. return result;
  741. }
  742. if (widgetPoint.y() <= st::mediaviewTitleButton.height) {
  743. result |= Flag::Menu | Flag::FullScreen;
  744. }
  745. const auto inControls = ((_over != Over::None) && (_over != Over::Video));
  746. if (inControls
  747. || (_streamed
  748. && _streamed->controls
  749. && _streamed->controls->dragging())) {
  750. } else if ((_w > _widget->width() || _h > _maxUsedHeight)
  751. && (widgetPoint.y() > st::mediaviewHeaderTop)
  752. && QRect(_x, _y, _w, _h).contains(widgetPoint)) {
  753. } else if (_stories && _stories->ignoreWindowMove(widgetPoint)) {
  754. } else if (_sponsoredButton
  755. && _sponsoredButton->geometry().contains(widgetPoint)) {
  756. } else if (_windowed) {
  757. result |= Flag::Move;
  758. }
  759. return result;
  760. });
  761. _window->setAttribute(Qt::WA_NoSystemBackground, true);
  762. _window->setAttribute(Qt::WA_TranslucentBackground, true);
  763. _window->setMinimumSize(
  764. { st::mediaviewMinWidth, st::mediaviewMinHeight });
  765. _window->shownValue(
  766. ) | rpl::start_with_next([=](bool shown) {
  767. toggleApplicationEventFilter(shown);
  768. if (!shown) {
  769. clearAfterHide();
  770. } else {
  771. const auto geometry = _window->geometry();
  772. const auto screenList = QGuiApplication::screens();
  773. DEBUG_LOG(("Viewer Pos: Shown, geometry: %1, %2, %3, %4, screen number: %5")
  774. .arg(geometry.x())
  775. .arg(geometry.y())
  776. .arg(geometry.width())
  777. .arg(geometry.height())
  778. .arg(screenList.indexOf(_window->screen())));
  779. moveToScreen();
  780. }
  781. }, lifetime());
  782. }
  783. void OverlayWidget::refreshLang() {
  784. InvokeQueued(_widget, [=] { updateThemePreviewGeometry(); });
  785. }
  786. void OverlayWidget::moveToScreen(bool inMove) {
  787. if (!_fullscreen || _wasWindowedMode) {
  788. return;
  789. }
  790. const auto applicationWindow = Core::App().activeWindow()
  791. ? Core::App().activeWindow()->widget().get()
  792. : nullptr;
  793. const auto activeWindowScreen = applicationWindow
  794. ? applicationWindow->screen()
  795. : nullptr;
  796. const auto myScreen = _window->screen();
  797. if (activeWindowScreen && myScreen != activeWindowScreen) {
  798. const auto screenList = QGuiApplication::screens();
  799. DEBUG_LOG(("Viewer Pos: Currently on screen %1, moving to screen %2")
  800. .arg(screenList.indexOf(myScreen))
  801. .arg(screenList.indexOf(activeWindowScreen)));
  802. #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
  803. _window->setScreen(activeWindowScreen);
  804. #else // Qt >= 6.0.0
  805. _window->createWinId();
  806. window()->setScreen(activeWindowScreen);
  807. #endif // Qt < 6.0.0
  808. DEBUG_LOG(("Viewer Pos: New actual screen: %1")
  809. .arg(screenList.indexOf(_window->screen())));
  810. }
  811. updateGeometry(inMove);
  812. }
  813. void OverlayWidget::initFullScreen() {
  814. if (_fullscreenInited) {
  815. return;
  816. }
  817. _fullscreenInited = true;
  818. switch (Core::App().settings().mediaViewPosition().maximized) {
  819. case 2:
  820. _fullscreen = true;
  821. _windowed = false;
  822. break;
  823. case 1:
  824. _fullscreen = Platform::IsMac();
  825. _windowed = false;
  826. break;
  827. }
  828. }
  829. void OverlayWidget::initNormalGeometry() {
  830. if (_normalGeometryInited) {
  831. return;
  832. }
  833. _normalGeometryInited = true;
  834. const auto saved = Core::App().settings().mediaViewPosition();
  835. const auto adjusted = Core::AdjustToScale(saved, u"Viewer"_q);
  836. const auto initial = DefaultPosition();
  837. _normalGeometry = initial.rect();
  838. if (const auto active = Core::App().activeWindow()) {
  839. _normalGeometry = active->widget()->countInitialGeometry(
  840. adjusted,
  841. initial,
  842. { st::mediaviewMinWidth, st::mediaviewMinHeight });
  843. }
  844. }
  845. void OverlayWidget::savePosition() {
  846. if (isHidden() || isMinimized() || !_normalGeometryInited) {
  847. return;
  848. }
  849. const auto &savedPosition = Core::App().settings().mediaViewPosition();
  850. auto realPosition = savedPosition;
  851. if (_fullscreen) {
  852. realPosition.maximized = 2;
  853. realPosition.moncrc = 0;
  854. DEBUG_LOG(("Viewer Pos: Saving fullscreen position."));
  855. } else if (!_windowed) {
  856. realPosition.maximized = 1;
  857. realPosition.moncrc = 0;
  858. DEBUG_LOG(("Viewer Pos: Saving maximized position."));
  859. } else if (!_wasWindowedMode && !Platform::IsMac()) {
  860. return;
  861. } else {
  862. auto r = _normalGeometry = _window->body()->mapToGlobal(
  863. _window->body()->rect());
  864. realPosition.x = r.x();
  865. realPosition.y = r.y();
  866. realPosition.w = r.width();
  867. realPosition.h = r.height();
  868. realPosition.scale = cScale();
  869. realPosition.maximized = 0;
  870. realPosition.moncrc = 0;
  871. DEBUG_LOG(("Viewer Pos: "
  872. "Saving non-maximized position: %1, %2, %3, %4"
  873. ).arg(realPosition.x
  874. ).arg(realPosition.y
  875. ).arg(realPosition.w
  876. ).arg(realPosition.h));
  877. }
  878. realPosition = Window::PositionWithScreen(
  879. realPosition,
  880. _window,
  881. { st::mediaviewMinWidth, st::mediaviewMinHeight });
  882. if (realPosition.w >= st::mediaviewMinWidth
  883. && realPosition.h >= st::mediaviewMinHeight
  884. && realPosition != savedPosition) {
  885. DEBUG_LOG(("Viewer Pos: "
  886. "Writing: %1, %2, %3, %4 (scale %5%, maximized %6)")
  887. .arg(realPosition.x)
  888. .arg(realPosition.y)
  889. .arg(realPosition.w)
  890. .arg(realPosition.h)
  891. .arg(realPosition.scale)
  892. .arg(Logs::b(realPosition.maximized)));
  893. Core::App().settings().setMediaViewPosition(realPosition);
  894. Core::App().saveSettingsDelayed();
  895. }
  896. }
  897. void OverlayWidget::updateGeometry(bool inMove) {
  898. initFullScreen();
  899. if (_fullscreen) {
  900. updateGeometryToScreen(inMove);
  901. } else if (_windowed && _normalGeometryInited) {
  902. DEBUG_LOG(("Viewer Pos: Setting %1, %2, %3, %4")
  903. .arg(_normalGeometry.x())
  904. .arg(_normalGeometry.y())
  905. .arg(_normalGeometry.width())
  906. .arg(_normalGeometry.height()));
  907. _window->setGeometry(_normalGeometry);
  908. }
  909. if constexpr (!Platform::IsMac()) {
  910. if (_fullscreen) {
  911. if (!isHidden() && !isMinimized()) {
  912. _window->showFullScreen();
  913. }
  914. } else if (!_windowed) {
  915. if (!isHidden() && !isMinimized()) {
  916. _window->showMaximized();
  917. }
  918. }
  919. }
  920. }
  921. void OverlayWidget::updateGeometryToScreen(bool inMove) {
  922. const auto available = _window->screen()->geometry();
  923. if (_window->geometry() == available) {
  924. return;
  925. }
  926. DEBUG_LOG(("Viewer Pos: Setting %1, %2, %3, %4")
  927. .arg(available.x())
  928. .arg(available.y())
  929. .arg(available.width())
  930. .arg(available.height()));
  931. _window->setGeometry(available);
  932. }
  933. void OverlayWidget::updateControlsGeometry() {
  934. updateNavigationControlsGeometry();
  935. _saveMsg.moveTo(
  936. (width() - _saveMsg.width()) / 2,
  937. _minUsedTop + (_maxUsedHeight - _saveMsg.height()) / 2);
  938. _photoRadialRect = QRect(
  939. QPoint(
  940. (width() - st::radialSize.width()) / 2,
  941. _minUsedTop + (_maxUsedHeight - st::radialSize.height()) / 2),
  942. st::radialSize);
  943. const auto bottom = st::mediaviewShadowBottom.height();
  944. const auto top = st::mediaviewShadowTop.size();
  945. _bottomShadowRect = QRect(0, height() - bottom, width(), bottom);
  946. _topShadowRect = QRect(
  947. QPoint(topShadowOnTheRight() ? (width() - top.width()) : 0, 0),
  948. top);
  949. if (_dropdown && !_dropdown->isHidden()) {
  950. _dropdown->moveToRight(0, height() - _dropdown->height());
  951. }
  952. updateControls();
  953. resizeContentByScreenSize();
  954. update();
  955. }
  956. void OverlayWidget::updateNavigationControlsGeometry() {
  957. _minUsedTop = topNotchSkip();
  958. _maxUsedHeight = height() - _minUsedTop;
  959. const auto overRect = QRect(
  960. QPoint(),
  961. QSize(st::mediaviewIconOver, st::mediaviewIconOver));
  962. const auto navSize = _stories
  963. ? st::storiesControlSize
  964. : st::mediaviewControlSize;
  965. const auto navSkip = st::mediaviewHeaderTop;
  966. const auto xLeft = _stories ? (_x - navSize) : 0;
  967. const auto xRight = _stories ? (_x + _w) : (width() - navSize);
  968. _leftNav = QRect(
  969. xLeft,
  970. _minUsedTop + navSkip,
  971. navSize,
  972. _maxUsedHeight - 2 * navSkip);
  973. _leftNavOver = _stories
  974. ? QRect()
  975. : style::centerrect(_leftNav, overRect);
  976. _leftNavIcon = style::centerrect(
  977. _leftNav,
  978. _stories ? st::storiesLeft : st::mediaviewLeft);
  979. _rightNav = QRect(
  980. xRight,
  981. _minUsedTop + navSkip,
  982. navSize,
  983. _maxUsedHeight - 2 * navSkip);
  984. _rightNavOver = _stories
  985. ? QRect()
  986. : style::centerrect(_rightNav, overRect);
  987. _rightNavIcon = style::centerrect(
  988. _rightNav,
  989. _stories ? st::storiesRight : st::mediaviewRight);
  990. }
  991. bool OverlayWidget::topShadowOnTheRight() const {
  992. return _topShadowRight.current();
  993. }
  994. QSize OverlayWidget::flipSizeByRotation(QSize size) const {
  995. return FlipSizeByRotation(size, _rotation);
  996. }
  997. bool OverlayWidget::hasCopyMediaRestriction(bool skipPremiumCheck) const {
  998. if (const auto story = _stories ? _stories->story() : nullptr) {
  999. return skipPremiumCheck
  1000. ? !story->canDownloadIfPremium()
  1001. : !story->canDownloadChecked();
  1002. }
  1003. return (_history && !_history->peer->allowsForwarding())
  1004. || (_message && _message->forbidsSaving());
  1005. }
  1006. bool OverlayWidget::showCopyMediaRestriction(bool skipPRemiumCheck) {
  1007. if (!hasCopyMediaRestriction(skipPRemiumCheck)) {
  1008. return false;
  1009. } else if (_stories) {
  1010. uiShow()->showToast(tr::lng_error_nocopy_story(tr::now));
  1011. } else if (_history) {
  1012. uiShow()->showToast(_history->peer->isBroadcast()
  1013. ? tr::lng_error_nocopy_channel(tr::now)
  1014. : tr::lng_error_nocopy_group(tr::now));
  1015. }
  1016. return true;
  1017. }
  1018. bool OverlayWidget::videoShown() const {
  1019. return _streamed
  1020. && _streamed->ready
  1021. && !_streamed->instance.info().video.cover.isNull();
  1022. }
  1023. QSize OverlayWidget::videoSize() const {
  1024. Expects(videoShown());
  1025. const auto use = (_document && _chosenQuality != _document)
  1026. ? _document->dimensions
  1027. : _streamed->instance.info().video.size;
  1028. return flipSizeByRotation(use);
  1029. }
  1030. bool OverlayWidget::streamingRequiresControls() const {
  1031. return !_stories
  1032. && _document
  1033. && (!_document->isAnimation() || _document->isVideoMessage());
  1034. }
  1035. QImage OverlayWidget::videoFrame() const {
  1036. Expects(videoShown());
  1037. auto request = Streaming::FrameRequest();
  1038. //request.radius = (_document && _document->isVideoMessage())
  1039. // ? ImageRoundRadius::Ellipse
  1040. // : ImageRoundRadius::None;
  1041. return _streamed->instance.player().ready()
  1042. ? _streamed->instance.frame(request)
  1043. : _streamed->instance.info().video.cover;
  1044. }
  1045. Streaming::FrameWithInfo OverlayWidget::videoFrameWithInfo() const {
  1046. Expects(videoShown());
  1047. return _streamed->instance.player().ready()
  1048. ? _streamed->instance.frameWithInfo()
  1049. : Streaming::FrameWithInfo{
  1050. .image = _streamed->instance.info().video.cover,
  1051. .format = Streaming::FrameFormat::ARGB32,
  1052. .index = -2,
  1053. .alpha = _streamed->instance.info().video.alpha,
  1054. };
  1055. }
  1056. QImage OverlayWidget::currentVideoFrameImage() const {
  1057. return _streamed->instance.player().ready()
  1058. ? _streamed->instance.player().currentFrameImage()
  1059. : _streamed->instance.info().video.cover;
  1060. }
  1061. int OverlayWidget::streamedIndex() const {
  1062. return _streamedCreated;
  1063. }
  1064. bool OverlayWidget::documentContentShown() const {
  1065. return _document && (!_staticContent.isNull() || videoShown());
  1066. }
  1067. bool OverlayWidget::documentBubbleShown() const {
  1068. return (!_photo && !_document)
  1069. || (_document
  1070. && !_themePreviewShown
  1071. && !_streamed
  1072. && _staticContent.isNull());
  1073. }
  1074. void OverlayWidget::setStaticContent(QImage image) {
  1075. constexpr auto kGood = QImage::Format_ARGB32_Premultiplied;
  1076. if (!image.isNull()
  1077. && image.format() != kGood
  1078. && image.format() != QImage::Format_RGB32) {
  1079. image = std::move(image).convertToFormat(kGood);
  1080. }
  1081. image.setDevicePixelRatio(style::DevicePixelRatio());
  1082. if (_flip) {
  1083. image = image.mirrored(_flip & Qt::Horizontal, _flip & Qt::Vertical);
  1084. }
  1085. _staticContent = std::move(image);
  1086. _staticContentTransparent = IsSemitransparent(_staticContent);
  1087. }
  1088. bool OverlayWidget::contentShown() const {
  1089. return _photo || documentContentShown();
  1090. }
  1091. bool OverlayWidget::opaqueContentShown() const {
  1092. return contentShown()
  1093. && (!_staticContentTransparent
  1094. || !_document
  1095. || (!_document->isVideoMessage()
  1096. && !_document->sticker()
  1097. && (!_streamed || !_streamed->instance.info().video.alpha)));
  1098. }
  1099. void OverlayWidget::clearStreaming(bool savePosition) {
  1100. if (_streamed && _document && savePosition) {
  1101. Media::Player::SaveLastPlaybackPosition(
  1102. _document,
  1103. _streamed->instance.player().prepareLegacyState());
  1104. }
  1105. _fullScreenVideo = false;
  1106. _streamed = nullptr;
  1107. }
  1108. void OverlayWidget::documentUpdated(not_null<DocumentData*> document) {
  1109. if (_document != document) {
  1110. return;
  1111. } else if (documentBubbleShown()) {
  1112. if ((_document->loading() && _docCancel->isHidden())
  1113. || (!_document->loading() && !_docCancel->isHidden())) {
  1114. updateControls();
  1115. } else if (_document->loading()) {
  1116. updateDocSize();
  1117. _widget->update(_docRect);
  1118. }
  1119. } else if (_streamed && _streamed->controls) {
  1120. const auto ready = _documentMedia->loaded()
  1121. ? _document->size
  1122. : _document->loading()
  1123. ? std::clamp(_document->loadOffset(), int64(), _document->size)
  1124. : 0;
  1125. _streamed->controls->setLoadingProgress(ready, _document->size);
  1126. }
  1127. if (_stories
  1128. && !_documentLoadingTo.isEmpty()
  1129. && _document->location(true).isEmpty()) {
  1130. showSaveMsgToast(
  1131. base::take(_documentLoadingTo),
  1132. tr::lng_mediaview_video_saved_to);
  1133. }
  1134. }
  1135. void OverlayWidget::changingMsgId(FullMsgId newId, MsgId oldId) {
  1136. if (_message && _message->fullId() == newId) {
  1137. refreshMediaViewer();
  1138. }
  1139. }
  1140. void OverlayWidget::updateDocSize() {
  1141. if (!_document || !documentBubbleShown()) {
  1142. return;
  1143. }
  1144. const auto size = _document->size;
  1145. _docSize = _document->loading()
  1146. ? Ui::FormatProgressText(_document->loadOffset(), size)
  1147. : Ui::FormatSizeText(size);
  1148. _docSizeWidth = st::mediaviewFont->width(_docSize);
  1149. int32 maxw = st::mediaviewFileSize.width() - st::mediaviewFileIconSize - st::mediaviewFilePadding * 3;
  1150. if (_docSizeWidth > maxw) {
  1151. _docSize = st::mediaviewFont->elided(_docSize, maxw);
  1152. _docSizeWidth = st::mediaviewFont->width(_docSize);
  1153. }
  1154. }
  1155. void OverlayWidget::refreshNavVisibility() {
  1156. if (_stories) {
  1157. _leftNavVisible = _stories->subjumpAvailable(-1);
  1158. _rightNavVisible = _stories->subjumpAvailable(1);
  1159. } else if (_sharedMediaData) {
  1160. _leftNavVisible = _index && (*_index > 0);
  1161. _rightNavVisible = _index && (*_index + 1 < _sharedMediaData->size());
  1162. } else if (_userPhotosData) {
  1163. _leftNavVisible = _index && (*_index > 0);
  1164. _rightNavVisible = _index && (*_index + 1 < _userPhotosData->size());
  1165. } else if (_collageData) {
  1166. _leftNavVisible = _index && (*_index > 0);
  1167. _rightNavVisible = _index && (*_index + 1 < _collageData->items.size());
  1168. } else {
  1169. _leftNavVisible = false;
  1170. _rightNavVisible = false;
  1171. }
  1172. }
  1173. bool OverlayWidget::computeSaveButtonVisible() const {
  1174. if (hasCopyMediaRestriction(true)) {
  1175. return false;
  1176. } else if (_photo) {
  1177. return _photo->hasVideo() || _photoMedia->loaded();
  1178. } else if (_document) {
  1179. return _document->filepath(true).isEmpty() && !_document->loading();
  1180. } else {
  1181. return false;
  1182. }
  1183. }
  1184. void OverlayWidget::checkForSaveLoaded() {
  1185. if (_savePhotoVideoWhenLoaded == SavePhotoVideo::None) {
  1186. return;
  1187. } else if (!_photo
  1188. || !_photo->hasVideo()
  1189. || _photoMedia->videoContent(Data::PhotoSize::Large).isEmpty()) {
  1190. return;
  1191. } else if (_savePhotoVideoWhenLoaded == SavePhotoVideo::QuickSave) {
  1192. _savePhotoVideoWhenLoaded = SavePhotoVideo::None;
  1193. downloadMedia();
  1194. } else if (_savePhotoVideoWhenLoaded == SavePhotoVideo::SaveAs) {
  1195. _savePhotoVideoWhenLoaded = SavePhotoVideo::None;
  1196. saveAs();
  1197. } else {
  1198. Unexpected("SavePhotoVideo in OverlayWidget::checkForSaveLoaded.");
  1199. }
  1200. }
  1201. void OverlayWidget::showPremiumDownloadPromo() {
  1202. const auto filter = [=](const auto &...) {
  1203. if (const auto window = uiShow()->resolveWindow()) {
  1204. ShowPremiumPreviewBox(window, PremiumFeature::Stories);
  1205. window->window().activate();
  1206. }
  1207. return false;
  1208. };
  1209. uiShow()->showToast({
  1210. .text = tr::lng_stories_save_promo(
  1211. tr::now,
  1212. lt_link,
  1213. Ui::Text::Link(
  1214. Ui::Text::Bold(
  1215. tr::lng_send_as_premium_required_link(tr::now))),
  1216. Ui::Text::WithEntities),
  1217. .filter = filter,
  1218. .adaptive = true,
  1219. .duration = kStorySavePromoDuration,
  1220. });
  1221. }
  1222. void OverlayWidget::updateControls() {
  1223. if (_document && documentBubbleShown()) {
  1224. _docRect = QRect(
  1225. (width() - st::mediaviewFileSize.width()) / 2,
  1226. _minUsedTop + (_maxUsedHeight - st::mediaviewFileSize.height()) / 2,
  1227. st::mediaviewFileSize.width(),
  1228. st::mediaviewFileSize.height());
  1229. _docIconRect = QRect(
  1230. _docRect.x() + st::mediaviewFilePadding,
  1231. _docRect.y() + st::mediaviewFilePadding,
  1232. st::mediaviewFileIconSize,
  1233. st::mediaviewFileIconSize);
  1234. if (_document->loading()) {
  1235. _docDownload->hide();
  1236. _docSaveAs->hide();
  1237. _docCancel->moveToLeft(_docRect.x() + 2 * st::mediaviewFilePadding + st::mediaviewFileIconSize, _docRect.y() + st::mediaviewFilePadding + st::mediaviewFileLinksTop);
  1238. _docCancel->show();
  1239. } else {
  1240. if (_documentMedia->loaded(true)) {
  1241. _docDownload->hide();
  1242. _docSaveAs->moveToLeft(_docRect.x() + 2 * st::mediaviewFilePadding + st::mediaviewFileIconSize, _docRect.y() + st::mediaviewFilePadding + st::mediaviewFileLinksTop);
  1243. _docSaveAs->show();
  1244. _docCancel->hide();
  1245. } else {
  1246. _docDownload->moveToLeft(_docRect.x() + 2 * st::mediaviewFilePadding + st::mediaviewFileIconSize, _docRect.y() + st::mediaviewFilePadding + st::mediaviewFileLinksTop);
  1247. _docDownload->show();
  1248. _docSaveAs->moveToLeft(_docRect.x() + 2.5 * st::mediaviewFilePadding + st::mediaviewFileIconSize + _docDownload->width(), _docRect.y() + st::mediaviewFilePadding + st::mediaviewFileLinksTop);
  1249. _docSaveAs->show();
  1250. _docCancel->hide();
  1251. }
  1252. }
  1253. updateDocSize();
  1254. } else {
  1255. _docIconRect = QRect(
  1256. (width() - st::mediaviewFileIconSize) / 2,
  1257. _minUsedTop + (_maxUsedHeight - st::mediaviewFileIconSize) / 2,
  1258. st::mediaviewFileIconSize,
  1259. st::mediaviewFileIconSize);
  1260. _docDownload->hide();
  1261. _docSaveAs->hide();
  1262. _docCancel->hide();
  1263. }
  1264. radialStart();
  1265. updateThemePreviewGeometry();
  1266. const auto story = _stories ? _stories->story() : nullptr;
  1267. const auto overRect = QRect(
  1268. QPoint(),
  1269. QSize(st::mediaviewIconOver, st::mediaviewIconOver));
  1270. _saveVisible = computeSaveButtonVisible();
  1271. _shareVisible = story && story->canShare();
  1272. _rotateVisible = !_themePreviewShown && !story;
  1273. const auto navRect = [&](int i) {
  1274. return QRect(
  1275. width() - st::mediaviewIconSize.width() * i,
  1276. height() - st::mediaviewIconSize.height(),
  1277. st::mediaviewIconSize.width(),
  1278. st::mediaviewIconSize.height());
  1279. };
  1280. auto index = 1;
  1281. _moreNav = navRect(index);
  1282. _moreNavOver = style::centerrect(_moreNav, overRect);
  1283. _moreNavIcon = style::centerrect(_moreNav, st::mediaviewMore);
  1284. ++index;
  1285. _rotateNav = navRect(index);
  1286. _rotateNavOver = style::centerrect(_rotateNav, overRect);
  1287. _rotateNavIcon = style::centerrect(_rotateNav, st::mediaviewRotate);
  1288. if (_rotateVisible) {
  1289. ++index;
  1290. }
  1291. _shareNav = navRect(index);
  1292. _shareNavOver = style::centerrect(_shareNav, overRect);
  1293. _shareNavIcon = style::centerrect(_shareNav, st::mediaviewShare);
  1294. if (_shareVisible) {
  1295. ++index;
  1296. }
  1297. _saveNav = navRect(index);
  1298. _saveNavOver = style::centerrect(_saveNav, overRect);
  1299. _saveNavIcon = style::centerrect(_saveNav, st::mediaviewSave);
  1300. Assert(st::mediaviewSave.size() == st::mediaviewSaveLocked.size());
  1301. const auto dNow = QDateTime::currentDateTime();
  1302. const auto d = [&] {
  1303. if (_message) {
  1304. return ItemDateTime(_message);
  1305. } else if (_photo) {
  1306. return base::unixtime::parse(_photo->date());
  1307. } else if (_document) {
  1308. return base::unixtime::parse(_document->date);
  1309. }
  1310. return dNow;
  1311. }();
  1312. _dateText = d.isValid() ? Ui::FormatDateTime(d) : QString();
  1313. if (!_fromName.isEmpty()) {
  1314. _fromNameLabel.setText(
  1315. st::mediaviewTextStyle,
  1316. _fromName,
  1317. Ui::NameTextOptions());
  1318. _nameNav = QRect(
  1319. st::mediaviewTextLeft,
  1320. height() - st::mediaviewTextTop,
  1321. qMin(_fromNameLabel.maxWidth(), width() / 3),
  1322. st::mediaviewFont->height);
  1323. _dateNav = QRect(
  1324. st::mediaviewTextLeft + _nameNav.width() + st::mediaviewTextSkip,
  1325. height() - st::mediaviewTextTop,
  1326. st::mediaviewFont->width(_dateText),
  1327. st::mediaviewFont->height);
  1328. } else {
  1329. _nameNav = QRect();
  1330. _dateNav = QRect(
  1331. st::mediaviewTextLeft,
  1332. height() - st::mediaviewTextTop,
  1333. st::mediaviewFont->width(_dateText),
  1334. st::mediaviewFont->height);
  1335. }
  1336. updateHeader();
  1337. refreshNavVisibility();
  1338. resizeCenteredControls();
  1339. updateOver(_widget->mapFromGlobal(QCursor::pos()));
  1340. update();
  1341. }
  1342. void OverlayWidget::resizeCenteredControls() {
  1343. const auto bottomSkip = std::max(
  1344. _dateNav.left() + _dateNav.width(),
  1345. _headerNav.left() + _headerNav.width())
  1346. + st::mediaviewCaptionMargin.width();
  1347. _groupThumbsAvailableWidth = std::max(
  1348. width() - 2 * bottomSkip,
  1349. st::msgMinWidth
  1350. + st::mediaviewCaptionPadding.left()
  1351. + st::mediaviewCaptionPadding.right());
  1352. _groupThumbsLeft = (width() - _groupThumbsAvailableWidth) / 2;
  1353. refreshGroupThumbs();
  1354. _groupThumbsTop = _groupThumbs ? (height() - _groupThumbs->height()) : 0;
  1355. refreshClipControllerGeometry();
  1356. refreshSponsoredButtonGeometry();
  1357. refreshCaptionGeometry();
  1358. refreshSponsoredButtonWidth();
  1359. }
  1360. void OverlayWidget::refreshCaptionGeometry() {
  1361. _caption.updateSkipBlock(0, 0);
  1362. _captionShowMoreWidth = 0;
  1363. _captionSkipBlockWidth = 0;
  1364. const auto storiesCaptionWidth = _w
  1365. - st::mediaviewCaptionPadding.left()
  1366. - st::mediaviewCaptionPadding.right();
  1367. if (_caption.isEmpty() && (!_stories || !_stories->repost())) {
  1368. _captionRect = QRect();
  1369. return;
  1370. } else if (_fullScreenVideo) {
  1371. _captionRect = QRect();
  1372. return;
  1373. }
  1374. if (_groupThumbs && _groupThumbs->hiding()) {
  1375. _groupThumbs = nullptr;
  1376. _groupThumbsRect = QRect();
  1377. }
  1378. const auto captionBottom = _stories
  1379. ? (_y + _h)
  1380. : _sponsoredButton
  1381. ? (_sponsoredButton->y() - st::mediaviewCaptionMargin.height())
  1382. : (_streamed && _streamed->controls)
  1383. ? (_streamed->controls->y() - st::mediaviewCaptionMargin.height())
  1384. : _groupThumbs
  1385. ? _groupThumbsTop
  1386. : height() - st::mediaviewCaptionMargin.height();
  1387. const auto captionWidth = _stories
  1388. ? storiesCaptionWidth
  1389. : std::min(
  1390. (_groupThumbsAvailableWidth
  1391. - st::mediaviewCaptionPadding.left()
  1392. - st::mediaviewCaptionPadding.right()),
  1393. _caption.maxWidth());
  1394. const auto lineHeight = st::mediaviewCaptionStyle.font->height;
  1395. const auto wantedHeight = _caption.countHeight(captionWidth);
  1396. const auto maxHeight = !_stories
  1397. ? (_maxUsedHeight / 4)
  1398. : (wantedHeight > lineHeight * Stories::kMaxShownCaptionLines)
  1399. ? (lineHeight * Stories::kCollapsedCaptionLines)
  1400. : wantedHeight;
  1401. const auto captionHeight = std::min(
  1402. wantedHeight,
  1403. (maxHeight / lineHeight) * lineHeight);
  1404. if (_stories && captionHeight < wantedHeight) {
  1405. const auto padding = st::storiesShowMorePadding;
  1406. _captionShowMoreWidth = st::storiesShowMoreFont->width(
  1407. tr::lng_stories_show_more(tr::now));
  1408. _captionSkipBlockWidth = _captionShowMoreWidth
  1409. + padding.left()
  1410. + padding.right()
  1411. - st::mediaviewCaptionPadding.right();
  1412. const auto skiph = st::storiesShowMoreFont->height
  1413. + padding.bottom()
  1414. - st::mediaviewCaptionPadding.bottom();
  1415. _caption.updateSkipBlock(_captionSkipBlockWidth, skiph);
  1416. }
  1417. _captionRect = QRect(
  1418. (width() - captionWidth) / 2,
  1419. (captionBottom
  1420. - captionHeight
  1421. - st::mediaviewCaptionPadding.bottom()),
  1422. captionWidth,
  1423. captionHeight);
  1424. }
  1425. void OverlayWidget::refreshSponsoredButtonGeometry() {
  1426. if (!_sponsoredButton) {
  1427. return;
  1428. }
  1429. const auto controllerBottom = (_groupThumbs && !_fullScreenVideo)
  1430. ? _groupThumbsTop
  1431. : height();
  1432. const auto captionRect = captionGeometry();
  1433. _sponsoredButton->resize(
  1434. captionRect.width(),
  1435. _sponsoredButton->height());
  1436. _sponsoredButton->move(
  1437. (width() - captionRect.width()) / 2,
  1438. (controllerBottom // Duplicated in recountSkipTop().
  1439. - ((_streamed && _streamed->controls)
  1440. ? (_streamed->controls->height()
  1441. + st::mediaviewCaptionPadding.bottom())
  1442. : 0)
  1443. - _sponsoredButton->height()
  1444. - st::mediaviewCaptionMargin.height()));
  1445. Ui::SendPendingMoveResizeEvents(_sponsoredButton.get());
  1446. }
  1447. void OverlayWidget::refreshSponsoredButtonWidth() {
  1448. if (!_sponsoredButton) {
  1449. return;
  1450. }
  1451. const auto captionWidth = captionGeometry().width();
  1452. _sponsoredButton->resize(captionWidth, _sponsoredButton->height());
  1453. _sponsoredButton->move(
  1454. (width() - captionWidth) / 2,
  1455. _sponsoredButton->y());
  1456. }
  1457. void OverlayWidget::fillContextMenuActions(
  1458. const Ui::Menu::MenuCallback &addAction) {
  1459. if (_message && _message->isSponsored()) {
  1460. if (const auto window = findWindow()) {
  1461. const auto show = window->uiShow();
  1462. const auto fullId = _message->fullId();
  1463. Menu::FillSponsored(_body, addAction, show, fullId, true);
  1464. }
  1465. return;
  1466. }
  1467. const auto story = _stories ? _stories->story() : nullptr;
  1468. if (!story && _document && _document->loading()) {
  1469. addAction(
  1470. tr::lng_cancel(tr::now),
  1471. [=] { saveCancel(); },
  1472. &st::mediaMenuIconCancel);
  1473. }
  1474. if (_message && _message->isRegular()) {
  1475. addAction(
  1476. tr::lng_context_to_msg(tr::now),
  1477. [=] { toMessage(); },
  1478. &st::mediaMenuIconShowInChat);
  1479. }
  1480. if (story && story->peer()->isSelf()) {
  1481. const auto inProfile = story->inProfile();
  1482. const auto text = inProfile
  1483. ? tr::lng_mediaview_archive_story(tr::now)
  1484. : tr::lng_mediaview_save_to_profile(tr::now);
  1485. addAction(text, [=] {
  1486. if (_stories) {
  1487. _stories->toggleInProfileRequested(!inProfile);
  1488. }
  1489. }, (inProfile
  1490. ? &st::mediaMenuIconArchiveStory
  1491. : &st::mediaMenuIconSaveStory));
  1492. }
  1493. if ((!story || story->canDownloadChecked())
  1494. && _document
  1495. && !_document->filepath(true).isEmpty()) {
  1496. const auto text = Platform::IsMac()
  1497. ? tr::lng_context_show_in_finder(tr::now)
  1498. : tr::lng_context_show_in_folder(tr::now);
  1499. addAction(
  1500. text,
  1501. [=] { showInFolder(); },
  1502. &st::mediaMenuIconShowInFolder);
  1503. }
  1504. if (!hasCopyMediaRestriction()) {
  1505. if ((_document && documentContentShown()) || (_photo && _photoMedia->loaded())) {
  1506. addAction(
  1507. ((_document && _streamed)
  1508. ? tr::lng_mediaview_copy_frame(tr::now)
  1509. : tr::lng_mediaview_copy(tr::now)),
  1510. [=] { copyMedia(); },
  1511. &st::mediaMenuIconCopy);
  1512. }
  1513. }
  1514. if ((_photo && _photo->hasAttachedStickers())
  1515. || (_document && _document->hasAttachedStickers())) {
  1516. addAction(
  1517. tr::lng_context_attached_stickers(tr::now),
  1518. [=] { showAttachedStickers(); },
  1519. &st::mediaMenuIconStickers);
  1520. }
  1521. if (_message && _message->allowsForward()) {
  1522. addAction(
  1523. tr::lng_mediaview_forward(tr::now),
  1524. [=] { forwardMedia(); },
  1525. &st::mediaMenuIconForward);
  1526. if (canShareAtTime()) {
  1527. const auto now = [=] {
  1528. return tr::lng_mediaview_share_at_time(
  1529. tr::now,
  1530. lt_time,
  1531. Stories::FormatShareAtTime(shareAtVideoTimestamp()));
  1532. };
  1533. const auto action = addAction(
  1534. now(),
  1535. [=] { shareAtTime(); },
  1536. &st::mediaMenuIconShare);
  1537. struct State {
  1538. rpl::variable<QString> text;
  1539. rpl::lifetime lifetime;
  1540. };
  1541. const auto state = Ui::CreateChild<State>(action);
  1542. state->text = rpl::single(
  1543. rpl::empty
  1544. ) | rpl::then(
  1545. base::timer_each(120)
  1546. ) | rpl::map(now);
  1547. state->text.changes() | rpl::start_with_next([=](QString value) {
  1548. action->setText(value);
  1549. }, state->lifetime);
  1550. }
  1551. }
  1552. if (story && story->canShare()) {
  1553. addAction(tr::lng_mediaview_forward(tr::now), [=] {
  1554. _stories->shareRequested();
  1555. }, &st::mediaMenuIconForward);
  1556. }
  1557. const auto canDelete = [&] {
  1558. if (story && story->canDelete()) {
  1559. return true;
  1560. } else if (_message && _message->canDelete()) {
  1561. return true;
  1562. } else if (!_message
  1563. && _photo
  1564. && _user
  1565. && _user == _user->session().user()) {
  1566. return _userPhotosData && _fullIndex && _fullCount;
  1567. } else if (_photo && _photo->peer && _photo->peer->userpicPhotoId() == _photo->id) {
  1568. if (auto chat = _photo->peer->asChat()) {
  1569. return chat->canEditInformation();
  1570. } else if (auto channel = _photo->peer->asChannel()) {
  1571. return channel->canEditInformation();
  1572. }
  1573. }
  1574. return false;
  1575. }();
  1576. if (canDelete) {
  1577. addAction(
  1578. tr::lng_mediaview_delete(tr::now),
  1579. [=] { deleteMedia(); },
  1580. &st::mediaMenuIconDelete);
  1581. }
  1582. if (!hasCopyMediaRestriction(true)) {
  1583. addAction(
  1584. tr::lng_mediaview_save_as(tr::now),
  1585. [=] { saveAs(); },
  1586. (saveControlLocked()
  1587. ? &st::mediaMenuIconDownloadLocked
  1588. : &st::mediaMenuIconDownload));
  1589. }
  1590. if (const auto overviewType = computeOverviewType()) {
  1591. const auto text = _document
  1592. ? tr::lng_mediaview_files_all(tr::now)
  1593. : tr::lng_mediaview_photos_all(tr::now);
  1594. addAction(
  1595. text,
  1596. [=] { showMediaOverview(); },
  1597. &st::mediaMenuIconShowAll);
  1598. }
  1599. [&] { // Set userpic.
  1600. if (!_peer || !_photo || (_peer->userpicPhotoId() == _photo->id)) {
  1601. return;
  1602. }
  1603. using Type = SharedMediaType;
  1604. if (sharedMediaType().value_or(Type::File) == Type::ChatPhoto) {
  1605. if (const auto chat = _peer->asChat()) {
  1606. if (!chat->canEditInformation()) {
  1607. return;
  1608. }
  1609. } else if (const auto channel = _peer->asChannel()) {
  1610. if (!channel->canEditInformation()) {
  1611. return;
  1612. }
  1613. } else {
  1614. return;
  1615. }
  1616. } else if (userPhotosKey()) {
  1617. if (_user != _user->session().user()) {
  1618. return;
  1619. }
  1620. } else {
  1621. return;
  1622. }
  1623. const auto photo = _photo;
  1624. const auto peer = _peer;
  1625. addAction(tr::lng_mediaview_set_userpic(tr::now), [=] {
  1626. auto lifetime = std::make_shared<rpl::lifetime>();
  1627. peer->session().changes().peerFlagsValue(
  1628. peer,
  1629. Data::PeerUpdate::Flag::Photo
  1630. ) | rpl::start_with_next([=]() mutable {
  1631. if (lifetime) {
  1632. base::take(lifetime)->destroy();
  1633. }
  1634. close();
  1635. }, *lifetime);
  1636. peer->session().api().peerPhoto().set(peer, photo);
  1637. }, &st::mediaMenuIconProfile);
  1638. }();
  1639. [&] { // Report userpic.
  1640. if (!_peer || !_photo) {
  1641. return;
  1642. }
  1643. using Type = SharedMediaType;
  1644. if (userPhotosKey()) {
  1645. if (_peer->isSelf() || _peer->isNotificationsUser()) {
  1646. return;
  1647. } else if (const auto user = _peer->asUser()) {
  1648. if (user->hasPersonalPhoto()
  1649. && user->userpicPhotoId() == _photo->id) {
  1650. return;
  1651. }
  1652. }
  1653. } else if ((sharedMediaType().value_or(Type::File) == Type::ChatPhoto)
  1654. || (_peer->userpicPhotoId() == _photo->id)) {
  1655. if (const auto chat = _peer->asChat()) {
  1656. if (chat->canEditInformation()) {
  1657. return;
  1658. }
  1659. } else if (const auto channel = _peer->asChannel()) {
  1660. if (channel->canEditInformation()) {
  1661. return;
  1662. }
  1663. } else {
  1664. return;
  1665. }
  1666. } else {
  1667. return;
  1668. }
  1669. const auto photo = _photo;
  1670. const auto peer = _peer;
  1671. addAction(tr::lng_mediaview_report_profile_photo(tr::now), [=] {
  1672. if (const auto window = findWindow()) {
  1673. close();
  1674. window->show(
  1675. ReportProfilePhotoBox(peer, photo),
  1676. Ui::LayerOption::CloseOther);
  1677. }
  1678. }, &st::mediaMenuIconReport);
  1679. }();
  1680. {
  1681. const auto channel = story ? story->peer()->asChannel() : nullptr;
  1682. using Flag = ChannelDataFlag;
  1683. if (channel && (channel->flags() & Flag::CanGetStatistics)) {
  1684. const auto peer = channel;
  1685. const auto fullId = story->fullId();
  1686. addAction(tr::lng_stats_title(tr::now), [=] {
  1687. if (const auto window = findWindow()) {
  1688. close();
  1689. using namespace Info;
  1690. window->showSection(Statistics::Make(peer, {}, fullId));
  1691. }
  1692. }, &st::mediaMenuIconStats);
  1693. }
  1694. }
  1695. if (_stories
  1696. && _stories->allowStealthMode()
  1697. && story
  1698. && story->peer()->isUser()) {
  1699. const auto now = base::unixtime::now();
  1700. const auto stealth = _session->data().stories().stealthMode();
  1701. addAction(tr::lng_stealth_mode_menu_item(tr::now), [=] {
  1702. _stories->setupStealthMode();
  1703. }, ((_session->premium() || (stealth.enabledTill > now))
  1704. ? &st::mediaMenuIconStealth
  1705. : &st::mediaMenuIconStealthLocked));
  1706. }
  1707. if (story && story->canReport()) {
  1708. addAction(tr::lng_profile_report(tr::now), [=] {
  1709. _stories->reportRequested();
  1710. }, &st::mediaMenuIconReport);
  1711. }
  1712. }
  1713. auto OverlayWidget::computeOverviewType() const
  1714. -> std::optional<SharedMediaType> {
  1715. if (const auto mediaType = sharedMediaType()) {
  1716. if (const auto overviewType = SharedMediaOverviewType(*mediaType)) {
  1717. return overviewType;
  1718. } else if (mediaType == SharedMediaType::PhotoVideo) {
  1719. if (_photo) {
  1720. return SharedMediaOverviewType(SharedMediaType::Photo);
  1721. } else if (_document) {
  1722. return SharedMediaOverviewType(SharedMediaType::Video);
  1723. }
  1724. }
  1725. }
  1726. return std::nullopt;
  1727. }
  1728. bool OverlayWidget::stateAnimationCallback(crl::time now) {
  1729. if (anim::Disabled()) {
  1730. now += st::mediaviewShowDuration + st::mediaviewHideDuration;
  1731. }
  1732. for (auto i = begin(_animations); i != end(_animations);) {
  1733. const auto &[state, started] = *i;
  1734. updateOverRect(state);
  1735. const auto dt = float64(now - started) / st::mediaviewFadeDuration;
  1736. if (dt >= 1) {
  1737. _animationOpacities.erase(state);
  1738. i = _animations.erase(i);
  1739. } else {
  1740. _animationOpacities[state].update(dt, anim::linear);
  1741. ++i;
  1742. }
  1743. }
  1744. return !_animations.empty() || updateControlsAnimation(now);
  1745. }
  1746. bool OverlayWidget::updateControlsAnimation(crl::time now) {
  1747. if (_controlsState != ControlsShowing
  1748. && _controlsState != ControlsHiding) {
  1749. return false;
  1750. }
  1751. const auto duration = (_controlsState == ControlsShowing)
  1752. ? st::mediaviewShowDuration
  1753. : st::mediaviewHideDuration;
  1754. const auto dt = float64(now - _controlsAnimStarted)
  1755. / duration;
  1756. if (dt >= 1) {
  1757. _controlsOpacity.finish();
  1758. _controlsState = (_controlsState == ControlsShowing)
  1759. ? ControlsShown
  1760. : ControlsHidden;
  1761. updateCursor();
  1762. } else {
  1763. _controlsOpacity.update(dt, anim::linear);
  1764. }
  1765. if (_sponsoredButton) {
  1766. const auto value = _controlsOpacity.current();
  1767. _sponsoredButton->setOpacity(value);
  1768. _sponsoredButton->setAttribute(
  1769. Qt::WA_TransparentForMouseEvents,
  1770. value < 1);
  1771. }
  1772. _helper->setControlsOpacity(_controlsOpacity.current());
  1773. const auto content = finalContentRect();
  1774. const auto siblingType = (_over == Over::LeftStories)
  1775. ? Stories::SiblingType::Left
  1776. : Stories::SiblingType::Right;
  1777. const auto toUpdate = QRegion()
  1778. + (_over == Over::Left ? _leftNavOver : _leftNavIcon)
  1779. + (_over == Over::Right ? _rightNavOver : _rightNavIcon)
  1780. + (_over == Over::Save ? _saveNavOver : _saveNavIcon)
  1781. + (_over == Over::Share ? _shareNavOver : _shareNavIcon)
  1782. + (_over == Over::Rotate ? _rotateNavOver : _rotateNavIcon)
  1783. + (_over == Over::More ? _moreNavOver : _moreNavIcon)
  1784. + ((_stories
  1785. && (_over == Over::LeftStories || _over == Over::RightStories))
  1786. ? _stories->sibling(siblingType).layout.geometry
  1787. : QRect())
  1788. + _headerNav
  1789. + _nameNav
  1790. + _dateNav
  1791. + _captionRect.marginsAdded(st::mediaviewCaptionPadding)
  1792. + _groupThumbsRect
  1793. + content.intersected(_bottomShadowRect)
  1794. + content.intersected(_topShadowRect);
  1795. update(toUpdate);
  1796. return (dt < 1);
  1797. }
  1798. void OverlayWidget::waitingAnimationCallback() {
  1799. if (!anim::Disabled()) {
  1800. update(radialRect());
  1801. }
  1802. }
  1803. void OverlayWidget::updateCursor() {
  1804. setCursor((_controlsState == ControlsHidden)
  1805. ? Qt::BlankCursor
  1806. : (_over == Over::None || (_over == Over::Video && _stories))
  1807. ? style::cur_default
  1808. : style::cur_pointer);
  1809. }
  1810. int OverlayWidget::finalContentRotation() const {
  1811. return _streamed
  1812. ? ((_rotation + (_streamed
  1813. ? _streamed->instance.info().video.rotation
  1814. : 0)) % 360)
  1815. : _rotation;
  1816. }
  1817. QRect OverlayWidget::finalContentRect() const {
  1818. return { _x, _y, _w, _h };
  1819. }
  1820. OverlayWidget::ContentGeometry OverlayWidget::contentGeometry() const {
  1821. if (_stories) {
  1822. auto result = storiesContentGeometry(_stories->contentLayout());
  1823. if (!_caption.isEmpty()) {
  1824. result.bottomShadowSkip = _widget->height()
  1825. - _captionRect.y()
  1826. + st::mediaviewCaptionStyle.font->height
  1827. - st::storiesShadowBottom.height();
  1828. }
  1829. return result;
  1830. }
  1831. const auto controlsOpacity = _controlsOpacity.current();
  1832. const auto toRotation = qreal(finalContentRotation());
  1833. const auto toRectRotated = QRectF(finalContentRect());
  1834. const auto toRectCenter = toRectRotated.center();
  1835. const auto toRect = ((int(toRotation) % 180) == 90)
  1836. ? QRectF(
  1837. toRectCenter.x() - toRectRotated.height() / 2.,
  1838. toRectCenter.y() - toRectRotated.width() / 2.,
  1839. toRectRotated.height(),
  1840. toRectRotated.width())
  1841. : toRectRotated;
  1842. if (!_geometryAnimation.animating()) {
  1843. return { toRect, toRotation, controlsOpacity };
  1844. }
  1845. const auto fromRect = _oldGeometry.rect;
  1846. const auto fromRotation = _oldGeometry.rotation;
  1847. const auto progress = _geometryAnimation.value(1.);
  1848. const auto rotationDelta = (toRotation - fromRotation);
  1849. const auto useRotationDelta = (rotationDelta > 180.)
  1850. ? (rotationDelta - 360.)
  1851. : (rotationDelta <= -180.)
  1852. ? (rotationDelta + 360.)
  1853. : rotationDelta;
  1854. const auto rotation = fromRotation + useRotationDelta * progress;
  1855. const auto useRotation = (rotation > 360.)
  1856. ? (rotation - 360.)
  1857. : (rotation < 0.)
  1858. ? (rotation + 360.)
  1859. : rotation;
  1860. const auto useRect = QRectF(
  1861. fromRect.x() + (toRect.x() - fromRect.x()) * progress,
  1862. fromRect.y() + (toRect.y() - fromRect.y()) * progress,
  1863. fromRect.width() + (toRect.width() - fromRect.width()) * progress,
  1864. fromRect.height() + (toRect.height() - fromRect.height()) * progress
  1865. );
  1866. return { useRect, useRotation, controlsOpacity };
  1867. }
  1868. OverlayWidget::ContentGeometry OverlayWidget::storiesContentGeometry(
  1869. const Stories::ContentLayout &layout,
  1870. float64 scale) const {
  1871. return {
  1872. .rect = QRectF(layout.geometry),
  1873. .controlsOpacity = kStoriesControlsOpacity,
  1874. .fade = layout.fade,
  1875. .scale = scale,
  1876. .roundRadius = layout.radius,
  1877. .topShadowShown = !layout.headerOutside,
  1878. };
  1879. }
  1880. void OverlayWidget::updateContentRect() {
  1881. if (_opengl) {
  1882. update();
  1883. } else {
  1884. update(finalContentRect());
  1885. }
  1886. }
  1887. void OverlayWidget::contentSizeChanged() {
  1888. _width = _w;
  1889. _height = _h;
  1890. resizeContentByScreenSize();
  1891. }
  1892. void OverlayWidget::recountSkipTop() {
  1893. const auto controllerBottomNoFullScreenVideo = _groupThumbs
  1894. ? _groupThumbsTop
  1895. : height();
  1896. // We need the bottom in case of non-full-screen-video mode
  1897. // to count correct _availableHeight in non-full-screen-video mode.
  1898. //
  1899. // Originally this is controls->y() - padding.bottom().
  1900. const auto bottom = (_streamed && _streamed->controls)
  1901. ? (controllerBottomNoFullScreenVideo
  1902. - _streamed->controls->height()
  1903. - 2 * st::mediaviewCaptionPadding.bottom())
  1904. : height();
  1905. const auto skipHeightBottom = (height() - bottom);
  1906. _skipTop = _minUsedTop + std::min(
  1907. std::max(
  1908. st::mediaviewCaptionMargin.height(),
  1909. height() - _height - skipHeightBottom),
  1910. skipHeightBottom);
  1911. _availableHeight = height() - skipHeightBottom - _skipTop;
  1912. if (_fullScreenVideo && skipHeightBottom > 0 && _width > 0) {
  1913. const auto h = width() * _height / _width;
  1914. const auto topAllFit = _maxUsedHeight - skipHeightBottom - h;
  1915. if (_skipTop > topAllFit) {
  1916. _skipTop = std::max(topAllFit, 0);
  1917. }
  1918. }
  1919. }
  1920. void OverlayWidget::resizeContentByScreenSize() {
  1921. if (_stories) {
  1922. const auto content = _stories->finalShownGeometry();
  1923. _x = content.x();
  1924. _y = content.y();
  1925. _w = content.width();
  1926. _h = content.height();
  1927. _zoom = 0;
  1928. updateNavigationControlsGeometry();
  1929. return;
  1930. }
  1931. recountSkipTop();
  1932. const auto availableWidth = width();
  1933. const auto countZoomFor = [&](int outerw, int outerh) {
  1934. auto result = float64(outerw) / _width;
  1935. if (_height * result > outerh) {
  1936. result = float64(outerh) / _height;
  1937. }
  1938. if (result >= 1.) {
  1939. result -= 1.;
  1940. } else {
  1941. result = 1. - (1. / result);
  1942. }
  1943. return result;
  1944. };
  1945. if (_width > 0 && _height > 0) {
  1946. _zoomToDefault = countZoomFor(availableWidth, _availableHeight);
  1947. _zoomToScreen = countZoomFor(width(), _maxUsedHeight);
  1948. } else {
  1949. _zoomToDefault = _zoomToScreen = 0;
  1950. }
  1951. const auto usew = _fullScreenVideo ? width() : availableWidth;
  1952. const auto useh = _fullScreenVideo ? _maxUsedHeight : _availableHeight;
  1953. if ((_width > usew) || (_height > useh) || _fullScreenVideo) {
  1954. const auto use = _fullScreenVideo ? _zoomToScreen : _zoomToDefault;
  1955. _zoom = kZoomToScreenLevel;
  1956. if (use >= 0) {
  1957. _w = qRound(_width * (use + 1));
  1958. _h = qRound(_height * (use + 1));
  1959. } else {
  1960. _w = qRound(_width / (-use + 1));
  1961. _h = qRound(_height / (-use + 1));
  1962. }
  1963. } else {
  1964. _zoom = 0;
  1965. _w = _width;
  1966. _h = _height;
  1967. }
  1968. _x = (width() - _w) / 2;
  1969. _y = _skipTop + (useh - _h) / 2;
  1970. _geometryAnimation.stop();
  1971. }
  1972. float64 OverlayWidget::radialProgress() const {
  1973. if (_document) {
  1974. return _documentMedia->progress();
  1975. } else if (_photo) {
  1976. return _photoMedia->progress();
  1977. }
  1978. return 1.;
  1979. }
  1980. bool OverlayWidget::radialLoading() const {
  1981. if (_streamed) {
  1982. return false;
  1983. } else if (_document) {
  1984. return _document->loading();
  1985. } else if (_photo) {
  1986. return _photo->displayLoading();
  1987. }
  1988. return false;
  1989. }
  1990. QRect OverlayWidget::radialRect() const {
  1991. if (_photo) {
  1992. return _photoRadialRect;
  1993. } else if (_document) {
  1994. return QRect(
  1995. QPoint(
  1996. _docIconRect.x() + ((_docIconRect.width() - st::radialSize.width()) / 2),
  1997. _docIconRect.y() + ((_docIconRect.height() - st::radialSize.height()) / 2)),
  1998. st::radialSize);
  1999. }
  2000. return QRect();
  2001. }
  2002. void OverlayWidget::radialStart() {
  2003. if (radialLoading() && !_radial.animating()) {
  2004. _radial.start(radialProgress());
  2005. if (auto shift = radialTimeShift()) {
  2006. _radial.update(radialProgress(), !radialLoading(), crl::now() + shift);
  2007. }
  2008. }
  2009. }
  2010. crl::time OverlayWidget::radialTimeShift() const {
  2011. return _photo ? st::radialDuration : 0;
  2012. }
  2013. bool OverlayWidget::radialAnimationCallback(crl::time now) {
  2014. if ((!_document && !_photo) || _streamed) {
  2015. return false;
  2016. }
  2017. const auto wasAnimating = _radial.animating();
  2018. const auto updated = _radial.update(
  2019. radialProgress(),
  2020. !radialLoading(),
  2021. now + radialTimeShift());
  2022. if ((wasAnimating || _radial.animating())
  2023. && (!anim::Disabled() || updated)) {
  2024. update(radialRect());
  2025. }
  2026. const auto ready = _document && _documentMedia->loaded();
  2027. const auto streamVideo = ready && _documentMedia->canBePlayed(_message);
  2028. const auto tryOpenImage = ready
  2029. && (_document->size < Images::kReadBytesLimit);
  2030. if (ready && ((tryOpenImage && !_radial.animating()) || streamVideo)) {
  2031. _streamingStartPaused = false;
  2032. if (streamVideo) {
  2033. redisplayContent();
  2034. } else {
  2035. auto &location = _document->location(true);
  2036. if (location.accessEnable()) {
  2037. if (_document->isTheme()
  2038. || QImageReader(location.name()).canRead()) {
  2039. redisplayContent();
  2040. }
  2041. location.accessDisable();
  2042. }
  2043. }
  2044. }
  2045. return true;
  2046. }
  2047. void OverlayWidget::zoomIn() {
  2048. auto newZoom = _zoom;
  2049. const auto full = _fullScreenVideo ? _zoomToScreen : _zoomToDefault;
  2050. if (newZoom == kZoomToScreenLevel) {
  2051. if (qCeil(full) <= kMaxZoomLevel) {
  2052. newZoom = qCeil(full);
  2053. }
  2054. } else {
  2055. if (newZoom < full && (newZoom + 1 > full || (full > kMaxZoomLevel && newZoom == kMaxZoomLevel))) {
  2056. newZoom = kZoomToScreenLevel;
  2057. } else if (newZoom < kMaxZoomLevel) {
  2058. ++newZoom;
  2059. }
  2060. }
  2061. zoomUpdate(newZoom);
  2062. }
  2063. void OverlayWidget::zoomOut() {
  2064. auto newZoom = _zoom;
  2065. const auto full = _fullScreenVideo ? _zoomToScreen : _zoomToDefault;
  2066. if (newZoom == kZoomToScreenLevel) {
  2067. if (qFloor(full) >= -kMaxZoomLevel) {
  2068. newZoom = qFloor(full);
  2069. }
  2070. } else {
  2071. if (newZoom > full && (newZoom - 1 < full || (full < -kMaxZoomLevel && newZoom == -kMaxZoomLevel))) {
  2072. newZoom = kZoomToScreenLevel;
  2073. } else if (newZoom > -kMaxZoomLevel) {
  2074. --newZoom;
  2075. }
  2076. }
  2077. zoomUpdate(newZoom);
  2078. }
  2079. void OverlayWidget::zoomReset() {
  2080. if (_stories || _fullScreenVideo) {
  2081. return;
  2082. }
  2083. auto newZoom = _zoom;
  2084. const auto full = _fullScreenVideo ? _zoomToScreen : _zoomToDefault;
  2085. if (_zoom == 0) {
  2086. if (qFloor(full) == qCeil(full) && qRound(full) >= -kMaxZoomLevel && qRound(full) <= kMaxZoomLevel) {
  2087. newZoom = qRound(full);
  2088. } else {
  2089. newZoom = kZoomToScreenLevel;
  2090. }
  2091. } else {
  2092. newZoom = 0;
  2093. }
  2094. _x = -_width / 2;
  2095. _y = _skipTop - (_height / 2);
  2096. float64 z = (_zoom == kZoomToScreenLevel) ? full : _zoom;
  2097. if (z >= 0) {
  2098. _x = qRound(_x * (z + 1));
  2099. _y = qRound(_y * (z + 1));
  2100. } else {
  2101. _x = qRound(_x / (-z + 1));
  2102. _y = qRound(_y / (-z + 1));
  2103. }
  2104. _x += width() / 2;
  2105. _y += _availableHeight / 2;
  2106. update();
  2107. zoomUpdate(newZoom);
  2108. }
  2109. void OverlayWidget::zoomUpdate(int32 &newZoom) {
  2110. if (newZoom != kZoomToScreenLevel) {
  2111. while ((newZoom < 0 && (-newZoom + 1) > _w) || (-newZoom + 1) > _h) {
  2112. ++newZoom;
  2113. }
  2114. }
  2115. setZoomLevel(newZoom);
  2116. }
  2117. void OverlayWidget::clearSession() {
  2118. if (!isHidden()) {
  2119. hide();
  2120. }
  2121. _sessionLifetime.destroy();
  2122. if (!_animations.empty()) {
  2123. _animations.clear();
  2124. _stateAnimation.stop();
  2125. }
  2126. if (!_animationOpacities.empty()) {
  2127. _animationOpacities.clear();
  2128. }
  2129. clearStreaming();
  2130. setContext(v::null);
  2131. _from = nullptr;
  2132. _fromName = QString();
  2133. assignMediaPointer(nullptr);
  2134. _fullScreenVideo = false;
  2135. _caption.clear();
  2136. _sharedMedia = nullptr;
  2137. _userPhotos = nullptr;
  2138. _collage = nullptr;
  2139. _session = nullptr;
  2140. }
  2141. OverlayWidget::~OverlayWidget() {
  2142. clearSession();
  2143. // Otherwise dropdownHidden() may be called from the destructor.
  2144. _dropdown.destroy();
  2145. }
  2146. void OverlayWidget::assignMediaPointer(DocumentData *document) {
  2147. _savePhotoVideoWhenLoaded = SavePhotoVideo::None;
  2148. _flip = {};
  2149. _photo = nullptr;
  2150. _photoMedia = nullptr;
  2151. if (_document != document) {
  2152. _streamedQualityChangeFrame = QImage();
  2153. _streamedQualityChangeFinished = false;
  2154. if ((_document = document)) {
  2155. _quality = Core::App().settings().videoQuality();
  2156. _chosenQuality = _document->chooseQuality(_message, _quality);
  2157. _documentMedia = _document->createMediaView();
  2158. _videoCover = LookupVideoCover(_document, _message);
  2159. if (_videoCover) {
  2160. _videoCoverMedia = _videoCover->createMediaView();
  2161. _videoCoverMedia->wanted(
  2162. Data::PhotoSize::Large,
  2163. fileOrigin());
  2164. } else {
  2165. _videoCoverMedia = nullptr;
  2166. _documentMedia->goodThumbnailWanted();
  2167. _documentMedia->thumbnailWanted(fileOrigin());
  2168. }
  2169. } else {
  2170. _chosenQuality = nullptr;
  2171. _documentMedia = nullptr;
  2172. _videoCover = nullptr;
  2173. _videoCoverMedia = nullptr;
  2174. }
  2175. _documentLoadingTo = QString();
  2176. }
  2177. }
  2178. void OverlayWidget::assignMediaPointer(not_null<PhotoData*> photo) {
  2179. _savePhotoVideoWhenLoaded = SavePhotoVideo::None;
  2180. _chosenQuality = nullptr;
  2181. _streamedQualityChangeFrame = QImage();
  2182. _streamedQualityChangeFinished = false;
  2183. _document = nullptr;
  2184. _documentMedia = nullptr;
  2185. _documentLoadingTo = QString();
  2186. _videoCover = nullptr;
  2187. _videoCoverMedia = nullptr;
  2188. if (_photo != photo) {
  2189. _flip = {};
  2190. _photo = photo;
  2191. _photoMedia = _photo->createMediaView();
  2192. _photoMedia->wanted(Data::PhotoSize::Small, fileOrigin());
  2193. if (!_photo->hasVideo() || _photo->videoPlaybackFailed()) {
  2194. _photo->load(fileOrigin(), LoadFromCloudOrLocal, true);
  2195. }
  2196. }
  2197. }
  2198. void OverlayWidget::clickHandlerActiveChanged(
  2199. const ClickHandlerPtr &p,
  2200. bool active) {
  2201. setCursor((active || ClickHandler::getPressed())
  2202. ? style::cur_pointer
  2203. : style::cur_default);
  2204. update(QRegion(_saveMsg) + captionGeometry());
  2205. }
  2206. void OverlayWidget::clickHandlerPressedChanged(
  2207. const ClickHandlerPtr &p,
  2208. bool pressed) {
  2209. setCursor((pressed || ClickHandler::getActive())
  2210. ? style::cur_pointer
  2211. : style::cur_default);
  2212. update(QRegion(_saveMsg) + captionGeometry());
  2213. }
  2214. rpl::lifetime &OverlayWidget::lifetime() {
  2215. return _surface->lifetime();
  2216. }
  2217. void OverlayWidget::showSaveMsgFile() {
  2218. File::ShowInFolder(_saveMsgFilename);
  2219. }
  2220. void OverlayWidget::close() {
  2221. if (isHidden()) {
  2222. return;
  2223. }
  2224. hide();
  2225. _helper->clearState();
  2226. }
  2227. void OverlayWidget::minimize() {
  2228. if (isHidden()) {
  2229. return;
  2230. }
  2231. _helper->minimize(_window);
  2232. }
  2233. void OverlayWidget::toggleFullScreen() {
  2234. toggleFullScreen(!_fullscreen);
  2235. }
  2236. void OverlayWidget::toggleFullScreen(bool fullscreen) {
  2237. _helper->clearState();
  2238. _fullscreen = fullscreen;
  2239. _windowed = !fullscreen;
  2240. initNormalGeometry();
  2241. if constexpr (Platform::IsMac()) {
  2242. _helper->beforeShow(_fullscreen);
  2243. updateGeometry();
  2244. _helper->afterShow(_fullscreen);
  2245. } else if (_fullscreen) {
  2246. updateGeometry();
  2247. _window->showFullScreen();
  2248. } else {
  2249. _wasWindowedMode = false;
  2250. _window->showNormal();
  2251. updateGeometry();
  2252. _wasWindowedMode = true;
  2253. }
  2254. savePosition();
  2255. _helper->clearState();
  2256. }
  2257. void OverlayWidget::activateControls() {
  2258. if (!_menu && !_mousePressed && !_stories) {
  2259. _controlsHideTimer.callOnce(st::mediaviewWaitHide);
  2260. }
  2261. if (_fullScreenVideo) {
  2262. if (_streamed && _streamed->controls) {
  2263. _streamed->controls->showAnimated();
  2264. }
  2265. }
  2266. if (_controlsState == ControlsHiding || _controlsState == ControlsHidden) {
  2267. _controlsState = ControlsShowing;
  2268. _controlsAnimStarted = crl::now();
  2269. _controlsOpacity.start(1);
  2270. if (!_stateAnimation.animating()) {
  2271. _stateAnimation.start();
  2272. }
  2273. }
  2274. }
  2275. void OverlayWidget::hideControls(bool force) {
  2276. if (_stories) {
  2277. _controlsState = ControlsShown;
  2278. _controlsOpacity = anim::value(1);
  2279. _helper->setControlsOpacity(1.);
  2280. return;
  2281. } else if (!force) {
  2282. if (!_dropdown->isHidden()
  2283. || (_streamed
  2284. && _streamed->controls
  2285. && _streamed->controls->hasMenu())
  2286. || _menu
  2287. || _mousePressed) {
  2288. return;
  2289. }
  2290. }
  2291. if (_fullScreenVideo && _streamed && _streamed->controls) {
  2292. _streamed->controls->hideAnimated();
  2293. }
  2294. if (_controlsState == ControlsHiding || _controlsState == ControlsHidden) return;
  2295. _lastMouseMovePos = _widget->mapFromGlobal(QCursor::pos());
  2296. _controlsState = ControlsHiding;
  2297. _controlsAnimStarted = crl::now();
  2298. _controlsOpacity.start(0);
  2299. if (!_stateAnimation.animating()) {
  2300. _stateAnimation.start();
  2301. }
  2302. }
  2303. void OverlayWidget::dropdownHidden() {
  2304. setFocus();
  2305. if (_stories) {
  2306. _stories->menuShown(false);
  2307. }
  2308. _ignoringDropdown = true;
  2309. _lastMouseMovePos = _widget->mapFromGlobal(QCursor::pos());
  2310. updateOver(_lastMouseMovePos);
  2311. _ignoringDropdown = false;
  2312. if (!_controlsHideTimer.isActive()) {
  2313. hideControls(true);
  2314. }
  2315. }
  2316. void OverlayWidget::handleScreenChanged(not_null<QScreen*> screen) {
  2317. subscribeToScreenGeometry();
  2318. if (isHidden()) {
  2319. return;
  2320. }
  2321. const auto screenList = QGuiApplication::screens();
  2322. DEBUG_LOG(("Viewer Pos: Screen changed to: %1")
  2323. .arg(screenList.indexOf(screen)));
  2324. moveToScreen();
  2325. }
  2326. void OverlayWidget::subscribeToScreenGeometry() {
  2327. _screenGeometryLifetime.destroy();
  2328. const auto screen = _window->screen();
  2329. if (!screen) {
  2330. return;
  2331. }
  2332. base::qt_signal_producer(
  2333. screen,
  2334. &QScreen::geometryChanged
  2335. ) | rpl::filter([=] {
  2336. return !isHidden() && !isMinimized() && _fullscreen;
  2337. }) | rpl::start_with_next([=] {
  2338. updateGeometry();
  2339. }, _screenGeometryLifetime);
  2340. }
  2341. void OverlayWidget::toMessage() {
  2342. if (const auto item = _message) {
  2343. close();
  2344. if (const auto window = findWindow()) {
  2345. window->showMessage(item);
  2346. }
  2347. }
  2348. }
  2349. void OverlayWidget::notifyFileDialogShown(bool shown) {
  2350. _helper->notifyFileDialogShown(shown);
  2351. }
  2352. void OverlayWidget::saveAs() {
  2353. if (showCopyMediaRestriction(true)) {
  2354. return;
  2355. } else if (hasCopyMediaRestriction()) {
  2356. Assert(_stories != nullptr);
  2357. showPremiumDownloadPromo();
  2358. return;
  2359. }
  2360. QString file;
  2361. if (_document) {
  2362. const auto &location = _document->location(true);
  2363. const auto bytes = _documentMedia->bytes();
  2364. if (!bytes.isEmpty() || location.accessEnable()) {
  2365. QFileInfo alreadyInfo(location.name());
  2366. QDir alreadyDir(alreadyInfo.dir());
  2367. QString name = alreadyInfo.fileName(), filter;
  2368. const auto mimeType = Core::MimeTypeForName(_document->mimeString());
  2369. QStringList p = mimeType.globPatterns();
  2370. QString pattern = p.isEmpty() ? QString() : p.front();
  2371. if (name.isEmpty()) {
  2372. name = pattern.isEmpty() ? u".unknown"_q : pattern.replace('*', QString());
  2373. }
  2374. if (pattern.isEmpty()) {
  2375. filter = QString();
  2376. } else {
  2377. filter = mimeType.filterString() + u";;"_q + FileDialog::AllFilesFilter();
  2378. }
  2379. file = FileNameForSave(
  2380. _session,
  2381. tr::lng_save_file(tr::now),
  2382. filter,
  2383. u"doc"_q,
  2384. name,
  2385. true,
  2386. alreadyDir);
  2387. if (!file.isEmpty() && file != location.name()) {
  2388. if (bytes.isEmpty()) {
  2389. QFile(file).remove();
  2390. QFile(location.name()).copy(file);
  2391. } else {
  2392. QFile f(file);
  2393. f.open(QIODevice::WriteOnly);
  2394. f.write(bytes);
  2395. }
  2396. if (_message) {
  2397. auto &manager = Core::App().downloadManager();
  2398. manager.addLoaded({
  2399. .item = _message,
  2400. .document = _document,
  2401. }, file, manager.computeNextStartDate());
  2402. }
  2403. }
  2404. if (bytes.isEmpty()) {
  2405. location.accessDisable();
  2406. }
  2407. } else {
  2408. DocumentSaveClickHandler::SaveAndTrack(
  2409. _message ? _message->fullId() : FullMsgId(),
  2410. _document,
  2411. DocumentSaveClickHandler::Mode::ToNewFile);
  2412. updateControls();
  2413. updateOver(_lastMouseMovePos);
  2414. }
  2415. } else if (_photo && _photo->hasVideo()) {
  2416. constexpr auto large = Data::PhotoSize::Large;
  2417. if (const auto bytes = _photoMedia->videoContent(large); !bytes.isEmpty()) {
  2418. const auto photo = _photo;
  2419. auto filter = u"Video Files (*.mp4);;"_q + FileDialog::AllFilesFilter();
  2420. FileDialog::GetWritePath(
  2421. _window.get(),
  2422. tr::lng_save_video(tr::now),
  2423. filter,
  2424. filedialogDefaultName(
  2425. u"photo"_q,
  2426. u".mp4"_q,
  2427. QString(),
  2428. false,
  2429. _photo->date()),
  2430. crl::guard(_window, [=](const QString &result) {
  2431. QFile f(result);
  2432. if (!result.isEmpty()
  2433. && _photo == photo
  2434. && f.open(QIODevice::WriteOnly)) {
  2435. f.write(bytes);
  2436. }
  2437. }));
  2438. } else {
  2439. _photo->loadVideo(large, fileOrigin());
  2440. _savePhotoVideoWhenLoaded = SavePhotoVideo::SaveAs;
  2441. }
  2442. } else {
  2443. if (!_photo || !_photoMedia->loaded()) {
  2444. return;
  2445. }
  2446. const auto media = _photoMedia;
  2447. const auto photo = _photo;
  2448. const auto filter = u"JPEG Image (*.jpg);;"_q
  2449. + FileDialog::AllFilesFilter();
  2450. FileDialog::GetWritePath(
  2451. _window.get(),
  2452. tr::lng_save_photo(tr::now),
  2453. filter,
  2454. filedialogDefaultName(
  2455. u"photo"_q,
  2456. u".jpg"_q,
  2457. QString(),
  2458. false,
  2459. _photo->date()),
  2460. crl::guard(_window, [=](const QString &result) {
  2461. if (!result.isEmpty() && _photo == photo) {
  2462. media->saveToFile(result);
  2463. }
  2464. }));
  2465. }
  2466. activate();
  2467. }
  2468. void OverlayWidget::handleDocumentClick() {
  2469. if (_document->loading()) {
  2470. saveCancel();
  2471. } else {
  2472. _reShow = true;
  2473. Data::ResolveDocument(
  2474. findWindow(),
  2475. _document,
  2476. _message,
  2477. _topicRootId);
  2478. if (_document && _document->loading() && !_radial.animating()) {
  2479. _radial.start(_documentMedia->progress());
  2480. }
  2481. _reShow = false;
  2482. }
  2483. }
  2484. bool OverlayWidget::canShareAtTime() const {
  2485. const auto media = _message ? _message->media() : nullptr;
  2486. return _document
  2487. && media
  2488. && _streamed
  2489. && (_document == media->document())
  2490. && _document->isVideoFile()
  2491. && !media->webpage();
  2492. }
  2493. TimeId OverlayWidget::shareAtVideoTimestamp() const {
  2494. return _streamedPosition / crl::time(1000);
  2495. }
  2496. void OverlayWidget::shareAtTime() {
  2497. if (!canShareAtTime()) {
  2498. return;
  2499. }
  2500. if (!_streamed->instance.player().paused()
  2501. && !_streamed->instance.player().finished()) {
  2502. playbackPauseResume();
  2503. }
  2504. const auto show = uiShow();
  2505. const auto timestamp = shareAtVideoTimestamp();
  2506. show->show(Stories::PrepareShareAtTimeBox(show, _message, timestamp));
  2507. }
  2508. void OverlayWidget::downloadMedia() {
  2509. if (!_photo && !_document) {
  2510. return;
  2511. } else if (Core::App().settings().askDownloadPath()) {
  2512. return saveAs();
  2513. } else if (hasCopyMediaRestriction()) {
  2514. if (_stories && !hasCopyMediaRestriction(true)) {
  2515. showPremiumDownloadPromo();
  2516. }
  2517. return;
  2518. }
  2519. QString path;
  2520. const auto session = _photo ? &_photo->session() : &_document->session();
  2521. if (Core::App().settings().downloadPath().isEmpty()) {
  2522. path = File::DefaultDownloadPath(session);
  2523. } else if (Core::App().settings().downloadPath() == FileDialog::Tmp()) {
  2524. path = session->local().tempDirectory();
  2525. } else {
  2526. path = Core::App().settings().downloadPath();
  2527. }
  2528. if (path.isEmpty()) return;
  2529. QString toName;
  2530. if (_document) {
  2531. const auto &location = _document->location(true);
  2532. if (location.accessEnable()) {
  2533. if (!QDir().exists(path)) QDir().mkpath(path);
  2534. toName = filedialogNextFilename(
  2535. _document->filename(),
  2536. location.name(),
  2537. path);
  2538. if (!toName.isEmpty() && toName != location.name()) {
  2539. QFile(toName).remove();
  2540. if (!QFile(location.name()).copy(toName)) {
  2541. toName = QString();
  2542. } else if (_message) {
  2543. auto &manager = Core::App().downloadManager();
  2544. manager.addLoaded({
  2545. .item = _message,
  2546. .document = _document,
  2547. }, toName, manager.computeNextStartDate());
  2548. }
  2549. }
  2550. if (_stories && !toName.isEmpty()) {
  2551. showSaveMsgToast(toName, tr::lng_mediaview_video_saved_to);
  2552. }
  2553. location.accessDisable();
  2554. } else {
  2555. if (_document->filepath(true).isEmpty()
  2556. && !_document->loading()) {
  2557. const auto document = _document;
  2558. const auto checkSaveStarted = [=] {
  2559. if (isHidden() || _document != document) {
  2560. return;
  2561. }
  2562. _documentLoadingTo = _document->loadingFilePath();
  2563. if (_stories && _documentLoadingTo.isEmpty()) {
  2564. const auto toName = _document->filepath(true);
  2565. if (!toName.isEmpty()) {
  2566. showSaveMsgToast(
  2567. toName,
  2568. tr::lng_mediaview_video_saved_to);
  2569. }
  2570. }
  2571. };
  2572. DocumentSaveClickHandler::SaveAndTrack(
  2573. _message ? _message->fullId() : FullMsgId(),
  2574. _document,
  2575. DocumentSaveClickHandler::Mode::ToFile,
  2576. crl::guard(_widget, checkSaveStarted));
  2577. } else {
  2578. _saveVisible = computeSaveButtonVisible();
  2579. update(_saveNavOver);
  2580. }
  2581. updateOver(_lastMouseMovePos);
  2582. }
  2583. } else if (_photo && _photo->hasVideo()) {
  2584. if (!_photoMedia->videoContent(Data::PhotoSize::Large).isEmpty()) {
  2585. if (!QDir().exists(path)) {
  2586. QDir().mkpath(path);
  2587. }
  2588. toName = filedialogDefaultName(u"photo"_q, u".mp4"_q, path);
  2589. if (!_photoMedia->saveToFile(toName)) {
  2590. toName = QString();
  2591. }
  2592. } else {
  2593. _photo->loadVideo(Data::PhotoSize::Large, fileOrigin());
  2594. _savePhotoVideoWhenLoaded = SavePhotoVideo::QuickSave;
  2595. }
  2596. } else {
  2597. if (!_photo || !_photoMedia->loaded()) {
  2598. _saveVisible = computeSaveButtonVisible();
  2599. update(_saveNavOver);
  2600. } else {
  2601. if (!QDir().exists(path)) {
  2602. QDir().mkpath(path);
  2603. }
  2604. toName = filedialogDefaultName(u"photo"_q, u".jpg"_q, path);
  2605. const auto saved = _photoMedia->saveToFile(toName);
  2606. if (!saved) {
  2607. toName = QString();
  2608. }
  2609. }
  2610. }
  2611. if (!toName.isEmpty()) {
  2612. showSaveMsgToast(toName, (_stories && _document)
  2613. ? tr::lng_mediaview_video_saved_to
  2614. : tr::lng_mediaview_saved_to);
  2615. }
  2616. }
  2617. void OverlayWidget::saveCancel() {
  2618. if (_document && _document->loading()) {
  2619. _document->cancel();
  2620. if (_documentMedia->canBePlayed(_message)) {
  2621. redisplayContent();
  2622. }
  2623. }
  2624. }
  2625. void OverlayWidget::showInFolder() {
  2626. if (!_document) return;
  2627. auto filepath = _document->filepath(true);
  2628. if (!filepath.isEmpty()) {
  2629. File::ShowInFolder(filepath);
  2630. if (!_windowed) {
  2631. close();
  2632. }
  2633. }
  2634. }
  2635. void OverlayWidget::forwardMedia() {
  2636. if (!_session) {
  2637. return;
  2638. }
  2639. const auto &active = _session->windows();
  2640. if (active.empty()) {
  2641. return;
  2642. }
  2643. const auto id = (_message && _message->allowsForward())
  2644. ? _message->fullId()
  2645. : FullMsgId();
  2646. if (id) {
  2647. if (!_windowed) {
  2648. close();
  2649. }
  2650. Window::ShowForwardMessagesBox(active.front(), { 1, id });
  2651. }
  2652. }
  2653. void OverlayWidget::deleteMedia() {
  2654. if (_stories) {
  2655. _stories->deleteRequested();
  2656. return;
  2657. } else if (!_session) {
  2658. return;
  2659. }
  2660. const auto session = _session;
  2661. const auto photo = _photo;
  2662. const auto message = _message;
  2663. const auto deletingPeerPhoto = [&] {
  2664. if (!_message) {
  2665. return true;
  2666. } else if (_photo && _history) {
  2667. if (_history->peer->userpicPhotoId() == _photo->id) {
  2668. return _firstOpenedPeerPhoto;
  2669. }
  2670. }
  2671. return false;
  2672. }();
  2673. close();
  2674. if (const auto window = findWindow()) {
  2675. if (deletingPeerPhoto) {
  2676. if (photo) {
  2677. window->show(
  2678. Ui::MakeConfirmBox({
  2679. .text = tr::lng_delete_photo_sure(),
  2680. .confirmed = crl::guard(_widget, [=] {
  2681. session->api().peerPhoto().clear(photo);
  2682. window->hideLayer();
  2683. }),
  2684. .confirmText = tr::lng_box_delete(),
  2685. }),
  2686. Ui::LayerOption::CloseOther);
  2687. }
  2688. } else if (message) {
  2689. const auto suggestModerateActions = true;
  2690. window->show(
  2691. Box<DeleteMessagesBox>(message, suggestModerateActions),
  2692. Ui::LayerOption::CloseOther);
  2693. }
  2694. }
  2695. }
  2696. void OverlayWidget::showMediaOverview() {
  2697. if (_menu) {
  2698. _menu->hideMenu(true);
  2699. }
  2700. update();
  2701. if (const auto overviewType = computeOverviewType()) {
  2702. if (!_windowed) {
  2703. close();
  2704. }
  2705. if (SharedMediaOverviewType(*overviewType)) {
  2706. if (const auto window = findWindow()) {
  2707. const auto topic = _topicRootId
  2708. ? _history->peer->forumTopicFor(_topicRootId)
  2709. : nullptr;
  2710. if (_topicRootId && !topic) {
  2711. return;
  2712. }
  2713. window->showSection(_topicRootId
  2714. ? std::make_shared<Info::Memento>(
  2715. topic,
  2716. Info::Section(*overviewType))
  2717. : std::make_shared<Info::Memento>(
  2718. _history->peer,
  2719. Info::Section(*overviewType)));
  2720. }
  2721. }
  2722. }
  2723. }
  2724. void OverlayWidget::copyMedia() {
  2725. if (showCopyMediaRestriction()) {
  2726. return;
  2727. }
  2728. _dropdown->hideAnimated(Ui::DropdownMenu::HideOption::IgnoreShow);
  2729. if (_document) {
  2730. const auto filepath = _document->filepath(true);
  2731. auto image = transformedShownContent();
  2732. if (!image.isNull() || !filepath.isEmpty()) {
  2733. auto mime = std::make_unique<QMimeData>();
  2734. if (!image.isNull()) {
  2735. mime->setImageData(std::move(image));
  2736. }
  2737. if (!filepath.isEmpty() && !videoShown()) {
  2738. mime->setUrls({ QUrl::fromLocalFile(filepath) });
  2739. KUrlMimeData::exportUrlsToPortal(mime.get());
  2740. }
  2741. QGuiApplication::clipboard()->setMimeData(mime.release());
  2742. }
  2743. } else if (_photo && _photoMedia->loaded()) {
  2744. _photoMedia->setToClipboard();
  2745. }
  2746. }
  2747. void OverlayWidget::showAttachedStickers() {
  2748. if (!_session) {
  2749. return;
  2750. }
  2751. const auto &active = _session->windows();
  2752. if (active.empty()) {
  2753. return;
  2754. }
  2755. const auto window = active.front();
  2756. auto &attachedStickers = _session->api().attachedStickers();
  2757. if (_photo) {
  2758. attachedStickers.requestAttachedStickerSets(window, _photo);
  2759. } else if (_document) {
  2760. attachedStickers.requestAttachedStickerSets(window, _document);
  2761. } else {
  2762. return;
  2763. }
  2764. if (!_windowed) {
  2765. close();
  2766. }
  2767. }
  2768. auto OverlayWidget::sharedMediaType() const
  2769. -> std::optional<SharedMediaType> {
  2770. using Type = SharedMediaType;
  2771. if (_message) {
  2772. if (const auto media = _message->media()) {
  2773. if (media->webpage() || media->invoice()) {
  2774. return std::nullopt;
  2775. }
  2776. }
  2777. if (_photo) {
  2778. if (_message->isService()) {
  2779. return Type::ChatPhoto;
  2780. }
  2781. return Type::PhotoVideo;
  2782. } else if (_document) {
  2783. if (_document->isGifv()) {
  2784. return Type::GIF;
  2785. } else if (_document->isVideoFile()) {
  2786. return Type::PhotoVideo;
  2787. }
  2788. return Type::File;
  2789. }
  2790. }
  2791. return std::nullopt;
  2792. }
  2793. auto OverlayWidget::sharedMediaKey() const -> std::optional<SharedMediaKey> {
  2794. if (!_message
  2795. && _peer
  2796. && !_user
  2797. && _photo
  2798. && _peer->userpicPhotoId() == _photo->id) {
  2799. return SharedMediaKey{
  2800. _history->peer->id,
  2801. MsgId(0), // topicRootId
  2802. _migrated ? _migrated->peer->id : 0,
  2803. SharedMediaType::ChatPhoto,
  2804. _photo
  2805. };
  2806. }
  2807. if (!_message) {
  2808. return std::nullopt;
  2809. }
  2810. const auto isScheduled = _message->isScheduled();
  2811. const auto keyForType = [&](SharedMediaType type) -> SharedMediaKey {
  2812. return {
  2813. _history->peer->id,
  2814. (isScheduled
  2815. ? SparseIdsMergedSlice::kScheduledTopicId
  2816. : _topicRootId),
  2817. _migrated ? _migrated->peer->id : 0,
  2818. type,
  2819. (_message->history() == _history
  2820. ? _message->id
  2821. : (_message->id - ServerMaxMsgId))
  2822. };
  2823. };
  2824. if (!_message->isRegular() && !isScheduled) {
  2825. return std::nullopt;
  2826. }
  2827. return sharedMediaType() | keyForType;
  2828. }
  2829. Data::FileOrigin OverlayWidget::fileOrigin() const {
  2830. if (_stories) {
  2831. return _stories->fileOrigin();
  2832. } else if (_message) {
  2833. return _message->fullId();
  2834. } else if (_photo && _user) {
  2835. return Data::FileOriginUserPhoto(peerToUser(_user->id), _photo->id);
  2836. } else if (_photo && _peer && _peer->userpicPhotoId() == _photo->id) {
  2837. return Data::FileOriginPeerPhoto(_peer->id);
  2838. }
  2839. return Data::FileOrigin();
  2840. }
  2841. Data::FileOrigin OverlayWidget::fileOrigin(const Entity &entity) const {
  2842. if (const auto item = entity.item) {
  2843. return item->fullId();
  2844. } else if (!v::is<not_null<PhotoData*>>(entity.data)) {
  2845. return Data::FileOrigin();
  2846. }
  2847. const auto photo = v::get<not_null<PhotoData*>>(entity.data);
  2848. if (_user) {
  2849. return Data::FileOriginUserPhoto(peerToUser(_user->id), photo->id);
  2850. } else if (_peer && _peer->userpicPhotoId() == photo->id) {
  2851. return Data::FileOriginPeerPhoto(_peer->id);
  2852. }
  2853. return Data::FileOrigin();
  2854. }
  2855. bool OverlayWidget::validSharedMedia() const {
  2856. if (auto key = sharedMediaKey()) {
  2857. if (!_sharedMedia) {
  2858. return false;
  2859. }
  2860. using Key = SharedMediaWithLastSlice::Key;
  2861. auto inSameDomain = [](const Key &a, const Key &b) {
  2862. return (a.type == b.type)
  2863. && (a.peerId == b.peerId)
  2864. && (a.topicRootId == b.topicRootId)
  2865. && (a.migratedPeerId == b.migratedPeerId);
  2866. };
  2867. auto countDistanceInData = [&](const Key &a, const Key &b) {
  2868. return [&](const SharedMediaWithLastSlice &data) {
  2869. return inSameDomain(a, b)
  2870. ? data.distance(a, b)
  2871. : std::optional<int>();
  2872. };
  2873. };
  2874. if (key == _sharedMedia->key) {
  2875. return true;
  2876. } else if (!_sharedMediaDataKey
  2877. || _sharedMedia->key != *_sharedMediaDataKey) {
  2878. return false;
  2879. }
  2880. auto distance = _sharedMediaData
  2881. | countDistanceInData(*key, _sharedMedia->key)
  2882. | func::abs;
  2883. if (distance) {
  2884. return (*distance < kIdsPreloadAfter);
  2885. }
  2886. }
  2887. return (_sharedMedia == nullptr);
  2888. }
  2889. void OverlayWidget::validateSharedMedia() {
  2890. if (const auto key = sharedMediaKey()) {
  2891. Assert(_history != nullptr);
  2892. _sharedMedia = std::make_unique<SharedMedia>(*key);
  2893. auto viewer = (key->type == SharedMediaType::ChatPhoto)
  2894. ? SharedMediaWithLastReversedViewer
  2895. : SharedMediaWithLastViewer;
  2896. viewer(
  2897. &_history->session(),
  2898. *key,
  2899. kIdsLimit,
  2900. kIdsLimit
  2901. ) | rpl::start_with_next([this](
  2902. SharedMediaWithLastSlice &&update) {
  2903. handleSharedMediaUpdate(std::move(update));
  2904. }, _sharedMedia->lifetime);
  2905. } else {
  2906. _sharedMedia = nullptr;
  2907. _sharedMediaData = std::nullopt;
  2908. _sharedMediaDataKey = std::nullopt;
  2909. }
  2910. }
  2911. void OverlayWidget::handleSharedMediaUpdate(SharedMediaWithLastSlice &&update) {
  2912. if ((!_photo && !_document) || !_sharedMedia) {
  2913. _sharedMediaData = std::nullopt;
  2914. _sharedMediaDataKey = std::nullopt;
  2915. } else {
  2916. _sharedMediaData = std::move(update);
  2917. _sharedMediaDataKey = _sharedMedia->key;
  2918. }
  2919. findCurrent();
  2920. updateControls();
  2921. preloadData(0);
  2922. }
  2923. std::optional<OverlayWidget::UserPhotosKey> OverlayWidget::userPhotosKey() const {
  2924. if (!_message && _user && _photo) {
  2925. return UserPhotosKey{ peerToUser(_user->id), _photo->id };
  2926. }
  2927. return std::nullopt;
  2928. }
  2929. bool OverlayWidget::validUserPhotos() const {
  2930. if (const auto key = userPhotosKey()) {
  2931. if (!_userPhotos) {
  2932. return false;
  2933. }
  2934. const auto countDistanceInData = [](const auto &a, const auto &b) {
  2935. return [&](const UserPhotosSlice &data) {
  2936. return data.distance(a, b);
  2937. };
  2938. };
  2939. const auto distance = (key == _userPhotos->key) ? 0 :
  2940. _userPhotosData
  2941. | countDistanceInData(*key, _userPhotos->key)
  2942. | func::abs;
  2943. if (distance) {
  2944. return (*distance < kIdsPreloadAfter);
  2945. }
  2946. }
  2947. return (_userPhotos == nullptr);
  2948. }
  2949. void OverlayWidget::validateUserPhotos() {
  2950. if (const auto key = userPhotosKey()) {
  2951. Assert(_user != nullptr);
  2952. _userPhotos = std::make_unique<UserPhotos>(*key);
  2953. UserPhotosReversedViewer(
  2954. &_user->session(),
  2955. *key,
  2956. kIdsLimit,
  2957. kIdsLimit
  2958. ) | rpl::start_with_next([this](
  2959. UserPhotosSlice &&update) {
  2960. handleUserPhotosUpdate(std::move(update));
  2961. }, _userPhotos->lifetime);
  2962. } else {
  2963. _userPhotos = nullptr;
  2964. _userPhotosData = std::nullopt;
  2965. }
  2966. }
  2967. void OverlayWidget::handleUserPhotosUpdate(UserPhotosSlice &&update) {
  2968. if (!_photo || !_userPhotos) {
  2969. _userPhotosData = std::nullopt;
  2970. } else {
  2971. _userPhotosData = std::move(update);
  2972. }
  2973. findCurrent();
  2974. updateControls();
  2975. preloadData(0);
  2976. }
  2977. std::optional<OverlayWidget::CollageKey> OverlayWidget::collageKey() const {
  2978. if (_message) {
  2979. if (const auto media = _message->media()) {
  2980. if (const auto page = media->webpage()) {
  2981. for (const auto &item : page->collage.items) {
  2982. if (item == _photo || item == _document) {
  2983. return item;
  2984. }
  2985. }
  2986. } else if (const auto invoice = media->invoice()) {
  2987. for (const auto &item : invoice->extendedMedia) {
  2988. if (_photo && item->photo() == _photo) {
  2989. return _photo;
  2990. } else if (_document && item->document() == _document) {
  2991. return _document;
  2992. }
  2993. }
  2994. }
  2995. }
  2996. }
  2997. return std::nullopt;
  2998. }
  2999. bool OverlayWidget::validCollage() const {
  3000. if (const auto key = collageKey()) {
  3001. if (!_collage) {
  3002. return false;
  3003. }
  3004. if (key == _collage->key) {
  3005. return true;
  3006. } else if (_collageData) {
  3007. const auto &items = _collageData->items;
  3008. if (ranges::find(items, *key) != end(items)
  3009. && ranges::find(items, _collage->key) != end(items)) {
  3010. return true;
  3011. }
  3012. }
  3013. }
  3014. return (_collage == nullptr);
  3015. }
  3016. void OverlayWidget::validateCollage() {
  3017. if (const auto key = collageKey()) {
  3018. _collage = std::make_unique<Collage>(*key);
  3019. _collageData = WebPageCollage();
  3020. if (_message) {
  3021. if (const auto media = _message->media()) {
  3022. if (const auto page = media->webpage()) {
  3023. _collageData = page->collage;
  3024. } else if (const auto invoice = media->invoice()) {
  3025. auto &data = *_collageData;
  3026. data.items.reserve(invoice->extendedMedia.size());
  3027. for (const auto &item : invoice->extendedMedia) {
  3028. if (const auto photo = item->photo()) {
  3029. data.items.push_back(photo);
  3030. } else if (const auto document = item->document()) {
  3031. data.items.push_back(document);
  3032. }
  3033. }
  3034. }
  3035. }
  3036. }
  3037. } else {
  3038. _collage = nullptr;
  3039. _collageData = std::nullopt;
  3040. }
  3041. }
  3042. void OverlayWidget::refreshMediaViewer() {
  3043. if (!validSharedMedia()) {
  3044. validateSharedMedia();
  3045. }
  3046. if (!validUserPhotos()) {
  3047. validateUserPhotos();
  3048. }
  3049. if (!validCollage()) {
  3050. validateCollage();
  3051. }
  3052. findCurrent();
  3053. updateControls();
  3054. }
  3055. void OverlayWidget::refreshFromLabel() {
  3056. if (_message) {
  3057. _from = _message->originalSender();
  3058. if (const auto info = _message->originalHiddenSenderInfo()) {
  3059. _fromName = info->name;
  3060. } else {
  3061. Assert(_from != nullptr);
  3062. const auto from = _from->migrateTo()
  3063. ? _from->migrateTo()
  3064. : _from;
  3065. _fromName = from->name();
  3066. }
  3067. } else {
  3068. _from = _user;
  3069. _fromName = _user ? _user->name() : QString();
  3070. }
  3071. }
  3072. void OverlayWidget::refreshCaption() {
  3073. _caption = Ui::Text::String();
  3074. const auto caption = [&] {
  3075. if (_stories) {
  3076. return _stories->captionText();
  3077. } else if (_message) {
  3078. if (const auto media = _message->media()) {
  3079. if (media->webpage()) {
  3080. if (_message->isSponsored()) {
  3081. return TextWithEntities()
  3082. .append(Ui::Text::Bold(media->webpage()->title))
  3083. .append('\n')
  3084. .append(media->webpage()->description);
  3085. }
  3086. return TextWithEntities();
  3087. }
  3088. }
  3089. return _message->translatedText();
  3090. }
  3091. return TextWithEntities();
  3092. }();
  3093. if (caption.text.isEmpty()) {
  3094. return;
  3095. }
  3096. using namespace HistoryView;
  3097. _caption = Ui::Text::String(st::msgMinWidth);
  3098. const auto duration = (_streamed && _document && _message)
  3099. ? DurationForTimestampLinks(_document)
  3100. : 0;
  3101. const auto base = duration
  3102. ? TimestampLinkBase(_document, _message->fullId())
  3103. : QString();
  3104. const auto captionRepaint = [=] {
  3105. if (_fullScreenVideo || !_controlsOpacity.current()) {
  3106. return;
  3107. }
  3108. update(captionGeometry());
  3109. };
  3110. const auto context = Core::TextContext({
  3111. .session = (_stories
  3112. ? _storiesSession
  3113. : &_message->history()->session()),
  3114. .repaint = captionRepaint,
  3115. });
  3116. _caption.setMarkedText(
  3117. st::mediaviewCaptionStyle,
  3118. (base.isEmpty()
  3119. ? caption
  3120. : AddTimestampLinks(caption, duration, base)),
  3121. (_message
  3122. ? Ui::ItemTextOptions(_message)
  3123. : Ui::ItemTextDefaultOptions()),
  3124. context);
  3125. if (_caption.hasSpoilers()) {
  3126. const auto weak = Ui::MakeWeak(widget());
  3127. _caption.setSpoilerLinkFilter([=](const ClickContext &context) {
  3128. return (weak != nullptr);
  3129. });
  3130. }
  3131. }
  3132. void OverlayWidget::refreshGroupThumbs() {
  3133. const auto existed = (_groupThumbs != nullptr);
  3134. if (_index && _sharedMediaData) {
  3135. View::GroupThumbs::Refresh(
  3136. _session,
  3137. _groupThumbs,
  3138. *_sharedMediaData,
  3139. *_index,
  3140. _groupThumbsAvailableWidth);
  3141. } else if (_index && _userPhotosData) {
  3142. View::GroupThumbs::Refresh(
  3143. _session,
  3144. _groupThumbs,
  3145. *_userPhotosData,
  3146. *_index,
  3147. _groupThumbsAvailableWidth);
  3148. } else if (_index && _collageData) {
  3149. const auto messageId = _message ? _message->fullId() : FullMsgId();
  3150. View::GroupThumbs::Refresh(
  3151. _session,
  3152. _groupThumbs,
  3153. { messageId, &*_collageData },
  3154. *_index,
  3155. _groupThumbsAvailableWidth);
  3156. } else if (_groupThumbs) {
  3157. _groupThumbs->clear();
  3158. _groupThumbs->resizeToWidth(_groupThumbsAvailableWidth);
  3159. }
  3160. if (_groupThumbs && !existed) {
  3161. initGroupThumbs();
  3162. }
  3163. }
  3164. void OverlayWidget::initGroupThumbs() {
  3165. Expects(_groupThumbs != nullptr);
  3166. _groupThumbs->updateRequests(
  3167. ) | rpl::start_with_next([this](QRect rect) {
  3168. const auto shift = (width() / 2);
  3169. _groupThumbsRect = QRect(
  3170. shift + rect.x(),
  3171. _groupThumbsTop,
  3172. rect.width(),
  3173. _groupThumbs->height());
  3174. update(_groupThumbsRect);
  3175. }, _groupThumbs->lifetime());
  3176. _groupThumbs->activateRequests(
  3177. ) | rpl::start_with_next([this](View::GroupThumbs::Key key) {
  3178. using CollageKey = View::GroupThumbs::CollageKey;
  3179. if (const auto photoId = std::get_if<PhotoId>(&key)) {
  3180. const auto photo = _session->data().photo(*photoId);
  3181. moveToEntity({ photo, nullptr });
  3182. } else if (const auto itemId = std::get_if<FullMsgId>(&key)) {
  3183. moveToEntity(entityForItemId(*itemId));
  3184. } else if (const auto collageKey = std::get_if<CollageKey>(&key)) {
  3185. if (_collageData) {
  3186. moveToEntity(entityForCollage(collageKey->index));
  3187. }
  3188. }
  3189. }, _groupThumbs->lifetime());
  3190. _groupThumbsRect = QRect(
  3191. _groupThumbsLeft,
  3192. _groupThumbsTop,
  3193. width() - 2 * _groupThumbsLeft,
  3194. height() - _groupThumbsTop);
  3195. }
  3196. void OverlayWidget::clearControlsState() {
  3197. _saveMsgAnimation.stop();
  3198. _saveMsgTimer.cancel();
  3199. _loadRequest = 0;
  3200. _over = _down = Over::None;
  3201. _pressed = false;
  3202. _dragging = 0;
  3203. setCursor(style::cur_default);
  3204. if (!_animations.empty()) {
  3205. _animations.clear();
  3206. _stateAnimation.stop();
  3207. }
  3208. if (!_animationOpacities.empty()) {
  3209. _animationOpacities.clear();
  3210. }
  3211. }
  3212. not_null<QWindow*> OverlayWidget::window() const {
  3213. return _window->windowHandle();
  3214. }
  3215. int OverlayWidget::width() const {
  3216. return _widget->width();
  3217. }
  3218. int OverlayWidget::height() const {
  3219. return _widget->height();
  3220. }
  3221. void OverlayWidget::update() {
  3222. _widget->update();
  3223. }
  3224. void OverlayWidget::update(const QRegion &region) {
  3225. _widget->update(region);
  3226. }
  3227. bool OverlayWidget::isActive() const {
  3228. return !isHidden() && !isMinimized() && _window->isActiveWindow();
  3229. }
  3230. bool OverlayWidget::isHidden() const {
  3231. return _window->isHidden();
  3232. }
  3233. bool OverlayWidget::isMinimized() const {
  3234. return _window->isMinimized();
  3235. }
  3236. bool OverlayWidget::isFullScreen() const {
  3237. return _fullscreen;
  3238. }
  3239. not_null<QWidget*> OverlayWidget::widget() const {
  3240. return _widget;
  3241. }
  3242. void OverlayWidget::hide() {
  3243. clearBeforeHide();
  3244. applyHideWindowWorkaround();
  3245. _window->hide();
  3246. }
  3247. void OverlayWidget::setCursor(style::cursor cursor) {
  3248. _widget->setCursor(cursor);
  3249. }
  3250. void OverlayWidget::setFocus() {
  3251. _body->setFocus();
  3252. }
  3253. bool OverlayWidget::takeFocusFrom(not_null<QWidget*> window) const {
  3254. return _fullscreen
  3255. && !isHidden()
  3256. && !isMinimized()
  3257. && (_window->screen() == window->screen());
  3258. }
  3259. void OverlayWidget::activate() {
  3260. _window->raise();
  3261. _window->activateWindow();
  3262. setFocus();
  3263. QApplication::setActiveWindow(_window);
  3264. setFocus();
  3265. }
  3266. void OverlayWidget::show(OpenRequest request) {
  3267. const auto story = request.story();
  3268. const auto document = story ? story->document() : request.document();
  3269. const auto photo = story ? story->photo() : request.photo();
  3270. const auto contextItem = request.item();
  3271. const auto contextPeer = request.peer();
  3272. const auto contextTopicRootId = request.topicRootId();
  3273. if (!request.continueStreaming() && !request.startTime() && !_reShow) {
  3274. if (_message && (_message == contextItem)) {
  3275. return close();
  3276. } else if (_user && (_user == contextPeer)) {
  3277. if ((_photo && (_photo == photo))
  3278. || (_document && (_document == document))) {
  3279. return close();
  3280. }
  3281. }
  3282. }
  3283. if (isHidden() || isMinimized()) {
  3284. // Count top notch on macOS before counting geometry.
  3285. _helper->beforeShow(_fullscreen);
  3286. }
  3287. if (_cachedShow) {
  3288. _cachedShow->showOrHideBoxOrLayer(
  3289. v::null,
  3290. Ui::LayerOption::CloseOther,
  3291. anim::type::instant);
  3292. }
  3293. if (photo) {
  3294. if (contextItem && contextPeer) {
  3295. return;
  3296. }
  3297. setSession(&photo->session());
  3298. if (story) {
  3299. setContext(StoriesContext{
  3300. story->peer(),
  3301. story->id(),
  3302. request.storiesContext(),
  3303. });
  3304. } else if (contextPeer) {
  3305. setContext(contextPeer);
  3306. } else if (contextItem) {
  3307. setContext(ItemContext{ contextItem, contextTopicRootId });
  3308. } else {
  3309. setContext(v::null);
  3310. }
  3311. clearControlsState();
  3312. _firstOpenedPeerPhoto = (contextPeer != nullptr);
  3313. assignMediaPointer(photo);
  3314. displayPhoto(photo);
  3315. preloadData(0);
  3316. activateControls();
  3317. } else if (story || document) {
  3318. setSession(document ? &document->session() : &story->session());
  3319. if (story) {
  3320. setContext(StoriesContext{
  3321. story->peer(),
  3322. story->id(),
  3323. request.storiesContext(),
  3324. });
  3325. } else if (contextItem) {
  3326. setContext(ItemContext{ contextItem, contextTopicRootId });
  3327. } else {
  3328. setContext(v::null);
  3329. }
  3330. clearControlsState();
  3331. _streamingStartPaused = false;
  3332. displayDocument(
  3333. document,
  3334. anim::activation::normal,
  3335. request.cloudTheme()
  3336. ? *request.cloudTheme()
  3337. : Data::CloudTheme(),
  3338. { request.continueStreaming(), request.startTime() });
  3339. if (!isHidden()) {
  3340. preloadData(0);
  3341. activateControls();
  3342. }
  3343. }
  3344. if (const auto controller = request.controller()) {
  3345. _openedFrom = base::make_weak(&controller->window());
  3346. }
  3347. }
  3348. void OverlayWidget::displayPhoto(
  3349. not_null<PhotoData*> photo,
  3350. anim::activation activation) {
  3351. if (photo->isNull()) {
  3352. displayDocument(nullptr, activation);
  3353. return;
  3354. }
  3355. _touchbarDisplay.fire(TouchBarItemType::Photo);
  3356. clearStreaming();
  3357. destroyThemePreview();
  3358. _fullScreenVideo = false;
  3359. assignMediaPointer(photo);
  3360. _rotation = _photo->owner().mediaRotation().get(_photo);
  3361. _radial.stop();
  3362. refreshMediaViewer();
  3363. _staticContent = QImage();
  3364. if (!_stories && _photo->videoCanBePlayed()) {
  3365. initStreaming();
  3366. }
  3367. initSponsoredButton();
  3368. refreshCaption();
  3369. _blurred = true;
  3370. _down = Over::None;
  3371. if (!_staticContent.isNull()) {
  3372. // Video thumbnail.
  3373. const auto size = style::ConvertScale(
  3374. flipSizeByRotation(_staticContent.size()));
  3375. _w = size.width();
  3376. _h = size.height();
  3377. } else {
  3378. const auto size = style::ConvertScale(flipSizeByRotation(QSize(
  3379. photo->width(),
  3380. photo->height())));
  3381. _w = size.width();
  3382. _h = size.height();
  3383. }
  3384. contentSizeChanged();
  3385. refreshFromLabel();
  3386. displayFinished(activation);
  3387. }
  3388. void OverlayWidget::destroyThemePreview() {
  3389. _themePreviewId = 0;
  3390. _themePreviewShown = false;
  3391. _themePreview.reset();
  3392. _themeApply.destroy();
  3393. _themeCancel.destroy();
  3394. _themeShare.destroy();
  3395. }
  3396. void OverlayWidget::redisplayContent() {
  3397. if (isHidden() || !_session) {
  3398. return;
  3399. } else if (_photo) {
  3400. displayPhoto(_photo, anim::activation::background);
  3401. } else {
  3402. displayDocument(_document, anim::activation::background);
  3403. }
  3404. }
  3405. // Empty messages shown as docs: doc can be nullptr.
  3406. void OverlayWidget::displayDocument(
  3407. DocumentData *doc,
  3408. anim::activation activation,
  3409. const Data::CloudTheme &cloud,
  3410. const StartStreaming &startStreaming) {
  3411. _fullScreenVideo = false;
  3412. _staticContent = QImage();
  3413. clearStreaming(_document != doc);
  3414. destroyThemePreview();
  3415. assignMediaPointer(doc);
  3416. _rotation = _document
  3417. ? _document->owner().mediaRotation().get(_document)
  3418. : 0;
  3419. _themeCloudData = cloud;
  3420. _radial.stop();
  3421. _touchbarDisplay.fire(TouchBarItemType::None);
  3422. refreshMediaViewer();
  3423. if (_document) {
  3424. if (_document->sticker()) {
  3425. if (const auto image = _documentMedia->getStickerLarge()) {
  3426. setStaticContent(image->original());
  3427. } else if (const auto thumbnail = _documentMedia->thumbnail()) {
  3428. setStaticContent(thumbnail->pix(
  3429. _document->dimensions,
  3430. { .options = Images::Option::Blur }
  3431. ).toImage());
  3432. }
  3433. } else {
  3434. initSponsoredButton();
  3435. if (_documentMedia->canBePlayed(_message)
  3436. && initStreaming(startStreaming)) {
  3437. } else if (_document->isVideoFile()) {
  3438. _documentMedia->automaticLoad(fileOrigin(), _message);
  3439. initStreamingThumbnail();
  3440. } else if (_document->isTheme()) {
  3441. _documentMedia->automaticLoad(fileOrigin(), _message);
  3442. initThemePreview();
  3443. } else {
  3444. _documentMedia->automaticLoad(fileOrigin(), _message);
  3445. _document->saveFromDataSilent();
  3446. auto &location = _document->location(true);
  3447. if (location.accessEnable()) {
  3448. setStaticContent(PrepareStaticImage({
  3449. .path = location.name(),
  3450. }));
  3451. if (!_staticContent.isNull()) {
  3452. _touchbarDisplay.fire(TouchBarItemType::Photo);
  3453. }
  3454. } else {
  3455. setStaticContent(PrepareStaticImage({
  3456. .content = _documentMedia->bytes(),
  3457. }));
  3458. if (!_staticContent.isNull()) {
  3459. _touchbarDisplay.fire(TouchBarItemType::Photo);
  3460. }
  3461. }
  3462. location.accessDisable();
  3463. }
  3464. }
  3465. }
  3466. refreshCaption();
  3467. const auto docGeneric = Layout::DocumentGenericPreview::Create(_document);
  3468. _docExt = docGeneric.ext;
  3469. _docIconColor = docGeneric.color;
  3470. _docIcon = docGeneric.icon();
  3471. int32 extmaxw = (st::mediaviewFileIconSize - st::mediaviewFileExtPadding * 2);
  3472. _docExtWidth = st::mediaviewFileExtFont->width(_docExt);
  3473. if (_docExtWidth > extmaxw) {
  3474. _docExt = st::mediaviewFileExtFont->elided(_docExt, extmaxw, Qt::ElideMiddle);
  3475. _docExtWidth = st::mediaviewFileExtFont->width(_docExt);
  3476. }
  3477. if (documentBubbleShown()) {
  3478. if (_document && _document->hasThumbnail()) {
  3479. _document->loadThumbnail(fileOrigin());
  3480. const auto tw = _documentMedia->thumbnailSize().width();
  3481. const auto th = _documentMedia->thumbnailSize().height();
  3482. if (!tw || !th) {
  3483. _docThumbx = _docThumby = _docThumbw = 0;
  3484. } else if (tw > th) {
  3485. _docThumbw = (tw * st::mediaviewFileIconSize) / th;
  3486. _docThumbx = (_docThumbw - st::mediaviewFileIconSize) / 2;
  3487. _docThumby = 0;
  3488. } else {
  3489. _docThumbw = st::mediaviewFileIconSize;
  3490. _docThumbx = 0;
  3491. _docThumby = ((th * _docThumbw) / tw - st::mediaviewFileIconSize) / 2;
  3492. }
  3493. }
  3494. int32 maxw = st::mediaviewFileSize.width() - st::mediaviewFileIconSize - st::mediaviewFilePadding * 3;
  3495. if (_document) {
  3496. _docName = (_document->type == StickerDocument)
  3497. ? tr::lng_in_dlg_sticker(tr::now)
  3498. : (_document->type == AnimatedDocument
  3499. ? u"GIF"_q
  3500. : (_document->filename().isEmpty()
  3501. ? tr::lng_mediaview_doc_image(tr::now)
  3502. : _document->filename()));
  3503. } else {
  3504. _docName = tr::lng_message_empty(tr::now);
  3505. }
  3506. _docNameWidth = st::mediaviewFileNameFont->width(_docName);
  3507. if (_docNameWidth > maxw) {
  3508. _docName = st::mediaviewFileNameFont->elided(_docName, maxw, Qt::ElideMiddle);
  3509. _docNameWidth = st::mediaviewFileNameFont->width(_docName);
  3510. }
  3511. } else if (_themePreviewShown) {
  3512. updateThemePreviewGeometry();
  3513. } else if (!_staticContent.isNull()) {
  3514. const auto size = style::ConvertScale(
  3515. flipSizeByRotation(_staticContent.size()));
  3516. _w = size.width();
  3517. _h = size.height();
  3518. } else if (videoShown()) {
  3519. const auto contentSize = style::ConvertScale(videoSize());
  3520. _w = contentSize.width();
  3521. _h = contentSize.height();
  3522. }
  3523. contentSizeChanged();
  3524. if (videoShown()) {
  3525. applyVideoSize();
  3526. }
  3527. refreshFromLabel();
  3528. _blurred = false;
  3529. if (_showAsPip && _streamed && _streamed->controls) {
  3530. switchToPip();
  3531. } else {
  3532. displayFinished(activation);
  3533. }
  3534. }
  3535. void OverlayWidget::initSponsoredButton() {
  3536. const auto has = _message && _message->isSponsored() && _session;
  3537. if (has && _sponsoredButton) {
  3538. return;
  3539. } else if (!has && _sponsoredButton) {
  3540. _sponsoredButton = nullptr;
  3541. return;
  3542. } else if (!has && !_sponsoredButton) {
  3543. return;
  3544. }
  3545. const auto sponsoredMessages = &_session->sponsoredMessages();
  3546. const auto fullId = _message->fullId();
  3547. const auto details = sponsoredMessages->lookupDetails(fullId);
  3548. _sponsoredButton = base::make_unique_q<SponsoredButton>(_body);
  3549. _sponsoredButton->setText(details.buttonText);
  3550. _sponsoredButton->setOpacity(1.0);
  3551. _sponsoredButton->setClickedCallback([=, link = details.link] {
  3552. UrlClickHandler::Open(link);
  3553. sponsoredMessages->clicked(fullId, false, true);
  3554. hide();
  3555. });
  3556. }
  3557. void OverlayWidget::updateThemePreviewGeometry() {
  3558. if (_themePreviewShown) {
  3559. auto previewRect = QRect((width() - st::themePreviewSize.width()) / 2, (height() - st::themePreviewSize.height()) / 2, st::themePreviewSize.width(), st::themePreviewSize.height());
  3560. _themePreviewRect = previewRect.marginsAdded(st::themePreviewMargin);
  3561. if (_themeApply) {
  3562. auto right = qMax(width() - _themePreviewRect.x() - _themePreviewRect.width(), 0) + st::themePreviewMargin.right();
  3563. auto bottom = qMin(height(), _themePreviewRect.y() + _themePreviewRect.height());
  3564. _themeApply->moveToRight(right, bottom - st::themePreviewMargin.bottom() + (st::themePreviewMargin.bottom() - _themeApply->height()) / 2);
  3565. right += _themeApply->width() + st::themePreviewButtonsSkip;
  3566. _themeCancel->moveToRight(right, _themeApply->y());
  3567. if (_themeShare) {
  3568. _themeShare->moveToLeft(previewRect.x(), _themeApply->y());
  3569. }
  3570. }
  3571. // For context menu event.
  3572. _x = _themePreviewRect.x();
  3573. _y = _themePreviewRect.y();
  3574. _w = _themePreviewRect.width();
  3575. _h = _themePreviewRect.height();
  3576. }
  3577. }
  3578. void OverlayWidget::displayFinished(anim::activation activation) {
  3579. updateControls();
  3580. if (isHidden()) {
  3581. _helper->beforeShow(_fullscreen);
  3582. moveToScreen();
  3583. showAndActivate();
  3584. } else if (activation == anim::activation::background) {
  3585. return;
  3586. } else if (isMinimized()) {
  3587. _helper->beforeShow(_fullscreen);
  3588. showAndActivate();
  3589. } else {
  3590. activate();
  3591. }
  3592. }
  3593. void OverlayWidget::showAndActivate() {
  3594. _body->show();
  3595. initNormalGeometry();
  3596. if (_windowed || Platform::IsMac()) {
  3597. _wasWindowedMode = false;
  3598. }
  3599. updateGeometry();
  3600. if (_windowed || Platform::IsMac()) {
  3601. _window->showNormal();
  3602. _wasWindowedMode = true;
  3603. } else if (_fullscreen) {
  3604. _window->showFullScreen();
  3605. } else {
  3606. _window->showMaximized();
  3607. }
  3608. _helper->afterShow(_fullscreen);
  3609. _widget->update();
  3610. activate();
  3611. }
  3612. bool OverlayWidget::canInitStreaming() const {
  3613. return (_document && _documentMedia->canBePlayed(_message))
  3614. || (_photo && _photo->videoCanBePlayed());
  3615. }
  3616. bool OverlayWidget::initStreaming(const StartStreaming &startStreaming) {
  3617. Expects(canInitStreaming());
  3618. if (_streamed) {
  3619. return true;
  3620. }
  3621. initStreamingThumbnail();
  3622. if (!createStreamingObjects()) {
  3623. if (_document) {
  3624. _document->setInappPlaybackFailed();
  3625. } else {
  3626. _photo->setVideoPlaybackFailed();
  3627. }
  3628. return false;
  3629. }
  3630. Core::App().updateNonIdle();
  3631. _streamed->instance.player().updates(
  3632. ) | rpl::start_with_next_error([=](Streaming::Update &&update) {
  3633. handleStreamingUpdate(std::move(update));
  3634. }, [=](Streaming::Error &&error) {
  3635. handleStreamingError(std::move(error));
  3636. }, _streamed->instance.lifetime());
  3637. _streamed->instance.switchQualityRequests(
  3638. ) | rpl::filter([=](int quality) {
  3639. return !_quality.manual && _quality.height != quality;
  3640. }) | rpl::start_with_next([=](int quality) {
  3641. applyVideoQuality({
  3642. .manual = 0,
  3643. .height = uint32(quality),
  3644. });
  3645. }, _streamed->instance.lifetime());
  3646. const auto continuing = startStreaming.continueStreaming
  3647. && _pip
  3648. && (_pip->wrapped.shared().get()
  3649. == _streamed->instance.shared().get());
  3650. if (startStreaming.continueStreaming) {
  3651. _pip = nullptr;
  3652. }
  3653. if (!continuing
  3654. || (!_streamed->instance.player().active()
  3655. && !_streamed->instance.player().finished())) {
  3656. startStreamingPlayer(startStreaming);
  3657. } else {
  3658. _streamed->ready = _streamed->instance.player().ready();
  3659. updatePlaybackState();
  3660. }
  3661. return true;
  3662. }
  3663. void OverlayWidget::startStreamingPlayer(
  3664. const StartStreaming &startStreaming) {
  3665. Expects(_streamed != nullptr);
  3666. const auto &player = _streamed->instance.player();
  3667. if (player.playing()) {
  3668. if (!_streamed->withSound) {
  3669. _streamed->ready = true;
  3670. return;
  3671. }
  3672. _pip = nullptr;
  3673. } else if (!player.paused() && !player.finished() && !player.failed()) {
  3674. _pip = nullptr;
  3675. } else if (_pip && _streamed->withSound) {
  3676. return;
  3677. }
  3678. _streamedPosition = _document
  3679. ? startStreaming.startTime
  3680. : _photo
  3681. ? _photo->videoStartPosition()
  3682. : 0;
  3683. restartAtSeekPosition(_streamedPosition);
  3684. }
  3685. void OverlayWidget::initStreamingThumbnail() {
  3686. Expects(_photo || _document);
  3687. _touchbarDisplay.fire(TouchBarItemType::Video);
  3688. auto userpicImage = std::optional<Image>();
  3689. const auto computePhotoThumbnail = [&] {
  3690. const auto thumbnail = _photoMedia->image(Data::PhotoSize::Thumbnail);
  3691. if (thumbnail) {
  3692. return thumbnail;
  3693. } else if (_peer && _peer->userpicPhotoId() == _photo->id) {
  3694. if (const auto view = _peer->activeUserpicView(); view.cloud) {
  3695. if (!view.cloud->isNull()) {
  3696. userpicImage.emplace(base::duplicate(*view.cloud));
  3697. return &*userpicImage;
  3698. }
  3699. }
  3700. }
  3701. return thumbnail;
  3702. };
  3703. const auto good = _videoCover
  3704. ? _videoCoverMedia->image(Data::PhotoSize::Large)
  3705. : _document
  3706. ? _documentMedia->goodThumbnail()
  3707. : _photoMedia->image(Data::PhotoSize::Large);
  3708. const auto thumbnail = _videoCover
  3709. ? _videoCoverMedia->image(Data::PhotoSize::Small)
  3710. : _document
  3711. ? _documentMedia->thumbnail()
  3712. : computePhotoThumbnail();
  3713. const auto blurred = _videoCover
  3714. ? _videoCoverMedia->thumbnailInline()
  3715. : _document
  3716. ? _documentMedia->thumbnailInline()
  3717. : _photoMedia->thumbnailInline();
  3718. const auto size = _photo
  3719. ? QSize(
  3720. _photo->videoLocation(Data::PhotoSize::Large).width(),
  3721. _photo->videoLocation(Data::PhotoSize::Large).height())
  3722. : good
  3723. ? good->size()
  3724. : _document->dimensions;
  3725. if (size.isEmpty()) {
  3726. return;
  3727. } else if (!_streamedQualityChangeFrame.isNull()) {
  3728. setStaticContent(_streamedQualityChangeFrame.scaled(
  3729. size,
  3730. Qt::IgnoreAspectRatio,
  3731. Qt::SmoothTransformation));
  3732. return;
  3733. } else if (!good && !thumbnail && !blurred) {
  3734. return;
  3735. }
  3736. const auto options = VideoThumbOptions(_document);
  3737. const auto goodOptions = (options & ~Images::Option::Blur);
  3738. setStaticContent((good
  3739. ? good
  3740. : thumbnail
  3741. ? thumbnail
  3742. : blurred
  3743. ? blurred
  3744. : Image::BlankMedia().get())->pixNoCache(
  3745. size,
  3746. {
  3747. .options = good ? goodOptions : options,
  3748. .outer = size / style::DevicePixelRatio(),
  3749. }
  3750. ).toImage());
  3751. }
  3752. void OverlayWidget::streamingReady(Streaming::Information &&info) {
  3753. _streamed->ready = true;
  3754. if (videoShown()) {
  3755. applyVideoSize();
  3756. _streamedQualityChangeFrame = QImage();
  3757. } else {
  3758. updateContentRect();
  3759. }
  3760. }
  3761. void OverlayWidget::applyVideoSize() {
  3762. const auto contentSize = style::ConvertScale(videoSize());
  3763. if (contentSize != QSize(_width, _height)) {
  3764. updateContentRect();
  3765. _w = contentSize.width();
  3766. _h = contentSize.height();
  3767. contentSizeChanged();
  3768. }
  3769. updateContentRect();
  3770. }
  3771. bool OverlayWidget::createStreamingObjects() {
  3772. Expects(_photo || _document);
  3773. const auto origin = fileOrigin();
  3774. const auto callback = [=] { waitingAnimationCallback(); };
  3775. const auto video = _chosenQuality ? _chosenQuality : _document;
  3776. if (video) {
  3777. _streamed = std::make_unique<Streamed>(
  3778. video,
  3779. _document,
  3780. _message,
  3781. origin,
  3782. callback);
  3783. } else {
  3784. _streamed = std::make_unique<Streamed>(_photo, origin, callback);
  3785. }
  3786. if (!_streamed->instance.valid()) {
  3787. _streamed = nullptr;
  3788. return false;
  3789. }
  3790. ++_streamedCreated;
  3791. _streamed->instance.setPriority(kOverlayLoaderPriority);
  3792. _streamed->instance.lockPlayer();
  3793. _streamed->withSound = video
  3794. && !video->isSilentVideo()
  3795. && (_document->isAudioFile()
  3796. || _document->isVideoFile()
  3797. || _document->isVoiceMessage()
  3798. || _document->isVideoMessage());
  3799. if (streamingRequiresControls()) {
  3800. _streamed->controls = std::make_unique<PlaybackControls>(
  3801. _body,
  3802. static_cast<PlaybackControls::Delegate*>(this));
  3803. _streamed->controls->show();
  3804. refreshClipControllerGeometry();
  3805. }
  3806. return true;
  3807. }
  3808. void OverlayWidget::updatePowerSaveBlocker(
  3809. const Player::TrackState &state) {
  3810. Expects(_streamed != nullptr);
  3811. const auto block = (_document != nullptr)
  3812. && _document->isVideoFile()
  3813. && !IsPausedOrPausing(state.state)
  3814. && !IsStoppedOrStopping(state.state);
  3815. base::UpdatePowerSaveBlocker(
  3816. _streamed->powerSaveBlocker,
  3817. block,
  3818. base::PowerSaveBlockType::PreventDisplaySleep,
  3819. [] { return u"Video playback is active"_q; },
  3820. [=] { return _window->windowHandle(); });
  3821. }
  3822. QImage OverlayWidget::transformedShownContent() const {
  3823. return transformShownContent(
  3824. videoShown() ? currentVideoFrameImage() : _staticContent,
  3825. finalContentRotation());
  3826. }
  3827. QImage OverlayWidget::transformShownContent(
  3828. QImage content,
  3829. int rotation) const {
  3830. if (rotation) {
  3831. content = RotateFrameImage(std::move(content), rotation);
  3832. }
  3833. if (videoShown()) {
  3834. const auto requiredSize = videoSize();
  3835. if (content.size() != requiredSize) {
  3836. content = content.scaled(
  3837. requiredSize,
  3838. Qt::IgnoreAspectRatio,
  3839. Qt::SmoothTransformation);
  3840. }
  3841. }
  3842. return content;
  3843. }
  3844. void OverlayWidget::handleStreamingUpdate(Streaming::Update &&update) {
  3845. using namespace Streaming;
  3846. v::match(update.data, [&](Information &update) {
  3847. streamingReady(std::move(update));
  3848. }, [&](PreloadedVideo) {
  3849. updatePlaybackState();
  3850. }, [&](UpdateVideo update) {
  3851. updateContentRect();
  3852. Core::App().updateNonIdle();
  3853. updatePlaybackState();
  3854. _streamedPosition = update.position;
  3855. }, [&](PreloadedAudio) {
  3856. updatePlaybackState();
  3857. }, [&](UpdateAudio) {
  3858. updatePlaybackState();
  3859. }, [](WaitingForData) {
  3860. }, [](SpeedEstimate) {
  3861. }, [](MutedByOther) {
  3862. }, [&](Finished) {
  3863. updatePlaybackState();
  3864. });
  3865. }
  3866. void OverlayWidget::handleStreamingError(Streaming::Error &&error) {
  3867. Expects(_document || _photo);
  3868. if (error == Streaming::Error::NotStreamable) {
  3869. if (_document) {
  3870. _document->setNotSupportsStreaming();
  3871. } else {
  3872. _photo->setVideoPlaybackFailed();
  3873. }
  3874. } else if (error == Streaming::Error::OpenFailed) {
  3875. if (_document) {
  3876. _document->setInappPlaybackFailed();
  3877. } else {
  3878. _photo->setVideoPlaybackFailed();
  3879. }
  3880. }
  3881. if (canInitStreaming()) {
  3882. updatePlaybackState();
  3883. } else {
  3884. redisplayContent();
  3885. }
  3886. }
  3887. void OverlayWidget::initThemePreview() {
  3888. using namespace Window::Theme;
  3889. Assert(_document && _document->isTheme());
  3890. const auto bytes = _documentMedia->bytes();
  3891. auto &location = _document->location();
  3892. if (bytes.isEmpty()
  3893. && (location.isEmpty() || !location.accessEnable())) {
  3894. return;
  3895. }
  3896. _themePreviewShown = true;
  3897. auto current = CurrentData();
  3898. current.backgroundId = Background()->id();
  3899. current.backgroundImage = Background()->createCurrentImage();
  3900. current.backgroundTiled = Background()->tile();
  3901. const auto &cloudList = _document->session().data().cloudThemes().list();
  3902. const auto i = ranges::find(
  3903. cloudList,
  3904. _document->id,
  3905. &Data::CloudTheme::documentId);
  3906. const auto cloud = (i != end(cloudList)) ? *i : Data::CloudTheme();
  3907. const auto isTrusted = (cloud.documentId != 0);
  3908. const auto fields = [&] {
  3909. auto result = _themeCloudData.id ? _themeCloudData : cloud;
  3910. if (!result.documentId) {
  3911. result.documentId = _document->id;
  3912. }
  3913. return result;
  3914. }();
  3915. const auto weakSession = base::make_weak(&_document->session());
  3916. const auto path = _document->location().name();
  3917. const auto id = _themePreviewId = base::RandomValue<uint64>();
  3918. const auto weak = Ui::MakeWeak(_widget);
  3919. crl::async([=, data = std::move(current)]() mutable {
  3920. auto preview = GeneratePreview(
  3921. bytes,
  3922. path,
  3923. fields,
  3924. std::move(data),
  3925. Window::Theme::PreviewType::Extended);
  3926. crl::on_main(weak, [=, result = std::move(preview)]() mutable {
  3927. const auto session = weakSession.get();
  3928. if (id != _themePreviewId || !session) {
  3929. return;
  3930. }
  3931. _themePreviewId = 0;
  3932. _themePreview = std::move(result);
  3933. if (_themePreview) {
  3934. using TextTransform = Ui::RoundButton::TextTransform;
  3935. _themeApply.create(
  3936. _body,
  3937. tr::lng_theme_preview_apply(),
  3938. st::themePreviewApplyButton);
  3939. _themeApply->setTextTransform(TextTransform::NoTransform);
  3940. _themeApply->show();
  3941. _themeApply->setClickedCallback([=] {
  3942. const auto &object = Background()->themeObject();
  3943. const auto currentlyIsCustom = !object.cloud.id
  3944. && !IsEmbeddedTheme(object.pathAbsolute);
  3945. auto preview = std::move(_themePreview);
  3946. close();
  3947. Apply(std::move(preview));
  3948. if (isTrusted && !currentlyIsCustom) {
  3949. KeepApplied();
  3950. }
  3951. });
  3952. _themeCancel.create(
  3953. _body,
  3954. tr::lng_cancel(),
  3955. st::themePreviewCancelButton);
  3956. _themeCancel->setTextTransform(TextTransform::NoTransform);
  3957. _themeCancel->show();
  3958. _themeCancel->setClickedCallback([this] { close(); });
  3959. if (const auto slug = _themeCloudData.slug; !slug.isEmpty()) {
  3960. _themeShare.create(
  3961. _body,
  3962. tr::lng_theme_share(),
  3963. st::themePreviewCancelButton);
  3964. _themeShare->setTextTransform(TextTransform::NoTransform);
  3965. _themeShare->show();
  3966. _themeShare->setClickedCallback([=] {
  3967. QGuiApplication::clipboard()->setText(
  3968. session->createInternalLinkFull("addtheme/" + slug));
  3969. uiShow()->showToast(
  3970. tr::lng_background_link_copied(tr::now));
  3971. });
  3972. } else {
  3973. _themeShare.destroy();
  3974. }
  3975. updateControls();
  3976. }
  3977. update();
  3978. });
  3979. });
  3980. location.accessDisable();
  3981. }
  3982. void OverlayWidget::refreshClipControllerGeometry() {
  3983. if (!_streamed || !_streamed->controls) {
  3984. return;
  3985. }
  3986. if (_groupThumbs && _groupThumbs->hiding()) {
  3987. _groupThumbs = nullptr;
  3988. _groupThumbsRect = QRect();
  3989. }
  3990. const auto controllerBottom = (_groupThumbs && !_fullScreenVideo)
  3991. ? _groupThumbsTop
  3992. : height();
  3993. const auto skip = st::mediaviewCaptionPadding.bottom();
  3994. const auto controllerWidth = std::min(
  3995. st::mediaviewControllerSize.width(),
  3996. width() - 2 * skip);
  3997. _streamed->controls->resize(
  3998. controllerWidth,
  3999. st::mediaviewControllerSize.height());
  4000. _streamed->controls->move(
  4001. (width() - controllerWidth) / 2,
  4002. (controllerBottom // Duplicated in recountSkipTop().
  4003. - _streamed->controls->height()
  4004. - st::mediaviewCaptionPadding.bottom()));
  4005. Ui::SendPendingMoveResizeEvents(_streamed->controls.get());
  4006. }
  4007. void OverlayWidget::playbackControlsPlay() {
  4008. playbackPauseResume();
  4009. activateControls();
  4010. }
  4011. void OverlayWidget::playbackControlsPause() {
  4012. playbackPauseResume();
  4013. activateControls();
  4014. }
  4015. void OverlayWidget::playbackControlsToFullScreen() {
  4016. playbackToggleFullScreen();
  4017. activateControls();
  4018. }
  4019. void OverlayWidget::playbackControlsFromFullScreen() {
  4020. playbackToggleFullScreen();
  4021. activateControls();
  4022. }
  4023. void OverlayWidget::playbackControlsToPictureInPicture() {
  4024. if (_streamed && _streamed->controls) {
  4025. switchToPip();
  4026. }
  4027. }
  4028. void OverlayWidget::playbackControlsRotate() {
  4029. _oldGeometry = contentGeometry();
  4030. _geometryAnimation.stop();
  4031. if (_photo) {
  4032. auto &storage = _photo->owner().mediaRotation();
  4033. storage.set(_photo, storage.get(_photo) - 90);
  4034. _rotation = storage.get(_photo);
  4035. redisplayContent();
  4036. } else if (_document) {
  4037. auto &storage = _document->owner().mediaRotation();
  4038. storage.set(_document, storage.get(_document) - 90);
  4039. _rotation = storage.get(_document);
  4040. if (videoShown()) {
  4041. applyVideoSize();
  4042. } else {
  4043. redisplayContent();
  4044. }
  4045. }
  4046. if (_opengl) {
  4047. _geometryAnimation.start(
  4048. [=] { update(); },
  4049. 0.,
  4050. 1.,
  4051. st::widgetFadeDuration/*,
  4052. st::easeOutCirc*/);
  4053. }
  4054. }
  4055. void OverlayWidget::playbackPauseResume() {
  4056. Expects(_streamed != nullptr);
  4057. _streamed->resumeOnCallEnd = false;
  4058. if (_streamed->instance.player().failed()) {
  4059. clearStreaming();
  4060. if (!canInitStreaming() || !initStreaming()) {
  4061. redisplayContent();
  4062. }
  4063. } else if (_streamed->instance.player().finished()
  4064. || !_streamed->instance.player().active()
  4065. || _streamedQualityChangeFinished) {
  4066. _streamedQualityChangeFinished = false;
  4067. _streamingStartPaused = false;
  4068. restartAtSeekPosition(0);
  4069. } else if (_streamed->instance.player().paused()) {
  4070. _streamed->instance.resume();
  4071. updatePlaybackState();
  4072. playbackPauseMusic();
  4073. } else {
  4074. _streamed->instance.pause();
  4075. updatePlaybackState();
  4076. }
  4077. }
  4078. void OverlayWidget::seekRelativeTime(crl::time time) {
  4079. Expects(_streamed != nullptr);
  4080. const auto newTime = std::clamp(
  4081. _streamed->instance.info().video.state.position + time,
  4082. crl::time(0),
  4083. _streamed->instance.info().video.state.duration);
  4084. restartAtSeekPosition(newTime);
  4085. }
  4086. void OverlayWidget::restartAtProgress(float64 progress) {
  4087. Expects(_streamed != nullptr);
  4088. restartAtSeekPosition(_streamed->instance.info().video.state.duration
  4089. * std::clamp(progress, 0., 1.));
  4090. }
  4091. void OverlayWidget::restartAtSeekPosition(crl::time position) {
  4092. Expects(_streamed != nullptr);
  4093. if (videoShown()) {
  4094. _streamed->instance.saveFrameToCover();
  4095. const auto saved = base::take(_rotation);
  4096. setStaticContent(transformedShownContent());
  4097. _rotation = saved;
  4098. updateContentRect();
  4099. }
  4100. const auto overrideDuration = _stories
  4101. || (_chosenQuality && _chosenQuality != _document);
  4102. auto options = Streaming::PlaybackOptions{
  4103. .position = position,
  4104. .durationOverride = ((overrideDuration
  4105. && _document
  4106. && _document->hasDuration())
  4107. ? _document->duration()
  4108. : crl::time(0)),
  4109. .hwAllowed = Core::App().settings().hardwareAcceleratedVideo(),
  4110. .seekable = !_stories,
  4111. };
  4112. if (!_streamed->withSound) {
  4113. options.mode = Streaming::Mode::Video;
  4114. options.loop = !_stories;
  4115. } else {
  4116. Assert(_document != nullptr);
  4117. const auto messageId = _message ? _message->fullId() : FullMsgId();
  4118. options.audioId = AudioMsgId(_document, messageId);
  4119. options.speed = _stories
  4120. ? 1.
  4121. : Core::App().settings().videoPlaybackSpeed();
  4122. if (_pip) {
  4123. _pip = nullptr;
  4124. }
  4125. }
  4126. _streamed->instance.play(options);
  4127. if (_streamingStartPaused) {
  4128. _streamed->instance.pause();
  4129. } else {
  4130. playbackPauseMusic();
  4131. _streamedQualityChangeFinished = false;
  4132. }
  4133. _streamed->pausedBySeek = false;
  4134. updatePlaybackState();
  4135. }
  4136. void OverlayWidget::playbackControlsSeekProgress(crl::time position) {
  4137. Expects(_streamed != nullptr);
  4138. if (!_streamed->instance.player().paused()
  4139. && !_streamed->instance.player().finished()) {
  4140. _streamed->pausedBySeek = true;
  4141. playbackPauseResume();
  4142. }
  4143. }
  4144. void OverlayWidget::playbackControlsSeekFinished(crl::time position) {
  4145. Expects(_streamed != nullptr);
  4146. _streamingStartPaused = !_streamed->pausedBySeek
  4147. && !_streamed->instance.player().finished();
  4148. restartAtSeekPosition(position);
  4149. activateControls();
  4150. }
  4151. void OverlayWidget::playbackControlsVolumeChanged(float64 volume) {
  4152. if (_streamed) {
  4153. Player::mixer()->setVideoVolume(volume);
  4154. }
  4155. Core::App().settings().setVideoVolume(volume);
  4156. Core::App().saveSettingsDelayed();
  4157. }
  4158. float64 OverlayWidget::playbackControlsCurrentVolume() {
  4159. return Core::App().settings().videoVolume();
  4160. }
  4161. void OverlayWidget::playbackControlsVolumeToggled() {
  4162. const auto volume = Core::App().settings().videoVolume();
  4163. playbackControlsVolumeChanged(volume ? 0. : _lastPositiveVolume);
  4164. activateControls();
  4165. }
  4166. void OverlayWidget::playbackControlsVolumeChangeFinished() {
  4167. const auto volume = Core::App().settings().videoVolume();
  4168. if (volume > 0.) {
  4169. _lastPositiveVolume = volume;
  4170. }
  4171. activateControls();
  4172. }
  4173. void OverlayWidget::playbackControlsSpeedChanged(float64 speed) {
  4174. DEBUG_LOG(("Media playback speed: change to %1.").arg(speed));
  4175. if (_document) {
  4176. DEBUG_LOG(("Media playback speed: %1 to settings.").arg(speed));
  4177. Core::App().settings().setVideoPlaybackSpeed(speed);
  4178. Core::App().saveSettingsDelayed();
  4179. }
  4180. if (_streamed && _streamed->controls && !_stories) {
  4181. DEBUG_LOG(("Media playback speed: %1 to _streamed.").arg(speed));
  4182. _streamed->instance.setSpeed(speed);
  4183. }
  4184. }
  4185. float64 OverlayWidget::playbackControlsCurrentSpeed(bool lastNonDefault) {
  4186. return Core::App().settings().videoPlaybackSpeed(lastNonDefault);
  4187. }
  4188. std::vector<int> OverlayWidget::playbackControlsQualities() {
  4189. if (!_document) {
  4190. return {};
  4191. }
  4192. const auto &list = _document->resolveQualities(_message);
  4193. if (list.empty()) {
  4194. return {};
  4195. }
  4196. auto result = std::vector<int>();
  4197. result.reserve(list.size());
  4198. for (const auto &quality : list) {
  4199. result.push_back(quality->resolveVideoQuality());
  4200. }
  4201. return result;
  4202. }
  4203. VideoQuality OverlayWidget::playbackControlsCurrentQuality() {
  4204. return _chosenQuality
  4205. ? VideoQuality{
  4206. .manual = _quality.manual,
  4207. .height = uint32(_chosenQuality->resolveVideoQuality()),
  4208. }
  4209. : _quality;
  4210. }
  4211. void OverlayWidget::playbackControlsQualityChanged(int quality) {
  4212. applyVideoQuality({
  4213. .manual = (quality > 0),
  4214. .height = quality ? uint32(quality) : _quality.height,
  4215. });
  4216. }
  4217. void OverlayWidget::applyVideoQuality(VideoQuality value) {
  4218. if (_quality == value) {
  4219. return;
  4220. }
  4221. _quality = value;
  4222. Core::App().settings().setVideoQuality(value);
  4223. Core::App().saveSettingsDelayed();
  4224. if (!_document) {
  4225. return;
  4226. }
  4227. const auto resolved = _document->chooseQuality(_message, _quality);
  4228. if (_chosenQuality == resolved) {
  4229. return;
  4230. }
  4231. _chosenQuality = resolved;
  4232. if (_streamed && _streamed->instance.ready()) {
  4233. _streamedQualityChangeFrame = currentVideoFrameImage();
  4234. }
  4235. if (_streamed
  4236. && (!_streamed->instance.player().active()
  4237. || _streamed->instance.player().finished())) {
  4238. _streamedQualityChangeFinished = true;
  4239. }
  4240. _streamingStartPaused = _streamedQualityChangeFinished
  4241. || (_streamed && _streamed->instance.player().paused());
  4242. clearStreaming();
  4243. const auto time = _streamedPosition;
  4244. const auto startStreaming = StartStreaming(false, time);
  4245. if (!canInitStreaming() || !initStreaming(startStreaming)) {
  4246. redisplayContent();
  4247. }
  4248. }
  4249. void OverlayWidget::switchToPip() {
  4250. Expects(_streamed != nullptr);
  4251. Expects(_document != nullptr);
  4252. const auto document = _document;
  4253. const auto messageId = _message ? _message->fullId() : FullMsgId();
  4254. const auto topicRootId = _topicRootId;
  4255. const auto closeAndContinue = [=] {
  4256. _showAsPip = false;
  4257. show(OpenRequest(
  4258. findWindow(false),
  4259. document,
  4260. document->owner().message(messageId),
  4261. topicRootId,
  4262. true));
  4263. };
  4264. _showAsPip = true;
  4265. _pip = std::make_unique<PipWrap>(
  4266. _window,
  4267. document,
  4268. fileOrigin(),
  4269. _chosenQuality ? _chosenQuality : document,
  4270. _message,
  4271. _quality,
  4272. _streamed->instance.shared(),
  4273. closeAndContinue,
  4274. [=] { _pip = nullptr; });
  4275. if (const auto raw = _message) {
  4276. raw->history()->owner().itemRemoved(
  4277. ) | rpl::filter([=](not_null<const HistoryItem*> item) {
  4278. return (raw == item);
  4279. }) | rpl::start_with_next([=] {
  4280. _pip = nullptr;
  4281. }, _pip->lifetime);
  4282. Core::App().passcodeLockChanges(
  4283. ) | rpl::filter(
  4284. rpl::mappers::_1
  4285. ) | rpl::start_with_next([=] {
  4286. _pip = nullptr;
  4287. }, _pip->lifetime);
  4288. }
  4289. if (isHidden()) {
  4290. clearBeforeHide();
  4291. clearAfterHide();
  4292. } else {
  4293. close();
  4294. if (const auto window = Core::App().activeWindow()) {
  4295. window->activate();
  4296. }
  4297. }
  4298. }
  4299. not_null<Ui::RpWidget*> OverlayWidget::storiesWrap() {
  4300. return _body;
  4301. }
  4302. std::shared_ptr<ChatHelpers::Show> OverlayWidget::storiesShow() {
  4303. return uiShow();
  4304. }
  4305. std::shared_ptr<ChatHelpers::Show> OverlayWidget::uiShow() {
  4306. if (!_cachedShow) {
  4307. _cachedShow = std::make_shared<Show>(this);
  4308. }
  4309. return _cachedShow;
  4310. }
  4311. auto OverlayWidget::storiesStickerOrEmojiChosen()
  4312. -> rpl::producer<ChatHelpers::FileChosen> {
  4313. return _storiesStickerOrEmojiChosen.events();
  4314. }
  4315. void OverlayWidget::storiesJumpTo(
  4316. not_null<Main::Session*> session,
  4317. FullStoryId id,
  4318. Data::StoriesContext context) {
  4319. Expects(_stories != nullptr);
  4320. Expects(id.valid());
  4321. const auto maybeStory = session->data().stories().lookup(id);
  4322. if (!maybeStory) {
  4323. close();
  4324. return;
  4325. }
  4326. const auto story = *maybeStory;
  4327. setContext(StoriesContext{
  4328. story->peer(),
  4329. story->id(),
  4330. context,
  4331. });
  4332. clearStreaming();
  4333. _streamingStartPaused = false;
  4334. v::match(story->media().data, [&](not_null<PhotoData*> photo) {
  4335. displayPhoto(photo, anim::activation::background);
  4336. }, [&](not_null<DocumentData*> document) {
  4337. displayDocument(document, anim::activation::background);
  4338. }, [&](v::null_t) {
  4339. displayDocument(nullptr, anim::activation::background);
  4340. });
  4341. }
  4342. void OverlayWidget::storiesRedisplay(not_null<Data::Story*> story) {
  4343. Expects(_stories != nullptr);
  4344. clearStreaming();
  4345. _streamingStartPaused = false;
  4346. v::match(story->media().data, [&](not_null<PhotoData*> photo) {
  4347. displayPhoto(photo, anim::activation::background);
  4348. }, [&](not_null<DocumentData*> document) {
  4349. displayDocument(document, anim::activation::background);
  4350. }, [&](v::null_t) {
  4351. displayDocument(nullptr, anim::activation::background);
  4352. });
  4353. }
  4354. void OverlayWidget::storiesClose() {
  4355. close();
  4356. }
  4357. bool OverlayWidget::storiesPaused() {
  4358. return _streamed
  4359. && !_streamed->instance.player().failed()
  4360. && !_streamed->instance.player().finished()
  4361. && _streamed->instance.player().active()
  4362. && _streamed->instance.player().paused();
  4363. }
  4364. rpl::producer<bool> OverlayWidget::storiesLayerShown() {
  4365. return _layerBg->layerShownValue();
  4366. }
  4367. void OverlayWidget::storiesTogglePaused(bool paused) {
  4368. if (!_streamed
  4369. || _streamed->instance.player().failed()
  4370. || _streamed->instance.player().finished()
  4371. || !_streamed->instance.player().active()) {
  4372. return;
  4373. } else if (_streamed->instance.player().paused()) {
  4374. if (!paused) {
  4375. _streamed->instance.resume();
  4376. updatePlaybackState();
  4377. playbackPauseMusic();
  4378. }
  4379. } else if (paused) {
  4380. _streamed->instance.pause();
  4381. updatePlaybackState();
  4382. }
  4383. }
  4384. float64 OverlayWidget::storiesSiblingOver(Stories::SiblingType type) {
  4385. return (type == Stories::SiblingType::Left)
  4386. ? overLevel(Over::LeftStories)
  4387. : overLevel(Over::RightStories);
  4388. }
  4389. void OverlayWidget::storiesRepaint() {
  4390. update();
  4391. }
  4392. void OverlayWidget::storiesVolumeToggle() {
  4393. playbackControlsVolumeToggled();
  4394. }
  4395. void OverlayWidget::storiesVolumeChanged(float64 volume) {
  4396. playbackControlsVolumeChanged(volume);
  4397. }
  4398. void OverlayWidget::storiesVolumeChangeFinished() {
  4399. playbackControlsVolumeChangeFinished();
  4400. }
  4401. int OverlayWidget::topNotchSkip() const {
  4402. return _fullscreen ? _topNotchSize : 0;
  4403. }
  4404. int OverlayWidget::storiesTopNotchSkip() {
  4405. return topNotchSkip();
  4406. }
  4407. void OverlayWidget::playbackToggleFullScreen() {
  4408. Expects(_streamed != nullptr);
  4409. if (_stories
  4410. || !videoShown()
  4411. || (!_streamed->controls && !_fullScreenVideo)) {
  4412. return;
  4413. }
  4414. _fullScreenVideo = !_fullScreenVideo;
  4415. if (_fullScreenVideo) {
  4416. _fullScreenZoomCache = _zoom;
  4417. }
  4418. resizeCenteredControls();
  4419. recountSkipTop();
  4420. setZoomLevel(
  4421. _fullScreenVideo ? kZoomToScreenLevel : _fullScreenZoomCache,
  4422. true);
  4423. if (_streamed->controls) {
  4424. if (!_fullScreenVideo) {
  4425. _streamed->controls->showAnimated();
  4426. }
  4427. _streamed->controls->setInFullScreen(_fullScreenVideo);
  4428. }
  4429. _touchbarFullscreenToggled.fire_copy(_fullScreenVideo);
  4430. updateControls();
  4431. update();
  4432. }
  4433. void OverlayWidget::playbackPauseOnCall() {
  4434. Expects(_streamed != nullptr);
  4435. if (_streamed->instance.player().finished()
  4436. || _streamed->instance.player().paused()) {
  4437. return;
  4438. }
  4439. _streamed->resumeOnCallEnd = true;
  4440. _streamed->instance.pause();
  4441. updatePlaybackState();
  4442. }
  4443. void OverlayWidget::playbackResumeOnCall() {
  4444. Expects(_streamed != nullptr);
  4445. if (_streamed->resumeOnCallEnd) {
  4446. _streamed->resumeOnCallEnd = false;
  4447. _streamed->instance.resume();
  4448. updatePlaybackState();
  4449. playbackPauseMusic();
  4450. }
  4451. }
  4452. void OverlayWidget::playbackPauseMusic() {
  4453. Expects(_streamed != nullptr);
  4454. if (!_streamed->withSound) {
  4455. return;
  4456. }
  4457. Player::instance()->pause(AudioMsgId::Type::Voice);
  4458. Player::instance()->pause(AudioMsgId::Type::Song);
  4459. }
  4460. void OverlayWidget::updatePlaybackState() {
  4461. Expects(_streamed != nullptr);
  4462. if (!_streamed->controls && !_stories) {
  4463. return;
  4464. }
  4465. const auto state = _streamed->instance.player().prepareLegacyState();
  4466. if (state.position != kTimeUnknown && state.length != kTimeUnknown) {
  4467. _streamedPosition = state.position;
  4468. if (_streamed->controls) {
  4469. _streamed->controls->updatePlayback(state);
  4470. _touchbarTrackState.fire_copy(state);
  4471. updatePowerSaveBlocker(state);
  4472. }
  4473. if (_stories) {
  4474. _stories->updatePlayback(state);
  4475. }
  4476. }
  4477. }
  4478. void OverlayWidget::validatePhotoImage(Image *image, bool blurred) {
  4479. if (!image) {
  4480. return;
  4481. } else if (!_staticContent.isNull() && (blurred || !_blurred)) {
  4482. return;
  4483. }
  4484. const auto use = flipSizeByRotation({ _width, _height })
  4485. * style::DevicePixelRatio();
  4486. setStaticContent(image->pixNoCache(
  4487. use,
  4488. { .options = (blurred ? Images::Option::Blur : Images::Option()) }
  4489. ).toImage());
  4490. _blurred = blurred;
  4491. }
  4492. void OverlayWidget::validatePhotoCurrentImage() {
  4493. if (!_photo) {
  4494. return;
  4495. }
  4496. validatePhotoImage(_photoMedia->image(Data::PhotoSize::Large), false);
  4497. validatePhotoImage(_photoMedia->image(Data::PhotoSize::Thumbnail), true);
  4498. validatePhotoImage(_photoMedia->image(Data::PhotoSize::Small), true);
  4499. validatePhotoImage(_photoMedia->thumbnailInline(), true);
  4500. if (_staticContent.isNull()
  4501. && !_message
  4502. && _peer
  4503. && _peer->hasUserpic()) {
  4504. if (const auto view = _peer->activeUserpicView(); view.cloud) {
  4505. if (!view.cloud->isNull()) {
  4506. auto image = Image(base::duplicate(*view.cloud));
  4507. validatePhotoImage(&image, true);
  4508. }
  4509. }
  4510. }
  4511. if (_staticContent.isNull()) {
  4512. _photoMedia->wanted(Data::PhotoSize::Small, fileOrigin());
  4513. }
  4514. }
  4515. Ui::GL::ChosenRenderer OverlayWidget::chooseRenderer(
  4516. Ui::GL::Backend backend) {
  4517. _opengl = (backend == Ui::GL::Backend::OpenGL);
  4518. return {
  4519. .renderer = (_opengl
  4520. ? std::unique_ptr<Ui::GL::Renderer>(
  4521. std::make_unique<RendererGL>(this))
  4522. : std::make_unique<RendererSW>(this)),
  4523. .backend = backend,
  4524. };
  4525. }
  4526. void OverlayWidget::paint(not_null<Renderer*> renderer) {
  4527. renderer->paintBackground();
  4528. if (contentShown()) {
  4529. if (videoShown()) {
  4530. renderer->paintTransformedVideoFrame(contentGeometry());
  4531. if (_streamed->instance.player().ready()) {
  4532. _streamed->instance.markFrameShown();
  4533. if (_stories) {
  4534. _stories->ready();
  4535. }
  4536. }
  4537. } else {
  4538. validatePhotoCurrentImage();
  4539. if (_stories && !_blurred) {
  4540. _stories->ready();
  4541. }
  4542. const auto fillTransparentBackground = (!_document
  4543. || (!_document->sticker() && !_document->isVideoMessage()))
  4544. && _staticContentTransparent;
  4545. renderer->paintTransformedStaticContent(
  4546. _staticContent,
  4547. contentGeometry(),
  4548. _staticContentTransparent,
  4549. fillTransparentBackground);
  4550. }
  4551. paintRadialLoading(renderer);
  4552. if (_stories) {
  4553. using namespace Stories;
  4554. const auto paint = [&](const SiblingView &view, int index) {
  4555. renderer->paintTransformedStaticContent(
  4556. view.image,
  4557. storiesContentGeometry(view.layout, view.scale),
  4558. false, // semi-transparent
  4559. false, // fill transparent background
  4560. index);
  4561. const auto base = (index - 1) * 2;
  4562. const auto userpicSize = view.userpic.size()
  4563. / view.userpic.devicePixelRatio();
  4564. renderer->paintStoriesSiblingPart(
  4565. base,
  4566. view.userpic,
  4567. QRect(view.userpicPosition, userpicSize));
  4568. const auto nameSize = view.name.size()
  4569. / view.name.devicePixelRatio();
  4570. renderer->paintStoriesSiblingPart(
  4571. base + 1,
  4572. view.name,
  4573. QRect(view.namePosition, nameSize),
  4574. view.nameOpacity);
  4575. };
  4576. if (const auto left = _stories->sibling(SiblingType::Left)) {
  4577. paint(left, kLeftSiblingTextureIndex);
  4578. }
  4579. if (const auto right = _stories->sibling(SiblingType::Right)) {
  4580. paint(right, kRightSiblingTextureIndex);
  4581. }
  4582. }
  4583. } else if (_stories) {
  4584. // Unsupported story.
  4585. } else if (_themePreviewShown) {
  4586. renderer->paintThemePreview(_themePreviewRect);
  4587. } else if (documentBubbleShown() && !_docRect.isEmpty()) {
  4588. renderer->paintDocumentBubble(_docRect, _docIconRect);
  4589. }
  4590. if (isSaveMsgShown()) {
  4591. renderer->paintSaveMsg(_saveMsg);
  4592. }
  4593. const auto opacity = _fullScreenVideo ? 0. : _controlsOpacity.current();
  4594. if (opacity > 0) {
  4595. paintControls(renderer, opacity);
  4596. if (!_stories) {
  4597. renderer->paintFooter(footerGeometry(), opacity);
  4598. }
  4599. if (!(_stories ? _stories->skipCaption() : _caption.isEmpty())) {
  4600. renderer->paintCaption(captionGeometry(), opacity);
  4601. }
  4602. if (_groupThumbs) {
  4603. renderer->paintGroupThumbs(
  4604. QRect(
  4605. _groupThumbsLeft,
  4606. _groupThumbsTop,
  4607. width() - 2 * _groupThumbsLeft,
  4608. _groupThumbs->height()),
  4609. opacity);
  4610. }
  4611. }
  4612. checkGroupThumbsAnimation();
  4613. if (const auto radius = _window->manualRoundingRadius()) {
  4614. renderer->paintRoundedCorners(radius);
  4615. }
  4616. }
  4617. void OverlayWidget::checkGroupThumbsAnimation() {
  4618. if (_groupThumbs
  4619. && (!_streamed || _streamed->instance.player().ready())) {
  4620. _groupThumbs->checkForAnimationStart();
  4621. }
  4622. }
  4623. void OverlayWidget::paintRadialLoading(not_null<Renderer*> renderer) {
  4624. const auto radial = _radial.animating();
  4625. if (_streamed) {
  4626. if (!_streamed->instance.waitingShown()) {
  4627. return;
  4628. }
  4629. } else if (!radial && (!_document || _documentMedia->loaded())) {
  4630. return;
  4631. }
  4632. const auto radialOpacity = radial ? _radial.opacity() : 0.;
  4633. const auto inner = radialRect();
  4634. Assert(!inner.isEmpty());
  4635. renderer->paintRadialLoading(inner, radial, radialOpacity);
  4636. }
  4637. void OverlayWidget::paintRadialLoadingContent(
  4638. Painter &p,
  4639. QRect inner,
  4640. bool radial,
  4641. float64 radialOpacity) const {
  4642. const auto arc = inner.marginsRemoved(QMargins(
  4643. st::radialLine,
  4644. st::radialLine,
  4645. st::radialLine,
  4646. st::radialLine));
  4647. const auto paintBg = [&](float64 opacity, QBrush brush) {
  4648. p.setOpacity(opacity);
  4649. p.setPen(Qt::NoPen);
  4650. p.setBrush(brush);
  4651. {
  4652. PainterHighQualityEnabler hq(p);
  4653. p.drawEllipse(inner);
  4654. }
  4655. p.setOpacity(1.);
  4656. };
  4657. if (_streamed) {
  4658. paintBg(
  4659. _streamed->instance.waitingOpacity(),
  4660. st::radialBg);
  4661. Ui::InfiniteRadialAnimation::Draw(
  4662. p,
  4663. _streamed->instance.waitingState(),
  4664. arc.topLeft(),
  4665. arc.size(),
  4666. width(),
  4667. st::radialFg,
  4668. st::radialLine);
  4669. return;
  4670. }
  4671. if (_photo) {
  4672. paintBg(radialOpacity, st::radialBg);
  4673. } else {
  4674. const auto o = overLevel(Over::Icon);
  4675. paintBg(
  4676. _documentMedia->loaded() ? radialOpacity : 1.,
  4677. anim::brush(st::msgDateImgBg, st::msgDateImgBgOver, o));
  4678. const auto icon = [&]() -> const style::icon * {
  4679. if (radial || _document->loading()) {
  4680. return &st::historyFileThumbCancel;
  4681. }
  4682. return &st::historyFileThumbDownload;
  4683. }();
  4684. if (icon) {
  4685. icon->paintInCenter(p, inner);
  4686. }
  4687. }
  4688. if (radial) {
  4689. p.setOpacity(1);
  4690. _radial.draw(p, arc, st::radialLine, st::radialFg);
  4691. }
  4692. }
  4693. void OverlayWidget::paintThemePreviewContent(
  4694. Painter &p,
  4695. QRect outer,
  4696. QRect clip) {
  4697. const auto fill = outer.intersected(clip);
  4698. if (!fill.isEmpty()) {
  4699. if (_themePreview) {
  4700. p.drawImage(
  4701. outer.topLeft(),
  4702. _themePreview->preview);
  4703. } else {
  4704. p.fillRect(fill, st::themePreviewBg);
  4705. p.setFont(st::themePreviewLoadingFont);
  4706. p.setPen(st::themePreviewLoadingFg);
  4707. p.drawText(
  4708. outer,
  4709. (_themePreviewId
  4710. ? tr::lng_theme_preview_generating(tr::now)
  4711. : tr::lng_theme_preview_invalid(tr::now)),
  4712. QTextOption(style::al_center));
  4713. }
  4714. }
  4715. const auto fillOverlay = [&](QRect fill) {
  4716. const auto clipped = fill.intersected(clip);
  4717. if (!clipped.isEmpty()) {
  4718. p.setOpacity(st::themePreviewOverlayOpacity);
  4719. p.fillRect(clipped, st::themePreviewBg);
  4720. p.setOpacity(1.);
  4721. }
  4722. };
  4723. auto titleRect = QRect(
  4724. outer.x(),
  4725. outer.y(),
  4726. outer.width(),
  4727. st::themePreviewMargin.top());
  4728. if (titleRect.x() < 0) {
  4729. titleRect = QRect(
  4730. 0,
  4731. outer.y(),
  4732. width(),
  4733. st::themePreviewMargin.top());
  4734. }
  4735. if (titleRect.y() < 0) {
  4736. titleRect.moveTop(0);
  4737. fillOverlay(titleRect);
  4738. }
  4739. titleRect = titleRect.marginsRemoved(QMargins(
  4740. st::themePreviewMargin.left(),
  4741. st::themePreviewTitleTop,
  4742. st::themePreviewMargin.right(),
  4743. (titleRect.height()
  4744. - st::themePreviewTitleTop
  4745. - st::themePreviewTitleFont->height)));
  4746. if (titleRect.intersects(clip)) {
  4747. p.setFont(st::themePreviewTitleFont);
  4748. p.setPen(st::themePreviewTitleFg);
  4749. const auto title = _themeCloudData.title.isEmpty()
  4750. ? tr::lng_theme_preview_title(tr::now)
  4751. : _themeCloudData.title;
  4752. const auto elided = st::themePreviewTitleFont->elided(
  4753. title,
  4754. titleRect.width());
  4755. p.drawTextLeft(titleRect.x(), titleRect.y(), width(), elided);
  4756. }
  4757. auto buttonsRect = QRect(
  4758. outer.x(),
  4759. outer.y() + outer.height() - st::themePreviewMargin.bottom(),
  4760. outer.width(),
  4761. st::themePreviewMargin.bottom());
  4762. if (buttonsRect.y() + buttonsRect.height() > height()) {
  4763. buttonsRect.moveTop(height() - buttonsRect.height());
  4764. fillOverlay(buttonsRect);
  4765. }
  4766. if (_themeShare && _themeCloudData.usersCount > 0) {
  4767. p.setFont(st::boxTextFont);
  4768. p.setPen(st::windowSubTextFg);
  4769. const auto left = outer.x()
  4770. + (_themeShare->x() - _themePreviewRect.x())
  4771. + _themeShare->width()
  4772. - (st::themePreviewCancelButton.width / 2);
  4773. const auto baseline = outer.y()
  4774. + (_themeShare->y() - _themePreviewRect.y())
  4775. + st::themePreviewCancelButton.padding.top()
  4776. + st::themePreviewCancelButton.textTop
  4777. + st::themePreviewCancelButton.style.font->ascent;
  4778. p.drawText(
  4779. left,
  4780. baseline,
  4781. tr::lng_theme_preview_users(
  4782. tr::now,
  4783. lt_count,
  4784. _themeCloudData.usersCount));
  4785. }
  4786. }
  4787. void OverlayWidget::paintDocumentBubbleContent(
  4788. Painter &p,
  4789. QRect outer,
  4790. QRect icon,
  4791. QRect clip) const {
  4792. p.fillRect(outer, st::mediaviewFileBg);
  4793. if (icon.intersects(clip)) {
  4794. if (!_document || !_document->hasThumbnail()) {
  4795. p.fillRect(icon, _docIconColor);
  4796. const auto radial = _radial.animating();
  4797. const auto radialOpacity = radial ? _radial.opacity() : 0.;
  4798. if ((!_document || _documentMedia->loaded()) && (!radial || radialOpacity < 1) && _docIcon) {
  4799. _docIcon->paint(p, icon.x() + (icon.width() - _docIcon->width()), icon.y(), width());
  4800. p.setPen(st::mediaviewFileExtFg);
  4801. p.setFont(st::mediaviewFileExtFont);
  4802. if (!_docExt.isEmpty()) {
  4803. p.drawText(icon.x() + (icon.width() - _docExtWidth) / 2, icon.y() + st::mediaviewFileExtTop + st::mediaviewFileExtFont->ascent, _docExt);
  4804. }
  4805. }
  4806. } else if (const auto thumbnail = _documentMedia->thumbnail()) {
  4807. int32 rf(style::DevicePixelRatio());
  4808. p.drawPixmap(icon.topLeft(), thumbnail->pix(_docThumbw), QRect(_docThumbx * rf, _docThumby * rf, st::mediaviewFileIconSize * rf, st::mediaviewFileIconSize * rf));
  4809. }
  4810. }
  4811. if (!icon.contains(clip)) {
  4812. p.setPen(st::mediaviewFileNameFg);
  4813. p.setFont(st::mediaviewFileNameFont);
  4814. p.drawTextLeft(outer.x() + 2 * st::mediaviewFilePadding + st::mediaviewFileIconSize, outer.y() + st::mediaviewFilePadding + st::mediaviewFileNameTop, width(), _docName, _docNameWidth);
  4815. p.setPen(st::mediaviewFileSizeFg);
  4816. p.setFont(st::mediaviewFont);
  4817. p.drawTextLeft(outer.x() + 2 * st::mediaviewFilePadding + st::mediaviewFileIconSize, outer.y() + st::mediaviewFilePadding + st::mediaviewFileSizeTop, width(), _docSize, _docSizeWidth);
  4818. }
  4819. }
  4820. void OverlayWidget::paintSaveMsgContent(
  4821. Painter &p,
  4822. QRect outer,
  4823. QRect clip) {
  4824. p.setOpacity(_saveMsgAnimation.value(1.));
  4825. Ui::FillRoundRect(p, outer, st::mediaviewSaveMsgBg, Ui::MediaviewSaveCorners);
  4826. st::mediaviewSaveMsgCheck.paint(p, outer.topLeft() + st::mediaviewSaveMsgCheckPos, width());
  4827. p.setPen(st::mediaviewSaveMsgFg);
  4828. _saveMsgText.draw(p, {
  4829. .position = QPoint(
  4830. outer.x() + st::mediaviewSaveMsgPadding.left(),
  4831. outer.y() + st::mediaviewSaveMsgPadding.top()),
  4832. .availableWidth = outer.width() - st::mediaviewSaveMsgPadding.left() - st::mediaviewSaveMsgPadding.right(),
  4833. .palette = &st::mediaviewTextPalette,
  4834. });
  4835. p.setOpacity(1);
  4836. }
  4837. bool OverlayWidget::saveControlLocked() const {
  4838. const auto story = _stories ? _stories->story() : nullptr;
  4839. return story
  4840. && story->canDownloadIfPremium()
  4841. && !story->canDownloadChecked();
  4842. }
  4843. void OverlayWidget::paintControls(
  4844. not_null<Renderer*> renderer,
  4845. float64 opacity) {
  4846. struct Control {
  4847. Over state = Over::None;
  4848. bool visible = false;
  4849. const QRect &over;
  4850. const QRect &inner;
  4851. const style::icon &icon;
  4852. bool nonbright = false;
  4853. };
  4854. // When adding / removing controls please update RendererGL.
  4855. const Control controls[] = {
  4856. {
  4857. Over::Left,
  4858. _leftNavVisible,
  4859. _leftNavOver,
  4860. _leftNavIcon,
  4861. _stories ? st::storiesLeft : st::mediaviewLeft,
  4862. true },
  4863. {
  4864. Over::Right,
  4865. _rightNavVisible,
  4866. _rightNavOver,
  4867. _rightNavIcon,
  4868. _stories ? st::storiesRight : st::mediaviewRight,
  4869. true },
  4870. {
  4871. Over::Save,
  4872. _saveVisible,
  4873. _saveNavOver,
  4874. _saveNavIcon,
  4875. (saveControlLocked()
  4876. ? st::mediaviewSaveLocked
  4877. : st::mediaviewSave) },
  4878. {
  4879. Over::Share,
  4880. _shareVisible,
  4881. _shareNavOver,
  4882. _shareNavIcon,
  4883. st::mediaviewShare },
  4884. {
  4885. Over::Rotate,
  4886. _rotateVisible,
  4887. _rotateNavOver,
  4888. _rotateNavIcon,
  4889. st::mediaviewRotate },
  4890. {
  4891. Over::More,
  4892. true,
  4893. _moreNavOver,
  4894. _moreNavIcon,
  4895. st::mediaviewMore },
  4896. };
  4897. renderer->paintControlsStart();
  4898. for (const auto &control : controls) {
  4899. if (!control.visible) {
  4900. continue;
  4901. }
  4902. const auto progress = overLevel(control.state);
  4903. const auto bg = progress;
  4904. const auto icon = controlOpacity(progress, control.nonbright);
  4905. renderer->paintControl(
  4906. control.state,
  4907. control.over,
  4908. bg * opacity,
  4909. control.inner,
  4910. icon * opacity,
  4911. control.icon);
  4912. }
  4913. }
  4914. float64 OverlayWidget::controlOpacity(
  4915. float64 progress,
  4916. bool nonbright) const {
  4917. if (nonbright && _stories) {
  4918. return progress * kStoriesNavOverOpacity
  4919. + (1. - progress) * kStoriesNavOpacity;
  4920. }
  4921. const auto normal = _windowed
  4922. ? kNormalIconOpacity
  4923. : kMaximizedIconOpacity;
  4924. return progress + (1. - progress) * normal;
  4925. }
  4926. void OverlayWidget::paintFooterContent(
  4927. Painter &p,
  4928. QRect outer,
  4929. QRect clip,
  4930. float64 opacity) {
  4931. p.setPen(st::mediaviewControlFg);
  4932. p.setFont(st::mediaviewThickFont);
  4933. // header
  4934. const auto shift = outer.topLeft() - _headerNav.topLeft();
  4935. const auto header = _headerNav.translated(shift);
  4936. const auto name = _nameNav.translated(shift);
  4937. const auto date = _dateNav.translated(shift);
  4938. if (header.intersects(clip)) {
  4939. auto o = _headerHasLink ? overLevel(Over::Header) : 0;
  4940. p.setOpacity(controlOpacity(o) * opacity);
  4941. p.drawText(header.left(), header.top() + st::mediaviewThickFont->ascent, _headerText);
  4942. if (o > 0) {
  4943. p.setOpacity(o * opacity);
  4944. p.drawLine(header.left(), header.top() + st::mediaviewThickFont->ascent + 1, header.right(), header.top() + st::mediaviewThickFont->ascent + 1);
  4945. }
  4946. }
  4947. p.setFont(st::mediaviewFont);
  4948. // name
  4949. if (_nameNav.isValid() && name.intersects(clip)) {
  4950. float64 o = _from ? overLevel(Over::Name) : 0.;
  4951. p.setOpacity(controlOpacity(o) * opacity);
  4952. _fromNameLabel.drawElided(p, name.left(), name.top(), name.width());
  4953. if (o > 0) {
  4954. p.setOpacity(o * opacity);
  4955. p.drawLine(name.left(), name.top() + st::mediaviewFont->ascent + 1, name.right(), name.top() + st::mediaviewFont->ascent + 1);
  4956. }
  4957. }
  4958. // date
  4959. if (date.intersects(clip)) {
  4960. float64 o = overLevel(Over::Date);
  4961. p.setOpacity(controlOpacity(o) * opacity);
  4962. p.drawText(date.left(), date.top() + st::mediaviewFont->ascent, _dateText);
  4963. if (o > 0) {
  4964. p.setOpacity(o * opacity);
  4965. p.drawLine(date.left(), date.top() + st::mediaviewFont->ascent + 1, date.right(), date.top() + st::mediaviewFont->ascent + 1);
  4966. }
  4967. }
  4968. }
  4969. QRect OverlayWidget::footerGeometry() const {
  4970. return _headerNav.united(_nameNav).united(_dateNav);
  4971. }
  4972. void OverlayWidget::paintCaptionContent(
  4973. Painter &p,
  4974. QRect outer,
  4975. QRect clip,
  4976. float64 opacity) {
  4977. const auto full = outer.marginsRemoved(st::mediaviewCaptionPadding);
  4978. const auto inner = full.marginsRemoved(
  4979. _stories ? _stories->repostCaptionPadding() : QMargins());
  4980. if (_stories) {
  4981. p.setOpacity(1.);
  4982. if (_stories->repost()) {
  4983. _stories->drawRepostInfo(p, full.x(), full.y(), full.width());
  4984. }
  4985. } else {
  4986. p.setOpacity(opacity);
  4987. p.setBrush(st::mediaviewCaptionBg);
  4988. p.setPen(Qt::NoPen);
  4989. p.drawRoundedRect(
  4990. outer,
  4991. st::mediaviewCaptionRadius,
  4992. st::mediaviewCaptionRadius);
  4993. }
  4994. if (inner.intersects(clip)) {
  4995. p.setPen(st::mediaviewCaptionFg);
  4996. _caption.draw(p, {
  4997. .position = inner.topLeft(),
  4998. .availableWidth = inner.width(),
  4999. .palette = &st::mediaviewTextPalette,
  5000. .spoiler = Ui::Text::DefaultSpoilerCache(),
  5001. .pausedEmoji = On(PowerSaving::kEmojiChat),
  5002. .pausedSpoiler = On(PowerSaving::kChatSpoiler),
  5003. .elisionHeight = inner.height(),
  5004. .elisionRemoveFromEnd = _captionSkipBlockWidth,
  5005. });
  5006. if (_captionShowMoreWidth > 0) {
  5007. const auto padding = st::storiesShowMorePadding;
  5008. const auto showMoreLeft = outer.x()
  5009. + outer.width()
  5010. - padding.right()
  5011. - _captionShowMoreWidth;
  5012. const auto showMoreTop = outer.y()
  5013. + outer.height()
  5014. - padding.bottom()
  5015. - st::storiesShowMoreFont->height;
  5016. const auto underline = _captionExpandLink
  5017. && ClickHandler::showAsActive(_captionExpandLink);
  5018. p.setFont(underline
  5019. ? st::storiesShowMoreFont->underline()
  5020. : st::storiesShowMoreFont);
  5021. p.drawTextLeft(
  5022. showMoreLeft,
  5023. showMoreTop,
  5024. width(),
  5025. tr::lng_stories_show_more(tr::now));
  5026. }
  5027. }
  5028. }
  5029. QRect OverlayWidget::captionGeometry() const {
  5030. return _captionRect.marginsAdded(
  5031. st::mediaviewCaptionPadding
  5032. ).marginsAdded(
  5033. _stories ? _stories->repostCaptionPadding() : QMargins());
  5034. }
  5035. void OverlayWidget::paintGroupThumbsContent(
  5036. Painter &p,
  5037. QRect outer,
  5038. QRect clip,
  5039. float64 opacity) {
  5040. p.setOpacity(opacity);
  5041. _groupThumbs->paint(p, outer.x(), outer.y(), width());
  5042. if (_groupThumbs->hidden()) {
  5043. _groupThumbs = nullptr;
  5044. _groupThumbsRect = QRect();
  5045. }
  5046. }
  5047. bool OverlayWidget::isSaveMsgShown() const {
  5048. return _saveMsgAnimation.animating() || _saveMsgTimer.isActive();
  5049. }
  5050. void OverlayWidget::handleKeyPress(not_null<QKeyEvent*> e) {
  5051. if (_processingKeyPress) {
  5052. return;
  5053. }
  5054. _processingKeyPress = true;
  5055. const auto guard = gsl::finally([&] { _processingKeyPress = false; });
  5056. const auto key = e->key();
  5057. const auto modifiers = e->modifiers();
  5058. const auto ctrl = modifiers.testFlag(Qt::ControlModifier);
  5059. if (_stories) {
  5060. if (key == Qt::Key_Space && _down != Over::Video) {
  5061. _stories->togglePaused(!_stories->paused());
  5062. return;
  5063. }
  5064. } else if (_streamed) {
  5065. // Ctrl + F for full screen toggle is in eventFilter().
  5066. const auto toggleFull = (modifiers.testFlag(Qt::AltModifier) || ctrl)
  5067. && (key == Qt::Key_Enter || key == Qt::Key_Return);
  5068. if (toggleFull) {
  5069. playbackToggleFullScreen();
  5070. return;
  5071. } else if (key == Qt::Key_Space) {
  5072. playbackPauseResume();
  5073. return;
  5074. } else if (_fullScreenVideo) {
  5075. if (key == Qt::Key_Escape) {
  5076. playbackToggleFullScreen();
  5077. } else if (ctrl) {
  5078. } else if (key == Qt::Key_0) {
  5079. activateControls();
  5080. restartAtSeekPosition(0);
  5081. } else if (key >= Qt::Key_1 && key <= Qt::Key_9) {
  5082. activateControls();
  5083. const auto index = int(key - Qt::Key_0);
  5084. restartAtProgress(index / 10.0);
  5085. } else if (key == Qt::Key_Left) {
  5086. activateControls();
  5087. seekRelativeTime(-kSeekTimeMs);
  5088. } else if (key == Qt::Key_Right) {
  5089. activateControls();
  5090. seekRelativeTime(kSeekTimeMs);
  5091. }
  5092. return;
  5093. }
  5094. }
  5095. if (!_menu && key == Qt::Key_Escape) {
  5096. if (_document && _document->loading() && !_streamed) {
  5097. handleDocumentClick();
  5098. } else {
  5099. close();
  5100. }
  5101. } else if (e == QKeySequence::Save || e == QKeySequence::SaveAs) {
  5102. saveAs();
  5103. } else if (key == Qt::Key_Copy || (key == Qt::Key_C && ctrl)) {
  5104. copyMedia();
  5105. } else if (key == Qt::Key_Enter
  5106. || key == Qt::Key_Return
  5107. || key == Qt::Key_Space) {
  5108. if (_streamed) {
  5109. playbackPauseResume();
  5110. } else if (_document
  5111. && !_document->loading()
  5112. && (documentBubbleShown() || !_documentMedia->loaded())) {
  5113. handleDocumentClick();
  5114. }
  5115. } else if (key == Qt::Key_Left) {
  5116. if (_controlsHideTimer.isActive()) {
  5117. activateControls();
  5118. }
  5119. moveToNext(-1);
  5120. } else if (key == Qt::Key_H) {
  5121. if (_flip & Qt::Horizontal) {
  5122. _flip &= ~Qt::Horizontal;
  5123. } else {
  5124. _flip |= Qt::Horizontal;
  5125. }
  5126. if (_photo) {
  5127. validatePhotoCurrentImage();
  5128. redisplayContent();
  5129. }
  5130. } else if (key == Qt::Key_V) {
  5131. if (_flip & Qt::Vertical) {
  5132. _flip &= ~Qt::Vertical;
  5133. } else {
  5134. _flip |= Qt::Vertical;
  5135. }
  5136. if (_photo) {
  5137. validatePhotoCurrentImage();
  5138. redisplayContent();
  5139. }
  5140. } else if (key == Qt::Key_Right) {
  5141. if (_controlsHideTimer.isActive()) {
  5142. activateControls();
  5143. }
  5144. if (!moveToNext(1) && _stories) {
  5145. storiesClose();
  5146. }
  5147. } else if (ctrl) {
  5148. if (key == Qt::Key_Plus
  5149. || key == Qt::Key_Equal
  5150. || key == Qt::Key_Asterisk
  5151. || key == ']') {
  5152. zoomIn();
  5153. } else if (key == Qt::Key_Minus || key == Qt::Key_Underscore) {
  5154. zoomOut();
  5155. }
  5156. } else if (_stories) {
  5157. _stories->tryProcessKeyInput(e);
  5158. }
  5159. }
  5160. void OverlayWidget::handleWheelEvent(not_null<QWheelEvent*> e) {
  5161. constexpr auto step = int(QWheelEvent::DefaultDeltasPerStep);
  5162. const auto acceptForJump = !_stories
  5163. && ((e->source() == Qt::MouseEventNotSynthesized)
  5164. || (e->source() == Qt::MouseEventSynthesizedBySystem));
  5165. _verticalWheelDelta += e->angleDelta().y();
  5166. while (qAbs(_verticalWheelDelta) >= step) {
  5167. if (_verticalWheelDelta < 0) {
  5168. _verticalWheelDelta += step;
  5169. if (e->modifiers().testFlag(Qt::ControlModifier)) {
  5170. zoomOut();
  5171. } else if (acceptForJump) {
  5172. moveToNext(1);
  5173. }
  5174. } else {
  5175. _verticalWheelDelta -= step;
  5176. if (e->modifiers().testFlag(Qt::ControlModifier)) {
  5177. zoomIn();
  5178. } else if (acceptForJump) {
  5179. moveToNext(-1);
  5180. }
  5181. }
  5182. }
  5183. }
  5184. void OverlayWidget::setZoomLevel(int newZoom, bool force) {
  5185. if (_stories
  5186. || (!force && _zoom == newZoom)
  5187. || (_fullScreenVideo && newZoom != kZoomToScreenLevel)) {
  5188. return;
  5189. }
  5190. const auto full = _fullScreenVideo ? _zoomToScreen : _zoomToDefault;
  5191. float64 nx, ny, z = (_zoom == kZoomToScreenLevel) ? full : _zoom;
  5192. const auto contentSize = videoShown()
  5193. ? style::ConvertScale(videoSize())
  5194. : QSize(_width, _height);
  5195. _oldGeometry = contentGeometry();
  5196. _geometryAnimation.stop();
  5197. _w = contentSize.width();
  5198. _h = contentSize.height();
  5199. if (z >= 0) {
  5200. nx = (_x - width() / 2.) / (z + 1);
  5201. ny = (_y - _availableHeight / 2.) / (z + 1);
  5202. } else {
  5203. nx = (_x - width() / 2.) * (-z + 1);
  5204. ny = (_y - _availableHeight / 2.) * (-z + 1);
  5205. }
  5206. _zoom = newZoom;
  5207. z = (_zoom == kZoomToScreenLevel) ? full : _zoom;
  5208. if (z > 0) {
  5209. _w = qRound(_w * (z + 1));
  5210. _h = qRound(_h * (z + 1));
  5211. _x = qRound(nx * (z + 1) + width() / 2.);
  5212. _y = qRound(ny * (z + 1) + _availableHeight / 2.);
  5213. } else {
  5214. _w = qRound(_w / (-z + 1));
  5215. _h = qRound(_h / (-z + 1));
  5216. _x = qRound(nx / (-z + 1) + width() / 2.);
  5217. _y = qRound(ny / (-z + 1) + _availableHeight / 2.);
  5218. }
  5219. snapXY();
  5220. if (_opengl) {
  5221. _geometryAnimation.start(
  5222. [=] { update(); },
  5223. 0.,
  5224. 1.,
  5225. st::widgetFadeDuration/*,
  5226. anim::easeOutCirc*/);
  5227. }
  5228. update();
  5229. }
  5230. OverlayWidget::Entity OverlayWidget::entityForUserPhotos(int index) const {
  5231. Expects(_userPhotosData.has_value());
  5232. Expects(_session != nullptr);
  5233. if (index < 0 || index >= _userPhotosData->size()) {
  5234. return { v::null, nullptr };
  5235. }
  5236. const auto id = (*_userPhotosData)[index];
  5237. if (const auto photo = _session->data().photo(id)) {
  5238. return { photo, nullptr };
  5239. }
  5240. return { v::null, nullptr };
  5241. }
  5242. OverlayWidget::Entity OverlayWidget::entityForSharedMedia(int index) const {
  5243. Expects(_sharedMediaData.has_value());
  5244. if (index < 0 || index >= _sharedMediaData->size()) {
  5245. return { v::null, nullptr };
  5246. }
  5247. auto value = (*_sharedMediaData)[index];
  5248. if (const auto photo = std::get_if<not_null<PhotoData*>>(&value)) {
  5249. // Last peer photo.
  5250. return { *photo, nullptr };
  5251. } else if (const auto itemId = std::get_if<FullMsgId>(&value)) {
  5252. return entityForItemId(*itemId);
  5253. }
  5254. return { v::null, nullptr };
  5255. }
  5256. OverlayWidget::Entity OverlayWidget::entityForCollage(int index) const {
  5257. Expects(_collageData.has_value());
  5258. Expects(_session != nullptr);
  5259. const auto &items = _collageData->items;
  5260. if (!_message || index < 0 || index >= items.size()) {
  5261. return { v::null, nullptr };
  5262. }
  5263. if (const auto document = std::get_if<DocumentData*>(&items[index])) {
  5264. return { *document, _message, _topicRootId };
  5265. } else if (const auto photo = std::get_if<PhotoData*>(&items[index])) {
  5266. return { *photo, _message, _topicRootId };
  5267. }
  5268. return { v::null, nullptr };
  5269. }
  5270. OverlayWidget::Entity OverlayWidget::entityForItemId(const FullMsgId &itemId) const {
  5271. Expects(_session != nullptr);
  5272. if (const auto item = _session->data().message(itemId)) {
  5273. if (const auto media = item->media()) {
  5274. if (const auto photo = media->photo()) {
  5275. return { photo, item, _topicRootId };
  5276. } else if (const auto document = media->document()) {
  5277. return { document, item, _topicRootId };
  5278. }
  5279. }
  5280. return { v::null, item, _topicRootId };
  5281. }
  5282. return { v::null, nullptr };
  5283. }
  5284. OverlayWidget::Entity OverlayWidget::entityByIndex(int index) const {
  5285. if (_sharedMediaData) {
  5286. return entityForSharedMedia(index);
  5287. } else if (_userPhotosData) {
  5288. return entityForUserPhotos(index);
  5289. } else if (_collageData) {
  5290. return entityForCollage(index);
  5291. }
  5292. return { v::null, nullptr };
  5293. }
  5294. void OverlayWidget::setContext(
  5295. std::variant<
  5296. v::null_t,
  5297. ItemContext,
  5298. not_null<PeerData*>,
  5299. StoriesContext> context) {
  5300. if (const auto item = std::get_if<ItemContext>(&context)) {
  5301. _message = item->item;
  5302. _history = _message->history();
  5303. _peer = _history->peer;
  5304. _topicRootId = _peer->isForum() ? item->topicRootId : MsgId();
  5305. setStoriesPeer(nullptr);
  5306. } else if (const auto peer = std::get_if<not_null<PeerData*>>(&context)) {
  5307. _peer = *peer;
  5308. _history = _peer->owner().history(_peer);
  5309. _message = nullptr;
  5310. _topicRootId = MsgId();
  5311. setStoriesPeer(nullptr);
  5312. } else if (const auto story = std::get_if<StoriesContext>(&context)) {
  5313. _message = nullptr;
  5314. _topicRootId = MsgId();
  5315. _history = nullptr;
  5316. _peer = nullptr;
  5317. setStoriesPeer(story->peer);
  5318. auto &stories = story->peer->owner().stories();
  5319. const auto maybeStory = stories.lookup(
  5320. { story->peer->id, story->id });
  5321. if (maybeStory) {
  5322. _stories->show(*maybeStory, story->within);
  5323. _dropdown->raise();
  5324. }
  5325. } else {
  5326. _message = nullptr;
  5327. _topicRootId = MsgId();
  5328. _history = nullptr;
  5329. _peer = nullptr;
  5330. setStoriesPeer(nullptr);
  5331. }
  5332. _migrated = nullptr;
  5333. if (_history) {
  5334. if (_history->peer->migrateFrom()) {
  5335. _migrated = _history->owner().history(
  5336. _history->peer->migrateFrom());
  5337. } else if (_history->peer->migrateTo()) {
  5338. _migrated = _history;
  5339. _history = _history->owner().history(_history->peer->migrateTo());
  5340. }
  5341. }
  5342. _user = _peer ? _peer->asUser() : nullptr;
  5343. }
  5344. void OverlayWidget::setStoriesPeer(PeerData *peer) {
  5345. const auto session = peer ? &peer->session() : nullptr;
  5346. if (!session && !_storiesSession) {
  5347. Assert(!_stories);
  5348. } else if (!peer) {
  5349. _stories = nullptr;
  5350. _storiesSession = nullptr;
  5351. _storiesChanged.fire({});
  5352. updateNavigationControlsGeometry();
  5353. } else if (_storiesSession != session) {
  5354. _stories = nullptr;
  5355. _storiesSession = session;
  5356. const auto delegate = static_cast<Stories::Delegate*>(this);
  5357. _stories = std::make_unique<Stories::View>(delegate);
  5358. _stories->finalShownGeometryValue(
  5359. ) | rpl::skip(1) | rpl::start_with_next([=] {
  5360. updateControlsGeometry();
  5361. }, _stories->lifetime());
  5362. _storiesChanged.fire({});
  5363. }
  5364. }
  5365. void OverlayWidget::setSession(not_null<Main::Session*> session) {
  5366. if (_session == session) {
  5367. return;
  5368. }
  5369. clearSession();
  5370. _session = session;
  5371. _window->setWindowIcon(Window::CreateIcon(session));
  5372. session->downloaderTaskFinished(
  5373. ) | rpl::start_with_next([=] {
  5374. if (!isHidden()) {
  5375. updateControls();
  5376. checkForSaveLoaded();
  5377. }
  5378. }, _sessionLifetime);
  5379. session->data().documentLoadProgress(
  5380. ) | rpl::filter([=] {
  5381. return !isHidden();
  5382. }) | rpl::start_with_next([=](not_null<DocumentData*> document) {
  5383. documentUpdated(document);
  5384. }, _sessionLifetime);
  5385. session->data().itemIdChanged(
  5386. ) | rpl::start_with_next([=](const Data::Session::IdChange &change) {
  5387. changingMsgId(change.newId, change.oldId);
  5388. }, _sessionLifetime);
  5389. session->data().itemRemoved(
  5390. ) | rpl::filter([=](not_null<const HistoryItem*> item) {
  5391. return (_message == item);
  5392. }) | rpl::start_with_next([=] {
  5393. close();
  5394. clearSession();
  5395. }, _sessionLifetime);
  5396. session->account().sessionChanges(
  5397. ) | rpl::start_with_next([=] {
  5398. clearSession();
  5399. }, _sessionLifetime);
  5400. }
  5401. bool OverlayWidget::moveToNext(int delta) {
  5402. if (_stories) {
  5403. return _stories->subjumpFor(delta);
  5404. } else if (!_index) {
  5405. return false;
  5406. }
  5407. auto newIndex = *_index + delta;
  5408. return moveToEntity(entityByIndex(newIndex), delta);
  5409. }
  5410. bool OverlayWidget::moveToEntity(const Entity &entity, int preloadDelta) {
  5411. if (v::is_null(entity.data) && !entity.item) {
  5412. return false;
  5413. }
  5414. if (const auto item = entity.item) {
  5415. setContext(ItemContext{ item, entity.topicRootId });
  5416. } else if (_peer) {
  5417. setContext(_peer);
  5418. } else {
  5419. setContext(v::null);
  5420. }
  5421. clearStreaming();
  5422. _streamingStartPaused = false;
  5423. if (auto photo = std::get_if<not_null<PhotoData*>>(&entity.data)) {
  5424. displayPhoto(*photo);
  5425. } else if (auto document = std::get_if<not_null<DocumentData*>>(&entity.data)) {
  5426. displayDocument(*document);
  5427. } else {
  5428. displayDocument(nullptr);
  5429. }
  5430. preloadData(preloadDelta);
  5431. return true;
  5432. }
  5433. void OverlayWidget::preloadData(int delta) {
  5434. if (!_index) {
  5435. return;
  5436. }
  5437. auto from = *_index + (delta ? -delta : -1);
  5438. auto till = *_index + (delta ? delta * kPreloadCount : 1);
  5439. if (from > till) std::swap(from, till);
  5440. auto photos = base::flat_set<std::shared_ptr<Data::PhotoMedia>>();
  5441. auto documents = base::flat_set<std::shared_ptr<Data::DocumentMedia>>();
  5442. for (auto index = from; index != till + 1; ++index) {
  5443. auto entity = entityByIndex(index);
  5444. if (auto photo = std::get_if<not_null<PhotoData*>>(&entity.data)) {
  5445. const auto &[i, ok] = photos.emplace((*photo)->createMediaView());
  5446. (*i)->wanted(Data::PhotoSize::Small, fileOrigin(entity));
  5447. (*photo)->load(fileOrigin(entity), LoadFromCloudOrLocal, true);
  5448. } else if (auto document = std::get_if<not_null<DocumentData*>>(
  5449. &entity.data)) {
  5450. const auto &[i, ok] = documents.emplace(
  5451. (*document)->createMediaView());
  5452. (*i)->thumbnailWanted(fileOrigin(entity));
  5453. if (!(*i)->canBePlayed(entity.item)) {
  5454. (*i)->automaticLoad(fileOrigin(entity), entity.item);
  5455. }
  5456. }
  5457. }
  5458. _preloadPhotos = std::move(photos);
  5459. _preloadDocuments = std::move(documents);
  5460. }
  5461. void OverlayWidget::handleMousePress(
  5462. QPoint position,
  5463. Qt::MouseButton button) {
  5464. updateOver(position);
  5465. if (_menu || !_receiveMouse) {
  5466. return;
  5467. }
  5468. ClickHandler::pressed();
  5469. if (button == Qt::LeftButton) {
  5470. _down = Over::None;
  5471. if (!ClickHandler::getPressed()) {
  5472. if ((_over == Over::Left && moveToNext(-1))
  5473. || (_over == Over::Right && moveToNext(1))
  5474. || (_stories
  5475. && _over == Over::LeftStories
  5476. && _stories->jumpFor(-1))
  5477. || (_stories
  5478. && _over == Over::RightStories
  5479. && _stories->jumpFor(1))) {
  5480. _lastAction = position;
  5481. } else if (_over == Over::Name
  5482. || _over == Over::Date
  5483. || _over == Over::Header
  5484. || _over == Over::Save
  5485. || _over == Over::Share
  5486. || _over == Over::Rotate
  5487. || _over == Over::Icon
  5488. || _over == Over::More
  5489. || _over == Over::Video) {
  5490. _down = _over;
  5491. if (_over == Over::Video && _stories) {
  5492. _stories->contentPressed(true);
  5493. }
  5494. } else if (!_saveMsg.contains(position) || !isSaveMsgShown()) {
  5495. _pressed = true;
  5496. _dragging = 0;
  5497. updateCursor();
  5498. _mStart = position;
  5499. _xStart = _x;
  5500. _yStart = _y;
  5501. }
  5502. }
  5503. } else if (button == Qt::MiddleButton) {
  5504. zoomReset();
  5505. }
  5506. activateControls();
  5507. }
  5508. bool OverlayWidget::handleDoubleClick(
  5509. QPoint position,
  5510. Qt::MouseButton button) {
  5511. updateOver(position);
  5512. if (_over != Over::Video || button != Qt::LeftButton) {
  5513. return false;
  5514. } else if (_stories) {
  5515. if (ClickHandler::getActive()) {
  5516. return false;
  5517. }
  5518. toggleFullScreen(_windowed);
  5519. } else if (!_streamed) {
  5520. return false;
  5521. } else {
  5522. playbackToggleFullScreen();
  5523. playbackPauseResume();
  5524. }
  5525. return true;
  5526. }
  5527. void OverlayWidget::snapXY() {
  5528. auto xmin = width() - _w, xmax = 0;
  5529. auto ymin = height() - _h, ymax = _minUsedTop;
  5530. accumulate_min(xmin, (width() - _w) / 2);
  5531. accumulate_max(xmax, (width() - _w) / 2);
  5532. accumulate_min(ymin, _skipTop + (_availableHeight - _h) / 2);
  5533. accumulate_max(ymax, _skipTop + (_availableHeight - _h) / 2);
  5534. accumulate_max(_x, xmin);
  5535. accumulate_min(_x, xmax);
  5536. accumulate_max(_y, ymin);
  5537. accumulate_min(_y, ymax);
  5538. }
  5539. void OverlayWidget::handleMouseMove(QPoint position) {
  5540. updateOver(position);
  5541. if (_lastAction.x() >= 0
  5542. && ((position - _lastAction).manhattanLength()
  5543. >= st::mediaviewDeltaFromLastAction)) {
  5544. _lastAction = QPoint(-st::mediaviewDeltaFromLastAction, -st::mediaviewDeltaFromLastAction);
  5545. }
  5546. if (_pressed) {
  5547. if (!_dragging
  5548. && ((position - _mStart).manhattanLength()
  5549. >= QApplication::startDragDistance())) {
  5550. _dragging = QRect(_x, _y, _w, _h).contains(_mStart) ? 1 : -1;
  5551. if (_dragging > 0) {
  5552. if (_w > width() || _h > _maxUsedHeight) {
  5553. setCursor(style::cur_sizeall);
  5554. } else {
  5555. setCursor(style::cur_default);
  5556. }
  5557. }
  5558. }
  5559. if (_dragging > 0) {
  5560. _x = _xStart + (position - _mStart).x();
  5561. _y = _yStart + (position - _mStart).y();
  5562. snapXY();
  5563. update();
  5564. }
  5565. }
  5566. }
  5567. void OverlayWidget::updateOverRect(Over state) {
  5568. using Type = Stories::SiblingType;
  5569. switch (state) {
  5570. case Over::Left:
  5571. update(_stories ? _leftNavIcon : _leftNavOver);
  5572. break;
  5573. case Over::Right:
  5574. update(_stories ? _rightNavIcon : _rightNavOver);
  5575. break;
  5576. case Over::LeftStories:
  5577. update(_stories
  5578. ? _stories->sibling(Type::Left).layout.geometry
  5579. : QRect());
  5580. break;
  5581. case Over::RightStories:
  5582. update(_stories
  5583. ? _stories->sibling(Type::Right).layout.geometry
  5584. : QRect());
  5585. break;
  5586. case Over::Name: update(_nameNav); break;
  5587. case Over::Date: update(_dateNav); break;
  5588. case Over::Save: update(_saveNavOver); break;
  5589. case Over::Share: update(_shareNavOver); break;
  5590. case Over::Rotate: update(_rotateNavOver); break;
  5591. case Over::Icon: update(_docIconRect); break;
  5592. case Over::Header: update(_headerNav); break;
  5593. case Over::More: update(_moreNavOver); break;
  5594. }
  5595. }
  5596. bool OverlayWidget::updateOverState(Over newState) {
  5597. bool result = true;
  5598. if (_over != newState) {
  5599. if (!_stories && newState == Over::More && !_ignoringDropdown) {
  5600. _dropdownShowTimer.callOnce(0);
  5601. } else {
  5602. _dropdownShowTimer.cancel();
  5603. }
  5604. updateOverRect(_over);
  5605. updateOverRect(newState);
  5606. if (_over != Over::None) {
  5607. _animations[_over] = crl::now();
  5608. const auto i = _animationOpacities.find(_over);
  5609. if (i != end(_animationOpacities)) {
  5610. i->second.start(0);
  5611. } else {
  5612. _animationOpacities.emplace(_over, anim::value(1, 0));
  5613. }
  5614. if (!_stateAnimation.animating()) {
  5615. _stateAnimation.start();
  5616. }
  5617. } else {
  5618. result = false;
  5619. }
  5620. _over = newState;
  5621. if (newState != Over::None) {
  5622. _animations[_over] = crl::now();
  5623. const auto i = _animationOpacities.find(_over);
  5624. if (i != end(_animationOpacities)) {
  5625. i->second.start(1);
  5626. } else {
  5627. _animationOpacities.emplace(_over, anim::value(0, 1));
  5628. }
  5629. if (!_stateAnimation.animating()) {
  5630. _stateAnimation.start();
  5631. }
  5632. }
  5633. updateCursor();
  5634. }
  5635. return result;
  5636. }
  5637. void OverlayWidget::updateOver(QPoint pos) {
  5638. ClickHandlerPtr lnk;
  5639. ClickHandlerHost *lnkhost = nullptr;
  5640. if (isSaveMsgShown() && _saveMsg.contains(pos)) {
  5641. auto textState = _saveMsgText.getState(pos - _saveMsg.topLeft() - QPoint(st::mediaviewSaveMsgPadding.left(), st::mediaviewSaveMsgPadding.top()), _saveMsg.width() - st::mediaviewSaveMsgPadding.left() - st::mediaviewSaveMsgPadding.right());
  5642. lnk = textState.link;
  5643. lnkhost = this;
  5644. } else if (_captionRect.contains(pos) && !_fullScreenVideo) {
  5645. auto request = Ui::Text::StateRequestElided();
  5646. const auto lineHeight = st::mediaviewCaptionStyle.font->height;
  5647. request.lines = _captionRect.height() / lineHeight;
  5648. request.removeFromEnd = _captionSkipBlockWidth;
  5649. auto textState = _caption.getStateElided(pos - _captionRect.topLeft(), _captionRect.width(), request);
  5650. lnk = textState.link;
  5651. if (_stories && !lnk) {
  5652. lnk = ensureCaptionExpandLink();
  5653. }
  5654. lnkhost = this;
  5655. } else if (_stories && captionGeometry().contains(pos)) {
  5656. const auto padding = st::mediaviewCaptionPadding;
  5657. const auto handler = _stories->lookupRepostHandler(
  5658. pos - captionGeometry().marginsRemoved(padding).topLeft());
  5659. if (handler) {
  5660. lnk = handler.link;
  5661. lnkhost = handler.host;
  5662. setCursor(style::cur_pointer);
  5663. _cursorOverriden = true;
  5664. }
  5665. } else if (_groupThumbs && _groupThumbsRect.contains(pos)) {
  5666. const auto point = pos - QPoint(_groupThumbsLeft, _groupThumbsTop);
  5667. lnk = _groupThumbs->getState(point);
  5668. lnkhost = this;
  5669. } else if (_stories) {
  5670. lnk = _stories->lookupAreaHandler(pos);
  5671. lnkhost = this;
  5672. }
  5673. // retina
  5674. if (pos.x() == width()) {
  5675. pos.setX(pos.x() - 1);
  5676. }
  5677. if (pos.y() == height()) {
  5678. pos.setY(pos.y() - 1);
  5679. }
  5680. if (_cursorOverriden && (!lnkhost || lnkhost == this)) {
  5681. _cursorOverriden = false;
  5682. setCursor(style::cur_default);
  5683. }
  5684. ClickHandler::setActive(lnk, lnkhost);
  5685. if (_pressed || _dragging) return;
  5686. using SiblingType = Stories::SiblingType;
  5687. if (_fullScreenVideo) {
  5688. updateOverState(Over::Video);
  5689. } else if (_leftNavVisible && _leftNav.contains(pos)) {
  5690. updateOverState(Over::Left);
  5691. } else if (_rightNavVisible && _rightNav.contains(pos)) {
  5692. updateOverState(Over::Right);
  5693. } else if (_stories
  5694. && _stories->sibling(
  5695. SiblingType::Left).layout.geometry.contains(pos)) {
  5696. updateOverState(Over::LeftStories);
  5697. } else if (_stories
  5698. && _stories->sibling(
  5699. SiblingType::Right).layout.geometry.contains(pos)) {
  5700. updateOverState(Over::RightStories);
  5701. } else if (!_stories && _from && _nameNav.contains(pos)) {
  5702. updateOverState(Over::Name);
  5703. } else if (!_stories
  5704. && _message
  5705. && _message->isRegular()
  5706. && _dateNav.contains(pos)) {
  5707. updateOverState(Over::Date);
  5708. } else if (!_stories && _headerHasLink && _headerNav.contains(pos)) {
  5709. updateOverState(Over::Header);
  5710. } else if (_saveVisible && _saveNav.contains(pos)) {
  5711. updateOverState(Over::Save);
  5712. } else if (_shareVisible && _shareNav.contains(pos)) {
  5713. updateOverState(Over::Share);
  5714. } else if (_rotateVisible && _rotateNav.contains(pos)) {
  5715. updateOverState(Over::Rotate);
  5716. } else if (_document
  5717. && documentBubbleShown()
  5718. && _docIconRect.contains(pos)) {
  5719. updateOverState(Over::Icon);
  5720. } else if (_moreNav.contains(pos)) {
  5721. updateOverState(Over::More);
  5722. } else if (contentShown() && finalContentRect().contains(pos)) {
  5723. if (_stories) {
  5724. updateOverState(Over::Video);
  5725. } else if (_streamed
  5726. && _document
  5727. && (_document->isVideoFile() || _document->isVideoMessage())) {
  5728. updateOverState(Over::Video);
  5729. } else if (!_streamed && _document && !_documentMedia->loaded()) {
  5730. updateOverState(Over::Icon);
  5731. } else if (_over != Over::None) {
  5732. updateOverState(Over::None);
  5733. }
  5734. } else if (_over != Over::None) {
  5735. updateOverState(Over::None);
  5736. }
  5737. }
  5738. ClickHandlerPtr OverlayWidget::ensureCaptionExpandLink() {
  5739. if (!_captionExpandLink) {
  5740. const auto toggle = crl::guard(_widget, [=] {
  5741. if (_stories) {
  5742. _stories->showFullCaption();
  5743. }
  5744. });
  5745. _captionExpandLink = std::make_shared<LambdaClickHandler>(toggle);
  5746. }
  5747. return _captionExpandLink;
  5748. }
  5749. void OverlayWidget::handleMouseRelease(
  5750. QPoint position,
  5751. Qt::MouseButton button) {
  5752. updateOver(position);
  5753. if (const auto activated = ClickHandler::unpressed()) {
  5754. if (activated->url() == u"internal:show_saved_message"_q) {
  5755. showSaveMsgFile();
  5756. return;
  5757. }
  5758. // There may be a mention / hashtag / bot command link.
  5759. // For now activate account for all activated links.
  5760. // findWindow() will activate account.
  5761. ActivateClickHandler(_widget, activated, {
  5762. button,
  5763. QVariant::fromValue(ClickHandlerContext{
  5764. .itemId = _message ? _message->fullId() : FullMsgId(),
  5765. .sessionWindow = base::make_weak(findWindow()),
  5766. .show = _stories ? _stories->uiShow() : uiShow(),
  5767. .dark = true,
  5768. })
  5769. });
  5770. return;
  5771. }
  5772. if (_over == Over::Name && _down == Over::Name) {
  5773. if (_from) {
  5774. if (!_windowed) {
  5775. close();
  5776. }
  5777. if (const auto window = findWindow(true)) {
  5778. window->showPeerInfo(_from);
  5779. window->window().activate();
  5780. }
  5781. }
  5782. } else if (_over == Over::Date && _down == Over::Date) {
  5783. toMessage();
  5784. } else if (_over == Over::Header && _down == Over::Header) {
  5785. showMediaOverview();
  5786. } else if (_over == Over::Save && _down == Over::Save) {
  5787. downloadMedia();
  5788. } else if (_over == Over::Share && _down == Over::Share && _stories) {
  5789. _stories->shareRequested();
  5790. } else if (_over == Over::Rotate && _down == Over::Rotate) {
  5791. playbackControlsRotate();
  5792. } else if (_over == Over::Icon && _down == Over::Icon) {
  5793. handleDocumentClick();
  5794. } else if (_over == Over::More && _down == Over::More) {
  5795. InvokeQueued(_widget, [=] { showDropdown(); });
  5796. } else if (_over == Over::Video && _down == Over::Video) {
  5797. if (_stories) {
  5798. _stories->contentPressed(false);
  5799. } else if (_streamed && !_window->mousePressCancelled()) {
  5800. if (_sponsoredButton && _session && _message) {
  5801. const auto sponsoredMessages = &_session->sponsoredMessages();
  5802. const auto fullId = _message->fullId();
  5803. const auto details = sponsoredMessages->lookupDetails(fullId);
  5804. if (const auto link = details.link; !link.isEmpty()) {
  5805. UrlClickHandler::Open(link);
  5806. sponsoredMessages->clicked(fullId, true, true);
  5807. hide();
  5808. }
  5809. } else {
  5810. playbackPauseResume();
  5811. }
  5812. }
  5813. } else if (_pressed) {
  5814. if (_dragging) {
  5815. if (_dragging > 0) {
  5816. _x = _xStart + (position - _mStart).x();
  5817. _y = _yStart + (position - _mStart).y();
  5818. snapXY();
  5819. update();
  5820. }
  5821. _dragging = 0;
  5822. setCursor(style::cur_default);
  5823. } else if (!_windowed
  5824. && position.y() > st::mediaviewTitleButton.height
  5825. && (position - _lastAction).manhattanLength()
  5826. >= st::mediaviewDeltaFromLastAction) {
  5827. if (_themePreviewShown) {
  5828. if (!_themePreviewRect.contains(position)) {
  5829. close();
  5830. }
  5831. } else if (!_document
  5832. || documentContentShown()
  5833. || !documentBubbleShown()
  5834. || !_docRect.contains(position)) {
  5835. if (!_stories || _stories->closeByClickAt(position)) {
  5836. close();
  5837. }
  5838. }
  5839. }
  5840. _pressed = false;
  5841. }
  5842. _down = Over::None;
  5843. if (!isHidden()) {
  5844. activateControls();
  5845. }
  5846. }
  5847. bool OverlayWidget::handleContextMenu(std::optional<QPoint> position) {
  5848. if (position) {
  5849. if (!QRect(_x, _y, _w, _h).contains(*position)
  5850. || position->y() <= st::mediaviewTitleButton.height) {
  5851. return false;
  5852. }
  5853. }
  5854. _menu = base::make_unique_q<Ui::PopupMenu>(
  5855. _window,
  5856. st::mediaviewPopupMenu);
  5857. fillContextMenuActions(Ui::Menu::CreateAddActionCallback(_menu));
  5858. if (_menu->empty()) {
  5859. _menu = nullptr;
  5860. return true;
  5861. }
  5862. if (_stories) {
  5863. _stories->menuShown(true);
  5864. }
  5865. _menu->setDestroyedCallback(crl::guard(_widget, [=] {
  5866. if (_stories) {
  5867. _stories->menuShown(false);
  5868. }
  5869. activateControls();
  5870. _receiveMouse = false;
  5871. InvokeQueued(_widget, [=] { receiveMouse(); });
  5872. }));
  5873. using HistoryView::Reactions::AttachSelectorResult;
  5874. const auto attached = _stories
  5875. ? _stories->attachReactionsToMenu(_menu.get(), QCursor::pos())
  5876. : AttachSelectorResult::Skipped;
  5877. if (attached == AttachSelectorResult::Failed) {
  5878. _menu = nullptr;
  5879. return true;
  5880. } else if (attached == AttachSelectorResult::Attached) {
  5881. _menu->popupPrepared();
  5882. } else {
  5883. _menu->popup(QCursor::pos());
  5884. }
  5885. activateControls();
  5886. return true;
  5887. }
  5888. bool OverlayWidget::handleTouchEvent(not_null<QTouchEvent*> e) {
  5889. if (e->device()->type() != base::TouchDevice::TouchScreen) {
  5890. return false;
  5891. } else if (e->type() == QEvent::TouchBegin
  5892. && !e->touchPoints().isEmpty()
  5893. && _body->childAt(
  5894. _body->mapFromGlobal(
  5895. e->touchPoints().cbegin()->screenPos().toPoint()))) {
  5896. return false;
  5897. }
  5898. switch (e->type()) {
  5899. case QEvent::TouchBegin: {
  5900. if (_touchPress || e->touchPoints().isEmpty()) {
  5901. break;
  5902. }
  5903. _touchTimer.callOnce(QApplication::startDragTime());
  5904. _touchPress = true;
  5905. _touchMove = _touchRightButton = false;
  5906. _touchStart = e->touchPoints().cbegin()->screenPos().toPoint();
  5907. } break;
  5908. case QEvent::TouchUpdate: {
  5909. if (!_touchPress || e->touchPoints().isEmpty()) {
  5910. break;
  5911. }
  5912. if (!_touchMove && (e->touchPoints().cbegin()->screenPos().toPoint() - _touchStart).manhattanLength() >= QApplication::startDragDistance()) {
  5913. _touchMove = true;
  5914. }
  5915. } break;
  5916. case QEvent::TouchEnd: {
  5917. if (!_touchPress) {
  5918. break;
  5919. }
  5920. auto weak = Ui::MakeWeak(_widget);
  5921. if (!_touchMove) {
  5922. const auto button = _touchRightButton
  5923. ? Qt::RightButton
  5924. : Qt::LeftButton;
  5925. const auto position = _widget->mapFromGlobal(_touchStart);
  5926. if (weak) handleMousePress(position, button);
  5927. if (weak) handleMouseRelease(position, button);
  5928. if (weak && _touchRightButton) {
  5929. handleContextMenu(position);
  5930. }
  5931. } else if (_touchMove) {
  5932. if ((!_leftNavVisible || !_leftNav.contains(_widget->mapFromGlobal(_touchStart))) && (!_rightNavVisible || !_rightNav.contains(_widget->mapFromGlobal(_touchStart)))) {
  5933. QPoint d = (e->touchPoints().cbegin()->screenPos().toPoint() - _touchStart);
  5934. if (d.x() * d.x() > d.y() * d.y() && (d.x() > st::mediaviewSwipeDistance || d.x() < -st::mediaviewSwipeDistance)) {
  5935. moveToNext(d.x() > 0 ? -1 : 1);
  5936. }
  5937. }
  5938. }
  5939. if (weak) {
  5940. _touchTimer.cancel();
  5941. _touchPress = _touchMove = _touchRightButton = false;
  5942. }
  5943. } break;
  5944. case QEvent::TouchCancel: {
  5945. _touchPress = false;
  5946. _touchTimer.cancel();
  5947. } break;
  5948. }
  5949. return true;
  5950. }
  5951. void OverlayWidget::toggleApplicationEventFilter(bool install) {
  5952. if (!install) {
  5953. _applicationEventFilter = nullptr;
  5954. return;
  5955. } else if (_applicationEventFilter) {
  5956. return;
  5957. }
  5958. class Filter final : public QObject {
  5959. public:
  5960. explicit Filter(not_null<OverlayWidget*> owner) : _owner(owner) {
  5961. }
  5962. private:
  5963. bool eventFilter(QObject *obj, QEvent *e) override {
  5964. return obj && e && _owner->filterApplicationEvent(obj, e);
  5965. }
  5966. const not_null<OverlayWidget*> _owner;
  5967. };
  5968. _applicationEventFilter = std::make_unique<Filter>(this);
  5969. qApp->installEventFilter(_applicationEventFilter.get());
  5970. }
  5971. bool OverlayWidget::filterApplicationEvent(
  5972. not_null<QObject*> object,
  5973. not_null<QEvent*> e) {
  5974. const auto type = e->type();
  5975. if (type == QEvent::ShortcutOverride) {
  5976. const auto event = static_cast<QKeyEvent*>(e.get());
  5977. const auto key = event->key();
  5978. const auto ctrl = event->modifiers().testFlag(Qt::ControlModifier);
  5979. if (key == Qt::Key_F && ctrl && _streamed) {
  5980. playbackToggleFullScreen();
  5981. return true;
  5982. } else if (key == Qt::Key_0 && ctrl) {
  5983. zoomReset();
  5984. return true;
  5985. }
  5986. return false;
  5987. } else if (type == QEvent::MouseMove
  5988. || type == QEvent::MouseButtonPress
  5989. || type == QEvent::MouseButtonRelease) {
  5990. if (object->isWidgetType()
  5991. && static_cast<QWidget*>(object.get())->window() == _window) {
  5992. const auto mouseEvent = static_cast<QMouseEvent*>(e.get());
  5993. const auto mousePosition = _body->mapFromGlobal(
  5994. mouseEvent->globalPos());
  5995. const auto delta = (mousePosition - _lastMouseMovePos);
  5996. auto activate = delta.manhattanLength()
  5997. >= st::mediaviewDeltaFromLastAction;
  5998. if (activate) {
  5999. _lastMouseMovePos = mousePosition;
  6000. }
  6001. if (type == QEvent::MouseButtonPress) {
  6002. _mousePressed = true;
  6003. activate = true;
  6004. } else if (type == QEvent::MouseButtonRelease) {
  6005. _mousePressed = false;
  6006. activate = true;
  6007. }
  6008. if (activate) {
  6009. activateControls();
  6010. }
  6011. }
  6012. }
  6013. return false;
  6014. }
  6015. void OverlayWidget::applyHideWindowWorkaround() {
  6016. // QOpenGLWidget can't properly destroy a child widget if it is hidden
  6017. // exactly after that, the child is cached in the backing store.
  6018. // So on next paint we force full backing store repaint.
  6019. if (!isHidden() && !_hideWorkaround) {
  6020. _hideWorkaround = std::make_unique<Ui::RpWidget>(_window);
  6021. const auto raw = _hideWorkaround.get();
  6022. raw->setGeometry(_window->rect());
  6023. raw->show();
  6024. raw->paintRequest(
  6025. ) | rpl::start_with_next([=] {
  6026. if (_hideWorkaround.get() == raw) {
  6027. _hideWorkaround.release();
  6028. }
  6029. QPainter(raw).fillRect(raw->rect(), QColor(0, 1, 0, 1));
  6030. crl::on_main(raw, [=] {
  6031. delete raw;
  6032. });
  6033. }, raw->lifetime());
  6034. raw->update();
  6035. _widget->update();
  6036. if (!Platform::IsMac()) {
  6037. Ui::ForceFullRepaintSync(_window);
  6038. }
  6039. _hideWorkaround = nullptr;
  6040. }
  6041. }
  6042. Window::SessionController *OverlayWidget::findWindow(bool switchTo) const {
  6043. if (!_session) {
  6044. return nullptr;
  6045. }
  6046. const auto window = _openedFrom.get();
  6047. if (window) {
  6048. if (const auto controller = window->sessionController()) {
  6049. if (&controller->session() == _session) {
  6050. return controller;
  6051. }
  6052. }
  6053. }
  6054. if (switchTo) {
  6055. auto controllerPtr = (Window::SessionController*)nullptr;
  6056. const auto account = not_null(&_session->account());
  6057. const auto sessionWindow = Core::App().windowFor(account);
  6058. const auto anyWindow = (sessionWindow
  6059. && &sessionWindow->account() == account)
  6060. ? sessionWindow
  6061. : window
  6062. ? window
  6063. : sessionWindow;
  6064. if (anyWindow) {
  6065. anyWindow->invokeForSessionController(
  6066. &_session->account(),
  6067. _history ? _history->peer.get() : nullptr,
  6068. [&](not_null<Window::SessionController*> newController) {
  6069. controllerPtr = newController;
  6070. });
  6071. }
  6072. return controllerPtr;
  6073. }
  6074. return nullptr;
  6075. }
  6076. // #TODO unite and check
  6077. void OverlayWidget::clearBeforeHide() {
  6078. _message = nullptr;
  6079. _sharedMedia = nullptr;
  6080. _sharedMediaData = std::nullopt;
  6081. _sharedMediaDataKey = std::nullopt;
  6082. _userPhotos = nullptr;
  6083. _userPhotosData = std::nullopt;
  6084. _collage = nullptr;
  6085. _collageData = std::nullopt;
  6086. clearStreaming();
  6087. setStoriesPeer(nullptr);
  6088. _layerBg->hideAll(anim::type::instant);
  6089. assignMediaPointer(nullptr);
  6090. _preloadPhotos.clear();
  6091. _preloadDocuments.clear();
  6092. if (_menu) {
  6093. _menu->hideMenu(true);
  6094. }
  6095. _controlsHideTimer.cancel();
  6096. _controlsState = ControlsShown;
  6097. _controlsOpacity = anim::value(1);
  6098. _helper->setControlsOpacity(1.);
  6099. _groupThumbs = nullptr;
  6100. _groupThumbsRect = QRect();
  6101. _sponsoredButton = nullptr;
  6102. }
  6103. void OverlayWidget::clearAfterHide() {
  6104. _body->hide();
  6105. clearStreaming();
  6106. destroyThemePreview();
  6107. _radial.stop();
  6108. _staticContent = QImage();
  6109. _themePreview = nullptr;
  6110. _themeApply.destroyDelayed();
  6111. _themeCancel.destroyDelayed();
  6112. _themeShare.destroyDelayed();
  6113. }
  6114. void OverlayWidget::receiveMouse() {
  6115. _receiveMouse = true;
  6116. }
  6117. void OverlayWidget::showDropdown() {
  6118. _dropdown->clearActions();
  6119. fillContextMenuActions(Ui::Menu::CreateAddActionCallback(_dropdown));
  6120. _dropdown->moveToRight(0, height() - _dropdown->height());
  6121. _dropdown->showAnimated(Ui::PanelAnimation::Origin::BottomRight);
  6122. _dropdown->setFocus();
  6123. if (_stories) {
  6124. _stories->menuShown(true);
  6125. }
  6126. }
  6127. void OverlayWidget::handleTouchTimer() {
  6128. _touchRightButton = true;
  6129. }
  6130. void OverlayWidget::updateSaveMsg() {
  6131. update(_saveMsg);
  6132. }
  6133. void OverlayWidget::findCurrent() {
  6134. using namespace rpl::mappers;
  6135. if (_sharedMediaData) {
  6136. _index = _message
  6137. ? _sharedMediaData->indexOf(_message->fullId())
  6138. : _photo ? _sharedMediaData->indexOf(_photo) : std::nullopt;
  6139. _fullIndex = _sharedMediaData->skippedBefore()
  6140. ? (_index | func::add(*_sharedMediaData->skippedBefore()))
  6141. : std::nullopt;
  6142. _fullCount = _sharedMediaData->fullCount();
  6143. } else if (_userPhotosData) {
  6144. _index = _photo ? _userPhotosData->indexOf(_photo->id) : std::nullopt;
  6145. _fullIndex = _userPhotosData->skippedBefore()
  6146. ? (_index | func::add(*_userPhotosData->skippedBefore()))
  6147. : std::nullopt;
  6148. _fullCount = _userPhotosData->fullCount();
  6149. } else if (_collageData) {
  6150. const auto item = _photo ? WebPageCollage::Item(_photo) : _document;
  6151. const auto &items = _collageData->items;
  6152. const auto i = ranges::find(items, item);
  6153. _index = (i != end(items))
  6154. ? std::make_optional(int(i - begin(items)))
  6155. : std::nullopt;
  6156. _fullIndex = _index;
  6157. _fullCount = items.size();
  6158. } else {
  6159. _index = _fullIndex = _fullCount = std::nullopt;
  6160. }
  6161. }
  6162. void OverlayWidget::updateHeader() {
  6163. auto index = _fullIndex ? *_fullIndex : -1;
  6164. auto count = _fullCount ? *_fullCount : -1;
  6165. if (index >= 0 && index < count && count > 1) {
  6166. if (_document) {
  6167. _headerText = tr::lng_mediaview_file_n_of_amount(
  6168. tr::now,
  6169. lt_file,
  6170. (_document->filename().isEmpty()
  6171. ? tr::lng_mediaview_doc_image(tr::now)
  6172. : _document->filename()),
  6173. lt_n,
  6174. QString::number(index + 1),
  6175. lt_amount,
  6176. QString::number(count));
  6177. } else {
  6178. if (_user
  6179. && (index == count - 1)
  6180. && SyncUserFallbackPhotoViewer(_user)) {
  6181. _headerText = tr::lng_mediaview_profile_public_photo(tr::now);
  6182. } else if (_user
  6183. && _user->hasPersonalPhoto()
  6184. && _photo
  6185. && (_photo->id == _user->userpicPhotoId())) {
  6186. _headerText = tr::lng_mediaview_profile_photo_by_you(tr::now);
  6187. } else {
  6188. _headerText = tr::lng_mediaview_n_of_amount(
  6189. tr::now,
  6190. lt_n,
  6191. QString::number(index + 1),
  6192. lt_amount,
  6193. QString::number(count));
  6194. }
  6195. }
  6196. } else {
  6197. if (_document) {
  6198. _headerText = _document->filename().isEmpty()
  6199. ? tr::lng_mediaview_doc_image(tr::now)
  6200. : _document->filename();
  6201. } else if (_message) {
  6202. _headerText = tr::lng_mediaview_single_photo(tr::now);
  6203. } else if (_user) {
  6204. if (_user->hasPersonalPhoto()
  6205. && _photo
  6206. && (_photo->id == _user->userpicPhotoId())) {
  6207. _headerText = tr::lng_mediaview_profile_photo_by_you(tr::now);
  6208. } else {
  6209. _headerText = tr::lng_mediaview_profile_photo(tr::now);
  6210. }
  6211. } else if ((_history && _history->peer->isBroadcast())
  6212. || (_peer && _peer->isChannel() && !_peer->isMegagroup())) {
  6213. _headerText = tr::lng_mediaview_channel_photo(tr::now);
  6214. } else if (_peer) {
  6215. _headerText = tr::lng_mediaview_group_photo(tr::now);
  6216. } else {
  6217. _headerText = tr::lng_mediaview_single_photo(tr::now);
  6218. }
  6219. }
  6220. _headerHasLink = computeOverviewType() != std::nullopt;
  6221. auto hwidth = st::mediaviewThickFont->width(_headerText);
  6222. if (hwidth > width() / 3) {
  6223. hwidth = width() / 3;
  6224. _headerText = st::mediaviewThickFont->elided(_headerText, hwidth, Qt::ElideMiddle);
  6225. }
  6226. _headerNav = QRect(st::mediaviewTextLeft, height() - st::mediaviewHeaderTop, hwidth, st::mediaviewThickFont->height);
  6227. }
  6228. float64 OverlayWidget::overLevel(Over control) const {
  6229. auto i = _animationOpacities.find(control);
  6230. return (i == end(_animationOpacities))
  6231. ? (_over == control ? 1. : 0.)
  6232. : i->second.current();
  6233. }
  6234. } // namespace View
  6235. } // namespace Media