123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580 |
- /**
- * @file lltexteditor.cpp
- * @brief LLTextEditor base class
- *
- * $LicenseInfo:firstyear=2001&license=viewergpl$
- *
- * Copyright (c) 2001-2009, Linden Research, Inc.
- *
- * Second Life Viewer Source Code
- * The source code in this file ("Source Code") is provided by Linden Lab
- * to you under the terms of the GNU General Public License, version 2.0
- * ("GPL"), unless you have obtained a separate licensing agreement
- * ("Other License"), formally executed by you and Linden Lab. Terms of
- * the GPL can be found in doc/GPL-license.txt in this distribution, or
- * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
- *
- * There are special exceptions to the terms and conditions of the GPL as
- * it is applied to this Source Code. View the full text of the exception
- * in the file doc/FLOSS-exception.txt in this software distribution, or
- * online at
- * http://secondlifegrid.net/programs/open_source/licensing/flossexception
- *
- * By copying, modifying or distributing this software, you acknowledge
- * that you have read and understood your obligations described above,
- * and agree to abide by those obligations.
- *
- * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
- * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
- * COMPLETENESS OR PERFORMANCE.
- * $/LicenseInfo$
- */
- // Text editor widget to let users enter a a multi-line ASCII document.
- #include "linden_common.h"
- #include <queue>
- #include "lltexteditor.h"
- #include "llclipboard.h"
- #include "llcontrol.h"
- #include "lldir.h"
- #include "llfasttimer.h"
- #include "llfontfreetype.h"
- #include "llimagegl.h"
- #include "llkeyboard.h"
- #include "llmenugl.h"
- #include "llrender.h"
- #include "llscrollbar.h"
- #include "llstl.h"
- #include "llspellcheck.h"
- #include "lltimer.h"
- #include "lluictrlfactory.h"
- #include "llundo.h"
- #include "llviewborder.h"
- #include "llwindow.h"
- // gcc 12.4+ sees array bound issues where there are none... HB
- #if defined(GCC_VERSION) && GCC_VERSION >= 120400
- # pragma GCC diagnostic ignored "-Warray-bounds"
- # pragma GCC diagnostic ignored "-Wstringop-overflow"
- #endif
- static const std::string LL_SIMPLE_TEXT_EDITOR_TAG = "simple_text_editor";
- static LLRegisterWidget<LLTextEditor> r27(LL_SIMPLE_TEXT_EDITOR_TAG);
- //
- // Constants
- //
- constexpr S32 UI_TEXTEDITOR_BORDER = 1;
- constexpr S32 UI_TEXTEDITOR_H_PAD = 4;
- constexpr S32 UI_TEXTEDITOR_V_PAD_TOP = 4;
- constexpr S32 UI_TEXTEDITOR_LINE_NUMBER_MARGIN = 32;
- constexpr S32 UI_TEXTEDITOR_LINE_NUMBER_DIGITS = 4;
- constexpr F32 CURSOR_FLASH_DELAY = 1.f; // In seconds
- constexpr S32 CURSOR_THICKNESS = 2;
- constexpr S32 SPACES_PER_TAB = 4;
- constexpr F32 PREEDIT_MARKER_BRIGHTNESS = 0.4f;
- constexpr S32 PREEDIT_MARKER_GAP = 1;
- constexpr S32 PREEDIT_MARKER_POSITION = 2;
- constexpr S32 PREEDIT_MARKER_THICKNESS = 1;
- constexpr F32 PREEDIT_STANDOUT_BRIGHTNESS = 0.6f;
- constexpr S32 PREEDIT_STANDOUT_GAP = 1;
- constexpr S32 PREEDIT_STANDOUT_POSITION = 2;
- constexpr S32 PREEDIT_STANDOUT_THICKNESS = 2;
- LLColor4 LLTextEditor::sLinkColor = LLColor4::blue;
- void (*LLTextEditor::sURLcallback)(const std::string&) = NULL;
- bool (*LLTextEditor::sSecondlifeURLcallback)(const std::string&) = NULL;
- bool (*LLTextEditor::sSecondlifeURLcallbackRightClick)(const std::string&) = NULL;
- ///////////////////////////////////////////////////////////////////
- class LLTextEditor::LLTextCmdInsert : public LLTextEditor::LLTextCmd
- {
- public:
- LLTextCmdInsert(S32 pos, bool group_with_next, const LLWString& ws)
- : LLTextCmd(pos, group_with_next),
- mWString(ws)
- {
- }
- virtual ~LLTextCmdInsert()
- {
- }
- virtual bool execute(LLTextEditor* editor, S32* delta)
- {
- *delta = insert(editor, getPosition(), mWString);
- LLWStringUtil::truncate(mWString, *delta);
- //mWString = wstring_truncate(mWString, *delta);
- return (*delta != 0);
- }
- virtual S32 undo(LLTextEditor* editor)
- {
- remove(editor, getPosition(), mWString.length());
- return getPosition();
- }
- virtual S32 redo(LLTextEditor* editor)
- {
- insert(editor, getPosition(), mWString);
- return getPosition() + mWString.length();
- }
- private:
- LLWString mWString;
- };
- ///////////////////////////////////////////////////////////////////
- class LLTextEditor::LLTextCmdAddChar : public LLTextEditor::LLTextCmd
- {
- public:
- LLTextCmdAddChar(S32 pos, bool group_with_next, llwchar wc)
- : LLTextCmd(pos, group_with_next),
- mWString(1, wc),
- mBlockExtensions(false)
- {
- }
- virtual void blockExtensions()
- {
- mBlockExtensions = true;
- }
- virtual bool canExtend(S32 pos) const
- {
- return !mBlockExtensions &&
- pos == getPosition() + (S32)mWString.length();
- }
- virtual bool execute(LLTextEditor* editor, S32* delta)
- {
- *delta = insert(editor, getPosition(), mWString);
- LLWStringUtil::truncate(mWString, *delta);
- //mWString = wstring_truncate(mWString, *delta);
- return *delta != 0;
- }
- virtual bool extendAndExecute(LLTextEditor* editor, S32 pos, llwchar wc,
- S32* delta)
- {
- LLWString ws;
- ws += wc;
- *delta = insert(editor, pos, ws);
- if (*delta > 0)
- {
- mWString += wc;
- }
- return *delta != 0;
- }
- virtual S32 undo(LLTextEditor* editor)
- {
- remove(editor, getPosition(), mWString.length());
- return getPosition();
- }
- virtual S32 redo(LLTextEditor* editor)
- {
- insert(editor, getPosition(), mWString);
- return getPosition() + mWString.length();
- }
- private:
- LLWString mWString;
- bool mBlockExtensions;
- };
- ///////////////////////////////////////////////////////////////////
- class LLTextEditor::LLTextCmdOverwriteChar : public LLTextEditor::LLTextCmd
- {
- public:
- LLTextCmdOverwriteChar(S32 pos, bool group_with_next, llwchar wc)
- : LLTextCmd(pos, group_with_next),
- mChar(wc),
- mOldChar(0)
- {
- }
- virtual bool execute(LLTextEditor* editor, S32* delta)
- {
- mOldChar = editor->getWChar(getPosition());
- overwrite(editor, getPosition(), mChar);
- *delta = 0;
- return true;
- }
- virtual S32 undo(LLTextEditor* editor)
- {
- overwrite(editor, getPosition(), mOldChar);
- return getPosition();
- }
- virtual S32 redo(LLTextEditor* editor)
- {
- overwrite(editor, getPosition(), mChar);
- return getPosition() + 1;
- }
- private:
- llwchar mChar;
- llwchar mOldChar;
- };
- ///////////////////////////////////////////////////////////////////
- class LLTextEditor::LLTextCmdRemove : public LLTextEditor::LLTextCmd
- {
- public:
- LLTextCmdRemove(S32 pos, bool group_with_next, S32 len)
- : LLTextCmd(pos, group_with_next),
- mLen(len)
- {
- }
- virtual bool execute(LLTextEditor* editor, S32* delta)
- {
- mWString = editor->getWSubString(getPosition(), mLen);
- *delta = remove(editor, getPosition(), mLen);
- return *delta != 0;
- }
- virtual S32 undo(LLTextEditor* editor)
- {
- insert(editor, getPosition(), mWString);
- return getPosition() + mWString.length();
- }
- virtual S32 redo(LLTextEditor* editor)
- {
- remove(editor, getPosition(), mLen);
- return getPosition();
- }
- private:
- LLWString mWString;
- S32 mLen;
- };
- ///////////////////////////////////////////////////////////////////
- LLTextEditor::LLTextEditor(const std::string& name, const LLRect& rect,
- S32 max_length, // In bytes
- const std::string& default_text, LLFontGL* font,
- bool allow_embedded_items)
- : LLEditMenuHandler(HAS_CONTEXT_MENU | HAS_UNDO_REDO | HAS_CUSTOM),
- LLUICtrl(name, rect, true, NULL, NULL, FOLLOWS_TOP | FOLLOWS_LEFT),
- mTextIsUpToDate(true),
- mMaxTextByteLength(max_length),
- mBaseDocIsPristine(true),
- mPristineCmd(NULL),
- mLastCmd(NULL),
- mCursorPos(0),
- mIsSelecting(false),
- mSelectionStart(0),
- mSelectionEnd(0),
- mScrolledToBottom(true),
- mOnScrollEndCallback(NULL),
- mOnScrollEndData(NULL),
- mKeystrokeCallback(NULL),
- mKeystrokeData(NULL),
- mOnHandleKeyCallback(NULL),
- mOnHandleKeyData(NULL),
- mCursorColor(LLUI::sTextCursorColor),
- mFgColor(LLUI::sTextFgColor),
- mDefaultColor(LLUI::sTextDefaultColor),
- mReadOnlyFgColor(LLUI::sTextFgReadOnlyColor),
- mWriteableBgColor(LLUI::sTextBgWriteableColor),
- mReadOnlyBgColor(LLUI::sTextBgReadOnlyColor),
- mFocusBgColor(LLUI::sTextBgFocusColor),
- mLinkColor(sLinkColor),
- mReadOnly(false),
- mWordWrap(false),
- mShowLineNumbers(false),
- mTabsToNextField(true),
- mCommitOnFocusLost(false),
- mHideScrollbarForShortDocs(false),
- mTrackBottom(false),
- mAllowEmbeddedItems(allow_embedded_items),
- mPreserveSegments(false),
- mHandleEditKeysDirectly(false),
- mMouseDownX(0),
- mMouseDownY(0),
- mLastSelectionX(-1),
- mLastSelectionY(-1),
- mReflowNeeded(false),
- mScrollNeeded(false),
- mParseHTML(false),
- mSpellCheck(true)
- {
- // Reset desired x cursor position
- mDesiredXPixel = -1;
- if (font)
- {
- mGLFont = font;
- }
- else
- {
- mGLFont = LLFontGL::getFontSansSerif();
- }
- updateTextRect();
- S32 line_height = ll_roundp(mGLFont->getLineHeight());
- S32 page_size = mTextRect.getHeight() / line_height;
- // Init the scrollbar
- LLRect scroll_rect;
- scroll_rect.setOriginAndSize(getRect().getWidth() - SCROLLBAR_SIZE, 1,
- SCROLLBAR_SIZE, getRect().getHeight() - 1);
- S32 lines_in_doc = getLineCount();
- mScrollbar = new LLScrollbar("Scrollbar", scroll_rect,
- LLScrollbar::VERTICAL, lines_in_doc, 0,
- page_size, NULL, this);
- mScrollbar->setFollowsRight();
- mScrollbar->setFollowsTop();
- mScrollbar->setFollowsBottom();
- mScrollbar->setEnabled(true);
- mScrollbar->setVisible(true);
- mScrollbar->setOnScrollEndCallback(mOnScrollEndCallback, mOnScrollEndData);
- addChild(mScrollbar);
- mBorder = new LLViewBorder("text ed border",
- LLRect(0, getRect().getHeight(),
- getRect().getWidth(), 0),
- LLViewBorder::BEVEL_IN,
- LLViewBorder::STYLE_LINE, UI_TEXTEDITOR_BORDER);
- addChild(mBorder);
- appendText(default_text, false, false);
- resetDirty(); // Update saved text state
- mHTML.clear();
- mShowMisspelled = LLSpellCheck::getInstance()->getShowMisspelled();
- }
- LLTextEditor::~LLTextEditor()
- {
- gFocusMgr.releaseFocusIfNeeded(this); // calls onCommit()
- // Scrollbar is deleted by LLView
- mHoverSegment = NULL;
- std::for_each(mSegments.begin(), mSegments.end(), DeletePointer());
- mSegments.clear();
- std::for_each(mUndoStack.begin(), mUndoStack.end(), DeletePointer());
- mUndoStack.clear();
- }
- void LLTextEditor::spellReplace(SpellMenuBind* data)
- {
- if (data)
- {
- S32 length = data->mWordPositionEnd - data->mWordPositionStart;
- remove(data->mWordPositionStart, length, true);
- LLWString clean_string = utf8str_to_wstring(data->mWord);
- insert(data->mWordPositionStart, clean_string, false);
- mCursorPos += clean_string.length() - length;
- needsReflow();
- }
- }
- void LLTextEditor::spellCorrect(void* data)
- {
- SpellMenuBind* menu_bind = (SpellMenuBind*)data;
- LLTextEditor* text = menu_bind->mOrigin;
- if (menu_bind && text)
- {
- LL_DEBUGS("SpellCheck") << menu_bind->mMenuItem->getName()
- << " : " << text->getName()
- << " : " << menu_bind->mWord << LL_ENDL;
- text->spellReplace(menu_bind);
- // Make it update:
- text->mKeystrokeTimer.reset();
- text->mPrevSpelledText.erase();
- }
- }
- void LLTextEditor::spellShow(void* data)
- {
- SpellMenuBind* menu_bind = (SpellMenuBind*)data;
- LLTextEditor* text = menu_bind->mOrigin;
- if (menu_bind && text)
- {
- text->mShowMisspelled = (menu_bind->mWord == "Show Misspellings");
- // Make it update:
- text->mKeystrokeTimer.reset();
- text->mPrevSpelledText.erase();
- }
- }
- void LLTextEditor::spellAdd(void* data)
- {
- SpellMenuBind* menu_bind = (SpellMenuBind*)data;
- LLTextEditor* text = menu_bind->mOrigin;
- if (menu_bind && text)
- {
- LLSpellCheck::getInstance()->addToCustomDictionary(menu_bind->mWord);
- // Make it update:
- text->mKeystrokeTimer.reset();
- text->mPrevSpelledText.erase();
- }
- }
- void LLTextEditor::spellIgnore(void* data)
- {
- SpellMenuBind* menu_bind = (SpellMenuBind*)data;
- LLTextEditor* text = menu_bind->mOrigin;
- if (menu_bind && text)
- {
- LLSpellCheck::getInstance()->addToIgnoreList(menu_bind->mWord);
- // Make it update:
- text->mKeystrokeTimer.reset();
- text->mPrevSpelledText.erase();
- }
- }
- std::vector<S32> LLTextEditor::getMisspelledWordsPositions()
- {
- std::vector<S32> bad_words_pos;
- const LLWString& text = mWText;
- std::string selected_word;
- S32 word_start = 0;
- S32 word_end = mSpellCheckStart;
- S32 true_end;
- while (word_end < mSpellCheckEnd)
- {
- if (LLWStringUtil::isPartOfLexicalWord(text[word_end]))
- {
- // Select the word under the cursor
- while (word_end > 0 &&
- LLWStringUtil::isPartOfLexicalWord(text[word_end - 1]))
- {
- --word_end;
- }
- if (text[word_end] == L'\'')
- {
- // Do not count "'" at the start of a word
- ++word_end;
- }
- word_start = word_end;
- while (word_end < (S32)text.length() &&
- LLWStringUtil::isPartOfLexicalWord(text[word_end]))
- {
- ++word_end;
- }
- if (text[word_end - 1] == L'\'')
- {
- // Do not count "'" at the end of a word
- true_end = word_end - 1;
- }
- else
- {
- true_end = word_end;
- }
- if (true_end > word_start + 2) // Do not bother for 2 or less characters words
- {
- std::string part(text.begin(), text.end());
- selected_word = part.substr(word_start, true_end - word_start);
- if (!LLSpellCheck::getInstance()->checkSpelling(selected_word))
- {
- // Misspelled word here
- bad_words_pos.emplace_back(word_start);
- bad_words_pos.push_back(true_end);
- }
- }
- }
- ++word_end;
- }
- return bad_words_pos;
- }
- void LLTextEditor::setTrackColor(const LLColor4& color)
- {
- mScrollbar->setTrackColor(color);
- }
- void LLTextEditor::setThumbColor(const LLColor4& color)
- {
- mScrollbar->setThumbColor(color);
- }
- void LLTextEditor::setHighlightColor(const LLColor4& color)
- {
- mScrollbar->setHighlightColor(color);
- }
- void LLTextEditor::setShadowColor(const LLColor4& color)
- {
- mScrollbar->setShadowColor(color);
- }
- void LLTextEditor::updateLineStartList(S32 startpos)
- {
- updateSegments();
- bindEmbeddedChars(mGLFont);
- S32 seg_num = mSegments.size();
- S32 seg_idx = 0;
- S32 seg_offset = 0;
- if (!mLineStartList.empty())
- {
- getSegmentAndOffset(startpos, &seg_idx, &seg_offset);
- line_info t(seg_idx, seg_offset);
- line_list_t::iterator iter = std::upper_bound(mLineStartList.begin(),
- mLineStartList.end(), t,
- line_info_compare());
- if (iter != mLineStartList.begin()) --iter;
- seg_idx = iter->mSegment;
- seg_offset = iter->mOffset;
- mLineStartList.erase(iter, mLineStartList.end());
- }
- while (seg_idx < seg_num)
- {
- mLineStartList.emplace_back(seg_idx, seg_offset);
- bool line_ended = false;
- S32 start_x = mShowLineNumbers ? UI_TEXTEDITOR_LINE_NUMBER_MARGIN : 0;
- S32 line_width = start_x;
- while (!line_ended && seg_idx < seg_num)
- {
- LLTextSegment* segment = mSegments[seg_idx];
- S32 start_idx = segment->getStart() + seg_offset;
- S32 end_idx = start_idx;
- while (end_idx < segment->getEnd() && mWText[end_idx] != '\n')
- {
- ++end_idx;
- }
- if (start_idx == end_idx)
- {
- if (end_idx >= segment->getEnd())
- {
- // Empty segment
- ++seg_idx;
- seg_offset = 0;
- }
- else
- {
- // Empty line
- line_ended = true;
- ++seg_offset;
- }
- }
- else
- {
- const llwchar* str = mWText.c_str() + start_idx;
- S32 drawn = mGLFont->maxDrawableChars(str,
- (F32)abs(mTextRect.getWidth()) - line_width,
- end_idx - start_idx,
- mWordWrap,
- mAllowEmbeddedItems);
- if (drawn == 0 && line_width == start_x)
- {
- // If at the beginning of a line, draw at least one
- // character, even if it does not all fit.
- drawn = 1;
- }
- seg_offset += drawn;
- line_width += mGLFont->getWidth(str, 0, drawn,
- mAllowEmbeddedItems);
- end_idx = segment->getStart() + seg_offset;
- if (end_idx < segment->getEnd())
- {
- line_ended = true;
- if (mWText[end_idx] == '\n')
- {
- ++seg_offset; // skip newline
- }
- }
- else
- {
- // Finished with segment
- ++seg_idx;
- seg_offset = 0;
- }
- }
- }
- }
- unbindEmbeddedChars(mGLFont);
- mScrollbar->setDocSize(getLineCount());
- if (mHideScrollbarForShortDocs)
- {
- bool short_doc = mScrollbar->getDocSize() <= mScrollbar->getPageSize();
- mScrollbar->setVisible(!short_doc);
- }
- // If scrolled to bottom, stay at bottom unless user is selecting text.
- // Do this after updating page size.
- if (mScrolledToBottom && mTrackBottom && !hasMouseCapture())
- {
- endOfDoc();
- }
- }
- ////////////////////////////////////////////////////////////
- // LLTextEditor
- // Public methods
- bool LLTextEditor::truncate()
- {
- bool did_truncate = false;
- // First rough check - if we are less than 1/4th the size, we are OK
- if (mWText.size() >= (size_t)(mMaxTextByteLength / 4))
- {
- // Have to check actual byte size
- S32 utf8_byte_size = wstring_utf8_length(mWText);
- if (utf8_byte_size > mMaxTextByteLength)
- {
- // Truncate safely in UTF-8
- std::string temp_utf8_text = wstring_to_utf8str(mWText);
- temp_utf8_text = utf8str_truncate(temp_utf8_text,
- mMaxTextByteLength);
- mWText = utf8str_to_wstring(temp_utf8_text);
- mTextIsUpToDate = false;
- did_truncate = true;
- }
- }
- return did_truncate;
- }
- void LLTextEditor::setText(const std::string& utf8str)
- {
- // LLStringUtil::removeCRLF(utf8str);
- mUTF8Text = utf8str_removeCRLF(utf8str);
- // mUTF8Text = utf8str;
- mWText = utf8str_to_wstring(mUTF8Text);
- mTextIsUpToDate = true;
- truncate();
- blockUndo();
- setCursorPos(0);
- deselect();
- needsReflow();
- resetDirty();
- }
- void LLTextEditor::setWText(const LLWString& wtext)
- {
- mWText = wtext;
- mUTF8Text.clear();
- mTextIsUpToDate = false;
- truncate();
- blockUndo();
- setCursorPos(0);
- deselect();
- needsReflow();
- resetDirty();
- }
- //virtual
- void LLTextEditor::setValue(const LLSD& value)
- {
- setText(value.asString());
- }
- const std::string& LLTextEditor::getText() const
- {
- if (!mTextIsUpToDate)
- {
- if (mAllowEmbeddedItems)
- {
- llwarns << "getText() called on text with embedded items (not supported)"
- << llendl;
- }
- mUTF8Text = wstring_to_utf8str(mWText);
- mTextIsUpToDate = true;
- }
- return mUTF8Text;
- }
- //virtual
- LLSD LLTextEditor::getValue() const
- {
- return LLSD(getText());
- }
- void LLTextEditor::setWordWrap(bool b)
- {
- mWordWrap = b;
- setCursorPos(0);
- deselect();
- needsReflow();
- }
- void LLTextEditor::setBorderVisible(bool b)
- {
- mBorder->setVisible(b);
- }
- bool LLTextEditor::isBorderVisible() const
- {
- return mBorder->getVisible();
- }
- void LLTextEditor::setHideScrollbarForShortDocs(bool b)
- {
- mHideScrollbarForShortDocs = b;
- if (mHideScrollbarForShortDocs)
- {
- bool short_doc = mScrollbar->getDocSize() <= mScrollbar->getPageSize();
- mScrollbar->setVisible(!short_doc);
- }
- }
- void LLTextEditor::selectNext(const std::string& search_text_in,
- bool case_insensitive, bool wrap)
- {
- if (search_text_in.empty())
- {
- return;
- }
- LLWString text = getWText();
- LLWString search_text = utf8str_to_wstring(search_text_in);
- if (case_insensitive)
- {
- LLWStringUtil::toLower(text);
- LLWStringUtil::toLower(search_text);
- }
- if (mIsSelecting)
- {
- LLWString selected_text = text.substr(mSelectionEnd,
- mSelectionStart - mSelectionEnd);
- if (selected_text == search_text)
- {
- // We already have this word selected, we are searching for the next.
- mCursorPos += search_text.size();
- }
- }
- size_t loc = text.find(search_text, mCursorPos);
- // If Maybe we wrapped, search again
- if (wrap && loc == std::string::npos)
- {
- loc = text.find(search_text);
- }
- // If still not found, then search_text just is not found.
- if (loc == std::string::npos)
- {
- mIsSelecting = false;
- mSelectionEnd = 0;
- mSelectionStart = 0;
- return;
- }
- setCursorPos(loc);
- scrollToPos(mCursorPos);
- mIsSelecting = true;
- mSelectionEnd = mCursorPos;
- mSelectionStart = llmin((S32)getLength(),
- (S32)(mCursorPos + search_text.size()));
- }
- bool LLTextEditor::replaceText(const std::string& search_text_in,
- const std::string& replace_text,
- bool case_insensitive, bool wrap)
- {
- bool replaced = false;
- if (search_text_in.empty())
- {
- return replaced;
- }
- LLWString search_text = utf8str_to_wstring(search_text_in);
- if (mIsSelecting)
- {
- LLWString text = getWText();
- LLWString selected_text = text.substr(mSelectionEnd,
- mSelectionStart - mSelectionEnd);
- if (case_insensitive)
- {
- LLWStringUtil::toLower(selected_text);
- LLWStringUtil::toLower(search_text);
- }
- if (selected_text == search_text)
- {
- // *HACK: this is used when replacing SLURLs with names in chat.
- // We invalidate any existing segment at this position then, when
- // the replacement text length does not match the replaced text
- // length, we shift the segments that follow...
- // *TODO: make it a proper text editor feature, and extend segments
- // preservation over text deletion and insertion.
- if (mPreserveSegments)
- {
- S32 offset = utf8str_to_wstring(replace_text).size() -
- search_text.size();
- if (offset != 0 && !mSegments.empty())
- {
- for (segment_list_t::iterator it = mSegments.begin(),
- end = mSegments.end();
- it != end; )
- {
- LLTextSegment* segment = *it;
- S32 seg_start = segment->getStart();
- S32 seg_end = segment->getEnd();
- if (seg_end > mCursorPos)
- {
- if (seg_start > mCursorPos)
- {
- segment->shift(offset);
- }
- else
- {
- // This is the current segment: only change
- // its end position.
- S32 new_end = seg_end + offset;
- if (seg_start >= new_end)
- {
- // If we replaced it with empty text, we
- // need to delete it entirely.
- delete *it;
- it = mSegments.erase(it);
- continue;
- }
- segment->setEnd(new_end);
- }
- }
- ++it;
- }
- }
- }
- insertText(replace_text);
- replaced = true;
- }
- }
- selectNext(search_text_in, case_insensitive, wrap);
- return replaced;
- }
- void LLTextEditor::replaceTextAll(const std::string& search_text,
- const std::string& replace_text,
- bool case_insensitive)
- {
- S32 cur_pos = mScrollbar->getDocPos();
- setCursorPos(0);
- selectNext(search_text, case_insensitive, false);
- bool replaced = true;
- while (replaced)
- {
- replaced = replaceText(search_text,replace_text, case_insensitive,
- false);
- }
- mScrollbar->setDocPos(cur_pos);
- }
- // Picks a new cursor position based on the screen size of text being drawn.
- void LLTextEditor::setCursorAtLocalPos(S32 local_x, S32 local_y, bool round)
- {
- setCursorPos(getCursorPosFromLocalCoord(local_x, local_y, round));
- }
- S32 LLTextEditor::prevWordPos(S32 cursorPos) const
- {
- const LLWString& wtext = mWText;
- while (cursorPos > 0 && wtext[cursorPos - 1] == ' ')
- {
- --cursorPos;
- }
- while (cursorPos > 0 && LLWStringUtil::isPartOfWord(wtext[cursorPos - 1]))
- {
- --cursorPos;
- }
- return cursorPos;
- }
- S32 LLTextEditor::nextWordPos(S32 cursorPos) const
- {
- const LLWString& wtext = mWText;
- while (cursorPos < getLength() &&
- LLWStringUtil::isPartOfWord(wtext[cursorPos]))
- {
- ++cursorPos;
- }
- while (cursorPos < getLength() && wtext[cursorPos] == ' ')
- {
- ++cursorPos;
- }
- return cursorPos;
- }
- bool LLTextEditor::getWordBoundriesAt(const S32 at, S32* word_begin,
- S32* word_length) const
- {
- S32 pos = at;
- S32 start;
- if (LLWStringUtil::isPartOfLexicalWord(mWText[pos]))
- {
- while (pos > 0 && LLWStringUtil::isPartOfLexicalWord(mWText[pos - 1]))
- {
- --pos;
- }
- if (mWText[pos] == L'\'')
- {
- // Do not count "'" at the start of a word
- ++pos;
- }
- start = pos;
- while (pos < getLength() &&
- LLWStringUtil::isPartOfLexicalWord(mWText[pos]))
- {
- ++pos;
- }
- if (mWText[pos - 1] == L'\'')
- {
- // Do not count "'" at the end of a word
- --pos;
- }
- if (start >= pos)
- {
- return false;
- }
- *word_begin = start;
- *word_length = pos - start;
- return true;
- }
- return false;
- }
- S32 LLTextEditor::getLineStart(S32 line) const
- {
- S32 num_lines = getLineCount();
- if (num_lines == 0)
- {
- return 0;
- }
- line = llclamp(line, 0, num_lines - 1);
- S32 segidx = mLineStartList[line].mSegment;
- S32 segoffset = mLineStartList[line].mOffset;
- LLTextSegment* seg = mSegments[segidx];
- S32 res = seg->getStart() + segoffset;
- if (res > seg->getEnd())
- {
- llwarns << "Text length (" << res << ") greater than text end ("
- << seg->getEnd() << ")." << llendl;
- res = seg->getEnd();
- }
- return res;
- }
- // Given an offset into text (pos), find the corresponding line (from the start
- // of the doc) and an offset into the line.
- void LLTextEditor::getLineAndOffset(S32 startpos, S32* linep, S32* offsetp) const
- {
- if (mLineStartList.empty())
- {
- *linep = 0;
- *offsetp = startpos;
- }
- else
- {
- S32 seg_idx, seg_offset;
- getSegmentAndOffset(startpos, &seg_idx, &seg_offset);
- line_info tline(seg_idx, seg_offset);
- line_list_t::const_iterator iter = std::upper_bound(mLineStartList.begin(),
- mLineStartList.end(),
- tline,
- line_info_compare());
- if (iter != mLineStartList.begin()) --iter;
- *linep = iter - mLineStartList.begin();
- S32 line_start = mSegments[iter->mSegment]->getStart() + iter->mOffset;
- *offsetp = startpos - line_start;
- }
- }
- void LLTextEditor::getSegmentAndOffset(S32 startpos, S32* segidxp,
- S32* offsetp) const
- {
- if (mSegments.empty())
- {
- *segidxp = -1;
- *offsetp = startpos;
- }
- LLTextSegment tseg(startpos);
- segment_list_t::const_iterator seg_iter;
- seg_iter = std::upper_bound(mSegments.begin(), mSegments.end(), &tseg,
- LLTextSegment::compare());
- if (seg_iter != mSegments.begin()) --seg_iter;
- *segidxp = seg_iter - mSegments.begin();
- *offsetp = startpos - (*seg_iter)->getStart();
- }
- const LLTextSegment* LLTextEditor::getPreviousSegment() const
- {
- // Find segment index at character to left of cursor (or rightmost edge of
- // selection)
- S32 idx = llmax(0, getSegmentIdxAtOffset(mCursorPos) - 1);
- return idx >= 0 ? mSegments[idx] : NULL;
- }
- void LLTextEditor::getSelectedSegments(std::vector<const LLTextSegment*>& segments) const
- {
- S32 left = hasSelection() ? llmin(mSelectionStart, mSelectionEnd)
- : mCursorPos;
- S32 right = hasSelection() ? llmax(mSelectionStart, mSelectionEnd)
- : mCursorPos;
- S32 first_idx = llmax(0, getSegmentIdxAtOffset(left));
- S32 last_idx = llmax(0, first_idx, getSegmentIdxAtOffset(right));
- for (S32 idx = first_idx; idx <= last_idx; ++idx)
- {
- segments.push_back(mSegments[idx]);
- }
- }
- S32 LLTextEditor::getCursorPosFromLocalCoord(S32 local_x, S32 local_y,
- bool round) const
- {
- if (mShowLineNumbers)
- {
- local_x -= UI_TEXTEDITOR_LINE_NUMBER_MARGIN;
- }
- // If round is true, if the position is on the right half of a character,
- // the cursor will be put to its right. If round is false, the cursor will
- // always be put to the character's left.
- // Figure out which line we are nearest to.
- S32 total_lines = getLineCount();
- S32 line_height = ll_roundp(mGLFont->getLineHeight());
- S32 max_visible_lines = mTextRect.getHeight() / line_height;
- S32 scroll_lines = mScrollbar->getDocPos();
- // Lines currently visible
- S32 visible_lines = llmin(total_lines - scroll_lines, max_visible_lines);
- //S32 line = S32(0.5f + ((mTextRect.mTop - local_y) / mGLFont->getLineHeight()));
- S32 line = (mTextRect.mTop - 1 - local_y) / line_height;
- if (line >= total_lines)
- {
- return getLength(); // past the end
- }
- line = llclamp(line, 0, visible_lines) + scroll_lines;
- S32 line_start = getLineStart(line);
- S32 next_start = getLineStart(line + 1);
- S32 line_end = (next_start != line_start) ? next_start - 1 : getLength();
- if (line_start == -1)
- {
- return 0;
- }
- S32 line_len = line_end - line_start;
- S32 pos;
- if (mAllowEmbeddedItems)
- {
- // Figure out which character we are nearest to.
- bindEmbeddedChars(mGLFont);
- pos = mGLFont->charFromPixelOffset(mWText.c_str(), line_start,
- (F32)(local_x - mTextRect.mLeft),
- (F32)(mTextRect.getWidth()),
- line_len, round, true);
- unbindEmbeddedChars(mGLFont);
- }
- else
- {
- pos = mGLFont->charFromPixelOffset(mWText.c_str(), line_start,
- (F32)(local_x - mTextRect.mLeft),
- (F32)mTextRect.getWidth(),
- line_len, round);
- }
- return line_start + pos;
- }
- void LLTextEditor::setCursor(S32 row, S32 column)
- {
- // Make sure we are not trying to set the cursor out of boundaries
- if (row < 0)
- {
- row = 0;
- }
- if (column < 0)
- {
- column = 0;
- }
- const llwchar* doc = mWText.c_str();
- while (row--)
- {
- while (*doc && *doc++ != '\n');
- }
- while (column-- && *doc && *doc++ != '\n');
- setCursorPos(doc - mWText.c_str());
- }
- void LLTextEditor::setCursorPos(S32 offset)
- {
- mCursorPos = llclamp(offset, 0, (S32)getLength());
- needsScroll();
- // Reset desired x cursor position
- mDesiredXPixel = -1;
- }
- void LLTextEditor::setFont(LLFontGL* fontp)
- {
- mGLFont = fontp ? fontp : LLFontGL::getFontSansSerif();
- // Take into account the new font size. HB
- updateLineStartList();
- // Propagate the new font size information to the scrollbar. HB
- mScrollbar->setDocSize(getLineCount());
- S32 line_height = ll_roundp(mGLFont->getLineHeight());
- S32 page_lines = mTextRect.getHeight() / line_height;
- mScrollbar->setPageSize(page_lines);
- }
- void LLTextEditor::deselect()
- {
- mSelectionStart = 0;
- mSelectionEnd = 0;
- mIsSelecting = false;
- }
- void LLTextEditor::startSelection()
- {
- if (!mIsSelecting)
- {
- mIsSelecting = true;
- mSelectionStart = mCursorPos;
- mSelectionEnd = mCursorPos;
- }
- }
- void LLTextEditor::endSelection()
- {
- if (mIsSelecting)
- {
- mIsSelecting = false;
- mSelectionEnd = mCursorPos;
- }
- }
- void LLTextEditor::setSelection(S32 start, S32 end)
- {
- setCursorPos(end);
- startSelection();
- setCursorPos(start);
- endSelection();
- }
- bool LLTextEditor::selectionContainsLineBreaks()
- {
- if (hasSelection())
- {
- S32 left = llmin(mSelectionStart, mSelectionEnd);
- S32 right = left + abs(mSelectionStart - mSelectionEnd);
- const LLWString& wtext = mWText;
- for (S32 i = left; i < right; ++i)
- {
- if (wtext[i] == '\n')
- {
- return true;
- }
- }
- }
- return false;
- }
- // Assumes that pos is at the start of the line spaces may be positive (indent)
- // or negative (unindent). Returns the actual number of characters added or
- // removed.
- S32 LLTextEditor::indentLine(S32 pos, S32 spaces)
- {
- llassert(pos >= 0);
- llassert(pos <= getLength());
- S32 delta_spaces = 0;
- if (spaces >= 0)
- {
- // Indent
- for (S32 i = 0; i < spaces; ++i)
- {
- delta_spaces += addChar(pos, ' ');
- }
- }
- else
- {
- // Unindent
- for (S32 i = 0; i < -spaces; ++i)
- {
- const LLWString& wtext = mWText;
- if (wtext[pos] == ' ')
- {
- delta_spaces += remove(pos, 1, false);
- }
- }
- }
- return delta_spaces;
- }
- void LLTextEditor::indentSelectedLines(S32 spaces)
- {
- if (hasSelection())
- {
- const LLWString& text = mWText;
- S32 left = llmin(mSelectionStart, mSelectionEnd);
- S32 right = left + abs(mSelectionStart - mSelectionEnd);
- bool cursor_on_right = mSelectionEnd > mSelectionStart;
- S32 cur = left;
- // Expand left to start of line
- while (cur > 0 && text[cur] != '\n')
- {
- --cur;
- }
- left = cur;
- if (cur > 0)
- {
- ++left;
- }
- // Expand right to end of line
- if (text[right - 1] == '\n')
- {
- --right;
- }
- else
- {
- while (text[right] != '\n' && right <= getLength())
- {
- ++right;
- }
- }
- // Find each start-of-line and indent it
- do
- {
- if (text[cur] == '\n')
- {
- ++cur;
- }
- S32 delta_spaces = indentLine(cur, spaces);
- if (delta_spaces > 0)
- {
- cur += delta_spaces;
- }
- right += delta_spaces;
- //text = mWText;
- // Find the next new line
- while (cur < right && text[cur] != '\n')
- {
- ++cur;
- }
- }
- while (cur < right);
- if (right < getLength() && text[right] == '\n')
- {
- ++right;
- }
- // Set the selection and cursor
- if (cursor_on_right)
- {
- mSelectionStart = left;
- mSelectionEnd = right;
- }
- else
- {
- mSelectionStart = right;
- mSelectionEnd = left;
- }
- mCursorPos = mSelectionEnd;
- }
- }
- //virtual
- void LLTextEditor::selectAll()
- {
- mSelectionStart = getLength();
- mSelectionEnd = 0;
- mCursorPos = mSelectionEnd;
- }
- bool LLTextEditor::handleToolTip(S32 x, S32 y, std::string& msg,
- LLRect* sticky_rect_screen)
- {
- for (child_list_const_iter_t child_it = getChildList()->begin(),
- end = getChildList()->end();
- child_it != end; ++child_it)
- {
- LLView* viewp = *child_it;
- S32 local_x = x - viewp->getRect().mLeft;
- S32 local_y = y - viewp->getRect().mBottom;
- if (viewp->handleToolTip(local_x, local_y, msg, sticky_rect_screen))
- {
- return true;
- }
- }
- if (mSegments.empty())
- {
- return true;
- }
- const LLTextSegment* cur_segment = getSegmentAtLocalPos(x, y);
- if (cur_segment)
- {
- bool has_tool_tip = cur_segment->getToolTip(msg);
- if (has_tool_tip)
- {
- // Just use a slop area around the cursor
- // Convert rect local to screen coordinates
- S32 SLOP = 8;
- localPointToScreen(x - SLOP, y - SLOP,
- &(sticky_rect_screen->mLeft),
- &(sticky_rect_screen->mBottom));
- sticky_rect_screen->mRight = sticky_rect_screen->mLeft + 2 * SLOP;
- sticky_rect_screen->mTop = sticky_rect_screen->mBottom + 2 * SLOP;
- }
- }
- return true;
- }
- bool LLTextEditor::handleScrollWheel(S32 x, S32 y, S32 clicks)
- {
- // Pretend the mouse is over the scrollbar
- return mScrollbar->handleScrollWheel(0, 0, clicks);
- }
- bool LLTextEditor::handleMouseDown(S32 x, S32 y, MASK mask)
- {
- bool handled = false;
- // Key presses are not being passed to the Popup menu.
- // A proper fix is non-trivial so instead just close the menu.
- LLMenuGL* menu = getContextMenu();
- if (menu && menu->isOpen())
- {
- LLMenuGL::sMenuContainer->hideMenus();
- }
- // Let scrollbar have first dibs
- handled = LLView::childrenHandleMouseDown(x, y, mask) != NULL;
- if (!handled)
- {
- if (!(mask & MASK_SHIFT))
- {
- deselect();
- }
- // If we are not scrolling (handled by child), then we are selecting
- if (mask & MASK_SHIFT)
- {
- S32 old_cursor_pos = mCursorPos;
- setCursorAtLocalPos(x, y, true);
- if (hasSelection())
- {
- #if 0 // Mac-like behavior - extend selection towards the cursor
- if (mCursorPos < mSelectionStart &&
- mCursorPos < mSelectionEnd)
- {
- // ...left of selection
- mSelectionStart = llmax(mSelectionStart,
- mSelectionEnd);
- mSelectionEnd = mCursorPos;
- }
- else if (mCursorPos > mSelectionStart &&
- mCursorPos > mSelectionEnd)
- {
- // ...right of selection
- mSelectionStart = llmin(mSelectionStart,
- mSelectionEnd);
- mSelectionEnd = mCursorPos;
- }
- else
- {
- mSelectionEnd = mCursorPos;
- }
- #else
- // Windows behavior
- mSelectionEnd = mCursorPos;
- #endif
- }
- else
- {
- mSelectionStart = old_cursor_pos;
- mSelectionEnd = mCursorPos;
- }
- // Assume we are starting a drag select
- mIsSelecting = true;
- }
- else
- {
- setCursorAtLocalPos(x, y, true);
- startSelection();
- }
- gFocusMgr.setMouseCapture(this);
- handled = true;
- }
- if (hasTabStop())
- {
- setFocus(true);
- handled = true;
- }
- // Delay cursor flashing
- resetKeystrokeTimer();
- return handled;
- }
- bool LLTextEditor::handleMiddleMouseDown(S32 x, S32 y, MASK mask)
- {
- setFocus(true);
- if (canPastePrimary())
- {
- setCursorAtLocalPos(x, y, true);
- pastePrimary();
- }
- return true;
- }
- bool LLTextEditor::handleRightMouseDown(S32 x, S32 y, MASK mask)
- {
- setFocus(true);
- S32 word_start = 0;
- S32 word_len = 0;
- S32 pos = getCursorPosFromLocalCoord(x, y, true);
- // If the context menu has not yet been created for this editor, this call
- // will create it now. HB
- LLMenuGL* menu = createContextMenu();
- if (menu)
- {
- SpellMenuBind* menu_bind;
- LLMenuItemCallGL* menu_item;
- // Remove old suggestions
- for (S32 i = 0, count = mSuggestionMenuItems.size();
- i < count; ++i)
- {
- menu_bind = mSuggestionMenuItems[i];
- if (menu_bind)
- {
- menu_item = menu_bind->mMenuItem;
- menu->remove(menu_item);
- menu_item->die();
- #if 0
- delete menu_bind->mMenuItem;
- menu_bind->mMenuItem = NULL;
- #endif
- delete menu_bind;
- }
- }
- mSuggestionMenuItems.clear();
- // Not read-only, spell_check="true" in xui and spell checking enabled
- bool spell_check = !mReadOnly && mSpellCheck &&
- LLSpellCheck::getInstance()->getSpellCheck();
- menu->setItemVisible("spell_sep", spell_check);
- if (spell_check)
- {
- // Search for word matches
- bool is_word_part = getWordBoundriesAt(pos, &word_start,
- &word_len);
- if (is_word_part)
- {
- const LLWString& text = mWText;
- std::string part(text.begin(), text.end());
- std::string selected_word = part.substr(word_start, word_len);
- if (!LLSpellCheck::getInstance()->checkSpelling(selected_word))
- {
- // Misspelled word here
- std::vector<std::string> suggestions;
- S32 count =
- LLSpellCheck::getInstance()->getSuggestions(selected_word,
- suggestions);
- for (S32 i = 0; i < count; ++i)
- {
- menu_bind = new SpellMenuBind;
- menu_bind->mOrigin = this;
- menu_bind->mWord = suggestions[i];
- menu_bind->mWordPositionEnd = word_start + word_len;
- menu_bind->mWordPositionStart = word_start;
- menu_item = new LLMenuItemCallGL(menu_bind->mWord,
- spellCorrect,
- NULL, menu_bind);
- menu_bind->mMenuItem = menu_item;
- mSuggestionMenuItems.push_back(menu_bind);
- menu->append(menu_item);
- }
- menu_bind = new SpellMenuBind;
- menu_bind->mOrigin = this;
- menu_bind->mWord = selected_word;
- menu_bind->mWordPositionEnd = word_start + word_len;
- menu_bind->mWordPositionStart = word_start;
- menu_item = new LLMenuItemCallGL("Add word", spellAdd,
- NULL, menu_bind);
- menu_bind->mMenuItem = menu_item;
- mSuggestionMenuItems.push_back(menu_bind);
- menu->append(menu_item);
- menu_bind = new SpellMenuBind;
- menu_bind->mOrigin = this;
- menu_bind->mWord = selected_word;
- menu_bind->mWordPositionEnd = word_start + word_len;
- menu_bind->mWordPositionStart = word_start;
- menu_item = new LLMenuItemCallGL("Ignore word",
- spellIgnore, NULL,
- menu_bind);
- menu_bind->mMenuItem = menu_item;
- mSuggestionMenuItems.push_back(menu_bind);
- menu->append(menu_item);
- }
- }
- menu_bind = new SpellMenuBind;
- menu_bind->mOrigin = this;
- if (mShowMisspelled)
- {
- menu_bind->mWord = "Hide misspellings";
- }
- else
- {
- menu_bind->mWord = "Show misspellings";
- }
- menu_item = new LLMenuItemCallGL(menu_bind->mWord, spellShow,
- NULL, menu_bind);
- menu_bind->mMenuItem = menu_item;
- mSuggestionMenuItems.push_back(menu_bind);
- menu->append(menu_item);
- }
- menu->buildDrawLabels();
- menu->updateParent(LLMenuGL::sMenuContainer);
- LLMenuGL::showPopup(this, menu, x, y);
- }
- return true;
- }
- bool LLTextEditor::handleHover(S32 x, S32 y, MASK mask)
- {
- bool handled = false;
- mHoverSegment = NULL;
- if (hasMouseCapture())
- {
- if (mIsSelecting)
- {
- if (x != mLastSelectionX || y != mLastSelectionY)
- {
- mLastSelectionX = x;
- mLastSelectionY = y;
- }
- if (y > mTextRect.mTop)
- {
- mScrollbar->setDocPos(mScrollbar->getDocPos() - 1);
- }
- else if (y < mTextRect.mBottom)
- {
- mScrollbar->setDocPos(mScrollbar->getDocPos() + 1);
- }
- setCursorAtLocalPos(x, y, true);
- mSelectionEnd = mCursorPos;
- }
- LL_DEBUGS("UserInput") << "hover handled by " << getName()
- << " (active)" << LL_ENDL;
- gWindowp->setCursor(UI_CURSOR_IBEAM);
- handled = true;
- }
- if (!handled)
- {
- // Pass to children
- handled = LLView::childrenHandleHover(x, y, mask) != NULL;
- }
- if (handled)
- {
- // Delay cursor flashing
- resetKeystrokeTimer();
- }
- // Opaque
- if (!handled)
- {
- // Check to see if we are over an HTML-style link
- if (!mSegments.empty())
- {
- const LLTextSegment* cur_segment = getSegmentAtLocalPos(x, y);
- if (cur_segment)
- {
- if (cur_segment->getStyle()->isLink())
- {
- LL_DEBUGS("UserInput") << "hover handled by " << getName()
- << " (over link, inactive)"
- << LL_ENDL;
- gWindowp->setCursor(UI_CURSOR_HAND);
- handled = true;
- }
- else if (cur_segment->getStyle()->getIsEmbeddedItem())
- {
- LL_DEBUGS("UserInput") << "hover handled by " << getName()
- << " (over embedded item, inactive)"
- << LL_ENDL;
- gWindowp->setCursor(UI_CURSOR_HAND);
- handled = true;
- }
- mHoverSegment = cur_segment;
- }
- }
- if (!handled)
- {
- LL_DEBUGS("UserInput") << "hover handled by " << getName()
- << " (inactive)" << LL_ENDL;
- if (!mScrollbar->getVisible() ||
- x < getRect().getWidth() - SCROLLBAR_SIZE)
- {
- gWindowp->setCursor(UI_CURSOR_IBEAM);
- }
- else
- {
- gWindowp->setCursor(UI_CURSOR_ARROW);
- }
- handled = true;
- }
- }
- if (mOnScrollEndCallback && mOnScrollEndData &&
- mScrollbar->getDocPos() == mScrollbar->getDocPosMax())
- {
- mOnScrollEndCallback(mOnScrollEndData);
- }
- return handled;
- }
- bool LLTextEditor::handleMouseUp(S32 x, S32 y, MASK mask)
- {
- bool handled = false;
- // let scrollbar have first dibs
- handled = LLView::childrenHandleMouseUp(x, y, mask) != NULL;
- if (!handled)
- {
- if (mIsSelecting)
- {
- // Finish selection
- if (y > mTextRect.mTop)
- {
- mScrollbar->setDocPos(mScrollbar->getDocPos() - 1);
- }
- else if (y < mTextRect.mBottom)
- {
- mScrollbar->setDocPos(mScrollbar->getDocPos() + 1);
- }
- setCursorAtLocalPos(x, y, true);
- endSelection();
- }
- if (!hasSelection())
- {
- handleMouseUpOverSegment(x, y, mask);
- }
- // Take selection to 'primary' clipboard
- updatePrimary();
- handled = true;
- }
- // Delay cursor flashing
- resetKeystrokeTimer();
- if (hasMouseCapture())
- {
- gFocusMgr.setMouseCapture(NULL);
- handled = true;
- }
- return handled;
- }
- bool LLTextEditor::handleDoubleClick(S32 x, S32 y, MASK mask)
- {
- bool handled = false;
- // Let scrollbar have first dibs
- handled = LLView::childrenHandleDoubleClick(x, y, mask) != NULL;
- if (!handled)
- {
- setCursorAtLocalPos(x, y, false);
- deselect();
- const LLWString& text = mWText;
- if (LLWStringUtil::isPartOfWord(text[mCursorPos]))
- {
- // Select word the cursor is over
- while (mCursorPos > 0 &&
- LLWStringUtil::isPartOfWord(text[mCursorPos - 1]))
- {
- --mCursorPos;
- }
- startSelection();
- while (mCursorPos < (S32)text.length() &&
- LLWStringUtil::isPartOfWord(text[mCursorPos]))
- {
- ++mCursorPos;
- }
- mSelectionEnd = mCursorPos;
- }
- else if (mCursorPos < (S32)text.length() &&
- !iswspace(text[mCursorPos]))
- {
- // Select the character the cursor is over
- startSelection();
- mSelectionEnd = ++mCursorPos;
- }
- // We do not want handleMouseUp() to "finish" the selection (and
- // thereby set mSelectionEnd to where the mouse is), so we finish the
- // selectionhere.
- mIsSelecting = false;
- // Delay cursor flashing
- resetKeystrokeTimer();
- // Take selection to 'primary' clipboard
- updatePrimary();
- handled = true;
- }
- return handled;
- }
- // Allow calling cards to be dropped onto text fields. Append the name and
- // a carriage return.
- //virtual
- bool LLTextEditor::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop,
- EDragAndDropType cargo_type,
- void* cargo_data, EAcceptance* accept,
- std::string& tooltip_msg)
- {
- *accept = ACCEPT_NO;
- return true;
- }
- // Returns change in number of characters in mText
- S32 LLTextEditor::execute(LLTextCmd* cmd)
- {
- S32 delta = 0;
- if (cmd->execute(this, &delta))
- {
- // Delete top of undo stack
- undo_stack_t::iterator enditer = std::find(mUndoStack.begin(),
- mUndoStack.end(), mLastCmd);
- if (enditer != mUndoStack.begin())
- {
- --enditer;
- std::for_each(mUndoStack.begin(), enditer, DeletePointer());
- mUndoStack.erase(mUndoStack.begin(), enditer);
- }
- // Push the new command is now on the top (front) of the undo stack.
- mUndoStack.push_front(cmd);
- mLastCmd = cmd;
- if (mKeystrokeCallback)
- {
- mKeystrokeCallback(this, mKeystrokeData);
- }
- }
- else
- {
- // Operation failed, so do not put it on the undo stack.
- delete cmd;
- }
- return delta;
- }
- S32 LLTextEditor::insert(S32 pos, const LLWString& wstr,
- bool group_with_next_op)
- {
- return execute(new LLTextCmdInsert(pos, group_with_next_op, wstr));
- }
- S32 LLTextEditor::remove(S32 pos, S32 length, bool group_with_next_op)
- {
- return execute(new LLTextCmdRemove(pos, group_with_next_op, length));
- }
- S32 LLTextEditor::append(const LLWString& wstr, bool group_with_next_op)
- {
- return insert(mWText.length(), wstr, group_with_next_op);
- }
- S32 LLTextEditor::overwriteChar(S32 pos, llwchar wc)
- {
- if ((S32)mWText.length() == pos)
- {
- return addChar(pos, wc);
- }
- else
- {
- return execute(new LLTextCmdOverwriteChar(pos, false, wc));
- }
- }
- // Removes a single character from the text. Tries to remove a pseudo-tab (up
- // to four spaces in a row)
- void LLTextEditor::removeCharOrTab()
- {
- if (!getEnabled())
- {
- return;
- }
- if (mCursorPos > 0)
- {
- S32 chars_to_remove = 1;
- const LLWString& text = mWText;
- if (text[mCursorPos - 1] == ' ')
- {
- // Try to remove a "tab"
- S32 line, offset;
- getLineAndOffset(mCursorPos, &line, &offset);
- if (offset > 0)
- {
- chars_to_remove = offset % SPACES_PER_TAB;
- if (chars_to_remove == 0)
- {
- chars_to_remove = SPACES_PER_TAB;
- }
- for (S32 i = 0; i < chars_to_remove; ++i)
- {
- if (text[ mCursorPos - i - 1] != ' ')
- {
- // Fewer than a full tab's worth of spaces, so just
- // delete a single character.
- chars_to_remove = 1;
- break;
- }
- }
- }
- }
- for (S32 i = 0; i < chars_to_remove; ++i)
- {
- setCursorPos(mCursorPos - 1);
- remove(mCursorPos, 1, false);
- }
- }
- else
- {
- reportBadKeystroke();
- }
- }
- // Remove a single character from the text
- S32 LLTextEditor::removeChar(S32 pos)
- {
- if (mKeystrokeCallback)
- {
- mKeystrokeCallback(this, mKeystrokeData);
- }
- return remove(pos, 1, false);
- }
- void LLTextEditor::removeChar()
- {
- if (!getEnabled())
- {
- return;
- }
- if (mCursorPos > 0)
- {
- setCursorPos(mCursorPos - 1);
- removeChar(mCursorPos);
- }
- else
- {
- reportBadKeystroke();
- }
- }
- // Add a single character to the text
- S32 LLTextEditor::addChar(S32 pos, llwchar wc)
- {
- if (wstring_utf8_length(mWText) +
- wchar_utf8_length(wc) >= mMaxTextByteLength)
- {
- make_ui_sound("UISndBadKeystroke");
- return 0;
- }
- if (mKeystrokeCallback)
- {
- mKeystrokeCallback(this, mKeystrokeData);
- }
- if (mLastCmd && mLastCmd->canExtend(pos))
- {
- S32 delta = 0;
- mLastCmd->extendAndExecute(this, pos, wc, &delta);
- return delta;
- }
- else
- {
- return execute(new LLTextCmdAddChar(pos, false, wc));
- }
- }
- void LLTextEditor::addChar(llwchar wc)
- {
- if (!getEnabled())
- {
- return;
- }
- if (hasSelection())
- {
- deleteSelection(true);
- }
- else if (gKeyboardp && gKeyboardp->getInsertMode() == LL_KIM_OVERWRITE)
- {
- removeChar(mCursorPos);
- }
- setCursorPos(mCursorPos + addChar(mCursorPos, wc));
- }
- bool LLTextEditor::handleSelectionKey(KEY key, MASK mask)
- {
- bool handled = false;
- if (mask & MASK_SHIFT)
- {
- handled = true;
- switch (key)
- {
- case KEY_LEFT:
- if (0 < mCursorPos)
- {
- startSelection();
- --mCursorPos;
- if (mask & MASK_CONTROL)
- {
- mCursorPos = prevWordPos(mCursorPos);
- }
- mSelectionEnd = mCursorPos;
- }
- break;
- case KEY_RIGHT:
- if (mCursorPos < getLength())
- {
- startSelection();
- ++mCursorPos;
- if (mask & MASK_CONTROL)
- {
- mCursorPos = nextWordPos(mCursorPos);
- }
- mSelectionEnd = mCursorPos;
- }
- break;
- case KEY_UP:
- startSelection();
- changeLine(-1);
- mSelectionEnd = mCursorPos;
- break;
- case KEY_PAGE_UP:
- startSelection();
- changePage(-1);
- mSelectionEnd = mCursorPos;
- break;
- case KEY_HOME:
- startSelection();
- if (mask & MASK_CONTROL)
- {
- mCursorPos = 0;
- }
- else
- {
- startOfLine();
- }
- mSelectionEnd = mCursorPos;
- break;
- case KEY_DOWN:
- startSelection();
- changeLine(1);
- mSelectionEnd = mCursorPos;
- break;
- case KEY_PAGE_DOWN:
- startSelection();
- changePage(1);
- mSelectionEnd = mCursorPos;
- break;
- case KEY_END:
- startSelection();
- if (mask & MASK_CONTROL)
- {
- mCursorPos = getLength();
- }
- else
- {
- endOfLine();
- }
- mSelectionEnd = mCursorPos;
- break;
- default:
- handled = false;
- }
- }
- if (!handled && mHandleEditKeysDirectly)
- {
- if ((MASK_CONTROL & mask) && key == 'A')
- {
- if (canSelectAll())
- {
- selectAll();
- }
- else
- {
- reportBadKeystroke();
- }
- handled = true;
- }
- }
- if (handled)
- {
- // Take selection to 'primary' clipboard
- updatePrimary();
- }
- return handled;
- }
- bool LLTextEditor::handleNavigationKey(KEY key, MASK mask)
- {
- bool handled = false;
- // Ignore capslock key
- if (MASK_NONE == mask)
- {
- handled = true;
- switch (key)
- {
- case KEY_UP:
- if (mReadOnly)
- {
- mScrollbar->setDocPos(mScrollbar->getDocPos() - 1);
- }
- else
- {
- changeLine(-1);
- }
- break;
- case KEY_PAGE_UP:
- changePage(-1);
- break;
- case KEY_HOME:
- if (mReadOnly)
- {
- mScrollbar->setDocPos(0);
- }
- else
- {
- startOfLine();
- }
- break;
- case KEY_DOWN:
- if (mReadOnly)
- {
- mScrollbar->setDocPos(mScrollbar->getDocPos() + 1);
- }
- else
- {
- changeLine(1);
- }
- break;
- case KEY_PAGE_DOWN:
- changePage(1);
- break;
- case KEY_END:
- if (mReadOnly)
- {
- mScrollbar->setDocPos(mScrollbar->getDocPosMax());
- }
- else
- {
- endOfLine();
- }
- break;
- case KEY_LEFT:
- if (mReadOnly)
- {
- break;
- }
- if (hasSelection())
- {
- setCursorPos(llmin(mCursorPos - 1, mSelectionStart,
- mSelectionEnd));
- }
- else
- {
- if (0 < mCursorPos)
- {
- setCursorPos(mCursorPos - 1);
- }
- else
- {
- reportBadKeystroke();
- }
- }
- break;
- case KEY_RIGHT:
- if (mReadOnly)
- {
- break;
- }
- if (hasSelection())
- {
- setCursorPos(llmax(mCursorPos + 1, mSelectionStart,
- mSelectionEnd));
- }
- else
- {
- if (mCursorPos < getLength())
- {
- setCursorPos(mCursorPos + 1);
- }
- else
- {
- reportBadKeystroke();
- }
- }
- break;
- default:
- handled = false;
- }
- }
- if (mOnScrollEndCallback && mOnScrollEndData &&
- mScrollbar->getDocPos() == mScrollbar->getDocPosMax())
- {
- mOnScrollEndCallback(mOnScrollEndData);
- }
- return handled;
- }
- void LLTextEditor::deleteSelection(bool group_with_next_op)
- {
- if (getEnabled() && hasSelection())
- {
- S32 pos = llmin(mSelectionStart, mSelectionEnd);
- S32 length = abs(mSelectionStart - mSelectionEnd);
- remove(pos, length, group_with_next_op);
- deselect();
- setCursorPos(pos);
- }
- }
- //virtual
- bool LLTextEditor::canCut() const
- {
- return !mReadOnly && hasSelection();
- }
- // Cuts selection to clipboard
- void LLTextEditor::cut()
- {
- if (!canCut())
- {
- return;
- }
- S32 left_pos = llmin(mSelectionStart, mSelectionEnd);
- S32 length = abs(mSelectionStart - mSelectionEnd);
- gClipboard.copyFromSubstring(mWText, left_pos, length);
- deleteSelection(false);
- needsReflow();
- if (mKeystrokeCallback)
- {
- mKeystrokeCallback(this, mKeystrokeData);
- }
- // Force spell-check update:
- mKeystrokeTimer.reset();
- mPrevSpelledText.erase();
- }
- bool LLTextEditor::canCopy() const
- {
- return hasSelection();
- }
- // Copies selection to clipboard
- void LLTextEditor::copy()
- {
- if (!canCopy())
- {
- return;
- }
- S32 left_pos = llmin(mSelectionStart, mSelectionEnd);
- S32 length = abs(mSelectionStart - mSelectionEnd);
- gClipboard.copyFromSubstring(mWText, left_pos, length);
- // Force spell-check update:
- mKeystrokeTimer.reset();
- mPrevSpelledText.erase();
- }
- bool LLTextEditor::canPaste() const
- {
- return !mReadOnly && gClipboard.canPasteString();
- }
- // Pastes from clipboard
- void LLTextEditor::paste()
- {
- pasteHelper(false);
- }
- // Pastes from primary
- void LLTextEditor::pastePrimary()
- {
- pasteHelper(true);
- }
- // Pastes from primary (is_primary == true) or clipboard (is_primary == false)
- void LLTextEditor::pasteHelper(bool is_primary)
- {
- bool can_paste_it;
- if (is_primary)
- {
- can_paste_it = canPastePrimary();
- }
- else
- {
- can_paste_it = canPaste();
- }
- if (!can_paste_it)
- {
- return;
- }
- LLWString paste;
- if (is_primary)
- {
- paste = gClipboard.getPastePrimaryWString();
- }
- else
- {
- paste = gClipboard.getPasteWString();
- }
- if (paste.empty())
- {
- return;
- }
- // Delete any selected characters (the paste replaces them)
- if (!is_primary && hasSelection())
- {
- deleteSelection(true);
- }
- // Clean up string (replace tabs and remove characters that our fonts do
- // not support).
- LLWString clean_string(paste);
- LLWStringUtil::replaceTabsWithSpaces(clean_string, SPACES_PER_TAB);
- if (mAllowEmbeddedItems)
- {
- constexpr llwchar LF = 10;
- S32 len = clean_string.length();
- for (S32 i = 0; i < len; ++i)
- {
- llwchar wc = clean_string[i];
- if (wc < LLFontFreetype::FIRST_CHAR && wc != LF)
- {
- clean_string[i] = LL_UNKNOWN_CHAR;
- }
- else if (wc >= FIRST_EMBEDDED_CHAR && wc <= LAST_EMBEDDED_CHAR)
- {
- clean_string[i] = pasteEmbeddedItem(wc);
- }
- }
- }
- // Insert the new text into the existing text.
- setCursorPos(mCursorPos + insert(mCursorPos, clean_string, false));
- deselect();
- needsReflow();
- if (mKeystrokeCallback)
- {
- mKeystrokeCallback(this, mKeystrokeData);
- }
- // Force spell-check update:
- mKeystrokeTimer.reset();
- mPrevSpelledText.erase();
- }
- // Copies selection to primary
- void LLTextEditor::copyPrimary()
- {
- if (!canCopy())
- {
- return;
- }
- S32 left_pos = llmin(mSelectionStart, mSelectionEnd);
- S32 length = abs(mSelectionStart - mSelectionEnd);
- gClipboard.copyFromPrimarySubstring(mWText, left_pos, length);
- // Force spell-check update:
- mKeystrokeTimer.reset();
- mPrevSpelledText.erase();
- }
- bool LLTextEditor::canPastePrimary() const
- {
- return !mReadOnly && gClipboard.canPastePrimaryString();
- }
- void LLTextEditor::updatePrimary()
- {
- if (canCopy())
- {
- copyPrimary();
- }
- }
- bool LLTextEditor::handleControlKey(KEY key, MASK mask)
- {
- bool handled = false;
- if (mask & MASK_CONTROL)
- {
- handled = true;
- switch (key)
- {
- case KEY_HOME:
- if (mask & MASK_SHIFT)
- {
- startSelection();
- mCursorPos = 0;
- mSelectionEnd = mCursorPos;
- }
- else
- {
- // Ctrl-Home, Ctrl-Left, Ctrl-Right, Ctrl-Down
- // all move the cursor as if clicking, so should deselect.
- deselect();
- setCursorPos(0);
- }
- break;
- case KEY_END:
- {
- if (mask & MASK_SHIFT)
- {
- startSelection();
- }
- else
- {
- // Ctrl-Home, Ctrl-Left, Ctrl-Right, Ctrl-Down
- // all move the cursor as if clicking, so should deselect.
- deselect();
- }
- endOfDoc();
- if (mask & MASK_SHIFT)
- {
- mSelectionEnd = mCursorPos;
- }
- break;
- }
- case KEY_RIGHT:
- if (mCursorPos < getLength())
- {
- // Ctrl-Home, Ctrl-Left, Ctrl-Right, Ctrl-Down
- // all move the cursor as if clicking, so should deselect.
- deselect();
- setCursorPos(nextWordPos(mCursorPos + 1));
- }
- break;
- case KEY_LEFT:
- if (mCursorPos > 0)
- {
- // Ctrl-Home, Ctrl-Left, Ctrl-Right, Ctrl-Down
- // all move the cursor as if clicking, so should deselect.
- deselect();
- setCursorPos(prevWordPos(mCursorPos - 1));
- }
- break;
- default:
- handled = false;
- }
- }
- if (handled)
- {
- updatePrimary();
- }
- return handled;
- }
- bool LLTextEditor::handleEditKey(KEY key, MASK mask)
- {
- bool handled = false;
- // Standard edit keys (Ctrl-X, Delete, etc,) are handled here instead of
- // routed by the menu system.
- if (KEY_DELETE == key)
- {
- if (canDoDelete())
- {
- doDelete();
- }
- else
- {
- reportBadKeystroke();
- }
- handled = true;
- }
- else if (MASK_CONTROL & mask)
- {
- if ('C' == key)
- {
- if (canCopy())
- {
- copy();
- }
- else
- {
- reportBadKeystroke();
- }
- handled = true;
- }
- else if ('V' == key)
- {
- if (canPaste())
- {
- paste();
- }
- else
- {
- reportBadKeystroke();
- }
- handled = true;
- }
- else if ('X' == key)
- {
- if (canCut())
- {
- cut();
- }
- else
- {
- reportBadKeystroke();
- }
- handled = true;
- }
- }
- if (handled)
- {
- // Take selection to 'primary' clipboard
- updatePrimary();
- }
- return handled;
- }
- bool LLTextEditor::handleSpecialKey(KEY key, MASK mask, bool* return_key_hit)
- {
- *return_key_hit = false;
- bool handled = true;
- switch (key)
- {
- case KEY_INSERT:
- if (mask == MASK_NONE && gKeyboardp)
- {
- gKeyboardp->toggleInsertMode();
- }
- break;
- case KEY_BACKSPACE:
- if (hasSelection())
- {
- deleteSelection(false);
- }
- else if (0 < mCursorPos)
- {
- removeCharOrTab();
- }
- else
- {
- reportBadKeystroke();
- }
- break;
- case KEY_RETURN:
- if (mask == MASK_NONE)
- {
- if (hasSelection())
- {
- deleteSelection(false);
- }
- autoIndent(); // *TODO: make this optional
- }
- else
- {
- handled = false;
- break;
- }
- break;
- case KEY_TAB:
- if (mask & (MASK_CONTROL | MASK_ALT))
- {
- handled = false;
- break;
- }
- if (hasSelection() && selectionContainsLineBreaks())
- {
- indentSelectedLines((mask & MASK_SHIFT) ? -SPACES_PER_TAB
- : SPACES_PER_TAB);
- }
- else
- {
- if (hasSelection())
- {
- deleteSelection(false);
- }
- S32 line, offset;
- getLineAndOffset(mCursorPos, &line, &offset);
- S32 spaces_needed = SPACES_PER_TAB - (offset % SPACES_PER_TAB);
- for (S32 i = 0; i < spaces_needed; ++i)
- {
- addChar(' ');
- }
- }
- break;
- default:
- handled = false;
- }
- return handled;
- }
- void LLTextEditor::unindentLineBeforeCloseBrace()
- {
- if (mCursorPos >= 1)
- {
- const LLWString& text = mWText;
- if (' ' == text[mCursorPos - 1])
- {
- removeCharOrTab();
- }
- }
- }
- bool LLTextEditor::handleKeyHere(KEY key, MASK mask)
- {
- bool handled = false;
- bool selection_modified = false;
- bool return_key_hit = false;
- bool text_may_have_changed = true;
- // Key presses are not being passed to the Popup menu.
- // A proper fix is non-trivial so instead just close the menu.
- LLMenuGL* menu = getContextMenu();
- if (menu && menu->isOpen())
- {
- LLMenuGL::sMenuContainer->hideMenus();
- }
- if (gFocusMgr.getKeyboardFocus() == this)
- {
- // Special case for TAB. If want to move to next field, report not
- // handled and let the parent take care of field movement.
- if (KEY_TAB == key && mTabsToNextField)
- {
- return false;
- }
- handled = handleNavigationKey(key, mask);
- if (handled)
- {
- text_may_have_changed = false;
- }
- if (!handled)
- {
- handled = handleSelectionKey(key, mask);
- if (handled)
- {
- selection_modified = true;
- }
- }
- if (!handled)
- {
- handled = handleControlKey(key, mask);
- if (handled)
- {
- selection_modified = true;
- }
- }
- if (!handled && mHandleEditKeysDirectly)
- {
- handled = handleEditKey(key, mask);
- if (handled)
- {
- selection_modified = true;
- text_may_have_changed = true;
- }
- }
- // Key presses are not being passed to the Popup menu.
- // A proper fix is non-trivial so instead just close the menu.
- LLMenuGL* menu = getContextMenu();
- if (menu && menu->isOpen())
- {
- LLMenuGL::sMenuContainer->hideMenus();
- }
- // Handle most keys only if the text editor is writeable.
- if (!mReadOnly)
- {
- if (!handled && mOnHandleKeyCallback)
- {
- handled = mOnHandleKeyCallback(key, mask, this,
- mOnHandleKeyData);
- }
- if (!handled)
- {
- handled = handleSpecialKey(key, mask, &return_key_hit);
- if (handled)
- {
- selection_modified = true;
- text_may_have_changed = true;
- }
- }
- }
- if (handled)
- {
- resetKeystrokeTimer();
- // Most keystrokes will make the selection box go away, but not all will.
- if (!selection_modified && KEY_SHIFT != key && KEY_TAB != key &&
- KEY_CONTROL != key && KEY_ALT != key && KEY_CAPSLOCK)
- {
- deselect();
- }
- if (text_may_have_changed)
- {
- needsReflow();
- }
- needsScroll();
- }
- }
- return handled;
- }
- bool LLTextEditor::handleUnicodeCharHere(llwchar uni_char)
- {
- if (uni_char < 0x20 || uni_char == 0x7F) // Control character or DEL
- {
- return false;
- }
- bool handled = false;
- if (gFocusMgr.getKeyboardFocus() == this)
- {
- // Handle most keys only if the text editor is writeable.
- if (!mReadOnly)
- {
- if ('}' == uni_char)
- {
- unindentLineBeforeCloseBrace();
- }
- // TODO: KLW Add auto show of tool tip on (
- addChar(uni_char);
- // Keys that add characters temporarily hide the cursor
- gWindowp->hideCursorUntilMouseMove();
- handled = true;
- }
- if (handled)
- {
- resetKeystrokeTimer();
- // Most keystrokes will make the selection box go away, but not all
- // will.
- deselect();
- needsReflow();
- }
- }
- return handled;
- }
- //virtual
- bool LLTextEditor::canDoDelete() const
- {
- return !mReadOnly && (hasSelection() || (mCursorPos < getLength()));
- }
- void LLTextEditor::doDelete()
- {
- if (!canDoDelete())
- {
- return;
- }
- if (hasSelection())
- {
- deleteSelection(false);
- }
- else if (mCursorPos < getLength())
- {
- S32 i;
- S32 chars_to_remove = 1;
- const LLWString& text = mWText;
- if (text[ mCursorPos ] == ' ' &&
- mCursorPos + SPACES_PER_TAB < getLength())
- {
- // Try to remove a full tab's worth of spaces
- S32 line, offset;
- getLineAndOffset(mCursorPos, &line, &offset);
- chars_to_remove = SPACES_PER_TAB - (offset % SPACES_PER_TAB);
- if (chars_to_remove == 0)
- {
- chars_to_remove = SPACES_PER_TAB;
- }
- for (i = 0; i < chars_to_remove; ++i)
- {
- if (text[mCursorPos + i] != ' ')
- {
- chars_to_remove = 1;
- break;
- }
- }
- }
- for (i = 0; i < chars_to_remove; ++i)
- {
- setCursorPos(mCursorPos + 1);
- removeChar();
- }
- }
- needsReflow();
- if (mKeystrokeCallback)
- {
- mKeystrokeCallback(this, mKeystrokeData);
- }
- // Force spell-check update:
- mKeystrokeTimer.reset();
- mPrevSpelledText.erase();
- }
- void LLTextEditor::blockUndo()
- {
- mBaseDocIsPristine = false;
- mLastCmd = NULL;
- std::for_each(mUndoStack.begin(), mUndoStack.end(), DeletePointer());
- mUndoStack.clear();
- // Force spell-check update:
- mKeystrokeTimer.reset();
- mPrevSpelledText.erase();
- }
- //virtual
- bool LLTextEditor::canUndo() const
- {
- return !mReadOnly && mLastCmd != NULL;
- }
- //virtual
- void LLTextEditor::undo()
- {
- if (!canUndo())
- {
- return;
- }
- S32 pos = 0;
- deselect();
- do
- {
- pos = mLastCmd->undo(this);
- undo_stack_t::iterator iter = std::find(mUndoStack.begin(),
- mUndoStack.end(), mLastCmd);
- if (iter != mUndoStack.end())
- {
- ++iter;
- }
- if (iter != mUndoStack.end())
- {
- mLastCmd = *iter;
- }
- else
- {
- mLastCmd = NULL;
- }
- }
- while (mLastCmd && mLastCmd->groupWithNext());
- setCursorPos(pos);
- needsReflow();
- // Force spell-check update:
- mKeystrokeTimer.reset();
- mPrevSpelledText.erase();
- }
- //virtual
- bool LLTextEditor::canRedo() const
- {
- return !mReadOnly && mUndoStack.size() > 0 &&
- mLastCmd != mUndoStack.front();
- }
- //virtual
- void LLTextEditor::redo()
- {
- if (!canRedo())
- {
- return;
- }
- S32 pos = 0;
- deselect();
- do
- {
- if (!mLastCmd)
- {
- mLastCmd = mUndoStack.back();
- }
- else
- {
- undo_stack_t::iterator iter = std::find(mUndoStack.begin(),
- mUndoStack.end(),
- mLastCmd);
- if (iter != mUndoStack.begin())
- {
- mLastCmd = *(--iter);
- }
- else
- {
- mLastCmd = NULL;
- }
- }
- if (mLastCmd)
- {
- pos = mLastCmd->redo(this);
- }
- }
- while (mLastCmd && mLastCmd->groupWithNext() &&
- mLastCmd != mUndoStack.front());
- setCursorPos(pos);
- needsReflow();
- // Force spell-check update:
- mKeystrokeTimer.reset();
- mPrevSpelledText.erase();
- }
- void LLTextEditor::onFocusReceived()
- {
- grabMenuHandler();
- LLUICtrl::onFocusReceived();
- updateAllowingLanguageInput();
- }
- // virtual, from LLView
- void LLTextEditor::onFocusLost()
- {
- updateAllowingLanguageInput();
- // Route menu back to the default
- releaseMenuHandler();
- if (mCommitOnFocusLost)
- {
- onCommit();
- }
- // Make sure cursor is shown again
- gWindowp->showCursorFromMouseMove();
- LLUICtrl::onFocusLost();
- }
- void LLTextEditor::setEnabled(bool enabled)
- {
- // just treat enabled as read-only flag
- bool read_only = !enabled;
- if (read_only != mReadOnly)
- {
- mReadOnly = read_only;
- updateSegments();
- updateAllowingLanguageInput();
- }
- }
- void LLTextEditor::drawBackground()
- {
- S32 left = 0;
- S32 top = getRect().getHeight();
- S32 right = getRect().getWidth();
- S32 bottom = 0;
- LLColor4 bg_color =
- mReadOnly ? mReadOnlyBgColor
- : gFocusMgr.getKeyboardFocus() == this ? mFocusBgColor
- : mWriteableBgColor;
- if (mShowLineNumbers)
- {
- gl_rect_2d(left, top, UI_TEXTEDITOR_LINE_NUMBER_MARGIN, bottom,
- mReadOnlyBgColor); // line number area always read-only
- gl_rect_2d(UI_TEXTEDITOR_LINE_NUMBER_MARGIN, top, right, bottom,
- bg_color); // body text area to the right of line numbers
- gl_rect_2d(UI_TEXTEDITOR_LINE_NUMBER_MARGIN, top,
- UI_TEXTEDITOR_LINE_NUMBER_MARGIN - 1, bottom,
- LLColor4::grey3); // separator
- }
- else
- {
- gl_rect_2d(left, top, right, bottom, bg_color); // body text area
- }
- LLView::draw();
- }
- // Draws the black box behind the selected text
- void LLTextEditor::drawSelectionBackground()
- {
- // Draw selection even if we do not have keyboard focus for search/replace
- if (hasSelection())
- {
- const LLWString& text = mWText;
- const S32 text_len = getLength();
- std::queue<S32> line_endings;
- S32 line_height = ll_roundp(mGLFont->getLineHeight());
- S32 selection_left = llmin(mSelectionStart, mSelectionEnd);
- S32 selection_right = llmax(mSelectionStart, mSelectionEnd);
- S32 selection_left_x = mTextRect.mLeft;
- S32 selection_left_y = mTextRect.mTop - line_height;
- S32 selection_right_x = mTextRect.mRight;
- S32 selection_right_y = mTextRect.mBottom;
- bool selection_right_visible = false;
- // Skip through the lines we are not drawing.
- S32 cur_line = mScrollbar->getDocPos();
- S32 left_line_num = cur_line;
- S32 num_lines = getLineCount();
- S32 line_start = -1;
- if (cur_line >= num_lines)
- {
- return;
- }
- line_start = getLineStart(cur_line);
- S32 left_visible_pos = line_start;
- S32 right_visible_pos = line_start;
- S32 text_y = mTextRect.mTop - line_height;
- // Find the coordinates of the selected area
- while (cur_line < num_lines)
- {
- S32 next_line = -1;
- S32 line_end = text_len;
- if (cur_line + 1 < num_lines)
- {
- next_line = getLineStart(cur_line + 1);
- line_end = next_line;
- line_end = line_end - line_start == 0 ||
- text[next_line - 1] == '\n' ||
- text[next_line - 1] == '\0' ||
- text[next_line - 1] == ' ' ||
- text[next_line - 1] == '\t' ? next_line - 1
- : next_line;
- }
- const llwchar* line = text.c_str() + line_start;
- if (line_start <= selection_left && selection_left <= line_end)
- {
- left_line_num = cur_line;
- selection_left_x =
- mTextRect.mLeft +
- mGLFont->getWidth(line, 0, selection_left - line_start,
- mAllowEmbeddedItems);
- selection_left_y = text_y;
- }
- if (line_start <= selection_right && selection_right <= line_end)
- {
- selection_right_visible = true;
- selection_right_x =
- mTextRect.mLeft +
- mGLFont->getWidth(line, 0, selection_right - line_start,
- mAllowEmbeddedItems);
- #if 0
- if (selection_right == line_end)
- {
- // Add empty space for "newline"
- selection_right_x += mGLFont->getWidth("n");
- }
- #endif
- selection_right_y = text_y;
- }
- // If selection spans end of current line...
- if (selection_left <= line_end && line_end < selection_right &&
- selection_left != selection_right)
- {
- // Extend selection slightly beyond end of line to indicate
- // selection of newline character (use "n" character to
- // determine width)
- const LLWString nstr(utf8str_to_wstring("n"));
- line_endings.push(mTextRect.mLeft +
- mGLFont->getWidth(line, 0,
- line_end - line_start,
- mAllowEmbeddedItems) +
- mGLFont->getWidth(nstr.c_str()));
- }
- // Move down one line
- text_y -= line_height;
- right_visible_pos = line_end;
- line_start = next_line;
- ++cur_line;
- if (selection_right_visible)
- {
- break;
- }
- }
- // Draw the selection box (we are using a box instead of reversing the
- // colors on the selected text).
- bool selection_visible = left_visible_pos <= selection_right &&
- selection_left <= right_visible_pos;
- if (selection_visible)
- {
- gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
- const LLColor4& color = mReadOnly ? mReadOnlyBgColor
- : mWriteableBgColor;
- F32 alpha = hasFocus() ? 1.f : 0.5f;
- gGL.color4f(1.f - color.mV[0], 1.f - color.mV[1],
- 1.f - color.mV[2], alpha);
- S32 margin_offset =
- mShowLineNumbers ? UI_TEXTEDITOR_LINE_NUMBER_MARGIN : 0;
- if (selection_left_y == selection_right_y)
- {
- // Draw from selection start to selection end
- gl_rect_2d(selection_left_x + margin_offset,
- selection_left_y + line_height + 1,
- selection_right_x + margin_offset,
- selection_right_y);
- }
- else
- {
- // Draw from selection start to the end of the first line
- if (mTextRect.mRight == selection_left_x)
- {
- selection_left_x -= CURSOR_THICKNESS;
- }
- S32 line_end = line_endings.front();
- line_endings.pop();
- gl_rect_2d(selection_left_x + margin_offset,
- selection_left_y + line_height + 1,
- line_end + margin_offset, selection_left_y);
- S32 line_num = left_line_num + 1;
- while (line_endings.size())
- {
- S32 vert_offset = -(line_num - left_line_num) * line_height;
- // Draw the block between the two lines
- gl_rect_2d(mTextRect.mLeft + margin_offset,
- selection_left_y + vert_offset + line_height + 1,
- line_endings.front() + margin_offset,
- selection_left_y + vert_offset);
- line_endings.pop();
- ++line_num;
- }
- // Draw from the start of the last line to selection end
- if (mTextRect.mLeft == selection_right_x)
- {
- selection_right_x += CURSOR_THICKNESS;
- }
- gl_rect_2d(mTextRect.mLeft + margin_offset,
- selection_right_y + line_height + 1,
- selection_right_x + margin_offset,
- selection_right_y);
- }
- }
- }
- }
- void LLTextEditor::drawMisspelled()
- {
- LL_FAST_TIMER(FTM_RENDER_SPELLCHECK);
- // Do not bother checking if the text did not change in a while, and
- // fire a spell checking every second while typing only when the text
- // is under 1024 characters large.
- S32 elapsed = (S32)mSpellTimer.getElapsedTimeF32();
- S32 keystroke = (S32)mKeystrokeTimer.getElapsedTimeF32();
- if (keystroke < 2 &&
- ((getLength() < 1024 && (elapsed & 1)) || keystroke > 0))
- {
- S32 new_start_spell = getLineStart(mScrollbar->getDocPos());
- S32 new_end_spell =
- getLineStart(mScrollbar->getDocPos() + 1 +
- mScrollbar->getDocSize()-mScrollbar->getDocPosMax());
- if (mScrollbar->getDocPos() == mScrollbar->getDocPosMax())
- {
- new_end_spell = (S32)mWText.length();
- }
- if (new_start_spell != mSpellCheckStart ||
- new_end_spell != mSpellCheckEnd || isSpellDirty())
- {
- mSpellCheckStart = new_start_spell;
- mSpellCheckEnd = new_end_spell;
- resetSpellDirty();
- mMisspellLocations = getMisspelledWordsPositions();
- }
- }
- if (mShowMisspelled)
- {
- const LLWString& text = mWText;
- const S32 text_len = getLength();
- const S32 num_lines = getLineCount();
- const F32 line_height = mGLFont->getLineHeight();
- const S32 start_search_pos = mScrollbar->getDocPos();
- // Skip through the lines we are not drawing.
- if (start_search_pos >= num_lines)
- {
- return;
- }
- const S32 start_line_start = getLineStart(start_search_pos);
- const F32 start_text_y = (F32)mTextRect.mTop - line_height;
- const S32 misspells = (S32)mMisspellLocations.size();
- bool found_first_visible = false;
- bool visible;
- for (S32 i = 0; i < misspells; ++i)
- {
- S32 wstart = mMisspellLocations[i++];
- S32 wend = mMisspellLocations[i];
- S32 search_pos = start_search_pos;
- S32 line_start = start_line_start;
- F32 text_y = start_text_y;
- F32 word_left = 0.f;
- F32 word_right = 0.f;
- S32 line_end = 0;
- // Determine if the word is visible and if so at what coordinates
- while (mTextRect.mBottom <= ll_round(text_y) &&
- search_pos < num_lines)
- {
- line_end = text_len + 1;
- S32 next_line = -1;
- visible = false;
- if (search_pos + 1 < num_lines)
- {
- next_line = getLineStart(search_pos + 1);
- line_end = next_line - 1;
- }
- const llwchar* line = text.c_str() + line_start;
- // Find the cursor and selection bounds
- if (line_start <= wstart && wend <= line_end)
- {
- visible = true;
- word_left = (F32)mTextRect.mLeft - 1.f +
- mGLFont->getWidthF32(line, 0,
- wstart - line_start,
- mAllowEmbeddedItems);
- word_right = (F32)mTextRect.mLeft + 1.f +
- mGLFont->getWidthF32(line, 0,
- wend - line_start,
- mAllowEmbeddedItems);
- // Draw the zig zag line
- gGL.color4ub(255, 0, 0, 200);
- while (word_left < word_right)
- {
- gl_line_2d((S32)word_left, (S32)text_y - 2,
- (S32)word_left + 3, (S32)text_y + 1);
- gl_line_2d((S32)word_left + 3, (S32)text_y + 1,
- (S32)word_left + 6, (S32)text_y - 2);
- word_left += 6;
- }
- break;
- }
- if (visible && !found_first_visible)
- {
- found_first_visible = true;
- }
- else if (!visible && found_first_visible)
- {
- // We found the last visible misspelled word. Stop now.
- return;
- }
- // move down one line
- text_y -= line_height;
- line_start = next_line;
- ++search_pos;
- }
- if (mShowLineNumbers)
- {
- word_left += UI_TEXTEDITOR_LINE_NUMBER_MARGIN;
- word_right += UI_TEXTEDITOR_LINE_NUMBER_MARGIN;
- }
- }
- }
- }
- void LLTextEditor::drawCursor()
- {
- if (gFocusMgr.getKeyboardFocus() == this && gShowTextEditCursor &&
- !mReadOnly)
- {
- const LLWString& text = mWText;
- const S32 text_len = getLength();
- // Skip through the lines we are not drawing.
- S32 cur_pos = mScrollbar->getDocPos();
- S32 num_lines = getLineCount();
- if (cur_pos >= num_lines)
- {
- return;
- }
- S32 line_start = getLineStart(cur_pos);
- F32 line_height = mGLFont->getLineHeight();
- F32 text_y = (F32)(mTextRect.mTop) - line_height;
- F32 cursor_left = 0.f;
- F32 next_char_left = 0.f;
- F32 cursor_bottom = 0.f;
- bool cursor_visible = false;
- S32 line_end = 0;
- // Determine if the cursor is visible and if so at what coordinates
- while (mTextRect.mBottom <= ll_round(text_y) && cur_pos < num_lines)
- {
- line_end = text_len + 1;
- S32 next_line = -1;
- if (cur_pos + 1 < num_lines)
- {
- next_line = getLineStart(cur_pos + 1);
- line_end = next_line - 1;
- }
- const llwchar* line = text.c_str() + line_start;
- // Find the cursor and selection bounds
- if (line_start <= mCursorPos && mCursorPos <= line_end)
- {
- cursor_visible = true;
- next_char_left = (F32)mTextRect.mLeft +
- mGLFont->getWidthF32(line, 0,
- mCursorPos - line_start,
- mAllowEmbeddedItems);
- cursor_left = next_char_left - 1.f;
- cursor_bottom = text_y;
- break;
- }
- // Move down one line
- text_y -= line_height;
- line_start = next_line;
- ++cur_pos;
- }
- if (mShowLineNumbers)
- {
- cursor_left += UI_TEXTEDITOR_LINE_NUMBER_MARGIN;
- }
- // Draw the cursor
- if (cursor_visible)
- {
- // Flash the cursor every half second starting a fixed time after
- // the last keystroke
- F32 elapsed = mKeystrokeTimer.getElapsedTimeF32();
- if (elapsed < CURSOR_FLASH_DELAY || (S32(elapsed * 2) & 1))
- {
- F32 cursor_top = cursor_bottom + line_height + 1.f;
- F32 cursor_right = cursor_left + (F32)CURSOR_THICKNESS;
- if (gKeyboardp &&
- gKeyboardp->getInsertMode() == LL_KIM_OVERWRITE &&
- !hasSelection())
- {
- cursor_left += CURSOR_THICKNESS;
- const LLWString space(utf8str_to_wstring(" "));
- F32 spacew = mGLFont->getWidthF32(space.c_str());
- if (mCursorPos == line_end)
- {
- cursor_right = cursor_left + spacew;
- }
- else
- {
- F32 width = mGLFont->getWidthF32(text.c_str(),
- mCursorPos, 1,
- mAllowEmbeddedItems);
- cursor_right = cursor_left + llmax(spacew, width);
- }
- }
- gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
- gGL.color4fv(mCursorColor.mV);
- gl_rect_2d(llfloor(cursor_left), llfloor(cursor_top),
- llfloor(cursor_right), llfloor(cursor_bottom));
- if (gKeyboardp &&
- gKeyboardp->getInsertMode() == LL_KIM_OVERWRITE &&
- !hasSelection() && text[mCursorPos] != '\n')
- {
- const LLTextSegment* segmentp =
- getSegmentAtOffset(mCursorPos);
- LLColor4 text_color;
- if (segmentp)
- {
- text_color = segmentp->getColor();
- }
- else if (mReadOnly)
- {
- text_color = mReadOnlyFgColor;
- }
- else
- {
- text_color = mFgColor;
- }
- mGLFont->render(text, mCursorPos, next_char_left,
- cursor_bottom + line_height,
- LLColor4(1.f - text_color.mV[VRED],
- 1.f - text_color.mV[VGREEN],
- 1.f - text_color.mV[VBLUE], 1.f),
- LLFontGL::LEFT, LLFontGL::TOP,
- LLFontGL::NORMAL, 1);
- }
- // Make sure the IME is in the right place
- LLRect screen_pos = getScreenRect();
- LLCoordGL ime_pos(screen_pos.mLeft + llfloor(cursor_left),
- screen_pos.mBottom + llfloor(cursor_top));
- ime_pos.mX = (S32) (ime_pos.mX * LLUI::sGLScaleFactor.mV[VX]);
- ime_pos.mY = (S32) (ime_pos.mY * LLUI::sGLScaleFactor.mV[VY]);
- gWindowp->setLanguageTextInput(ime_pos);
- }
- }
- }
- }
- void LLTextEditor::drawPreeditMarker()
- {
- if (!hasPreeditString())
- {
- return;
- }
- const llwchar* text = mWText.c_str();
- const S32 text_len = getLength();
- const S32 num_lines = getLineCount();
- S32 cur_line = mScrollbar->getDocPos();
- if (cur_line >= num_lines)
- {
- return;
- }
- const S32 line_height = ll_roundp(mGLFont->getLineHeight());
- S32 line_start = getLineStart(cur_line);
- S32 line_y = mTextRect.mTop - line_height;
- while (mTextRect.mBottom <= line_y && num_lines > cur_line)
- {
- S32 next_start = -1;
- S32 line_end = text_len;
- if (cur_line + 1 < num_lines)
- {
- next_start = getLineStart(cur_line + 1);
- line_end = next_start;
- }
- if (text[line_end-1] == '\n')
- {
- --line_end;
- }
- // Does this line contain preedits?
- if (line_start >= mPreeditPositions.back())
- {
- // We have passed the preedits.
- break;
- }
- if (line_end > mPreeditPositions.front())
- {
- for (U32 i = 0, count = mPreeditStandouts.size(); i < count; ++i)
- {
- S32 left = mPreeditPositions[i];
- S32 right = mPreeditPositions[i + 1];
- if (right <= line_start || left >= line_end)
- {
- continue;
- }
- S32 preedit_left = mTextRect.mLeft;
- if (left > line_start)
- {
- preedit_left += mGLFont->getWidth(text, line_start,
- left - line_start,
- mAllowEmbeddedItems);
- }
- S32 preedit_right = mTextRect.mLeft;
- if (right < line_end)
- {
- preedit_right += mGLFont->getWidth(text, line_start,
- right - line_start,
- mAllowEmbeddedItems);
- }
- else
- {
- preedit_right += mGLFont->getWidth(text, line_start,
- line_end - line_start,
- mAllowEmbeddedItems);
- }
- if (mPreeditStandouts[i])
- {
- gl_rect_2d(preedit_left + PREEDIT_STANDOUT_GAP,
- line_y + PREEDIT_STANDOUT_POSITION,
- preedit_right - PREEDIT_STANDOUT_GAP - 1,
- line_y + PREEDIT_STANDOUT_POSITION -
- PREEDIT_STANDOUT_THICKNESS,
- (mCursorColor * PREEDIT_STANDOUT_BRIGHTNESS +
- mWriteableBgColor *
- (1 - PREEDIT_STANDOUT_BRIGHTNESS)).setAlpha(1.f));
- }
- else
- {
- gl_rect_2d(preedit_left + PREEDIT_MARKER_GAP,
- line_y + PREEDIT_MARKER_POSITION,
- preedit_right - PREEDIT_MARKER_GAP - 1,
- line_y + PREEDIT_MARKER_POSITION -
- PREEDIT_MARKER_THICKNESS,
- (mCursorColor * PREEDIT_MARKER_BRIGHTNESS +
- mWriteableBgColor *
- (1 - PREEDIT_MARKER_BRIGHTNESS)).setAlpha(1.f));
- }
- }
- }
- // Move down one line
- line_y -= line_height;
- line_start = next_start;
- ++cur_line;
- }
- }
- void LLTextEditor::drawText()
- {
- const LLWString& text = mWText;
- const S32 text_len = getLength();
- if (text_len <= 0) return;
- S32 selection_left = -1;
- S32 selection_right = -1;
- // Draw selection even if we do not have keyboard focus for search/replace
- if (hasSelection())
- {
- selection_left = llmin(mSelectionStart, mSelectionEnd);
- selection_right = llmax(mSelectionStart, mSelectionEnd);
- }
- LLGLSUIDefault gls_ui;
- // There are several concepts that are important for understanding the
- // following drawing code.
- // The document is logically a sequence of characters (stored in a
- // LLWString).
- // Variables below with "start" or "end" in their names refer to positions
- // or offsets into it.
- // Next there are two kinds of "line" variables to understand. Newline
- // characters in the character sequence represent logical lines. These are
- // what get numbered and so variables representing this kind of line have
- // "num" in their names.
- // The others represent line fragments or displayed lines which the
- // scrollbar deals with.
- // When the "show line numbers" property is turned on, we draw line numbers
- // to the left of the beginning of each logical line and not in front of
- // wrapped "continuation" display lines. -MG
- // Scrollbar counts each wrap as a new line.
- S32 cur_line = mScrollbar->getDocPos();
- S32 num_lines = getLineCount();
- if (cur_line >= num_lines) return;
- S32 line_start = getLineStart(cur_line);
- S32 prev_start = getLineStart(cur_line - 1);
- // Does not count wraps. i.e. only counts newlines.
- S32 cur_line_num = getLineForPosition(line_start);
- S32 prev_line_num = getLineForPosition(prev_start);
- bool cur_line_is_continuation = cur_line_num > 0 &&
- cur_line_num == prev_line_num;
- bool line_wraps = false;
- LLTextSegment t(line_start);
- segment_list_t::iterator seg_iter;
- seg_iter = std::upper_bound(mSegments.begin(), mSegments.end(), &t,
- LLTextSegment::compare());
- if (seg_iter == mSegments.end() || (*seg_iter)->getStart() > line_start)
- {
- --seg_iter;
- }
- LLTextSegment* cur_segment = *seg_iter;
- S32 line_height = ll_roundp(mGLFont->getLineHeight());
- F32 text_y = (F32)(mTextRect.mTop - line_height);
- while (mTextRect.mBottom <= text_y && cur_line < num_lines)
- {
- S32 next_start = -1;
- S32 line_end = text_len;
- if (cur_line + 1 < num_lines)
- {
- next_start = getLineStart(cur_line + 1);
- line_end = next_start;
- }
- line_wraps = text[line_end - 1] != '\n';
- if (!line_wraps)
- {
- --line_end; // do not attempt to draw the newline char.
- }
- F32 text_start = (F32)mTextRect.mLeft;
- F32 text_x = text_start +
- (mShowLineNumbers ? UI_TEXTEDITOR_LINE_NUMBER_MARGIN : 0);
- // Draw the line numbers
- if (mShowLineNumbers && !cur_line_is_continuation)
- {
- const LLFontGL* num_font = LLFontGL::getFontMonospace();
- F32 y_top = text_y +
- (F32)ll_roundp(num_font->getLineHeight()) * 0.5f;
- const LLWString ltext =
- utf8str_to_wstring(llformat("%*d",
- UI_TEXTEDITOR_LINE_NUMBER_DIGITS,
- cur_line_num));
- bool is_cur_line = getCurrentLine() == cur_line_num;
- const U8 style = is_cur_line ? LLFontGL::BOLD : LLFontGL::NORMAL;
- const LLColor4 fg_color = is_cur_line ? mCursorColor
- : mReadOnlyFgColor;
- num_font->render(ltext, // string to draw
- 0, // begin offset
- 3., // x
- y_top, // y
- fg_color,
- LLFontGL::LEFT, // horizontal alignment
- LLFontGL::VCENTER, // vertical alignment
- style,
- S32_MAX, // max chars
- UI_TEXTEDITOR_LINE_NUMBER_MARGIN); // max pixels
- }
- S32 seg_start = line_start;
- while (seg_start < line_end)
- {
- while (cur_segment->getEnd() <= seg_start)
- {
- ++seg_iter;
- if (seg_iter == mSegments.end())
- {
- llwarns << "Ran off the segmentation end !" << llendl;
- return;
- }
- cur_segment = *seg_iter;
- }
- // Draw a segment within the line
- S32 clipped_end = llmin(line_end, cur_segment->getEnd());
- S32 clipped_len = clipped_end - seg_start;
- if (clipped_len > 0)
- {
- LLStyleSP style = cur_segment->getStyle();
- if (style->isImage() && cur_segment->getStart() >= seg_start &&
- cur_segment->getStart() <= clipped_end)
- {
- S32 style_image_height = style->mImageHeight;
- S32 style_image_width = style->mImageWidth;
- LLUIImagePtr image = style->getImage();
- image->draw(ll_round(text_x),
- ll_round(text_y) +
- line_height-style_image_height,
- style_image_width, style_image_height);
- }
- bool is_embedded = cur_segment == mHoverSegment &&
- style->getIsEmbeddedItem();
- if (is_embedded)
- {
- style->mUnderline = true;
- is_embedded = true;
- }
- S32 left_pos = llmin(mSelectionStart, mSelectionEnd);
- if (!is_embedded && mParseHTML && left_pos > seg_start &&
- left_pos < clipped_end && mIsSelecting &&
- mSelectionStart == mSelectionEnd)
- {
- mHTML = style->getLinkHREF();
- }
- drawClippedSegment(text, seg_start, clipped_end, text_x,
- text_y, selection_left, selection_right,
- style, &text_x);
- if (text_x == text_start && mShowLineNumbers)
- {
- text_x += UI_TEXTEDITOR_LINE_NUMBER_MARGIN;
- }
- // Note: text_x is incremented by drawClippedSegment()
- seg_start += clipped_len;
- }
- }
- // Move down one line
- text_y -= (F32)line_height;
- if (line_wraps)
- {
- --cur_line_num;
- }
- // So as to not not number the continuation lines
- cur_line_is_continuation = line_wraps;
- line_start = next_start;
- ++cur_line;
- ++cur_line_num;
- }
- }
- // Draws a single text segment, reversing the color for selection if needed.
- void LLTextEditor::drawClippedSegment(const LLWString& text, S32 seg_start,
- S32 seg_end, F32 x, F32 y,
- S32 selection_left, S32 selection_right,
- const LLStyleSP& style, F32* right_x)
- {
- if (!style->isVisible())
- {
- return;
- }
- const LLFontGL* font = mGLFont;
- LLColor4 color = style->getColor();
- if (style->getFontString()[0])
- {
- font = LLFontGL::getFont(style->getFontID());
- }
- U8 font_flags = LLFontGL::NORMAL;
- if (style->mBold)
- {
- font_flags |= LLFontGL::BOLD;
- }
- if (style->mItalic)
- {
- font_flags |= LLFontGL::ITALIC;
- }
- if (style->mUnderline)
- {
- font_flags |= LLFontGL::UNDERLINE;
- }
- if (style->getIsEmbeddedItem())
- {
- color = mReadOnly ? LLUI::sTextEmbeddedItemReadOnlyColor
- : LLUI::sTextEmbeddedItemColor;
- }
- F32 y_top = y + (F32)ll_roundp(font->getLineHeight());
- bool use_embedded = mAllowEmbeddedItems && style->getIsEmbeddedItem();
- if (selection_left > seg_start)
- {
- // Draw normally
- S32 start = seg_start;
- S32 end = llmin(selection_left, seg_end);
- S32 length = end - start;
- font->render(text, start, x, y_top, color, LLFontGL::LEFT,
- LLFontGL::TOP, font_flags, length, S32_MAX,
- right_x, use_embedded);
- }
- x = *right_x;
- if (selection_left < seg_end && selection_right > seg_start)
- {
- // Draw reversed
- S32 start = llmax(selection_left, seg_start);
- S32 end = llmin(selection_right, seg_end);
- S32 length = end - start;
- font->render(text, start, x, y_top,
- LLColor4(1.f - color.mV[0],
- 1.f - color.mV[1],
- 1.f - color.mV[2], 1.f),
- LLFontGL::LEFT, LLFontGL::TOP, font_flags, length,
- S32_MAX, right_x, use_embedded);
- }
- x = *right_x;
- if (selection_right < seg_end)
- {
- // Draw normally
- S32 start = llmax(selection_right, seg_start);
- S32 end = seg_end;
- S32 length = end - start;
- font->render(text, start, x, y_top, color, LLFontGL::LEFT,
- LLFontGL::TOP, font_flags, length, S32_MAX, right_x,
- use_embedded);
- }
- }
- void LLTextEditor::draw()
- {
- // Do on-demand reflow
- if (mReflowNeeded)
- {
- updateLineStartList();
- mReflowNeeded = false;
- }
- // Then update scroll position, as cursor may have moved
- if (mScrollNeeded)
- {
- updateScrollFromCursor();
- mScrollNeeded = false;
- }
- {
- LLLocalClipRect clip(LLRect(0, getRect().getHeight(),
- getRect().getWidth() -
- (mScrollbar->getVisible() ? SCROLLBAR_SIZE : 0),
- 0));
- bindEmbeddedChars(mGLFont);
- drawBackground();
- drawSelectionBackground();
- drawPreeditMarker();
- drawText();
- drawCursor();
- if (!mReadOnly && mSpellCheck && hasFocus() &&
- LLSpellCheck::getInstance()->getSpellCheck())
- {
- drawMisspelled();
- }
- unbindEmbeddedChars(mGLFont);
- // RN: the decision was made to always show the orange border for
- // keyboard focus but do not put an insertion caret when in readonly
- // mode
- mBorder->setKeyboardFocusHighlight(gFocusMgr.getKeyboardFocus() == this
- /*&& !mReadOnly*/);
- }
- LLView::draw(); // Draw children (scrollbar and border)
- // Remember if we are supposed to be at the bottom of the buffer
- mScrolledToBottom = isScrolledToBottom();
- }
- void LLTextEditor::onTabInto()
- {
- #if 0
- // Selecting all on tabInto causes users to hit tab twice and replace their
- // text with a tab character theoretically, one could selectAll if
- // mTabsToNextField is true, but we could not think of a use case where
- // you would want to select all anyway preserve insertion point when
- // returning to the editor
- selectAll();
- #endif
- }
- //virtual
- void LLTextEditor::clear()
- {
- setText(LLStringUtil::null);
- std::for_each(mSegments.begin(), mSegments.end(), DeletePointer());
- mSegments.clear();
- }
- // Start or stop the editor from accepting text-editing keystrokes
- // see also LLLineEditor
- void LLTextEditor::setFocus(bool new_state)
- {
- bool old_state = hasFocus();
- // Do not change anything if the focus state did not change
- if (new_state == old_state) return;
- // Notify early if we are losing focus.
- if (!new_state)
- {
- gWindowp->allowLanguageTextInput(this, false);
- }
- LLUICtrl::setFocus(new_state);
- if (new_state)
- {
- // Route menu to this class
- grabMenuHandler();
- // Do not start the cursor flashing right away
- resetKeystrokeTimer();
- }
- else
- {
- // Route menu back to the default
- releaseMenuHandler();
- endSelection();
- }
- }
- // Given a line (from the start of the doc) and an offset into the line, find
- // the offset (pos) into text.
- S32 LLTextEditor::getPos(S32 line, S32 offset)
- {
- S32 line_start = getLineStart(line);
- S32 next_start = getLineStart(line+1);
- if (next_start == line_start)
- {
- next_start = getLength() + 1;
- }
- S32 line_length = next_start - line_start - 1;
- line_length = llmax(line_length, 0);
- return line_start + llmin(offset, line_length);
- }
- void LLTextEditor::changePage(S32 delta)
- {
- S32 line, offset;
- getLineAndOffset(mCursorPos, &line, &offset);
- // Get desired x position to remember previous position
- S32 desired_x_pixel = mDesiredXPixel;
- // Allow one line overlap
- S32 page_size = mScrollbar->getPageSize() - 1;
- if (delta == -1)
- {
- line = llmax(line - page_size, 0);
- setCursorPos(getPos(line, offset));
- mScrollbar->setDocPos(mScrollbar->getDocPos() - page_size);
- }
- else
- if (delta == 1)
- {
- setCursorPos(getPos(line + page_size, offset));
- mScrollbar->setDocPos(mScrollbar->getDocPos() + page_size);
- }
- // Put desired position into remember-buffer after setCursorPos()
- mDesiredXPixel = desired_x_pixel;
- if (mOnScrollEndCallback && mOnScrollEndData &&
- mScrollbar->getDocPos() == mScrollbar->getDocPosMax())
- {
- mOnScrollEndCallback(mOnScrollEndData);
- }
- }
- void LLTextEditor::changeLine(S32 delta)
- {
- bindEmbeddedChars(mGLFont);
- S32 line, offset;
- getLineAndOffset(mCursorPos, &line, &offset);
- S32 line_start = getLineStart(line);
- // Set desired x position to remembered previous position
- S32 desired_x_pixel = mDesiredXPixel;
- // If remembered position was reset (thus -1), calculate new one here
- if (desired_x_pixel == -1)
- {
- desired_x_pixel = mGLFont->getWidth(mWText.c_str(), line_start, offset,
- mAllowEmbeddedItems);
- }
- S32 new_line = 0;
- if (delta < 0 && line > 0)
- {
- new_line = line - 1;
- }
- else
- if (delta > 0 && line < getLineCount() - 1)
- {
- new_line = line + 1;
- }
- else
- {
- unbindEmbeddedChars(mGLFont);
- return;
- }
- S32 num_lines = getLineCount();
- S32 new_line_start = getLineStart(new_line);
- S32 new_line_end = getLength();
- if (new_line + 1 < num_lines)
- {
- new_line_end = getLineStart(new_line + 1) - 1;
- }
- S32 new_line_len = new_line_end - new_line_start;
- S32 new_offset =
- mGLFont->charFromPixelOffset(mWText.c_str(), new_line_start,
- (F32)desired_x_pixel,
- (F32)mTextRect.getWidth(),
- new_line_len, mAllowEmbeddedItems);
- setCursorPos (getPos(new_line, new_offset));
- // Put desired position into remember-buffer after setCursorPos()
- mDesiredXPixel = desired_x_pixel;
- unbindEmbeddedChars(mGLFont);
- }
- bool LLTextEditor::isScrolledToTop()
- {
- return mScrollbar->isAtBeginning();
- }
- bool LLTextEditor::isScrolledToBottom()
- {
- return mScrollbar->isAtEnd();
- }
- void LLTextEditor::startOfLine()
- {
- S32 line, offset;
- getLineAndOffset(mCursorPos, &line, &offset);
- setCursorPos(mCursorPos - offset);
- }
- void LLTextEditor::setCursorAndScrollToEnd()
- {
- deselect();
- endOfDoc();
- needsReflow();
- }
- void LLTextEditor::scrollToPos(S32 pos)
- {
- mScrollbar->setDocSize(getLineCount());
- S32 line, offset;
- getLineAndOffset(pos, &line, &offset);
- S32 page_size = mScrollbar->getPageSize();
- if (line < mScrollbar->getDocPos())
- {
- // Scroll so that the cursor is at the top of the page
- mScrollbar->setDocPos(line);
- }
- else if (line >= mScrollbar->getDocPos() + page_size - 1)
- {
- S32 new_pos = 0;
- if (line < mScrollbar->getDocSize() - 1)
- {
- // Scroll so that the cursor is one line above the bottom of the
- // page
- new_pos = line - page_size + 1;
- }
- else
- {
- // If there is less than a page of text remaining, scroll so that
- // the cursor is at the bottom
- new_pos = mScrollbar->getDocPosMax();
- }
- mScrollbar->setDocPos(new_pos);
- }
- // Check if we have scrolled to bottom for callback if asked for callback
- if (mOnScrollEndCallback && mOnScrollEndData &&
- mScrollbar->getDocPos() == mScrollbar->getDocPosMax())
- {
- mOnScrollEndCallback(mOnScrollEndData);
- }
- }
- void LLTextEditor::getLineAndColumnForPosition(S32 position, S32* line,
- S32* col, bool include_wordwrap)
- {
- if (include_wordwrap)
- {
- getLineAndOffset(mCursorPos, line, col);
- }
- else
- {
- const LLWString& text = mWText;
- S32 line_count = 0;
- S32 line_start = 0;
- S32 i;
- for (i = 0; text[i] && i < position; ++i)
- {
- if ('\n' == text[i])
- {
- line_start = i + 1;
- ++line_count;
- }
- }
- *line = line_count;
- *col = i - line_start;
- }
- }
- void LLTextEditor::getCurrentLineAndColumn(S32* line, S32* col,
- bool include_wordwrap)
- {
- getLineAndColumnForPosition(mCursorPos, line, col, include_wordwrap);
- }
- S32 LLTextEditor::getCurrentLine()
- {
- return getLineForPosition(mCursorPos);
- }
- S32 LLTextEditor::getLineForPosition(S32 position)
- {
- S32 line, col;
- getLineAndColumnForPosition(position, &line, &col, false);
- return line;
- }
- void LLTextEditor::endOfLine()
- {
- S32 line, offset;
- getLineAndOffset(mCursorPos, &line, &offset);
- S32 num_lines = getLineCount();
- if (line + 1 >= num_lines)
- {
- setCursorPos(getLength());
- }
- else
- {
- setCursorPos(getLineStart(line + 1) - 1);
- }
- }
- void LLTextEditor::endOfDoc()
- {
- mScrollbar->setDocPos(mScrollbar->getDocPosMax());
- mScrolledToBottom = true;
- S32 len = getLength();
- if (len)
- {
- setCursorPos(len);
- }
- if (mOnScrollEndCallback && mOnScrollEndData &&
- mScrollbar->getDocPos() == mScrollbar->getDocPosMax())
- {
- mOnScrollEndCallback(mOnScrollEndData);
- }
- }
- // Sets the scrollbar from the cursor position
- void LLTextEditor::updateScrollFromCursor()
- {
- if (mReadOnly)
- {
- // No cursor in read only mode
- return;
- }
- scrollToPos(mCursorPos);
- }
- void LLTextEditor::reshape(S32 width, S32 height, bool called_from_parent)
- {
- LLView::reshape(width, height, called_from_parent);
- // Do this first after reshape, because other things depend on up-to-date
- // mTextRect
- updateTextRect();
- needsReflow();
- // Propagate shape information to scrollbar
- mScrollbar->setDocSize(getLineCount());
- S32 line_height = ll_roundp(mGLFont->getLineHeight());
- S32 page_lines = mTextRect.getHeight() / line_height;
- mScrollbar->setPageSize(page_lines);
- }
- void LLTextEditor::autoIndent()
- {
- // Count the number of spaces in the current line
- S32 line, offset;
- getLineAndOffset(mCursorPos, &line, &offset);
- S32 line_start = getLineStart(line);
- S32 space_count = 0;
- S32 i;
- const LLWString& text = mWText;
- while (' ' == text[line_start])
- {
- ++space_count;
- ++line_start;
- }
- // If we are starting a braced section, indent one level.
- if (mCursorPos > 0 && text[mCursorPos - 1] == '{')
- {
- space_count += SPACES_PER_TAB;
- }
- // Insert that number of spaces on the new line
- addChar('\n');
- for (i = 0; i < space_count; ++i)
- {
- addChar(' ');
- }
- }
- // Inserts new text at the cursor position
- void LLTextEditor::insertText(const std::string& new_text)
- {
- bool enabled = getEnabled();
- setEnabled(true);
- // Delete any selected characters (the insertion replaces them)
- if (hasSelection())
- {
- deleteSelection(true);
- }
- setCursorPos(mCursorPos + insert(mCursorPos, utf8str_to_wstring(new_text),
- false));
- needsReflow();
- setEnabled(enabled);
- }
- void LLTextEditor::appendColoredText(const std::string& new_text,
- bool allow_undo, bool prepend_newline,
- const LLColor4& color,
- const std::string& font_name)
- {
- LLStyleSP style(new LLStyle);
- style->setVisible(true);
- style->setColor(color);
- style->setFontName(font_name);
- appendStyledText(new_text, allow_undo, prepend_newline, style);
- }
- void LLTextEditor::appendStyledText(const std::string& new_text,
- bool allow_undo,
- bool prepend_newline,
- LLStyleSP stylep)
- {
- S32 part = (S32)WHOLE;
- if (mParseHTML)
- {
- S32 start = 0, end = 0;
- std::string text = new_text;
- while (findHTML(text, &start, &end))
- {
- LLStyleSP html(new LLStyle);
- html->setVisible(true);
- html->setColor(mLinkColor);
- if (stylep)
- {
- html->setFontName(stylep->getFontString());
- }
- html->mUnderline = true;
- if (start > 0)
- {
- if (part == (S32)WHOLE || part == (S32)START)
- {
- part = (S32)START;
- }
- else
- {
- part = (S32)MIDDLE;
- }
- std::string subtext = text.substr(0, start);
- appendText(subtext, allow_undo, prepend_newline, stylep);
- }
- html->setLinkHREF(text.substr(start, end - start));
- appendText(text.substr(start, end - start), allow_undo,
- prepend_newline, html);
- if (end < (S32)text.length())
- {
- text = text.substr(end, text.length() - end);
- end = 0;
- part = (S32)END;
- }
- else
- {
- break;
- }
- }
- if (part != (S32)WHOLE)
- {
- part = (S32)END;
- }
- if (end < (S32)text.length())
- {
- appendText(text, allow_undo, prepend_newline, stylep);
- }
- }
- else
- {
- appendText(new_text, allow_undo, prepend_newline, stylep);
- }
- }
- // Appends new text to end of document
- void LLTextEditor::appendText(const std::string& new_text, bool allow_undo,
- bool prepend_newline, const LLStyleSP stylep)
- {
- // Save old state
- bool was_scrolled_to_bottom =
- mScrollbar->getDocPos() == mScrollbar->getDocPosMax();
- S32 selection_start = mSelectionStart;
- S32 selection_end = mSelectionEnd;
- bool was_selecting = mIsSelecting;
- S32 cursor_pos = mCursorPos;
- S32 old_length = getLength();
- bool cursor_was_at_end = mCursorPos == old_length;
- deselect();
- setCursorPos(old_length);
- // Add carriage return if not first line
- if (getLength() != 0 && prepend_newline)
- {
- std::string final_text = "\n";
- final_text += new_text;
- append(utf8str_to_wstring(final_text), true);
- }
- else
- {
- append(utf8str_to_wstring(new_text), true);
- }
- if (stylep)
- {
- S32 segment_start = old_length;
- S32 segment_end = getLength();
- LLTextSegment* segment = new LLTextSegment(stylep, segment_start,
- segment_end);
- mSegments.push_back(segment);
- }
- needsReflow();
- // Set the cursor and scroll position
- // Maintain the scroll position unless the scroll was at the end of the doc
- // (in which case, move it to the new end of the doc) or unless the user
- // was doing actively selecting
- if (was_scrolled_to_bottom && !was_selecting)
- {
- if (selection_start != selection_end)
- {
- // maintain an existing non-active selection
- mSelectionStart = selection_start;
- mSelectionEnd = selection_end;
- }
- endOfDoc();
- }
- else if (selection_start != selection_end)
- {
- mSelectionStart = selection_start;
- mSelectionEnd = selection_end;
- mIsSelecting = was_selecting;
- setCursorPos(cursor_pos);
- }
- else if (cursor_was_at_end)
- {
- setCursorPos(getLength());
- }
- else
- {
- setCursorPos(cursor_pos);
- }
- if (!allow_undo)
- {
- blockUndo();
- }
- }
- S32 LLTextEditor::removeFirstLine()
- {
- S32 num_lines = getLineCount();
- if (!num_lines)
- {
- return 0;
- }
- S32 length = getLineStart(1) - 1;
- if (length <= 0)
- {
- length = getLength();
- }
- deselect();
- removeStringNoUndo(0, length);
- pruneSegments();
- updateLineStartList();
- needsScroll();
- return length;
- }
- void LLTextEditor::removeTextFromEnd(S32 num_chars)
- {
- if (num_chars <= 0) return;
- num_chars = llclamp(num_chars, 0, getLength());
- remove(getLength() - num_chars, num_chars, false);
- S32 len = getLength();
- mCursorPos = llclamp(mCursorPos, 0, len);
- mSelectionStart = llclamp(mSelectionStart, 0, len);
- mSelectionEnd = llclamp(mSelectionEnd, 0, len);
- pruneSegments();
- // pruneSegments will invalidate mLineStartList.
- updateLineStartList();
- needsScroll();
- }
- ///////////////////////////////////////////////////////////////////
- // Returns change in number of characters in mWText
- S32 LLTextEditor::insertStringNoUndo(S32 pos, const LLWString& wstr)
- {
- S32 old_len = mWText.length(); // length() returns character length
- S32 insert_len = wstr.length();
- mWText.insert(pos, wstr);
- mTextIsUpToDate = false;
- if (truncate())
- {
- // The user's not getting everything he's hoping for
- make_ui_sound("UISndBadKeystroke");
- insert_len = mWText.length() - old_len;
- }
- return insert_len;
- }
- S32 LLTextEditor::removeStringNoUndo(S32 pos, S32 length)
- {
- mWText.erase(pos, length);
- mTextIsUpToDate = false;
- // This will be wrong if someone calls removeStringNoUndo with an excessive
- // length
- return -length;
- }
- S32 LLTextEditor::overwriteCharNoUndo(S32 pos, llwchar wc)
- {
- if (pos > (S32)mWText.length())
- {
- return 0;
- }
- mWText[pos] = wc;
- mTextIsUpToDate = false;
- return 1;
- }
- void LLTextEditor::makePristine()
- {
- mPristineCmd = mLastCmd;
- mBaseDocIsPristine = !mLastCmd;
- // Create a clean partition in the undo stack. We do not want a single
- // command to extend from the "pre-pristine" state to the "post-pristine"
- // state.
- if (mLastCmd)
- {
- mLastCmd->blockExtensions();
- }
- }
- bool LLTextEditor::isPristine() const
- {
- if (mPristineCmd)
- {
- return mPristineCmd == mLastCmd;
- }
- // No undo stack, so check if the version before and commands were done was
- // the original version
- return !mLastCmd && mBaseDocIsPristine;
- }
- bool LLTextEditor::tryToRevertToPristineState()
- {
- if (!isPristine())
- {
- deselect();
- S32 i = 0;
- while (!isPristine() && canUndo())
- {
- undo();
- --i;
- }
- while (!isPristine() && canRedo())
- {
- redo();
- ++i;
- }
- if (!isPristine())
- {
- // failed, so go back to where we started
- while (i > 0)
- {
- undo();
- --i;
- }
- }
- needsReflow();
- }
- return isPristine(); // true => success
- }
- void LLTextEditor::updateTextRect()
- {
- mTextRect.setOriginAndSize(UI_TEXTEDITOR_BORDER + UI_TEXTEDITOR_H_PAD,
- UI_TEXTEDITOR_BORDER,
- getRect().getWidth() - SCROLLBAR_SIZE -
- 2 * (UI_TEXTEDITOR_BORDER +
- UI_TEXTEDITOR_H_PAD),
- getRect().getHeight() -
- 2 * UI_TEXTEDITOR_BORDER -
- UI_TEXTEDITOR_V_PAD_TOP);
- }
- void LLTextEditor::loadKeywords(const std::string& filename,
- const std::vector<std::string>& funcs,
- const std::vector<std::string>& tooltips,
- const LLColor3& color)
- {
- if (mKeywords.loadFromFile(filename))
- {
- S32 count = llmin(funcs.size(), tooltips.size());
- for (S32 i = 0; i < count; ++i)
- {
- std::string name = utf8str_trim(funcs[i]);
- mKeywords.addToken(LLKeywordToken::WORD, name, color, tooltips[i]);
- }
- mKeywords.findSegments(&mSegments, mWText, mDefaultColor);
- llassert(mSegments.front()->getStart() == 0 &&
- mSegments.back()->getEnd() == getLength());
- }
- }
- void LLTextEditor::updateSegments()
- {
- // For now, we allow keywords-based syntax highlighting (e.g. for the
- // script editor), or embedded items (e.g. for notecards and group notices
- // with inventory offers), or styled text (with colors, links, etc), the
- // latter staying untouched by updateSegments(). It is however not possible
- // to mix up the three types of text editors...
- if (mKeywords.isLoaded())
- {
- // *HACK: no non-ASCII keywords for now
- mKeywords.findSegments(&mSegments, mWText, mDefaultColor);
- }
- else if (mAllowEmbeddedItems)
- {
- findEmbeddedItemSegments();
- }
- // Make sure we have at least one segment
- if (mSegments.size() == 1 && mSegments[0]->getIsDefault())
- {
- delete mSegments[0];
- mSegments.clear(); // create default segment
- }
- if (mSegments.empty())
- {
- LLColor4& text_color = mReadOnly ? mReadOnlyFgColor : mFgColor;
- LLTextSegment* default_segment = new LLTextSegment(text_color, 0,
- mWText.length());
- default_segment->setIsDefault(true);
- mSegments.push_back(default_segment);
- }
- }
- // Only effective if text was removed from the end of the editor
- // *NOTE: Using this will invalidate references to mSegments from
- // mLineStartList.
- void LLTextEditor::pruneSegments()
- {
- S32 len = mWText.length();
- // Find and update the first valid segment
- segment_list_t::iterator segments_begin = mSegments.begin();
- segment_list_t::iterator iter = mSegments.end();
- while (iter != segments_begin)
- {
- LLTextSegment* seg = *(--iter);
- if (seg->getStart() < len)
- {
- // valid segment
- if (seg->getEnd() > len)
- {
- seg->setEnd(len);
- }
- break; // done
- }
- }
- if (iter != mSegments.end())
- {
- // erase invalid segments
- ++iter;
- std::for_each(iter, mSegments.end(), DeletePointer());
- mSegments.erase(iter, mSegments.end());
- }
- #if 0 // We do not care, and it can happen legally for
- // removeTextFromEnd(text->getMaxLength())
- else
- {
- llwarns << "Tried to erase end of empty LLTextEditor" << llendl;
- }
- #endif
- }
- void LLTextEditor::findEmbeddedItemSegments()
- {
- mHoverSegment = NULL;
- std::for_each(mSegments.begin(), mSegments.end(), DeletePointer());
- mSegments.clear();
- bool found_embedded_items = false;
- const LLWString& text = mWText;
- S32 idx = 0;
- while (text[idx])
- {
- if (text[idx] >= FIRST_EMBEDDED_CHAR &&
- text[idx] <= LAST_EMBEDDED_CHAR)
- {
- found_embedded_items = true;
- break;
- }
- ++idx;
- }
- if (!found_embedded_items)
- {
- return;
- }
- S32 text_len = text.length();
- LLColor4& text_color = mReadOnly ? mReadOnlyFgColor : mFgColor;
- bool in_text = false;
- if (idx > 0)
- {
- // text
- mSegments.push_back(new LLTextSegment(text_color, 0, text_len));
- in_text = true;
- }
- LLStyleSP embedded_style(new LLStyle);
- embedded_style->setIsEmbeddedItem(true);
- // Start with i just after the first embedded item
- while (text[idx])
- {
- if (text[idx] >= FIRST_EMBEDDED_CHAR &&
- text[idx] <= LAST_EMBEDDED_CHAR)
- {
- if (in_text)
- {
- mSegments.back()->setEnd(idx);
- }
- // item
- mSegments.push_back(new LLTextSegment(embedded_style, idx,
- idx + 1));
- in_text = false;
- }
- else if (!in_text)
- {
- // text
- mSegments.push_back(new LLTextSegment(text_color, idx, text_len));
- in_text = true;
- }
- ++idx;
- }
- }
- bool LLTextEditor::handleMouseUpOverSegment(S32 x, S32 y, MASK mask)
- {
- if (hasMouseCapture())
- {
- // This mouse up was part of a click. Regardless of where the cursor
- // is, see if we recently touched a link and launch it if we did.
- if (mParseHTML && mHTML.length() > 0)
- {
- // Special handling for slurls
- if (sSecondlifeURLcallback && !(*sSecondlifeURLcallback)(mHTML))
- {
- if (sURLcallback)
- {
- (*sURLcallback)(mHTML);
- }
- }
- mHTML.clear();
- }
- }
- return false;
- }
- // Finds the text segment (if any) at the give local screen position
- const LLTextSegment* LLTextEditor::getSegmentAtLocalPos(S32 x, S32 y) const
- {
- // Find the cursor position at the requested local screen position
- S32 offset = getCursorPosFromLocalCoord(x, y, false);
- S32 idx = getSegmentIdxAtOffset(offset);
- return idx >= 0 ? mSegments[idx] : NULL;
- }
- const LLTextSegment* LLTextEditor::getSegmentAtOffset(S32 offset) const
- {
- S32 idx = getSegmentIdxAtOffset(offset);
- return idx >= 0 ? mSegments[idx] : NULL;
- }
- S32 LLTextEditor::getSegmentIdxAtOffset(S32 offset) const
- {
- if (mSegments.empty() || offset < 0 || offset >= getLength())
- {
- return -1;
- }
- else
- {
- S32 segidx, segoff;
- getSegmentAndOffset(offset, &segidx, &segoff);
- return segidx;
- }
- }
- void LLTextEditor::onMouseCaptureLost()
- {
- endSelection();
- }
- void LLTextEditor::setOnScrollEndCallback(void (*callback)(void*),
- void* userdata)
- {
- mOnScrollEndCallback = callback;
- mOnScrollEndData = userdata;
- mScrollbar->setOnScrollEndCallback(callback, userdata);
- }
- void LLTextEditor::setKeystrokeCallback(void (*callback)(LLTextEditor*, void*),
- void* userdata)
- {
- mKeystrokeCallback = callback;
- mKeystrokeData = userdata;
- }
- void LLTextEditor::setOnHandleKeyCallback(bool (*callback)(KEY, MASK,
- LLTextEditor*,
- void*),
- void* userdata)
- {
- mOnHandleKeyCallback = callback;
- mOnHandleKeyData = userdata;
- }
- ///////////////////////////////////////////////////////////////////
- // Hack for Notecards
- bool LLTextEditor::importBuffer(const char* buffer, S32 length)
- {
- std::istringstream instream(buffer);
- // Version 1 format:
- // Linden text version 1\n
- // {\n
- // <EmbeddedItemList chunk>
- // Text length <bytes without \0>\n
- // <text without \0> (text may contain ext_char_values)
- // }\n
- char tbuf[MAX_STRING];
- S32 version = 0;
- instream.getline(tbuf, MAX_STRING);
- if (1 != sscanf(tbuf, "Linden text version %d", &version))
- {
- llwarns << "Invalid Linden text file header " << llendl;
- return false;
- }
- if (version != 1)
- {
- llwarns << "Invalid Linden text file version: " << version << llendl;
- return false;
- }
- instream.getline(tbuf, MAX_STRING);
- if (sscanf(tbuf, "{"))
- {
- llwarns << "Invalid Linden text file format" << llendl;
- return false;
- }
- S32 text_len = 0;
- instream.getline(tbuf, MAX_STRING);
- if (sscanf(tbuf, "Text length %d", &text_len) != 1)
- {
- llwarns << "Invalid Linden text length field" << llendl;
- return false;
- }
- if (text_len > mMaxTextByteLength)
- {
- llwarns << "Invalid Linden text length: " << text_len << llendl;
- return false;
- }
- char* text = new char[text_len + 1];
- if (!text)
- {
- llerrs << "Memory allocation failure." << llendl;
- return false;
- }
- bool success = true;
- instream.get(text, text_len + 1, '\0');
- text[text_len] = '\0';
- if (text_len != (S32)strlen(text))
- {
- llwarns << llformat("Invalid text length: %d != %d ",
- strlen(text), text_len) << llendl;
- success = false;
- }
- instream.getline(tbuf, MAX_STRING);
- if (success && sscanf(tbuf, "}"))
- {
- llwarns << "Invalid Linden text file format: missing terminal }"
- << llendl;
- success = false;
- }
- if (success)
- {
- // Actually set the text
- setText(text);
- }
- delete[] text;
- setCursorPos(mCursorPos);
- deselect();
- needsReflow();
- return success;
- }
- bool LLTextEditor::exportBuffer(std::string& buffer)
- {
- std::ostringstream outstream(buffer);
- outstream << "Linden text version 1\n";
- outstream << "{\n";
- outstream << llformat("Text length %d\n", mWText.length());
- outstream << getText();
- outstream << "}\n";
- return true;
- }
- //////////////////////////////////////////////////////////////////////////
- // LLTextSegment
- LLTextSegment::LLTextSegment(S32 start)
- : mStart(start),
- mEnd(0),
- mToken(NULL),
- mIsDefault(false)
- {
- }
- LLTextSegment::LLTextSegment(const LLStyleSP& style, S32 start, S32 end)
- : mStyle(style),
- mStart(start),
- mEnd(end),
- mToken(NULL),
- mIsDefault(false)
- {
- }
- LLTextSegment::LLTextSegment(const LLColor4& color, S32 start, S32 end,
- bool is_visible)
- : mStyle(new LLStyle(is_visible, color, LLStringUtil::null)),
- mStart(start),
- mEnd(end),
- mToken(NULL),
- mIsDefault(false)
- {
- }
- LLTextSegment::LLTextSegment(const LLColor4& color, S32 start, S32 end)
- : mStyle(new LLStyle(true, color, LLStringUtil::null)),
- mStart(start),
- mEnd(end),
- mToken(NULL),
- mIsDefault(false)
- {
- }
- LLTextSegment::LLTextSegment(const LLColor3& color, S32 start, S32 end)
- : mStyle(new LLStyle(true, color, LLStringUtil::null)),
- mStart(start),
- mEnd(end),
- mToken(NULL),
- mIsDefault(false)
- {
- }
- bool LLTextSegment::getToolTip(std::string& msg) const
- {
- if (mToken && !mToken->getToolTip().empty())
- {
- const LLWString& wmsg = mToken->getToolTip();
- msg = wstring_to_utf8str(wmsg);
- return true;
- }
- return false;
- }
- void LLTextSegment::dump() const
- {
- llinfos << "Segment "
- #if 0
- << "(color: " << mColor.mV[VX] << ", " << mColor.mV[VY] << ", "
- << mColor.mV[VZ] << ")"
- #endif
- << "[" << mStart << ", " << getEnd() << "]" << llendl;
- }
- //virtual
- const std::string& LLTextEditor::getTag() const
- {
- return LL_SIMPLE_TEXT_EDITOR_TAG;
- }
- //virtual
- LLXMLNodePtr LLTextEditor::getXML(bool save_children) const
- {
- LLXMLNodePtr node = LLUICtrl::getXML();
- node->setName(LL_SIMPLE_TEXT_EDITOR_TAG);
- // Attributes
- node->createChild("max_length", true)->setIntValue(getMaxLength());
- node->createChild("embedded_items",
- true)->setBoolValue(mAllowEmbeddedItems);
- node->createChild("font",
- true)->setStringValue(LLFontGL::nameFromFont(mGLFont));
- node->createChild("word_wrap", true)->setBoolValue(mWordWrap);
- node->createChild("hide_scrollbar",
- true)->setBoolValue(mHideScrollbarForShortDocs);
- addColorXML(node, mCursorColor, "cursor_color", "TextCursorColor");
- addColorXML(node, mFgColor, "text_color", "TextFgColor");
- addColorXML(node, mDefaultColor, "text_default_color", "TextDefaultColor");
- addColorXML(node, mReadOnlyFgColor, "text_readonly_color",
- "TextFgReadOnlyColor");
- addColorXML(node, mReadOnlyBgColor, "bg_readonly_color",
- "TextBgReadOnlyColor");
- addColorXML(node, mWriteableBgColor, "bg_writeable_color",
- "TextBgWriteableColor");
- addColorXML(node, mFocusBgColor, "bg_focus_color", "TextBgFocusColor");
- // Contents
- node->setStringValue(getText());
- return node;
- }
- //static
- LLView* LLTextEditor::fromXML(LLXMLNodePtr nodep, LLView* parentp,
- LLUICtrlFactory*)
- {
- std::string name = "text_editor";
- nodep->getAttributeString("name", name);
- LLRect rect;
- createRect(nodep, rect, parentp, LLRect());
- U32 max_text_length = 255;
- nodep->getAttributeU32("max_length", max_text_length);
- bool allow_embedded_items = false;
- nodep->getAttributeBool("embedded_items", allow_embedded_items);
- LLFontGL* fontp = LLView::selectFont(nodep);
- std::string text = nodep->getTextContents().substr(0, max_text_length - 1);
- LLTextEditor* self = new LLTextEditor(name, rect, max_text_length, text,
- fontp, allow_embedded_items);
- self->setTextEditorParameters(nodep);
- bool hide_scrollbar = false;
- nodep->getAttributeBool("hide_scrollbar", hide_scrollbar);
- self->setHideScrollbarForShortDocs(hide_scrollbar);
- self->initFromXML(nodep, parentp);
- return self;
- }
- void LLTextEditor::setTextEditorParameters(LLXMLNodePtr node)
- {
- bool word_wrap = false;
- node->getAttributeBool("word_wrap", word_wrap);
- setWordWrap(word_wrap);
- node->getAttributeBool("show_line_numbers", mShowLineNumbers);
- node->getAttributeBool("track_bottom", mTrackBottom);
- // By default, spell check is enabled for text editors
- if (node->hasAttribute("spell_check"))
- {
- node->getAttributeBool("spell_check", mSpellCheck);
- }
- LLColor4 color;
- if (LLUICtrlFactory::getAttributeColor(node, "cursor_color", color))
- {
- setCursorColor(color);
- }
- if (LLUICtrlFactory::getAttributeColor(node, "text_color", color))
- {
- setFgColor(color);
- }
- if (LLUICtrlFactory::getAttributeColor(node, "text_readonly_color", color))
- {
- setReadOnlyFgColor(color);
- }
- if (LLUICtrlFactory::getAttributeColor(node, "bg_readonly_color", color))
- {
- setReadOnlyBgColor(color);
- }
- if (LLUICtrlFactory::getAttributeColor(node, "bg_writeable_color", color))
- {
- setWriteableBgColor(color);
- }
- }
- // Refactoring note: we may eventually want to replace this with std::regex
- // or std::tokenizer capabilities since we have already fixed at least two
- // JIRAs concerning logic issues associated with this function.
- S32 LLTextEditor::findHTMLToken(const std::string& line, S32 pos,
- bool reverse) const
- {
- static const std::string openers = " \t\n('\"[{<>";
- static const std::string closers = " \t\n)'\"]}><;";
- if (reverse)
- {
- for (S32 index = pos; index >= 0; --index)
- {
- char c = line[index];
- S32 m2 = openers.find(c);
- if (m2 >= 0)
- {
- return index + 1;
- }
- }
- return 0; // index is -1, we do not want to return that.
- }
- // Adjust the search slightly, to allow matching parenthesis inside the URL
- S32 len = line.length();
- S32 paren_count = 0;
- for (S32 index = pos; index < len; ++index)
- {
- char c = line[index];
- if (c == '(')
- {
- ++paren_count;
- }
- else if (c == ')')
- {
- if (paren_count <= 0)
- {
- return index;
- }
- else
- {
- --paren_count;
- }
- }
- else
- {
- S32 m2 = closers.find(c);
- if (m2 >= 0)
- {
- return index;
- }
- }
- }
- return len;
- }
- bool LLTextEditor::findHTML(const std::string& line, S32* begin,
- S32* end) const
- {
- static const std::string badneighbors =
- ".,<>?';\"][}{=-+_)(*&^%$#@!~`\t\r\n\\";
- bool matched = false;
- size_t m1 = line.find("://", *end);
- if (m1 != std::string::npos) // Easy match.
- {
- *begin = findHTMLToken(line, m1, true);
- *end = findHTMLToken(line, m1, false);
- // Load_url only handles http and https so do not hilite ftp, smb, etc
- size_t m2 = line.substr(*begin, (m1 - *begin)).find("http");
- size_t m3 = line.substr(*begin, (m1 - *begin)).find("secondlife");
- if ((m2 != std::string::npos || m3 != std::string::npos) &&
- badneighbors.find(line.substr(m1 + 3, 1)) == std::string::npos)
- {
- matched = true;
- }
- }
- #if 0
- // Matches things like secondlife.com (no http://) needs a whitelist to
- // really be effective.
- else // Harder match.
- {
- m1 = line.find(".", *end);
- if (m1 != std::string::npos)
- {
- *end = findHTMLToken(line, m1, false);
- *begin = findHTMLToken(line, m1, true);
- m1 = line.rfind(".", *end);
- if ((*end - m1) > 2 && m1 > *begin)
- {
- size_t m2 = badneighbors.find(line.substr(m1 + 1, 1));
- size_t m3 = badneighbors.find(line.substr(m1 - 1, 1));
- if (m3 == std::string::npos && m2 == std::string::npos)
- {
- matched = true;
- }
- }
- }
- }
- #endif
- if (matched)
- {
- std::string url = line.substr(*begin, *end - *begin);
- std::string slurl_id = "slurl.com/secondlife/";
- size_t strpos = url.find(slurl_id);
- if (strpos == std::string::npos)
- {
- slurl_id = "maps.secondlife.com/secondlife/";
- strpos = url.find(slurl_id);
- if (strpos == std::string::npos)
- {
- slurl_id = "secondlife://";
- strpos = url.find(slurl_id);
- if (strpos == std::string::npos)
- {
- slurl_id = "sl://";
- strpos = url.find(slurl_id);
- }
- }
- }
- if (strpos != std::string::npos)
- {
- strpos += slurl_id.length();
- while (url.find("/", strpos) == std::string::npos)
- {
- if ((size_t)(*end + 2) >= line.length() ||
- line.substr(*end, 1) != " ")
- {
- matched = false;
- break;
- }
- strpos = (*end + 1) - *begin;
- *end = findHTMLToken(line,(*begin + strpos), false);
- url = line.substr(*begin, *end - *begin);
- }
- }
- }
- if (!matched)
- {
- *begin = *end = 0;
- }
- return matched;
- }
- void LLTextEditor::updateAllowingLanguageInput()
- {
- if (hasFocus() && !mReadOnly)
- {
- gWindowp->allowLanguageTextInput(this, true);
- }
- else
- {
- gWindowp->allowLanguageTextInput(this, false);
- }
- }
- // Preedit is managed off the undo/redo command stack.
- bool LLTextEditor::hasPreeditString() const
- {
- return mPreeditPositions.size() > 1;
- }
- void LLTextEditor::resetPreedit()
- {
- if (hasPreeditString())
- {
- if (hasSelection())
- {
- llwarns << "Preedit and selection !" << llendl;
- deselect();
- }
- mCursorPos = mPreeditPositions.front();
- removeStringNoUndo(mCursorPos, mPreeditPositions.back() - mCursorPos);
- insertStringNoUndo(mCursorPos, mPreeditOverwrittenWString);
- mPreeditWString.clear();
- mPreeditOverwrittenWString.clear();
- mPreeditPositions.clear();
- // A call to updatePreedit should soon follow under a normal course of
- // operation, so we do not need to maintain internal variables such as
- // line start positions now.
- }
- }
- void LLTextEditor::updatePreedit(const LLWString& preedit_string,
- const segment_lengths_t& preedit_segment_lengths,
- const standouts_t& preedit_standouts,
- S32 caret_position)
- {
- // Just in case.
- if (mReadOnly)
- {
- return;
- }
- gWindowp->hideCursorUntilMouseMove();
- S32 insert_preedit_at = mCursorPos;
- mPreeditWString = preedit_string;
- mPreeditPositions.resize(preedit_segment_lengths.size() + 1);
- S32 position = insert_preedit_at;
- for (segment_lengths_t::size_type i = 0,
- size = preedit_segment_lengths.size();
- i < size; ++i)
- {
- mPreeditPositions[i] = position;
- position += preedit_segment_lengths[i];
- }
- mPreeditPositions.back() = position;
- if (gKeyboardp && gKeyboardp->getInsertMode() == LL_KIM_OVERWRITE)
- {
- mPreeditOverwrittenWString = getWSubString(insert_preedit_at,
- mPreeditWString.length());
- removeStringNoUndo(insert_preedit_at, mPreeditWString.length());
- }
- else
- {
- mPreeditOverwrittenWString.clear();
- }
- insertStringNoUndo(insert_preedit_at, mPreeditWString);
- mPreeditStandouts = preedit_standouts;
- needsReflow();
- setCursorPos(insert_preedit_at + caret_position);
- // Update of the preedit should be caused by some key strokes.
- mKeystrokeTimer.reset();
- }
- bool LLTextEditor::getPreeditLocation(S32 query_offset, LLCoordGL* coord,
- LLRect* bounds, LLRect* control) const
- {
- if (control)
- {
- LLRect control_rect_screen;
- localRectToScreen(mTextRect, &control_rect_screen);
- LLUI::screenRectToGL(control_rect_screen, control);
- }
- S32 preedit_left_position, preedit_right_position;
- if (hasPreeditString())
- {
- preedit_left_position = mPreeditPositions.front();
- preedit_right_position = mPreeditPositions.back();
- }
- else
- {
- preedit_left_position = preedit_right_position = mCursorPos;
- }
- const S32 query = (query_offset >= 0 ? preedit_left_position + query_offset
- : mCursorPos);
- if (query < preedit_left_position || query > preedit_right_position)
- {
- return false;
- }
- const S32 first_visible_line = mScrollbar->getDocPos();
- if (query < getLineStart(first_visible_line))
- {
- return false;
- }
- S32 current_line = first_visible_line;
- S32 current_line_start, current_line_end;
- while (true)
- {
- current_line_start = getLineStart(current_line);
- current_line_end = getLineStart(current_line + 1);
- if (query >= current_line_start && query < current_line_end)
- {
- break;
- }
- if (current_line_start == current_line_end)
- {
- // We have reached on the last line. The query position must be
- // here.
- break;
- }
- ++current_line;
- }
- const llwchar* const text = mWText.c_str();
- const S32 line_height = ll_roundp(mGLFont->getLineHeight());
- if (coord)
- {
- const S32 query_x = mTextRect.mLeft +
- mGLFont->getWidth(text, current_line_start,
- query - current_line_start,
- mAllowEmbeddedItems);
- const S32 query_y = mTextRect.mTop -
- (current_line - first_visible_line) * line_height -
- line_height / 2;
- S32 query_screen_x, query_screen_y;
- localPointToScreen(query_x, query_y, &query_screen_x, &query_screen_y);
- LLUI::screenPointToGL(query_screen_x, query_screen_y, &coord->mX,
- &coord->mY);
- }
- if (bounds)
- {
- S32 preedit_left = mTextRect.mLeft;
- if (preedit_left_position > current_line_start)
- {
- preedit_left += mGLFont->getWidth(text, current_line_start,
- preedit_left_position -
- current_line_start,
- mAllowEmbeddedItems);
- }
- S32 preedit_right = mTextRect.mLeft;
- if (preedit_right_position < current_line_end)
- {
- preedit_right += mGLFont->getWidth(text, current_line_start,
- preedit_right_position -
- current_line_start,
- mAllowEmbeddedItems);
- }
- else
- {
- preedit_right += mGLFont->getWidth(text, current_line_start,
- current_line_end -
- current_line_start,
- mAllowEmbeddedItems);
- }
- const S32 preedit_top = mTextRect.mTop -
- (current_line - first_visible_line) *
- line_height;
- const S32 preedit_bottom = preedit_top - line_height;
- const LLRect preedit_rect_local(preedit_left, preedit_top,
- preedit_right, preedit_bottom);
- LLRect preedit_rect_screen;
- localRectToScreen(preedit_rect_local, &preedit_rect_screen);
- LLUI::screenRectToGL(preedit_rect_screen, bounds);
- }
- return true;
- }
- void LLTextEditor::getSelectionRange(S32* position, S32* length) const
- {
- if (hasSelection())
- {
- *position = llmin(mSelectionStart, mSelectionEnd);
- *length = abs(mSelectionStart - mSelectionEnd);
- }
- else
- {
- *position = mCursorPos;
- *length = 0;
- }
- }
- void LLTextEditor::getPreeditRange(S32* position, S32* length) const
- {
- if (hasPreeditString())
- {
- *position = mPreeditPositions.front();
- *length = mPreeditPositions.back() - mPreeditPositions.front();
- }
- else
- {
- *position = mCursorPos;
- *length = 0;
- }
- }
- void LLTextEditor::markAsPreedit(S32 position, S32 length)
- {
- deselect();
- setCursorPos(position);
- if (hasPreeditString())
- {
- llwarns << "markAsPreedit invoked when hasPreeditString is true."
- << llendl;
- }
- mPreeditWString = LLWString(mWText, position, length);
- if (length > 0)
- {
- mPreeditPositions.resize(2);
- mPreeditPositions[0] = position;
- mPreeditPositions[1] = position + length;
- mPreeditStandouts.resize(1);
- mPreeditStandouts[0] = false;
- }
- else
- {
- mPreeditPositions.clear();
- mPreeditStandouts.clear();
- }
- if (gKeyboardp && gKeyboardp->getInsertMode() == LL_KIM_OVERWRITE)
- {
- mPreeditOverwrittenWString = mPreeditWString;
- }
- else
- {
- mPreeditOverwrittenWString.clear();
- }
- }
- S32 LLTextEditor::getPreeditFontSize() const
- {
- return ll_roundp(mGLFont->getLineHeight() * LLUI::sGLScaleFactor.mV[VY]);
- }
|