You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Grid.java 215KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312
  1. /*
  2. * Copyright 2000-2014 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.client.widgets;
  17. import java.util.ArrayList;
  18. import java.util.Arrays;
  19. import java.util.Collection;
  20. import java.util.Collections;
  21. import java.util.HashMap;
  22. import java.util.HashSet;
  23. import java.util.Iterator;
  24. import java.util.LinkedHashMap;
  25. import java.util.List;
  26. import java.util.Map;
  27. import java.util.Set;
  28. import java.util.logging.Level;
  29. import java.util.logging.Logger;
  30. import com.google.gwt.core.client.Scheduler;
  31. import com.google.gwt.core.client.Scheduler.ScheduledCommand;
  32. import com.google.gwt.core.shared.GWT;
  33. import com.google.gwt.dom.client.BrowserEvents;
  34. import com.google.gwt.dom.client.DivElement;
  35. import com.google.gwt.dom.client.Element;
  36. import com.google.gwt.dom.client.EventTarget;
  37. import com.google.gwt.dom.client.Style;
  38. import com.google.gwt.dom.client.Style.Unit;
  39. import com.google.gwt.dom.client.TableCellElement;
  40. import com.google.gwt.dom.client.TableRowElement;
  41. import com.google.gwt.dom.client.TableSectionElement;
  42. import com.google.gwt.dom.client.Touch;
  43. import com.google.gwt.event.dom.client.ClickEvent;
  44. import com.google.gwt.event.dom.client.ClickHandler;
  45. import com.google.gwt.event.dom.client.KeyCodes;
  46. import com.google.gwt.event.dom.client.KeyEvent;
  47. import com.google.gwt.event.dom.client.MouseEvent;
  48. import com.google.gwt.event.logical.shared.ValueChangeEvent;
  49. import com.google.gwt.event.logical.shared.ValueChangeHandler;
  50. import com.google.gwt.event.shared.HandlerRegistration;
  51. import com.google.gwt.touch.client.Point;
  52. import com.google.gwt.user.client.DOM;
  53. import com.google.gwt.user.client.Event;
  54. import com.google.gwt.user.client.Timer;
  55. import com.google.gwt.user.client.ui.Button;
  56. import com.google.gwt.user.client.ui.CheckBox;
  57. import com.google.gwt.user.client.ui.HasEnabled;
  58. import com.google.gwt.user.client.ui.HasWidgets;
  59. import com.google.gwt.user.client.ui.ResizeComposite;
  60. import com.google.gwt.user.client.ui.Widget;
  61. import com.vaadin.client.DeferredWorker;
  62. import com.vaadin.client.WidgetUtil;
  63. import com.vaadin.client.data.DataChangeHandler;
  64. import com.vaadin.client.data.DataSource;
  65. import com.vaadin.client.renderers.ComplexRenderer;
  66. import com.vaadin.client.renderers.Renderer;
  67. import com.vaadin.client.renderers.WidgetRenderer;
  68. import com.vaadin.client.ui.SubPartAware;
  69. import com.vaadin.client.widget.escalator.Cell;
  70. import com.vaadin.client.widget.escalator.ColumnConfiguration;
  71. import com.vaadin.client.widget.escalator.EscalatorUpdater;
  72. import com.vaadin.client.widget.escalator.FlyweightCell;
  73. import com.vaadin.client.widget.escalator.Row;
  74. import com.vaadin.client.widget.escalator.RowContainer;
  75. import com.vaadin.client.widget.escalator.RowVisibilityChangeEvent;
  76. import com.vaadin.client.widget.escalator.RowVisibilityChangeHandler;
  77. import com.vaadin.client.widget.escalator.ScrollbarBundle.Direction;
  78. import com.vaadin.client.widget.grid.CellReference;
  79. import com.vaadin.client.widget.grid.CellStyleGenerator;
  80. import com.vaadin.client.widget.grid.DataAvailableEvent;
  81. import com.vaadin.client.widget.grid.DataAvailableHandler;
  82. import com.vaadin.client.widget.grid.EditorHandler;
  83. import com.vaadin.client.widget.grid.EditorHandler.EditorRequest;
  84. import com.vaadin.client.widget.grid.EventCellReference;
  85. import com.vaadin.client.widget.grid.RendererCellReference;
  86. import com.vaadin.client.widget.grid.RowReference;
  87. import com.vaadin.client.widget.grid.RowStyleGenerator;
  88. import com.vaadin.client.widget.grid.events.AbstractGridKeyEventHandler;
  89. import com.vaadin.client.widget.grid.events.AbstractGridMouseEventHandler;
  90. import com.vaadin.client.widget.grid.events.BodyClickHandler;
  91. import com.vaadin.client.widget.grid.events.BodyDoubleClickHandler;
  92. import com.vaadin.client.widget.grid.events.BodyKeyDownHandler;
  93. import com.vaadin.client.widget.grid.events.BodyKeyPressHandler;
  94. import com.vaadin.client.widget.grid.events.BodyKeyUpHandler;
  95. import com.vaadin.client.widget.grid.events.FooterClickHandler;
  96. import com.vaadin.client.widget.grid.events.FooterDoubleClickHandler;
  97. import com.vaadin.client.widget.grid.events.FooterKeyDownHandler;
  98. import com.vaadin.client.widget.grid.events.FooterKeyPressHandler;
  99. import com.vaadin.client.widget.grid.events.FooterKeyUpHandler;
  100. import com.vaadin.client.widget.grid.events.GridClickEvent;
  101. import com.vaadin.client.widget.grid.events.GridDoubleClickEvent;
  102. import com.vaadin.client.widget.grid.events.GridKeyDownEvent;
  103. import com.vaadin.client.widget.grid.events.GridKeyPressEvent;
  104. import com.vaadin.client.widget.grid.events.GridKeyUpEvent;
  105. import com.vaadin.client.widget.grid.events.HeaderClickHandler;
  106. import com.vaadin.client.widget.grid.events.HeaderDoubleClickHandler;
  107. import com.vaadin.client.widget.grid.events.HeaderKeyDownHandler;
  108. import com.vaadin.client.widget.grid.events.HeaderKeyPressHandler;
  109. import com.vaadin.client.widget.grid.events.HeaderKeyUpHandler;
  110. import com.vaadin.client.widget.grid.events.ScrollEvent;
  111. import com.vaadin.client.widget.grid.events.ScrollHandler;
  112. import com.vaadin.client.widget.grid.events.SelectAllEvent;
  113. import com.vaadin.client.widget.grid.events.SelectAllHandler;
  114. import com.vaadin.client.widget.grid.selection.HasSelectionHandlers;
  115. import com.vaadin.client.widget.grid.selection.SelectionEvent;
  116. import com.vaadin.client.widget.grid.selection.SelectionHandler;
  117. import com.vaadin.client.widget.grid.selection.SelectionModel;
  118. import com.vaadin.client.widget.grid.selection.SelectionModel.Multi;
  119. import com.vaadin.client.widget.grid.selection.SelectionModelMulti;
  120. import com.vaadin.client.widget.grid.selection.SelectionModelNone;
  121. import com.vaadin.client.widget.grid.selection.SelectionModelSingle;
  122. import com.vaadin.client.widget.grid.sort.Sort;
  123. import com.vaadin.client.widget.grid.sort.SortEvent;
  124. import com.vaadin.client.widget.grid.sort.SortHandler;
  125. import com.vaadin.client.widget.grid.sort.SortOrder;
  126. import com.vaadin.client.widgets.Escalator.AbstractRowContainer;
  127. import com.vaadin.client.widgets.Grid.Editor.State;
  128. import com.vaadin.shared.data.sort.SortDirection;
  129. import com.vaadin.shared.ui.grid.GridConstants;
  130. import com.vaadin.shared.ui.grid.GridStaticCellType;
  131. import com.vaadin.shared.ui.grid.HeightMode;
  132. import com.vaadin.shared.ui.grid.Range;
  133. import com.vaadin.shared.ui.grid.ScrollDestination;
  134. import com.vaadin.shared.util.SharedUtil;
  135. /**
  136. * A data grid view that supports columns and lazy loading of data rows from a
  137. * data source.
  138. *
  139. * <h1>Columns</h1>
  140. * <p>
  141. * Each column in Grid is represented by a {@link Column}. Each
  142. * {@code GridColumn} has a custom implementation for
  143. * {@link Column#getValue(Object)} that gets the row object as an argument, and
  144. * returns the value for that particular column, extracted from the row object.
  145. * <p>
  146. * Each column also has a Renderer. Its function is to take the value that is
  147. * given by the {@code GridColumn} and display it to the user. A simple column
  148. * might have a {@link com.vaadin.client.renderers.TextRenderer TextRenderer}
  149. * that simply takes in a {@code String} and displays it as the cell's content.
  150. * A more complex renderer might be
  151. * {@link com.vaadin.client.renderers.ProgressBarRenderer ProgressBarRenderer}
  152. * that takes in a floating point number, and displays a progress bar instead,
  153. * based on the given number.
  154. * <p>
  155. * <em>See:</em> {@link #addColumn(Column)}, {@link #addColumn(Column, int)} and
  156. * {@link #addColumns(Column...)}. <em>Also</em>
  157. * {@link Column#setRenderer(Renderer)}.
  158. *
  159. * <h1>Data Sources</h1>
  160. * <p>
  161. * Grid gets its data from a {@link DataSource}, providing row objects to Grid
  162. * from a user-defined endpoint. It can be either a local in-memory data source
  163. * (e.g. {@link com.vaadin.client.widget.grid.datasources.ListDataSource
  164. * ListDataSource}) or even a remote one, retrieving data from e.g. a REST API
  165. * (see {@link com.vaadin.client.data.AbstractRemoteDataSource
  166. * AbstractRemoteDataSource}).
  167. *
  168. *
  169. * @param <T>
  170. * The row type of the grid. The row type is the POJO type from where
  171. * the data is retrieved into the column cells.
  172. * @since 7.4
  173. * @author Vaadin Ltd
  174. */
  175. public class Grid<T> extends ResizeComposite implements
  176. HasSelectionHandlers<T>, SubPartAware, DeferredWorker, HasWidgets,
  177. HasEnabled {
  178. /**
  179. * Enum describing different sections of Grid.
  180. */
  181. public enum Section {
  182. HEADER, BODY, FOOTER
  183. }
  184. /**
  185. * Abstract base class for Grid header and footer sections.
  186. *
  187. * @param <ROWTYPE>
  188. * the type of the rows in the section
  189. */
  190. protected abstract static class StaticSection<ROWTYPE extends StaticSection.StaticRow<?>> {
  191. /**
  192. * A header or footer cell. Has a simple textual caption.
  193. *
  194. */
  195. static class StaticCell {
  196. private Object content = null;
  197. private int colspan = 1;
  198. private StaticSection<?> section;
  199. private GridStaticCellType type = GridStaticCellType.TEXT;
  200. private String styleName = null;
  201. /**
  202. * Sets the text displayed in this cell.
  203. *
  204. * @param text
  205. * a plain text caption
  206. */
  207. public void setText(String text) {
  208. this.content = text;
  209. this.type = GridStaticCellType.TEXT;
  210. section.requestSectionRefresh();
  211. }
  212. /**
  213. * Returns the text displayed in this cell.
  214. *
  215. * @return the plain text caption
  216. */
  217. public String getText() {
  218. if (type != GridStaticCellType.TEXT) {
  219. throw new IllegalStateException(
  220. "Cannot fetch Text from a cell with type " + type);
  221. }
  222. return (String) content;
  223. }
  224. protected StaticSection<?> getSection() {
  225. assert section != null;
  226. return section;
  227. }
  228. protected void setSection(StaticSection<?> section) {
  229. this.section = section;
  230. }
  231. /**
  232. * Returns the amount of columns the cell spans. By default is 1.
  233. *
  234. * @return The amount of columns the cell spans.
  235. */
  236. public int getColspan() {
  237. return colspan;
  238. }
  239. /**
  240. * Sets the amount of columns the cell spans. Must be more or equal
  241. * to 1. By default is 1.
  242. *
  243. * @param colspan
  244. * the colspan to set
  245. */
  246. public void setColspan(int colspan) {
  247. if (colspan < 1) {
  248. throw new IllegalArgumentException(
  249. "Colspan cannot be less than 1");
  250. }
  251. this.colspan = colspan;
  252. section.requestSectionRefresh();
  253. }
  254. /**
  255. * Returns the html inside the cell.
  256. *
  257. * @throws IllegalStateException
  258. * if trying to retrive HTML from a cell with a type
  259. * other than {@link GridStaticCellType#HTML}.
  260. * @return the html content of the cell.
  261. */
  262. public String getHtml() {
  263. if (type != GridStaticCellType.HTML) {
  264. throw new IllegalStateException(
  265. "Cannot fetch HTML from a cell with type " + type);
  266. }
  267. return (String) content;
  268. }
  269. /**
  270. * Sets the content of the cell to the provided html. All previous
  271. * content is discarded and the cell type is set to
  272. * {@link GridStaticCellType#HTML}.
  273. *
  274. * @param html
  275. * The html content of the cell
  276. */
  277. public void setHtml(String html) {
  278. this.content = html;
  279. this.type = GridStaticCellType.HTML;
  280. section.requestSectionRefresh();
  281. }
  282. /**
  283. * Returns the widget in the cell.
  284. *
  285. * @throws IllegalStateException
  286. * if the cell is not {@link GridStaticCellType#WIDGET}
  287. *
  288. * @return the widget in the cell
  289. */
  290. public Widget getWidget() {
  291. if (type != GridStaticCellType.WIDGET) {
  292. throw new IllegalStateException(
  293. "Cannot fetch Widget from a cell with type " + type);
  294. }
  295. return (Widget) content;
  296. }
  297. /**
  298. * Set widget as the content of the cell. The type of the cell
  299. * becomes {@link GridStaticCellType#WIDGET}. All previous content
  300. * is discarded.
  301. *
  302. * @param widget
  303. * The widget to add to the cell. Should not be
  304. * previously attached anywhere (widget.getParent ==
  305. * null).
  306. */
  307. public void setWidget(Widget widget) {
  308. this.content = widget;
  309. this.type = GridStaticCellType.WIDGET;
  310. section.requestSectionRefresh();
  311. }
  312. /**
  313. * Returns the type of the cell.
  314. *
  315. * @return the type of content the cell contains.
  316. */
  317. public GridStaticCellType getType() {
  318. return type;
  319. }
  320. /**
  321. * Returns the custom style name for this cell.
  322. *
  323. * @return the style name or null if no style name has been set
  324. */
  325. public String getStyleName() {
  326. return styleName;
  327. }
  328. /**
  329. * Sets a custom style name for this cell.
  330. *
  331. * @param styleName
  332. * the style name to set or null to not use any style
  333. * name
  334. */
  335. public void setStyleName(String styleName) {
  336. this.styleName = styleName;
  337. section.requestSectionRefresh();
  338. }
  339. }
  340. /**
  341. * Abstract base class for Grid header and footer rows.
  342. *
  343. * @param <CELLTYPE>
  344. * the type of the cells in the row
  345. */
  346. abstract static class StaticRow<CELLTYPE extends StaticCell> {
  347. private Map<Column<?, ?>, CELLTYPE> cells = new HashMap<Column<?, ?>, CELLTYPE>();
  348. private StaticSection<?> section;
  349. /**
  350. * Map from set of spanned columns to cell meta data.
  351. */
  352. private Map<Set<Column<?, ?>>, CELLTYPE> cellGroups = new HashMap<Set<Column<?, ?>>, CELLTYPE>();
  353. /**
  354. * A custom style name for the row or null if none is set.
  355. */
  356. private String styleName = null;
  357. /**
  358. * Returns the cell on given GridColumn. If the column is merged
  359. * returned cell is the cell for the whole group.
  360. *
  361. * @param column
  362. * the column in grid
  363. * @return the cell on given column, merged cell for merged columns,
  364. * null if not found
  365. */
  366. public CELLTYPE getCell(Column<?, ?> column) {
  367. Set<Column<?, ?>> cellGroup = getCellGroupForColumn(column);
  368. if (cellGroup != null) {
  369. return cellGroups.get(cellGroup);
  370. }
  371. return cells.get(column);
  372. }
  373. /**
  374. * Merges columns cells in a row
  375. *
  376. * @param columns
  377. * the columns which header should be merged
  378. * @return the remaining visible cell after the merge, or the cell
  379. * on first column if all are hidden
  380. */
  381. public CELLTYPE join(Column<?, ?>... columns) {
  382. if (columns.length <= 1) {
  383. throw new IllegalArgumentException(
  384. "You can't merge less than 2 columns together.");
  385. }
  386. HashSet<Column<?, ?>> columnGroup = new HashSet<Column<?, ?>>();
  387. for (Column<?, ?> column : columns) {
  388. if (!cells.containsKey(column)) {
  389. throw new IllegalArgumentException(
  390. "Given column does not exists on row " + column);
  391. } else if (getCellGroupForColumn(column) != null) {
  392. throw new IllegalStateException(
  393. "Column is already in a group.");
  394. }
  395. columnGroup.add(column);
  396. }
  397. CELLTYPE joinedCell = createCell();
  398. cellGroups.put(columnGroup, joinedCell);
  399. joinedCell.setSection(getSection());
  400. calculateColspans();
  401. return joinedCell;
  402. }
  403. /**
  404. * Merges columns cells in a row
  405. *
  406. * @param cells
  407. * The cells to merge. Must be from the same row.
  408. * @return The remaining visible cell after the merge, or the first
  409. * cell if all columns are hidden
  410. */
  411. public CELLTYPE join(CELLTYPE... cells) {
  412. if (cells.length <= 1) {
  413. throw new IllegalArgumentException(
  414. "You can't merge less than 2 cells together.");
  415. }
  416. Column<?, ?>[] columns = new Column<?, ?>[cells.length];
  417. int j = 0;
  418. for (Column<?, ?> column : this.cells.keySet()) {
  419. CELLTYPE cell = this.cells.get(column);
  420. if (!this.cells.containsValue(cells[j])) {
  421. throw new IllegalArgumentException(
  422. "Given cell does not exists on row");
  423. } else if (cell.equals(cells[j])) {
  424. columns[j++] = column;
  425. if (j == cells.length) {
  426. break;
  427. }
  428. }
  429. }
  430. return join(columns);
  431. }
  432. private Set<Column<?, ?>> getCellGroupForColumn(Column<?, ?> column) {
  433. for (Set<Column<?, ?>> group : cellGroups.keySet()) {
  434. if (group.contains(column)) {
  435. return group;
  436. }
  437. }
  438. return null;
  439. }
  440. void calculateColspans() {
  441. // Reset all cells
  442. for (CELLTYPE cell : this.cells.values()) {
  443. cell.setColspan(1);
  444. }
  445. List<Column<?, ?>> columnOrder = new ArrayList<Column<?, ?>>(
  446. section.grid.getColumns());
  447. // Set colspan for grouped cells
  448. for (Set<Column<?, ?>> group : cellGroups.keySet()) {
  449. if (!checkCellGroupAndOrder(columnOrder, group)) {
  450. cellGroups.get(group).setColspan(1);
  451. } else {
  452. int colSpan = group.size();
  453. cellGroups.get(group).setColspan(colSpan);
  454. }
  455. }
  456. }
  457. private boolean checkCellGroupAndOrder(
  458. List<Column<?, ?>> columnOrder, Set<Column<?, ?>> cellGroup) {
  459. if (!columnOrder.containsAll(cellGroup)) {
  460. return false;
  461. }
  462. for (int i = 0; i < columnOrder.size(); ++i) {
  463. if (!cellGroup.contains(columnOrder.get(i))) {
  464. continue;
  465. }
  466. for (int j = 1; j < cellGroup.size(); ++j) {
  467. if (!cellGroup.contains(columnOrder.get(i + j))) {
  468. return false;
  469. }
  470. }
  471. return true;
  472. }
  473. return false;
  474. }
  475. protected void addCell(Column<?, ?> column) {
  476. CELLTYPE cell = createCell();
  477. cell.setSection(getSection());
  478. cells.put(column, cell);
  479. }
  480. protected void removeCell(Column<?, ?> column) {
  481. cells.remove(column);
  482. }
  483. protected abstract CELLTYPE createCell();
  484. protected StaticSection<?> getSection() {
  485. return section;
  486. }
  487. protected void setSection(StaticSection<?> section) {
  488. this.section = section;
  489. }
  490. /**
  491. * Returns the custom style name for this row.
  492. *
  493. * @return the style name or null if no style name has been set
  494. */
  495. public String getStyleName() {
  496. return styleName;
  497. }
  498. /**
  499. * Sets a custom style name for this row.
  500. *
  501. * @param styleName
  502. * the style name to set or null to not use any style
  503. * name
  504. */
  505. public void setStyleName(String styleName) {
  506. this.styleName = styleName;
  507. section.requestSectionRefresh();
  508. }
  509. }
  510. private Grid<?> grid;
  511. private List<ROWTYPE> rows = new ArrayList<ROWTYPE>();
  512. private boolean visible = true;
  513. /**
  514. * Creates and returns a new instance of the row type.
  515. *
  516. * @return the created row
  517. */
  518. protected abstract ROWTYPE createRow();
  519. /**
  520. * Informs the grid that this section should be re-rendered.
  521. * <p>
  522. * <b>Note</b> that re-render means calling update() on each cell,
  523. * preAttach()/postAttach()/preDetach()/postDetach() is not called as
  524. * the cells are not removed from the DOM.
  525. */
  526. protected abstract void requestSectionRefresh();
  527. /**
  528. * Sets the visibility of the whole section.
  529. *
  530. * @param visible
  531. * true to show this section, false to hide
  532. */
  533. public void setVisible(boolean visible) {
  534. this.visible = visible;
  535. requestSectionRefresh();
  536. }
  537. /**
  538. * Returns the visibility of this section.
  539. *
  540. * @return true if visible, false otherwise.
  541. */
  542. public boolean isVisible() {
  543. return visible;
  544. }
  545. /**
  546. * Inserts a new row at the given position. Shifts the row currently at
  547. * that position and any subsequent rows down (adds one to their
  548. * indices).
  549. *
  550. * @param index
  551. * the position at which to insert the row
  552. * @return the new row
  553. *
  554. * @throws IndexOutOfBoundsException
  555. * if the index is out of bounds
  556. * @see #appendRow()
  557. * @see #prependRow()
  558. * @see #removeRow(int)
  559. * @see #removeRow(StaticRow)
  560. */
  561. public ROWTYPE addRowAt(int index) {
  562. ROWTYPE row = createRow();
  563. row.setSection(this);
  564. for (int i = 0; i < getGrid().getColumnCount(); ++i) {
  565. row.addCell(grid.getColumn(i));
  566. }
  567. rows.add(index, row);
  568. requestSectionRefresh();
  569. return row;
  570. }
  571. /**
  572. * Adds a new row at the top of this section.
  573. *
  574. * @return the new row
  575. * @see #appendRow()
  576. * @see #addRowAt(int)
  577. * @see #removeRow(int)
  578. * @see #removeRow(StaticRow)
  579. */
  580. public ROWTYPE prependRow() {
  581. return addRowAt(0);
  582. }
  583. /**
  584. * Adds a new row at the bottom of this section.
  585. *
  586. * @return the new row
  587. * @see #prependRow()
  588. * @see #addRowAt(int)
  589. * @see #removeRow(int)
  590. * @see #removeRow(StaticRow)
  591. */
  592. public ROWTYPE appendRow() {
  593. return addRowAt(rows.size());
  594. }
  595. /**
  596. * Removes the row at the given position.
  597. *
  598. * @param index
  599. * the position of the row
  600. *
  601. * @throws IndexOutOfBoundsException
  602. * if the index is out of bounds
  603. * @see #addRowAt(int)
  604. * @see #appendRow()
  605. * @see #prependRow()
  606. * @see #removeRow(StaticRow)
  607. */
  608. public void removeRow(int index) {
  609. rows.remove(index);
  610. requestSectionRefresh();
  611. }
  612. /**
  613. * Removes the given row from the section.
  614. *
  615. * @param row
  616. * the row to be removed
  617. *
  618. * @throws IllegalArgumentException
  619. * if the row does not exist in this section
  620. * @see #addRowAt(int)
  621. * @see #appendRow()
  622. * @see #prependRow()
  623. * @see #removeRow(int)
  624. */
  625. public void removeRow(ROWTYPE row) {
  626. try {
  627. removeRow(rows.indexOf(row));
  628. } catch (IndexOutOfBoundsException e) {
  629. throw new IllegalArgumentException(
  630. "Section does not contain the given row");
  631. }
  632. }
  633. /**
  634. * Returns the row at the given position.
  635. *
  636. * @param index
  637. * the position of the row
  638. * @return the row with the given index
  639. *
  640. * @throws IndexOutOfBoundsException
  641. * if the index is out of bounds
  642. */
  643. public ROWTYPE getRow(int index) {
  644. try {
  645. return rows.get(index);
  646. } catch (IndexOutOfBoundsException e) {
  647. throw new IllegalArgumentException("Row with index " + index
  648. + " does not exist");
  649. }
  650. }
  651. /**
  652. * Returns the number of rows in this section.
  653. *
  654. * @return the number of rows
  655. */
  656. public int getRowCount() {
  657. return rows.size();
  658. }
  659. protected List<ROWTYPE> getRows() {
  660. return rows;
  661. }
  662. protected int getVisibleRowCount() {
  663. return isVisible() ? getRowCount() : 0;
  664. }
  665. protected void addColumn(Column<?, ?> column) {
  666. for (ROWTYPE row : rows) {
  667. row.addCell(column);
  668. }
  669. }
  670. protected void removeColumn(Column<?, ?> column) {
  671. for (ROWTYPE row : rows) {
  672. row.removeCell(column);
  673. }
  674. }
  675. protected void setGrid(Grid<?> grid) {
  676. this.grid = grid;
  677. }
  678. protected Grid<?> getGrid() {
  679. assert grid != null;
  680. return grid;
  681. }
  682. }
  683. /**
  684. * Represents the header section of a Grid. A header consists of a single
  685. * header row containing a header cell for each column. Each cell has a
  686. * simple textual caption.
  687. */
  688. protected static class Header extends StaticSection<HeaderRow> {
  689. private HeaderRow defaultRow;
  690. private boolean markAsDirty = false;
  691. @Override
  692. public void removeRow(int index) {
  693. HeaderRow removedRow = getRow(index);
  694. super.removeRow(index);
  695. if (removedRow == defaultRow) {
  696. setDefaultRow(null);
  697. }
  698. }
  699. /**
  700. * Sets the default row of this header. The default row is a special
  701. * header row providing a user interface for sorting columns.
  702. *
  703. * @param row
  704. * the new default row, or null for no default row
  705. *
  706. * @throws IllegalArgumentException
  707. * this header does not contain the row
  708. */
  709. public void setDefaultRow(HeaderRow row) {
  710. if (row == defaultRow) {
  711. return;
  712. }
  713. if (row != null && !getRows().contains(row)) {
  714. throw new IllegalArgumentException(
  715. "Cannot set a default row that does not exist in the container");
  716. }
  717. if (defaultRow != null) {
  718. defaultRow.setDefault(false);
  719. }
  720. if (row != null) {
  721. row.setDefault(true);
  722. }
  723. defaultRow = row;
  724. requestSectionRefresh();
  725. }
  726. /**
  727. * Returns the current default row of this header. The default row is a
  728. * special header row providing a user interface for sorting columns.
  729. *
  730. * @return the default row or null if no default row set
  731. */
  732. public HeaderRow getDefaultRow() {
  733. return defaultRow;
  734. }
  735. @Override
  736. protected HeaderRow createRow() {
  737. return new HeaderRow();
  738. }
  739. @Override
  740. protected void requestSectionRefresh() {
  741. markAsDirty = true;
  742. /*
  743. * Defer the refresh so if we multiple times call refreshSection()
  744. * (for example when updating cell values) we only get one actual
  745. * refresh in the end.
  746. */
  747. Scheduler.get().scheduleFinally(new Scheduler.ScheduledCommand() {
  748. @Override
  749. public void execute() {
  750. if (markAsDirty) {
  751. markAsDirty = false;
  752. getGrid().refreshHeader();
  753. }
  754. }
  755. });
  756. }
  757. /**
  758. * Returns the events consumed by the header
  759. *
  760. * @return a collection of BrowserEvents
  761. */
  762. public Collection<String> getConsumedEvents() {
  763. return Arrays.asList(BrowserEvents.TOUCHSTART,
  764. BrowserEvents.TOUCHMOVE, BrowserEvents.TOUCHEND,
  765. BrowserEvents.TOUCHCANCEL, BrowserEvents.CLICK);
  766. }
  767. }
  768. /**
  769. * A single row in a grid header section.
  770. *
  771. */
  772. public static class HeaderRow extends StaticSection.StaticRow<HeaderCell> {
  773. private boolean isDefault = false;
  774. protected void setDefault(boolean isDefault) {
  775. this.isDefault = isDefault;
  776. }
  777. public boolean isDefault() {
  778. return isDefault;
  779. }
  780. @Override
  781. protected HeaderCell createCell() {
  782. return new HeaderCell();
  783. }
  784. }
  785. /**
  786. * A single cell in a grid header row. Has a textual caption.
  787. *
  788. */
  789. public static class HeaderCell extends StaticSection.StaticCell {
  790. }
  791. /**
  792. * Represents the footer section of a Grid. The footer is always empty.
  793. */
  794. protected static class Footer extends StaticSection<FooterRow> {
  795. private boolean markAsDirty = false;
  796. @Override
  797. protected FooterRow createRow() {
  798. return new FooterRow();
  799. }
  800. @Override
  801. protected void requestSectionRefresh() {
  802. markAsDirty = true;
  803. /*
  804. * Defer the refresh so if we multiple times call refreshSection()
  805. * (for example when updating cell values) we only get one actual
  806. * refresh in the end.
  807. */
  808. Scheduler.get().scheduleFinally(new Scheduler.ScheduledCommand() {
  809. @Override
  810. public void execute() {
  811. if (markAsDirty) {
  812. markAsDirty = false;
  813. getGrid().refreshFooter();
  814. }
  815. }
  816. });
  817. }
  818. }
  819. /**
  820. * A single cell in a grid Footer row. Has a textual caption.
  821. *
  822. */
  823. public static class FooterCell extends StaticSection.StaticCell {
  824. }
  825. /**
  826. * A single row in a grid Footer section.
  827. *
  828. */
  829. public static class FooterRow extends StaticSection.StaticRow<FooterCell> {
  830. @Override
  831. protected FooterCell createCell() {
  832. return new FooterCell();
  833. }
  834. }
  835. private static class EditorRequestImpl<T> implements EditorRequest<T> {
  836. /**
  837. * A callback interface used to notify the invoker of the editor handler
  838. * of completed editor requests.
  839. *
  840. * @param <T>
  841. * the row data type
  842. */
  843. public static interface RequestCallback<T> {
  844. /**
  845. * The method that must be called when the request has been
  846. * processed correctly.
  847. *
  848. * @param request
  849. * the original request object
  850. */
  851. public void onSuccess(EditorRequest<T> request);
  852. /**
  853. * The method that must be called when processing the request has
  854. * produced an aborting error.
  855. *
  856. * @param request
  857. * the original request object
  858. */
  859. public void onError(EditorRequest<T> request);
  860. }
  861. private Grid<T> grid;
  862. private int rowIndex;
  863. private RequestCallback<T> callback;
  864. private boolean completed = false;
  865. public EditorRequestImpl(Grid<T> grid, int rowIndex,
  866. RequestCallback<T> callback) {
  867. this.grid = grid;
  868. this.rowIndex = rowIndex;
  869. this.callback = callback;
  870. }
  871. @Override
  872. public int getRowIndex() {
  873. return rowIndex;
  874. }
  875. @Override
  876. public T getRow() {
  877. return grid.getDataSource().getRow(rowIndex);
  878. }
  879. @Override
  880. public Grid<T> getGrid() {
  881. return grid;
  882. }
  883. @Override
  884. public Widget getWidget(Grid.Column<?, T> column) {
  885. Widget w = grid.getEditorWidget(column);
  886. assert w != null;
  887. return w;
  888. }
  889. private void complete(String errorMessage,
  890. Collection<Column<?, T>> errorColumns) {
  891. if (completed) {
  892. throw new IllegalStateException(
  893. "An EditorRequest must be completed exactly once");
  894. }
  895. completed = true;
  896. grid.getEditor().setErrorMessage(errorMessage);
  897. grid.getEditor().clearEditorColumnErrors();
  898. if (errorColumns != null) {
  899. for (Column<?, T> column : errorColumns) {
  900. grid.getEditor().setEditorColumnError(column, true);
  901. }
  902. }
  903. }
  904. @Override
  905. public void success() {
  906. complete(null, null);
  907. if (callback != null) {
  908. callback.onSuccess(this);
  909. }
  910. }
  911. @Override
  912. public void failure(String errorMessage,
  913. Collection<Grid.Column<?, T>> errorColumns) {
  914. complete(errorMessage, errorColumns);
  915. if (callback != null) {
  916. callback.onError(this);
  917. }
  918. }
  919. @Override
  920. public boolean isCompleted() {
  921. return completed;
  922. }
  923. }
  924. /**
  925. * An editor UI for Grid rows. A single Grid row at a time can be opened for
  926. * editing.
  927. */
  928. protected static class Editor<T> {
  929. public static final int KEYCODE_SHOW = KeyCodes.KEY_ENTER;
  930. public static final int KEYCODE_HIDE = KeyCodes.KEY_ESCAPE;
  931. private static final String ERROR_CLASS_NAME = "error";
  932. protected enum State {
  933. INACTIVE, ACTIVATING, BINDING, ACTIVE, SAVING
  934. }
  935. private Grid<T> grid;
  936. private EditorHandler<T> handler;
  937. private DivElement editorOverlay = DivElement.as(DOM.createDiv());
  938. private DivElement cellWrapper = DivElement.as(DOM.createDiv());
  939. private DivElement messageAndButtonsWrapper = DivElement.as(DOM
  940. .createDiv());
  941. private DivElement messageWrapper = DivElement.as(DOM.createDiv());
  942. private DivElement buttonsWrapper = DivElement.as(DOM.createDiv());
  943. // Element which contains the error message for the editor
  944. // Should only be added to the DOM when there's a message to show
  945. private DivElement message = DivElement.as(DOM.createDiv());
  946. private Map<Column<?, T>, Widget> columnToWidget = new HashMap<Column<?, T>, Widget>();
  947. private boolean enabled = false;
  948. private State state = State.INACTIVE;
  949. private int rowIndex = -1;
  950. private String styleName = null;
  951. private HandlerRegistration scrollHandler;
  952. private final Button saveButton;
  953. private final Button cancelButton;
  954. private static final int SAVE_TIMEOUT_MS = 5000;
  955. private final Timer saveTimeout = new Timer() {
  956. @Override
  957. public void run() {
  958. getLogger().warning(
  959. "Editor save action is taking longer than expected ("
  960. + SAVE_TIMEOUT_MS + "ms). Does your "
  961. + EditorHandler.class.getSimpleName()
  962. + " remember to call success() or fail()?");
  963. }
  964. };
  965. private final EditorRequestImpl.RequestCallback<T> saveRequestCallback = new EditorRequestImpl.RequestCallback<T>() {
  966. @Override
  967. public void onSuccess(EditorRequest<T> request) {
  968. if (state == State.SAVING) {
  969. cleanup();
  970. cancel();
  971. }
  972. }
  973. @Override
  974. public void onError(EditorRequest<T> request) {
  975. if (state == State.SAVING) {
  976. cleanup();
  977. // TODO probably not the most correct thing to do...
  978. getLogger().warning(
  979. "An error occurred when trying to save the "
  980. + "modified row");
  981. }
  982. }
  983. private void cleanup() {
  984. state = State.ACTIVE;
  985. setButtonsEnabled(true);
  986. saveTimeout.cancel();
  987. }
  988. };
  989. private static final int BIND_TIMEOUT_MS = 5000;
  990. private final Timer bindTimeout = new Timer() {
  991. @Override
  992. public void run() {
  993. getLogger().warning(
  994. "Editor bind action is taking longer than expected ("
  995. + BIND_TIMEOUT_MS + "ms). Does your "
  996. + EditorHandler.class.getSimpleName()
  997. + " remember to call success() or fail()?");
  998. }
  999. };
  1000. private final EditorRequestImpl.RequestCallback<T> bindRequestCallback = new EditorRequestImpl.RequestCallback<T>() {
  1001. @Override
  1002. public void onSuccess(EditorRequest<T> request) {
  1003. if (state == State.BINDING) {
  1004. state = State.ACTIVE;
  1005. bindTimeout.cancel();
  1006. showOverlay(grid.getEscalator().getBody()
  1007. .getRowElement(request.getRowIndex()));
  1008. }
  1009. }
  1010. @Override
  1011. public void onError(EditorRequest<T> request) {
  1012. if (state == State.BINDING) {
  1013. state = State.INACTIVE;
  1014. bindTimeout.cancel();
  1015. // TODO show something in the DOM as well?
  1016. getLogger().warning(
  1017. "An error occurred while trying to show the "
  1018. + "Grid editor");
  1019. grid.getEscalator().setScrollLocked(Direction.VERTICAL,
  1020. false);
  1021. }
  1022. }
  1023. };
  1024. /** A set of all the columns that display an error flag. */
  1025. private final Set<Column<?, T>> columnErrors = new HashSet<Grid.Column<?, T>>();
  1026. public Editor() {
  1027. saveButton = new Button();
  1028. saveButton.setText(GridConstants.DEFAULT_SAVE_CAPTION);
  1029. saveButton.addClickHandler(new ClickHandler() {
  1030. @Override
  1031. public void onClick(ClickEvent event) {
  1032. save();
  1033. }
  1034. });
  1035. cancelButton = new Button();
  1036. cancelButton.setText(GridConstants.DEFAULT_CANCEL_CAPTION);
  1037. cancelButton.addClickHandler(new ClickHandler() {
  1038. @Override
  1039. public void onClick(ClickEvent event) {
  1040. cancel();
  1041. }
  1042. });
  1043. }
  1044. public void setErrorMessage(String errorMessage) {
  1045. if (errorMessage == null) {
  1046. message.removeFromParent();
  1047. } else {
  1048. message.setInnerText(errorMessage);
  1049. if (message.getParentElement() == null) {
  1050. messageWrapper.appendChild(message);
  1051. }
  1052. }
  1053. }
  1054. public int getRow() {
  1055. return rowIndex;
  1056. }
  1057. /**
  1058. * Opens the editor over the row with the given index.
  1059. *
  1060. * @param rowIndex
  1061. * the index of the row to be edited
  1062. *
  1063. * @throws IllegalStateException
  1064. * if this editor is not enabled
  1065. * @throws IllegalStateException
  1066. * if this editor is already in edit mode
  1067. */
  1068. public void editRow(int rowIndex) {
  1069. if (!enabled) {
  1070. throw new IllegalStateException(
  1071. "Cannot edit row: editor is not enabled");
  1072. }
  1073. if (state != State.INACTIVE) {
  1074. throw new IllegalStateException(
  1075. "Cannot edit row: editor already in edit mode");
  1076. }
  1077. this.rowIndex = rowIndex;
  1078. state = State.ACTIVATING;
  1079. if (grid.getEscalator().getVisibleRowRange().contains(rowIndex)) {
  1080. show();
  1081. } else {
  1082. grid.scrollToRow(rowIndex, ScrollDestination.MIDDLE);
  1083. }
  1084. }
  1085. /**
  1086. * Cancels the currently active edit and hides the editor. Any changes
  1087. * that are not {@link #save() saved} are lost.
  1088. *
  1089. * @throws IllegalStateException
  1090. * if this editor is not enabled
  1091. * @throws IllegalStateException
  1092. * if this editor is not in edit mode
  1093. */
  1094. public void cancel() {
  1095. if (!enabled) {
  1096. throw new IllegalStateException(
  1097. "Cannot cancel edit: editor is not enabled");
  1098. }
  1099. if (state == State.INACTIVE) {
  1100. throw new IllegalStateException(
  1101. "Cannot cancel edit: editor is not in edit mode");
  1102. }
  1103. hideOverlay();
  1104. grid.getEscalator().setScrollLocked(Direction.VERTICAL, false);
  1105. EditorRequest<T> request = new EditorRequestImpl<T>(grid, rowIndex,
  1106. null);
  1107. handler.cancel(request);
  1108. state = State.INACTIVE;
  1109. }
  1110. /**
  1111. * Saves any unsaved changes to the data source and hides the editor.
  1112. *
  1113. * @throws IllegalStateException
  1114. * if this editor is not enabled
  1115. * @throws IllegalStateException
  1116. * if this editor is not in edit mode
  1117. */
  1118. public void save() {
  1119. if (!enabled) {
  1120. throw new IllegalStateException(
  1121. "Cannot save: editor is not enabled");
  1122. }
  1123. if (state != State.ACTIVE) {
  1124. throw new IllegalStateException(
  1125. "Cannot save: editor is not in edit mode");
  1126. }
  1127. state = State.SAVING;
  1128. setButtonsEnabled(false);
  1129. saveTimeout.schedule(SAVE_TIMEOUT_MS);
  1130. EditorRequest<T> request = new EditorRequestImpl<T>(grid, rowIndex,
  1131. saveRequestCallback);
  1132. handler.save(request);
  1133. }
  1134. /**
  1135. * Returns the handler responsible for binding data and editor widgets
  1136. * to this editor.
  1137. *
  1138. * @return the editor handler or null if not set
  1139. */
  1140. public EditorHandler<T> getHandler() {
  1141. return handler;
  1142. }
  1143. /**
  1144. * Sets the handler responsible for binding data and editor widgets to
  1145. * this editor.
  1146. *
  1147. * @param rowHandler
  1148. * the new editor handler
  1149. *
  1150. * @throws IllegalStateException
  1151. * if this editor is currently in edit mode
  1152. */
  1153. public void setHandler(EditorHandler<T> rowHandler) {
  1154. if (state != State.INACTIVE) {
  1155. throw new IllegalStateException(
  1156. "Cannot set EditorHandler: editor is currently in edit mode");
  1157. }
  1158. handler = rowHandler;
  1159. }
  1160. public boolean isEnabled() {
  1161. return enabled;
  1162. }
  1163. /**
  1164. * Sets the enabled state of this editor.
  1165. *
  1166. * @param enabled
  1167. * true if enabled, false otherwise
  1168. *
  1169. * @throws IllegalStateException
  1170. * if in edit mode and trying to disable
  1171. * @throws IllegalStateException
  1172. * if the editor handler is not set
  1173. */
  1174. public void setEnabled(boolean enabled) {
  1175. if (enabled == false && state != State.INACTIVE) {
  1176. throw new IllegalStateException(
  1177. "Cannot disable: editor is in edit mode");
  1178. } else if (enabled == true && getHandler() == null) {
  1179. throw new IllegalStateException(
  1180. "Cannot enable: EditorHandler not set");
  1181. }
  1182. this.enabled = enabled;
  1183. }
  1184. protected void show() {
  1185. if (state == State.ACTIVATING) {
  1186. state = State.BINDING;
  1187. bindTimeout.schedule(BIND_TIMEOUT_MS);
  1188. EditorRequest<T> request = new EditorRequestImpl<T>(grid,
  1189. rowIndex, bindRequestCallback);
  1190. handler.bind(request);
  1191. grid.getEscalator().setScrollLocked(Direction.VERTICAL, true);
  1192. }
  1193. }
  1194. protected void setGrid(final Grid<T> grid) {
  1195. assert grid != null : "Grid cannot be null";
  1196. assert this.grid == null : "Can only attach editor to Grid once";
  1197. this.grid = grid;
  1198. grid.addDataAvailableHandler(new DataAvailableHandler() {
  1199. @Override
  1200. public void onDataAvailable(DataAvailableEvent event) {
  1201. if (event.getAvailableRows().contains(rowIndex)) {
  1202. show();
  1203. }
  1204. }
  1205. });
  1206. }
  1207. protected State getState() {
  1208. return state;
  1209. }
  1210. protected void setState(State state) {
  1211. this.state = state;
  1212. }
  1213. /**
  1214. * Returns the editor widget associated with the given column. If the
  1215. * editor is not active or the column is not
  1216. * {@link Grid.Column#isEditable() editable}, returns null.
  1217. *
  1218. * @param column
  1219. * the column
  1220. * @return the widget if the editor is open and the column is editable,
  1221. * null otherwise
  1222. */
  1223. protected Widget getWidget(Column<?, T> column) {
  1224. return columnToWidget.get(column);
  1225. }
  1226. /**
  1227. * Opens the editor overlay over the given table row.
  1228. *
  1229. * @param tr
  1230. * the row to be edited
  1231. */
  1232. protected void showOverlay(TableRowElement tr) {
  1233. DivElement gridElement = DivElement.as(grid.getElement());
  1234. scrollHandler = grid.addScrollHandler(new ScrollHandler() {
  1235. @Override
  1236. public void onScroll(ScrollEvent event) {
  1237. updateHorizontalScrollPosition();
  1238. }
  1239. });
  1240. gridElement.appendChild(editorOverlay);
  1241. editorOverlay.appendChild(cellWrapper);
  1242. editorOverlay.appendChild(messageAndButtonsWrapper);
  1243. for (int i = 0; i < tr.getCells().getLength(); i++) {
  1244. Element cell = createCell(tr.getCells().getItem(i));
  1245. cellWrapper.appendChild(cell);
  1246. Column<?, T> column = grid.getColumn(i);
  1247. if (column.isEditable()) {
  1248. Widget editor = getHandler().getWidget(column);
  1249. if (editor != null) {
  1250. columnToWidget.put(column, editor);
  1251. attachWidget(editor, cell);
  1252. }
  1253. }
  1254. }
  1255. // Only add these elements once
  1256. if (!messageAndButtonsWrapper.isOrHasChild(messageWrapper)) {
  1257. messageAndButtonsWrapper.appendChild(messageWrapper);
  1258. messageAndButtonsWrapper.appendChild(buttonsWrapper);
  1259. }
  1260. attachWidget(saveButton, buttonsWrapper);
  1261. attachWidget(cancelButton, buttonsWrapper);
  1262. updateHorizontalScrollPosition();
  1263. AbstractRowContainer body = (AbstractRowContainer) grid
  1264. .getEscalator().getBody();
  1265. double rowTop = body.getRowTop(tr);
  1266. int bodyTop = body.getElement().getAbsoluteTop();
  1267. int gridTop = gridElement.getAbsoluteTop();
  1268. double overlayTop = rowTop + bodyTop - gridTop;
  1269. if (buttonsShouldBeRenderedBelow(tr)) {
  1270. // Default case, editor buttons are below the edited row
  1271. editorOverlay.getStyle().setTop(overlayTop, Unit.PX);
  1272. editorOverlay.getStyle().clearBottom();
  1273. } else {
  1274. // Move message and buttons wrapper on top of cell wrapper if
  1275. // there is not enough space visible space under and fix the
  1276. // overlay from the bottom
  1277. editorOverlay.appendChild(cellWrapper);
  1278. int gridHeight = grid.getElement().getOffsetHeight();
  1279. editorOverlay.getStyle()
  1280. .setBottom(
  1281. gridHeight - overlayTop - tr.getOffsetHeight(),
  1282. Unit.PX);
  1283. editorOverlay.getStyle().clearTop();
  1284. }
  1285. // Do not render over the vertical scrollbar
  1286. int nativeScrollbarSize = WidgetUtil.getNativeScrollbarSize();
  1287. if (nativeScrollbarSize > 0) {
  1288. editorOverlay.getStyle().setRight(nativeScrollbarSize, Unit.PX);
  1289. }
  1290. }
  1291. private boolean buttonsShouldBeRenderedBelow(TableRowElement tr) {
  1292. TableSectionElement tfoot = grid.escalator.getFooter().getElement();
  1293. double tfootPageTop = WidgetUtil.getBoundingClientRect(tfoot)
  1294. .getTop();
  1295. double trPageBottom = WidgetUtil.getBoundingClientRect(tr)
  1296. .getBottom();
  1297. int messageAndButtonsHeight = messageAndButtonsWrapper
  1298. .getOffsetHeight();
  1299. double bottomOfButtons = trPageBottom + messageAndButtonsHeight;
  1300. return bottomOfButtons < tfootPageTop;
  1301. }
  1302. protected void hideOverlay() {
  1303. for (Widget w : columnToWidget.values()) {
  1304. setParent(w, null);
  1305. }
  1306. columnToWidget.clear();
  1307. detachWidget(saveButton);
  1308. detachWidget(cancelButton);
  1309. editorOverlay.removeAllChildren();
  1310. cellWrapper.removeAllChildren();
  1311. editorOverlay.removeFromParent();
  1312. scrollHandler.removeHandler();
  1313. clearEditorColumnErrors();
  1314. }
  1315. protected void setStylePrimaryName(String primaryName) {
  1316. if (styleName != null) {
  1317. editorOverlay.removeClassName(styleName);
  1318. cellWrapper.removeClassName(styleName + "-cells");
  1319. messageAndButtonsWrapper.removeClassName(styleName + "-footer");
  1320. messageWrapper.removeClassName(styleName + "-message");
  1321. buttonsWrapper.removeClassName(styleName + "-buttons");
  1322. saveButton.removeStyleName(styleName + "-save");
  1323. cancelButton.removeStyleName(styleName + "-cancel");
  1324. }
  1325. styleName = primaryName + "-editor";
  1326. editorOverlay.setClassName(styleName);
  1327. cellWrapper.setClassName(styleName + "-cells");
  1328. messageAndButtonsWrapper.setClassName(styleName + "-footer");
  1329. messageWrapper.setClassName(styleName + "-message");
  1330. buttonsWrapper.setClassName(styleName + "-buttons");
  1331. saveButton.setStyleName(styleName + "-save");
  1332. cancelButton.setStyleName(styleName + "-cancel");
  1333. }
  1334. /**
  1335. * Creates an editor cell corresponding to the given table cell. The
  1336. * returned element is empty and has the same dimensions and position as
  1337. * the table cell.
  1338. *
  1339. * @param td
  1340. * the table cell used as a reference
  1341. * @return an editor cell corresponding to the given cell
  1342. */
  1343. protected Element createCell(TableCellElement td) {
  1344. DivElement cell = DivElement.as(DOM.createDiv());
  1345. double width = WidgetUtil
  1346. .getRequiredWidthBoundingClientRectDouble(td);
  1347. double height = WidgetUtil
  1348. .getRequiredHeightBoundingClientRectDouble(td);
  1349. setBounds(cell, td.getOffsetLeft(), td.getOffsetTop(), width,
  1350. height);
  1351. return cell;
  1352. }
  1353. private void attachWidget(Widget w, Element parent) {
  1354. parent.appendChild(w.getElement());
  1355. setParent(w, grid);
  1356. }
  1357. private void detachWidget(Widget w) {
  1358. setParent(w, null);
  1359. w.getElement().removeFromParent();
  1360. }
  1361. private static void setBounds(Element e, double left, double top,
  1362. double width, double height) {
  1363. Style style = e.getStyle();
  1364. style.setLeft(left, Unit.PX);
  1365. style.setTop(top, Unit.PX);
  1366. style.setWidth(width, Unit.PX);
  1367. style.setHeight(height, Unit.PX);
  1368. }
  1369. private void updateHorizontalScrollPosition() {
  1370. double scrollLeft = grid.getScrollLeft();
  1371. cellWrapper.getStyle().setLeft(-scrollLeft, Unit.PX);
  1372. }
  1373. protected void setGridEnabled(boolean enabled) {
  1374. // TODO: This should be informed to handler as well so possible
  1375. // fields can be disabled.
  1376. setButtonsEnabled(enabled);
  1377. }
  1378. private void setButtonsEnabled(boolean enabled) {
  1379. saveButton.setEnabled(enabled);
  1380. cancelButton.setEnabled(enabled);
  1381. }
  1382. public void setSaveCaption(String saveCaption)
  1383. throws IllegalArgumentException {
  1384. if (saveCaption == null) {
  1385. throw new IllegalArgumentException(
  1386. "Save caption cannot be null");
  1387. }
  1388. saveButton.setText(saveCaption);
  1389. }
  1390. public String getSaveCaption() {
  1391. return saveButton.getText();
  1392. }
  1393. public void setCancelCaption(String cancelCaption)
  1394. throws IllegalArgumentException {
  1395. if (cancelCaption == null) {
  1396. throw new IllegalArgumentException(
  1397. "Cancel caption cannot be null");
  1398. }
  1399. cancelButton.setText(cancelCaption);
  1400. }
  1401. public String getCancelCaption() {
  1402. return cancelButton.getText();
  1403. }
  1404. public void setEditorColumnError(Column<?, T> column, boolean hasError) {
  1405. if (state != State.ACTIVE && state != State.SAVING) {
  1406. throw new IllegalStateException("Cannot set cell error "
  1407. + "status: editor is neither active nor saving.");
  1408. }
  1409. if (isEditorColumnError(column) == hasError) {
  1410. return;
  1411. }
  1412. Element editorCell = getWidget(column).getElement()
  1413. .getParentElement();
  1414. if (hasError) {
  1415. editorCell.addClassName(ERROR_CLASS_NAME);
  1416. columnErrors.add(column);
  1417. } else {
  1418. editorCell.removeClassName(ERROR_CLASS_NAME);
  1419. columnErrors.remove(column);
  1420. }
  1421. }
  1422. public void clearEditorColumnErrors() {
  1423. /*
  1424. * editorOverlay has no children if it's not active, effectively
  1425. * making this loop a NOOP.
  1426. */
  1427. Element e = editorOverlay.getFirstChildElement();
  1428. while (e != null) {
  1429. e.removeClassName(ERROR_CLASS_NAME);
  1430. e = e.getNextSiblingElement();
  1431. }
  1432. columnErrors.clear();
  1433. }
  1434. public boolean isEditorColumnError(Column<?, T> column) {
  1435. return columnErrors.contains(column);
  1436. }
  1437. }
  1438. public static abstract class AbstractGridKeyEvent<HANDLER extends AbstractGridKeyEventHandler>
  1439. extends KeyEvent<HANDLER> {
  1440. private Grid<?> grid;
  1441. private final Type<HANDLER> associatedType = new Type<HANDLER>(
  1442. getBrowserEventType(), this);
  1443. private final CellReference<?> targetCell;
  1444. public AbstractGridKeyEvent(Grid<?> grid, CellReference<?> targetCell) {
  1445. this.grid = grid;
  1446. this.targetCell = targetCell;
  1447. }
  1448. protected abstract String getBrowserEventType();
  1449. /**
  1450. * Gets the Grid instance for this event.
  1451. *
  1452. * @return grid
  1453. */
  1454. public Grid<?> getGrid() {
  1455. return grid;
  1456. }
  1457. /**
  1458. * Gets the focused cell for this event.
  1459. *
  1460. * @return focused cell
  1461. */
  1462. public CellReference<?> getFocusedCell() {
  1463. return targetCell;
  1464. }
  1465. @Override
  1466. protected void dispatch(HANDLER handler) {
  1467. EventTarget target = getNativeEvent().getEventTarget();
  1468. if (Element.is(target)
  1469. && !grid.isElementInChildWidget(Element.as(target))) {
  1470. Section section = Section.FOOTER;
  1471. final RowContainer container = grid.cellFocusHandler.containerWithFocus;
  1472. if (container == grid.escalator.getHeader()) {
  1473. section = Section.HEADER;
  1474. } else if (container == grid.escalator.getBody()) {
  1475. section = Section.BODY;
  1476. }
  1477. doDispatch(handler, section);
  1478. }
  1479. }
  1480. protected abstract void doDispatch(HANDLER handler, Section section);
  1481. @Override
  1482. public Type<HANDLER> getAssociatedType() {
  1483. return associatedType;
  1484. }
  1485. }
  1486. public static abstract class AbstractGridMouseEvent<HANDLER extends AbstractGridMouseEventHandler>
  1487. extends MouseEvent<HANDLER> {
  1488. private Grid<?> grid;
  1489. private final CellReference<?> targetCell;
  1490. private final Type<HANDLER> associatedType = new Type<HANDLER>(
  1491. getBrowserEventType(), this);
  1492. public AbstractGridMouseEvent(Grid<?> grid, CellReference<?> targetCell) {
  1493. this.grid = grid;
  1494. this.targetCell = targetCell;
  1495. }
  1496. protected abstract String getBrowserEventType();
  1497. /**
  1498. * Gets the Grid instance for this event.
  1499. *
  1500. * @return grid
  1501. */
  1502. public Grid<?> getGrid() {
  1503. return grid;
  1504. }
  1505. /**
  1506. * Gets the reference of target cell for this event.
  1507. *
  1508. * @return target cell
  1509. */
  1510. public CellReference<?> getTargetCell() {
  1511. return targetCell;
  1512. }
  1513. @Override
  1514. protected void dispatch(HANDLER handler) {
  1515. EventTarget target = getNativeEvent().getEventTarget();
  1516. if (!Element.is(target)) {
  1517. // Target is not an element
  1518. return;
  1519. }
  1520. Element targetElement = Element.as(target);
  1521. if (grid.isElementInChildWidget(targetElement)) {
  1522. // Target is some widget inside of Grid
  1523. return;
  1524. }
  1525. final RowContainer container = grid.escalator
  1526. .findRowContainer(targetElement);
  1527. if (container == null) {
  1528. // No container for given element
  1529. return;
  1530. }
  1531. Section section = Section.FOOTER;
  1532. if (container == grid.escalator.getHeader()) {
  1533. section = Section.HEADER;
  1534. } else if (container == grid.escalator.getBody()) {
  1535. section = Section.BODY;
  1536. }
  1537. doDispatch(handler, section);
  1538. }
  1539. protected abstract void doDispatch(HANDLER handler, Section section);
  1540. @Override
  1541. public Type<HANDLER> getAssociatedType() {
  1542. return associatedType;
  1543. }
  1544. }
  1545. private static final String CUSTOM_STYLE_PROPERTY_NAME = "customStyle";
  1546. private EventCellReference<T> eventCell = new EventCellReference<T>(this);
  1547. private GridKeyDownEvent keyDown = new GridKeyDownEvent(this, eventCell);
  1548. private GridKeyUpEvent keyUp = new GridKeyUpEvent(this, eventCell);
  1549. private GridKeyPressEvent keyPress = new GridKeyPressEvent(this, eventCell);
  1550. private GridClickEvent clickEvent = new GridClickEvent(this, eventCell);
  1551. private GridDoubleClickEvent doubleClickEvent = new GridDoubleClickEvent(
  1552. this, eventCell);
  1553. private class CellFocusHandler {
  1554. private RowContainer containerWithFocus = escalator.getBody();
  1555. private int rowWithFocus = 0;
  1556. private Range cellFocusRange = Range.withLength(0, 1);
  1557. private int lastFocusedBodyRow = 0;
  1558. private int lastFocusedHeaderRow = 0;
  1559. private int lastFocusedFooterRow = 0;
  1560. private TableCellElement cellWithFocusStyle = null;
  1561. private TableRowElement rowWithFocusStyle = null;
  1562. public CellFocusHandler() {
  1563. sinkEvents(getNavigationEvents());
  1564. }
  1565. private Cell getFocusedCell() {
  1566. return new Cell(rowWithFocus, cellFocusRange.getStart(),
  1567. cellWithFocusStyle);
  1568. }
  1569. /**
  1570. * Sets style names for given cell when needed.
  1571. */
  1572. public void updateFocusedCellStyle(FlyweightCell cell,
  1573. RowContainer cellContainer) {
  1574. int cellRow = cell.getRow();
  1575. int cellColumn = cell.getColumn();
  1576. int colSpan = cell.getColSpan();
  1577. boolean columnHasFocus = Range.withLength(cellColumn, colSpan)
  1578. .intersects(cellFocusRange);
  1579. if (cellContainer == containerWithFocus) {
  1580. // Cell is in the current container
  1581. if (cellRow == rowWithFocus && columnHasFocus) {
  1582. if (cellWithFocusStyle != cell.getElement()) {
  1583. // Cell is correct but it does not have focused style
  1584. if (cellWithFocusStyle != null) {
  1585. // Remove old focus style
  1586. setStyleName(cellWithFocusStyle,
  1587. cellFocusStyleName, false);
  1588. }
  1589. cellWithFocusStyle = cell.getElement();
  1590. // Add focus style to correct cell.
  1591. setStyleName(cellWithFocusStyle, cellFocusStyleName,
  1592. true);
  1593. }
  1594. } else if (cellWithFocusStyle == cell.getElement()) {
  1595. // Due to escalator reusing cells, a new cell has the same
  1596. // element but is not the focused cell.
  1597. setStyleName(cellWithFocusStyle, cellFocusStyleName, false);
  1598. cellWithFocusStyle = null;
  1599. }
  1600. }
  1601. }
  1602. /**
  1603. * Sets focus style for the given row if needed.
  1604. *
  1605. * @param row
  1606. * a row object
  1607. */
  1608. public void updateFocusedRowStyle(Row row) {
  1609. if (rowWithFocus == row.getRow()
  1610. && containerWithFocus == escalator.getBody()) {
  1611. if (row.getElement() != rowWithFocusStyle) {
  1612. // Row should have focus style but does not have it.
  1613. if (rowWithFocusStyle != null) {
  1614. setStyleName(rowWithFocusStyle, rowFocusStyleName,
  1615. false);
  1616. }
  1617. rowWithFocusStyle = row.getElement();
  1618. setStyleName(rowWithFocusStyle, rowFocusStyleName, true);
  1619. }
  1620. } else if (rowWithFocusStyle == row.getElement()
  1621. || (containerWithFocus != escalator.getBody() && rowWithFocusStyle != null)) {
  1622. // Remove focus style.
  1623. setStyleName(rowWithFocusStyle, rowFocusStyleName, false);
  1624. rowWithFocusStyle = null;
  1625. }
  1626. }
  1627. /**
  1628. * Sets the currently focused.
  1629. *
  1630. * @param row
  1631. * the index of the row having focus
  1632. * @param column
  1633. * the index of the column having focus
  1634. * @param container
  1635. * the row container having focus
  1636. */
  1637. private void setCellFocus(int row, int column, RowContainer container) {
  1638. if (row == rowWithFocus && cellFocusRange.contains(column)
  1639. && container == this.containerWithFocus) {
  1640. refreshRow(rowWithFocus);
  1641. return;
  1642. }
  1643. int oldRow = rowWithFocus;
  1644. rowWithFocus = row;
  1645. Range oldRange = cellFocusRange;
  1646. if (container == escalator.getBody()) {
  1647. scrollToRow(rowWithFocus);
  1648. cellFocusRange = Range.withLength(column, 1);
  1649. } else {
  1650. int i = 0;
  1651. Element cell = container.getRowElement(rowWithFocus)
  1652. .getFirstChildElement();
  1653. do {
  1654. int colSpan = cell
  1655. .getPropertyInt(FlyweightCell.COLSPAN_ATTR);
  1656. Range cellRange = Range.withLength(i, colSpan);
  1657. if (cellRange.contains(column)) {
  1658. cellFocusRange = cellRange;
  1659. break;
  1660. }
  1661. cell = cell.getNextSiblingElement();
  1662. ++i;
  1663. } while (cell != null);
  1664. }
  1665. if (column >= escalator.getColumnConfiguration()
  1666. .getFrozenColumnCount()) {
  1667. escalator.scrollToColumn(column, ScrollDestination.ANY, 10);
  1668. }
  1669. if (this.containerWithFocus == container) {
  1670. if (oldRange.equals(cellFocusRange) && oldRow != rowWithFocus) {
  1671. refreshRow(oldRow);
  1672. } else {
  1673. refreshHeader();
  1674. refreshFooter();
  1675. }
  1676. } else {
  1677. RowContainer oldContainer = this.containerWithFocus;
  1678. this.containerWithFocus = container;
  1679. if (oldContainer == escalator.getBody()) {
  1680. lastFocusedBodyRow = oldRow;
  1681. } else if (oldContainer == escalator.getHeader()) {
  1682. lastFocusedHeaderRow = oldRow;
  1683. } else {
  1684. lastFocusedFooterRow = oldRow;
  1685. }
  1686. if (!oldRange.equals(cellFocusRange)) {
  1687. refreshHeader();
  1688. refreshFooter();
  1689. if (oldContainer == escalator.getBody()) {
  1690. oldContainer.refreshRows(oldRow, 1);
  1691. }
  1692. } else {
  1693. oldContainer.refreshRows(oldRow, 1);
  1694. }
  1695. }
  1696. refreshRow(rowWithFocus);
  1697. }
  1698. /**
  1699. * Sets focus on a cell.
  1700. *
  1701. * <p>
  1702. * <em>Note</em>: cell focus is not the same as JavaScript's
  1703. * {@code document.activeElement}.
  1704. *
  1705. * @param cell
  1706. * a cell object
  1707. */
  1708. public void setCellFocus(CellReference<T> cell) {
  1709. setCellFocus(cell.getRowIndex(), cell.getColumnIndex(),
  1710. escalator.findRowContainer(cell.getElement()));
  1711. }
  1712. /**
  1713. * Gets list of events that can be used for cell focusing.
  1714. *
  1715. * @return list of navigation related event types
  1716. */
  1717. public Collection<String> getNavigationEvents() {
  1718. return Arrays.asList(BrowserEvents.KEYDOWN, BrowserEvents.CLICK);
  1719. }
  1720. /**
  1721. * Handle events that can move the cell focus.
  1722. */
  1723. public void handleNavigationEvent(Event event, CellReference<T> cell) {
  1724. if (event.getType().equals(BrowserEvents.CLICK)) {
  1725. setCellFocus(cell);
  1726. // Grid should have focus when clicked.
  1727. getElement().focus();
  1728. } else if (event.getType().equals(BrowserEvents.KEYDOWN)) {
  1729. int newRow = rowWithFocus;
  1730. RowContainer newContainer = containerWithFocus;
  1731. int newColumn = cellFocusRange.getStart();
  1732. switch (event.getKeyCode()) {
  1733. case KeyCodes.KEY_DOWN:
  1734. ++newRow;
  1735. break;
  1736. case KeyCodes.KEY_UP:
  1737. --newRow;
  1738. break;
  1739. case KeyCodes.KEY_RIGHT:
  1740. if (cellFocusRange.getEnd() >= getColumns().size()) {
  1741. return;
  1742. }
  1743. newColumn = cellFocusRange.getEnd();
  1744. break;
  1745. case KeyCodes.KEY_LEFT:
  1746. if (newColumn == 0) {
  1747. return;
  1748. }
  1749. --newColumn;
  1750. break;
  1751. case KeyCodes.KEY_TAB:
  1752. if (event.getShiftKey()) {
  1753. newContainer = getPreviousContainer(containerWithFocus);
  1754. } else {
  1755. newContainer = getNextContainer(containerWithFocus);
  1756. }
  1757. if (newContainer == containerWithFocus) {
  1758. return;
  1759. }
  1760. break;
  1761. default:
  1762. return;
  1763. }
  1764. if (newContainer != containerWithFocus) {
  1765. if (newContainer == escalator.getBody()) {
  1766. newRow = lastFocusedBodyRow;
  1767. } else if (newContainer == escalator.getHeader()) {
  1768. newRow = lastFocusedHeaderRow;
  1769. } else {
  1770. newRow = lastFocusedFooterRow;
  1771. }
  1772. } else if (newRow < 0) {
  1773. newContainer = getPreviousContainer(newContainer);
  1774. if (newContainer == containerWithFocus) {
  1775. newRow = 0;
  1776. } else if (newContainer == escalator.getBody()) {
  1777. newRow = getLastVisibleRowIndex();
  1778. } else {
  1779. newRow = newContainer.getRowCount() - 1;
  1780. }
  1781. } else if (newRow >= containerWithFocus.getRowCount()) {
  1782. newContainer = getNextContainer(newContainer);
  1783. if (newContainer == containerWithFocus) {
  1784. newRow = containerWithFocus.getRowCount() - 1;
  1785. } else if (newContainer == escalator.getBody()) {
  1786. newRow = getFirstVisibleRowIndex();
  1787. } else {
  1788. newRow = 0;
  1789. }
  1790. }
  1791. if (newContainer.getRowCount() == 0) {
  1792. /*
  1793. * There are no rows in the container. Can't change the
  1794. * focused cell.
  1795. */
  1796. return;
  1797. }
  1798. event.preventDefault();
  1799. event.stopPropagation();
  1800. setCellFocus(newRow, newColumn, newContainer);
  1801. }
  1802. }
  1803. private RowContainer getPreviousContainer(RowContainer current) {
  1804. if (current == escalator.getFooter()) {
  1805. current = escalator.getBody();
  1806. } else if (current == escalator.getBody()) {
  1807. current = escalator.getHeader();
  1808. } else {
  1809. return current;
  1810. }
  1811. if (current.getRowCount() == 0) {
  1812. return getPreviousContainer(current);
  1813. }
  1814. return current;
  1815. }
  1816. private RowContainer getNextContainer(RowContainer current) {
  1817. if (current == escalator.getHeader()) {
  1818. current = escalator.getBody();
  1819. } else if (current == escalator.getBody()) {
  1820. current = escalator.getFooter();
  1821. } else {
  1822. return current;
  1823. }
  1824. if (current.getRowCount() == 0) {
  1825. return getNextContainer(current);
  1826. }
  1827. return current;
  1828. }
  1829. private void refreshRow(int row) {
  1830. containerWithFocus.refreshRows(row, 1);
  1831. }
  1832. /**
  1833. * Offsets the focused cell's range.
  1834. *
  1835. * @param offset
  1836. * offset for fixing focused cell's range
  1837. */
  1838. public void offsetRangeBy(int offset) {
  1839. cellFocusRange = cellFocusRange.offsetBy(offset);
  1840. }
  1841. /**
  1842. * Informs {@link CellFocusHandler} that certain range of rows has been
  1843. * added to the Grid body. {@link CellFocusHandler} will fix indices
  1844. * accordingly.
  1845. *
  1846. * @param added
  1847. * a range of added rows
  1848. */
  1849. public void rowsAddedToBody(Range added) {
  1850. boolean bodyHasFocus = (containerWithFocus == escalator.getBody());
  1851. boolean insertionIsAboveFocusedCell = (added.getStart() <= rowWithFocus);
  1852. if (bodyHasFocus && insertionIsAboveFocusedCell) {
  1853. rowWithFocus += added.length();
  1854. refreshRow(rowWithFocus);
  1855. }
  1856. }
  1857. /**
  1858. * Informs {@link CellFocusHandler} that certain range of rows has been
  1859. * removed from the Grid body. {@link CellFocusHandler} will fix indices
  1860. * accordingly.
  1861. *
  1862. * @param removed
  1863. * a range of removed rows
  1864. */
  1865. public void rowsRemovedFromBody(Range removed) {
  1866. if (containerWithFocus != escalator.getBody()) {
  1867. return;
  1868. } else if (!removed.contains(rowWithFocus)) {
  1869. if (removed.getStart() > rowWithFocus) {
  1870. return;
  1871. }
  1872. rowWithFocus = rowWithFocus - removed.length();
  1873. } else {
  1874. if (containerWithFocus.getRowCount() > removed.getEnd()) {
  1875. rowWithFocus = removed.getStart();
  1876. } else if (removed.getStart() > 0) {
  1877. rowWithFocus = removed.getStart() - 1;
  1878. } else {
  1879. if (escalator.getHeader().getRowCount() > 0) {
  1880. rowWithFocus = lastFocusedHeaderRow;
  1881. containerWithFocus = escalator.getHeader();
  1882. } else if (escalator.getFooter().getRowCount() > 0) {
  1883. rowWithFocus = lastFocusedFooterRow;
  1884. containerWithFocus = escalator.getFooter();
  1885. }
  1886. }
  1887. }
  1888. refreshRow(rowWithFocus);
  1889. }
  1890. }
  1891. public final class SelectionColumn extends Column<Boolean, T> {
  1892. private boolean initDone = false;
  1893. SelectionColumn(final Renderer<Boolean> selectColumnRenderer) {
  1894. super(selectColumnRenderer);
  1895. }
  1896. void initDone() {
  1897. if (getSelectionModel() instanceof SelectionModel.Multi
  1898. && header.getDefaultRow() != null) {
  1899. /*
  1900. * TODO: Currently the select all check box is shown when multi
  1901. * selection is in use. This might result in malfunctions if no
  1902. * SelectAllHandlers are present.
  1903. *
  1904. * Later on this could be fixed so that it check such handlers
  1905. * exist.
  1906. */
  1907. final SelectionModel.Multi<T> model = (Multi<T>) getSelectionModel();
  1908. final CheckBox checkBox = new CheckBox();
  1909. checkBox.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
  1910. @Override
  1911. public void onValueChange(ValueChangeEvent<Boolean> event) {
  1912. if (event.getValue()) {
  1913. fireEvent(new SelectAllEvent<T>(model));
  1914. } else {
  1915. model.deselectAll();
  1916. }
  1917. }
  1918. });
  1919. header.getDefaultRow().getCell(this).setWidget(checkBox);
  1920. }
  1921. setWidth(-1);
  1922. setEditable(false);
  1923. initDone = true;
  1924. }
  1925. @Override
  1926. public Column<Boolean, T> setWidth(double pixels) {
  1927. if (pixels != getWidth() && initDone) {
  1928. throw new UnsupportedOperationException("The selection "
  1929. + "column cannot be modified after init");
  1930. } else {
  1931. super.setWidth(pixels);
  1932. }
  1933. return this;
  1934. }
  1935. @Override
  1936. public Boolean getValue(T row) {
  1937. return Boolean.valueOf(isSelected(row));
  1938. }
  1939. @Override
  1940. public Column<Boolean, T> setExpandRatio(int ratio) {
  1941. throw new UnsupportedOperationException(
  1942. "can't change the expand ratio of the selection column");
  1943. }
  1944. @Override
  1945. public int getExpandRatio() {
  1946. return 0;
  1947. }
  1948. @Override
  1949. public Column<Boolean, T> setMaximumWidth(double pixels) {
  1950. throw new UnsupportedOperationException(
  1951. "can't change the maximum width of the selection column");
  1952. }
  1953. @Override
  1954. public double getMaximumWidth() {
  1955. return -1;
  1956. }
  1957. @Override
  1958. public Column<Boolean, T> setMinimumWidth(double pixels) {
  1959. throw new UnsupportedOperationException(
  1960. "can't change the minimum width of the selection column");
  1961. }
  1962. @Override
  1963. public double getMinimumWidth() {
  1964. return -1;
  1965. }
  1966. @Override
  1967. public Column<Boolean, T> setEditable(boolean editable) {
  1968. if (initDone) {
  1969. throw new UnsupportedOperationException(
  1970. "can't set the selection column editable");
  1971. }
  1972. super.setEditable(editable);
  1973. return this;
  1974. }
  1975. }
  1976. /**
  1977. * Helper class for performing sorting through the user interface. Controls
  1978. * the sort() method, reporting USER as the event originator. This is a
  1979. * completely internal class, and is, as such, safe to re-name should a more
  1980. * descriptive name come to mind.
  1981. */
  1982. private final class UserSorter {
  1983. private final Timer timer;
  1984. private boolean scheduledMultisort;
  1985. private Column<?, T> column;
  1986. private UserSorter() {
  1987. timer = new Timer() {
  1988. @Override
  1989. public void run() {
  1990. UserSorter.this.sort(column, scheduledMultisort);
  1991. }
  1992. };
  1993. }
  1994. /**
  1995. * Toggle sorting for a cell. If the multisort parameter is set to true,
  1996. * the cell's sort order is modified as a natural part of a multi-sort
  1997. * chain. If false, the sorting order is set to ASCENDING for that
  1998. * cell's column. If that column was already the only sorted column in
  1999. * the Grid, the sort direction is flipped.
  2000. *
  2001. * @param cell
  2002. * a valid cell reference
  2003. * @param multisort
  2004. * whether the sort command should act as a multi-sort stack
  2005. * or not
  2006. */
  2007. public void sort(Column<?, ?> column, boolean multisort) {
  2008. if (!columns.contains(column)) {
  2009. throw new IllegalArgumentException(
  2010. "Given column is not a column in this grid. "
  2011. + column.toString());
  2012. }
  2013. if (!column.isSortable()) {
  2014. return;
  2015. }
  2016. final SortOrder so = getSortOrder(column);
  2017. if (multisort) {
  2018. // If the sort order exists, replace existing value with its
  2019. // opposite
  2020. if (so != null) {
  2021. final int idx = sortOrder.indexOf(so);
  2022. sortOrder.set(idx, so.getOpposite());
  2023. } else {
  2024. // If it doesn't, just add a new sort order to the end of
  2025. // the list
  2026. sortOrder.add(new SortOrder(column));
  2027. }
  2028. } else {
  2029. // Since we're doing single column sorting, first clear the
  2030. // list. Then, if the sort order existed, add its opposite,
  2031. // otherwise just add a new sort value
  2032. int items = sortOrder.size();
  2033. sortOrder.clear();
  2034. if (so != null && items == 1) {
  2035. sortOrder.add(so.getOpposite());
  2036. } else {
  2037. sortOrder.add(new SortOrder(column));
  2038. }
  2039. }
  2040. // sortOrder has been changed; tell the Grid to re-sort itself by
  2041. // user request.
  2042. Grid.this.sort(true);
  2043. }
  2044. /**
  2045. * Perform a sort after a delay.
  2046. *
  2047. * @param delay
  2048. * delay, in milliseconds
  2049. */
  2050. public void sortAfterDelay(int delay, boolean multisort) {
  2051. column = eventCell.getColumn();
  2052. scheduledMultisort = multisort;
  2053. timer.schedule(delay);
  2054. }
  2055. /**
  2056. * Check if a delayed sort command has been issued but not yet carried
  2057. * out.
  2058. *
  2059. * @return a boolean value
  2060. */
  2061. public boolean isDelayedSortScheduled() {
  2062. return timer.isRunning();
  2063. }
  2064. /**
  2065. * Cancel a scheduled sort.
  2066. */
  2067. public void cancelDelayedSort() {
  2068. timer.cancel();
  2069. }
  2070. }
  2071. /** @see Grid#autoColumnWidthsRecalculator */
  2072. private class AutoColumnWidthsRecalculator {
  2073. private final ScheduledCommand calculateCommand = new ScheduledCommand() {
  2074. @Override
  2075. public void execute() {
  2076. if (!isScheduled) {
  2077. // something cancelled running this.
  2078. return;
  2079. }
  2080. if (header.markAsDirty || footer.markAsDirty) {
  2081. if (rescheduleCount < 10) {
  2082. /*
  2083. * Headers and footers are rendered as finally, this way
  2084. * we re-schedule this loop as finally, at the end of
  2085. * the queue, so that the headers have a chance to
  2086. * render themselves.
  2087. */
  2088. Scheduler.get().scheduleFinally(this);
  2089. rescheduleCount++;
  2090. } else {
  2091. /*
  2092. * We've tried too many times reschedule finally. Seems
  2093. * like something is being deferred. Let the queue
  2094. * execute and retry again.
  2095. */
  2096. rescheduleCount = 0;
  2097. Scheduler.get().scheduleDeferred(this);
  2098. }
  2099. } else if (dataIsBeingFetched) {
  2100. Scheduler.get().scheduleDeferred(this);
  2101. } else {
  2102. calculate();
  2103. }
  2104. }
  2105. };
  2106. private int rescheduleCount = 0;
  2107. private boolean isScheduled;
  2108. /**
  2109. * Calculates and applies column widths, taking into account fixed
  2110. * widths and column expand rules
  2111. *
  2112. * @param immediately
  2113. * <code>true</code> if the widths should be executed
  2114. * immediately (ignoring lazy loading completely), or
  2115. * <code>false</code> if the command should be run after a
  2116. * while (duplicate non-immediately invocations are ignored).
  2117. * @see Column#setWidth(double)
  2118. * @see Column#setExpandRatio(int)
  2119. * @see Column#setMinimumWidth(double)
  2120. * @see Column#setMaximumWidth(double)
  2121. */
  2122. public void schedule() {
  2123. if (!isScheduled) {
  2124. isScheduled = true;
  2125. Scheduler.get().scheduleFinally(calculateCommand);
  2126. }
  2127. }
  2128. private void calculate() {
  2129. isScheduled = false;
  2130. rescheduleCount = 0;
  2131. assert !dataIsBeingFetched : "Trying to calculate column widths even though data is still being fetched.";
  2132. if (columnsAreGuaranteedToBeWiderThanGrid()) {
  2133. applyColumnWidths();
  2134. } else {
  2135. applyColumnWidthsWithExpansion();
  2136. /*
  2137. * [[subpixelworkaround]] (6.2.2015, Henrik Paul) FIXME: just
  2138. * dump all the remaining pixels into the last column and
  2139. * whistle loudly
  2140. */
  2141. boolean dumpIntoLastColumn = false;
  2142. double escalatorWidth = escalator.getInnerWidth();
  2143. double occupiedWidth = 0;
  2144. for (Column column : getColumns()) {
  2145. occupiedWidth += column.getWidthActual();
  2146. if (column.getWidth() < 0 && column.getExpandRatio() != 0) {
  2147. dumpIntoLastColumn = true;
  2148. }
  2149. }
  2150. if (dumpIntoLastColumn) {
  2151. Column<?, T> lastColumn = getColumn(getColumnCount() - 1);
  2152. double width = Math.floor(lastColumn.getWidthActual()
  2153. + (escalatorWidth - occupiedWidth));
  2154. escalator.getColumnConfiguration().setColumnWidth(
  2155. getColumnCount() - 1, width);
  2156. }
  2157. }
  2158. }
  2159. private boolean columnsAreGuaranteedToBeWiderThanGrid() {
  2160. double freeSpace = escalator.getInnerWidth();
  2161. for (Column<?, ?> column : getColumns()) {
  2162. if (column.getWidth() >= 0) {
  2163. freeSpace -= column.getWidth();
  2164. } else if (column.getMinimumWidth() >= 0) {
  2165. freeSpace -= column.getMinimumWidth();
  2166. }
  2167. }
  2168. return freeSpace < 0;
  2169. }
  2170. @SuppressWarnings("boxing")
  2171. private void applyColumnWidths() {
  2172. /* Step 1: Apply all column widths as they are. */
  2173. Map<Integer, Double> selfWidths = new LinkedHashMap<Integer, Double>();
  2174. List<Column<?, T>> columns = getColumns();
  2175. for (int index = 0; index < columns.size(); index++) {
  2176. selfWidths.put(index, columns.get(index).getWidth());
  2177. }
  2178. Grid.this.escalator.getColumnConfiguration().setColumnWidths(
  2179. selfWidths);
  2180. /*
  2181. * Step 2: Make sure that each column ends up obeying their min/max
  2182. * width constraints if defined as autowidth. If constraints are
  2183. * violated, fix it.
  2184. */
  2185. Map<Integer, Double> constrainedWidths = new LinkedHashMap<Integer, Double>();
  2186. for (int index = 0; index < columns.size(); index++) {
  2187. Column<?, T> column = columns.get(index);
  2188. boolean hasAutoWidth = column.getWidth() < 0;
  2189. if (!hasAutoWidth) {
  2190. continue;
  2191. }
  2192. // TODO: bug: these don't honor the CSS max/min. :(
  2193. double actualWidth = column.getWidthActual();
  2194. if (actualWidth < getMinWidth(column)) {
  2195. constrainedWidths.put(index, column.getMinimumWidth());
  2196. } else if (actualWidth > getMaxWidth(column)) {
  2197. constrainedWidths.put(index, column.getMaximumWidth());
  2198. }
  2199. }
  2200. Grid.this.escalator.getColumnConfiguration().setColumnWidths(
  2201. constrainedWidths);
  2202. }
  2203. private void applyColumnWidthsWithExpansion() {
  2204. boolean someColumnExpands = false;
  2205. int totalRatios = 0;
  2206. double reservedPixels = 0;
  2207. final Set<Column<?, ?>> columnsToExpand = new HashSet<Column<?, ?>>();
  2208. /*
  2209. * Set all fixed widths and also calculate the size-to-fit widths
  2210. * for the autocalculated columns.
  2211. *
  2212. * This way we know with how many pixels we have left to expand the
  2213. * rest.
  2214. */
  2215. for (Column<?, ?> column : getColumns()) {
  2216. final double widthAsIs = column.getWidth();
  2217. final boolean isFixedWidth = widthAsIs >= 0;
  2218. final double widthFixed = Math.max(widthAsIs,
  2219. column.getMinimumWidth());
  2220. final int expandRatio = column.getExpandRatio();
  2221. if (isFixedWidth) {
  2222. column.doSetWidth(widthFixed);
  2223. } else {
  2224. column.doSetWidth(-1);
  2225. final double newWidth = column.getWidthActual();
  2226. final double maxWidth = getMaxWidth(column);
  2227. boolean shouldExpand = newWidth < maxWidth
  2228. && expandRatio > 0;
  2229. if (shouldExpand) {
  2230. totalRatios += expandRatio;
  2231. columnsToExpand.add(column);
  2232. someColumnExpands = true;
  2233. }
  2234. }
  2235. reservedPixels += column.getWidthActual();
  2236. }
  2237. /*
  2238. * If no column has a positive expand ratio, all columns with a
  2239. * negative expand ratio has an expand ratio. Columns with 0 expand
  2240. * ratio are excluded.
  2241. *
  2242. * This means that if we only define one column to have 0 expand, it
  2243. * will be the only one not to expand, while all the others expand.
  2244. */
  2245. if (!someColumnExpands) {
  2246. assert totalRatios == 0 : "totalRatios should've been 0";
  2247. assert columnsToExpand.isEmpty() : "columnsToExpand should've been empty";
  2248. for (Column<?, ?> column : getColumns()) {
  2249. final double width = column.getWidth();
  2250. final int expandRatio = column.getExpandRatio();
  2251. if (width < 0 && expandRatio < 0) {
  2252. totalRatios++;
  2253. columnsToExpand.add(column);
  2254. }
  2255. }
  2256. }
  2257. /*
  2258. * Now that we know how many pixels we need at the very least, we
  2259. * can distribute the remaining pixels to all columns according to
  2260. * their expand ratios.
  2261. */
  2262. // [[subpixelworkaround]] (6.2.2015, Henrik Paul) FIXME: ceil
  2263. double pixelsToDistribute = Math.ceil(escalator.getInnerWidth())
  2264. - reservedPixels;
  2265. if (pixelsToDistribute <= 0 || totalRatios <= 0) {
  2266. return;
  2267. }
  2268. /*
  2269. * Check for columns that hit their max width. Adjust
  2270. * pixelsToDistribute and totalRatios accordingly. Recheck. Stop
  2271. * when no new columns hit their max width
  2272. */
  2273. boolean aColumnHasMaxedOut;
  2274. do {
  2275. aColumnHasMaxedOut = false;
  2276. // [[subpixelworkaround]] (6.2.2015, Henrik Paul) FIXME floor
  2277. final double widthPerRatio = Math.floor(pixelsToDistribute
  2278. / totalRatios);
  2279. final Iterator<Column<?, ?>> i = columnsToExpand.iterator();
  2280. while (i.hasNext()) {
  2281. final Column<?, ?> column = i.next();
  2282. final int expandRatio = getExpandRatio(column,
  2283. someColumnExpands);
  2284. final double autoWidth = column.getWidthActual();
  2285. final double maxWidth = getMaxWidth(column);
  2286. final double widthCandidate = autoWidth + widthPerRatio
  2287. * expandRatio;
  2288. if (maxWidth <= widthCandidate) {
  2289. column.doSetWidth(maxWidth);
  2290. totalRatios -= expandRatio;
  2291. pixelsToDistribute -= maxWidth - autoWidth;
  2292. i.remove();
  2293. aColumnHasMaxedOut = true;
  2294. }
  2295. }
  2296. } while (aColumnHasMaxedOut);
  2297. if (totalRatios <= 0 && columnsToExpand.isEmpty()) {
  2298. return;
  2299. }
  2300. assert pixelsToDistribute > 0 : "We've run out of pixels to distribute ("
  2301. + pixelsToDistribute
  2302. + "px to "
  2303. + totalRatios
  2304. + " ratios between " + columnsToExpand.size() + " columns)";
  2305. assert totalRatios > 0 && !columnsToExpand.isEmpty() : "Bookkeeping out of sync. Ratios: "
  2306. + totalRatios + " Columns: " + columnsToExpand.size();
  2307. /*
  2308. * If we still have anything left, distribute the remaining pixels
  2309. * to the remaining columns.
  2310. */
  2311. // [[subpixelworkaround]] (6.2.2015, Henrik Paul) FIXME: floor
  2312. final double widthPerRatio = Math.floor(pixelsToDistribute
  2313. / totalRatios);
  2314. for (Column<?, ?> column : columnsToExpand) {
  2315. final int expandRatio = getExpandRatio(column,
  2316. someColumnExpands);
  2317. final double autoWidth = column.getWidthActual();
  2318. final double totalWidth = autoWidth + widthPerRatio
  2319. * expandRatio;
  2320. column.doSetWidth(totalWidth);
  2321. totalRatios -= expandRatio;
  2322. }
  2323. assert totalRatios == 0 : "Bookkeeping error: there were still some ratios left undistributed: "
  2324. + totalRatios;
  2325. /*
  2326. * Check the guarantees for minimum width and scoot back the columns
  2327. * that don't care.
  2328. */
  2329. boolean minWidthsCausedReflows;
  2330. do {
  2331. minWidthsCausedReflows = false;
  2332. /*
  2333. * First, let's check which columns were too cramped, and expand
  2334. * them. Also keep track on how many pixels we grew - we need to
  2335. * remove those pixels from other columns
  2336. */
  2337. double pixelsToRemoveFromOtherColumns = 0;
  2338. for (Column<?, T> column : getColumns()) {
  2339. /*
  2340. * We can't iterate over columnsToExpand, even though that
  2341. * would be convenient. This is because some column without
  2342. * an expand ratio might still have a min width - those
  2343. * wouldn't show up in that set.
  2344. */
  2345. // [[subpixelworkaround]] (6.2.2015, Henrik Paul) FIXME ceil
  2346. double minWidth = Math.ceil(getMinWidth(column));
  2347. double currentWidth = column.getWidthActual();
  2348. boolean hasAutoWidth = column.getWidth() < 0;
  2349. if (hasAutoWidth && currentWidth < minWidth) {
  2350. column.doSetWidth(minWidth);
  2351. pixelsToRemoveFromOtherColumns += (minWidth - currentWidth);
  2352. minWidthsCausedReflows = true;
  2353. /*
  2354. * Remove this column form the set if it exists. This
  2355. * way we make sure that it doesn't get shrunk in the
  2356. * next step.
  2357. */
  2358. columnsToExpand.remove(column);
  2359. }
  2360. }
  2361. /*
  2362. * Now we need to shrink the remaining columns according to
  2363. * their ratios. Recalculate the sum of remaining ratios.
  2364. */
  2365. totalRatios = 0;
  2366. for (Column<?, ?> column : columnsToExpand) {
  2367. totalRatios += getExpandRatio(column, someColumnExpands);
  2368. }
  2369. // [[subpixelworkaround]] (6.2.2015, Henrik Paul) FIXME: ceil
  2370. final double pixelsToRemovePerRatio = Math
  2371. .ceil(pixelsToRemoveFromOtherColumns / totalRatios);
  2372. for (Column<?, ?> column : columnsToExpand) {
  2373. final double pixelsToRemove = pixelsToRemovePerRatio
  2374. * getExpandRatio(column, someColumnExpands);
  2375. column.doSetWidth(column.getWidthActual() - pixelsToRemove);
  2376. }
  2377. } while (minWidthsCausedReflows);
  2378. }
  2379. private int getExpandRatio(Column<?, ?> column,
  2380. boolean someColumnExpands) {
  2381. int expandRatio = column.getExpandRatio();
  2382. if (expandRatio > 0) {
  2383. return expandRatio;
  2384. } else if (expandRatio < 0) {
  2385. assert !someColumnExpands : "No columns should've expanded";
  2386. return 1;
  2387. } else {
  2388. assert false : "this method should've not been called at all if expandRatio is 0";
  2389. return 0;
  2390. }
  2391. }
  2392. /**
  2393. * Returns the maximum width of the column, or {@link Double#MAX_VALUE}
  2394. * if defined as negative.
  2395. */
  2396. private double getMaxWidth(Column<?, ?> column) {
  2397. double maxWidth = column.getMaximumWidth();
  2398. if (maxWidth >= 0) {
  2399. return maxWidth;
  2400. } else {
  2401. return Double.MAX_VALUE;
  2402. }
  2403. }
  2404. /**
  2405. * Returns the minimum width of the column, or {@link Double#MIN_VALUE}
  2406. * if defined as negative.
  2407. */
  2408. private double getMinWidth(Column<?, ?> column) {
  2409. double minWidth = column.getMinimumWidth();
  2410. if (minWidth >= 0) {
  2411. return minWidth;
  2412. } else {
  2413. return Double.MIN_VALUE;
  2414. }
  2415. }
  2416. /**
  2417. * Check whether the auto width calculation is currently scheduled.
  2418. *
  2419. * @return <code>true</code> if auto width calculation is currently
  2420. * scheduled
  2421. */
  2422. public boolean isScheduled() {
  2423. return isScheduled;
  2424. }
  2425. }
  2426. /**
  2427. * Escalator used internally by grid to render the rows
  2428. */
  2429. private Escalator escalator = GWT.create(Escalator.class);
  2430. private final Header header = GWT.create(Header.class);
  2431. private final Footer footer = GWT.create(Footer.class);
  2432. /**
  2433. * List of columns in the grid. Order defines the visible order.
  2434. */
  2435. private List<Column<?, T>> columns = new ArrayList<Column<?, T>>();
  2436. /**
  2437. * The datasource currently in use. <em>Note:</em> it is <code>null</code>
  2438. * on initialization, but not after that.
  2439. */
  2440. private DataSource<T> dataSource;
  2441. /**
  2442. * Currently available row range in DataSource.
  2443. */
  2444. private Range currentDataAvailable = Range.withLength(0, 0);
  2445. /**
  2446. * The number of frozen columns, 0 freezes the selection column if
  2447. * displayed, -1 also prevents selection col from freezing.
  2448. */
  2449. private int frozenColumnCount = 0;
  2450. /**
  2451. * Current sort order. The (private) sort() method reads this list to
  2452. * determine the order in which to present rows.
  2453. */
  2454. private List<SortOrder> sortOrder = new ArrayList<SortOrder>();
  2455. private Renderer<Boolean> selectColumnRenderer = null;
  2456. private SelectionColumn selectionColumn;
  2457. private String rowStripeStyleName;
  2458. private String rowHasDataStyleName;
  2459. private String rowSelectedStyleName;
  2460. private String cellFocusStyleName;
  2461. private String rowFocusStyleName;
  2462. /**
  2463. * Current selection model.
  2464. */
  2465. private SelectionModel<T> selectionModel;
  2466. protected final CellFocusHandler cellFocusHandler;
  2467. private final UserSorter sorter = new UserSorter();
  2468. private final Editor<T> editor = GWT.create(Editor.class);
  2469. private boolean dataIsBeingFetched = false;
  2470. /**
  2471. * The cell a click event originated from
  2472. * <p>
  2473. * This is a workaround to make Chrome work like Firefox. In Chrome,
  2474. * normally if you start a drag on one cell and release on:
  2475. * <ul>
  2476. * <li>that same cell, the click event is that {@code <td>}.
  2477. * <li>a cell on that same row, the click event is the parent {@code <tr>}.
  2478. * <li>a cell on another row, the click event is the table section ancestor
  2479. * ({@code <thead>}, {@code <tbody>} or {@code <tfoot>}).
  2480. * </ul>
  2481. *
  2482. * @see #onBrowserEvent(Event)
  2483. */
  2484. private Cell cellOnPrevMouseDown;
  2485. /**
  2486. * A scheduled command to re-evaluate the widths of <em>all columns</em>
  2487. * that have calculated widths. Most probably called because
  2488. * minwidth/maxwidth/expandratio has changed.
  2489. */
  2490. private final AutoColumnWidthsRecalculator autoColumnWidthsRecalculator = new AutoColumnWidthsRecalculator();
  2491. private boolean enabled = true;
  2492. /**
  2493. * Enumeration for easy setting of selection mode.
  2494. */
  2495. public enum SelectionMode {
  2496. /**
  2497. * Shortcut for {@link SelectionModelSingle}.
  2498. */
  2499. SINGLE {
  2500. @Override
  2501. protected <T> SelectionModel<T> createModel() {
  2502. return new SelectionModelSingle<T>();
  2503. }
  2504. },
  2505. /**
  2506. * Shortcut for {@link SelectionModelMulti}.
  2507. */
  2508. MULTI {
  2509. @Override
  2510. protected <T> SelectionModel<T> createModel() {
  2511. return new SelectionModelMulti<T>();
  2512. }
  2513. },
  2514. /**
  2515. * Shortcut for {@link SelectionModelNone}.
  2516. */
  2517. NONE {
  2518. @Override
  2519. protected <T> SelectionModel<T> createModel() {
  2520. return new SelectionModelNone<T>();
  2521. }
  2522. };
  2523. protected abstract <T> SelectionModel<T> createModel();
  2524. }
  2525. /**
  2526. * Base class for grid columns internally used by the Grid. The user should
  2527. * use {@link Column} when creating new columns.
  2528. *
  2529. * @param <C>
  2530. * the column type
  2531. *
  2532. * @param <T>
  2533. * the row type
  2534. */
  2535. public static abstract class Column<C, T> {
  2536. /**
  2537. * Default renderer for GridColumns. Renders everything into text
  2538. * through {@link Object#toString()}.
  2539. */
  2540. private final class DefaultTextRenderer implements Renderer<Object> {
  2541. boolean warned = false;
  2542. private final String DEFAULT_RENDERER_WARNING = "This column uses a dummy default TextRenderer. "
  2543. + "A more suitable renderer should be set using the setRenderer() method.";
  2544. @Override
  2545. public void render(RendererCellReference cell, Object data) {
  2546. if (!warned && !(data instanceof String)) {
  2547. getLogger().warning(
  2548. Column.this.toString() + ": "
  2549. + DEFAULT_RENDERER_WARNING);
  2550. warned = true;
  2551. }
  2552. final String text;
  2553. if (data == null) {
  2554. text = "";
  2555. } else {
  2556. text = data.toString();
  2557. }
  2558. cell.getElement().setInnerText(text);
  2559. }
  2560. }
  2561. /**
  2562. * the column is associated with
  2563. */
  2564. private Grid<T> grid;
  2565. /**
  2566. * Width of column in pixels as {@link #setWidth(double)} has been
  2567. * called
  2568. */
  2569. private double widthUser = GridConstants.DEFAULT_COLUMN_WIDTH_PX;
  2570. /**
  2571. * Renderer for rendering a value into the cell
  2572. */
  2573. private Renderer<? super C> bodyRenderer;
  2574. private boolean sortable = false;
  2575. private boolean editable = true;
  2576. private String headerCaption = "";
  2577. private double minimumWidthPx = GridConstants.DEFAULT_MIN_WIDTH;
  2578. private double maximumWidthPx = GridConstants.DEFAULT_MAX_WIDTH;
  2579. private int expandRatio = GridConstants.DEFAULT_EXPAND_RATIO;
  2580. /**
  2581. * Constructs a new column with a simple TextRenderer.
  2582. */
  2583. public Column() {
  2584. setRenderer(new DefaultTextRenderer());
  2585. }
  2586. /**
  2587. * Constructs a new column with a simple TextRenderer.
  2588. *
  2589. * @param caption
  2590. * The header caption for this column
  2591. *
  2592. * @throws IllegalArgumentException
  2593. * if given header caption is null
  2594. */
  2595. public Column(String caption) throws IllegalArgumentException {
  2596. this();
  2597. setHeaderCaption(caption);
  2598. }
  2599. /**
  2600. * Constructs a new column with a custom renderer.
  2601. *
  2602. * @param renderer
  2603. * The renderer to use for rendering the cells
  2604. *
  2605. * @throws IllegalArgumentException
  2606. * if given Renderer is null
  2607. */
  2608. public Column(Renderer<? super C> renderer)
  2609. throws IllegalArgumentException {
  2610. setRenderer(renderer);
  2611. }
  2612. /**
  2613. * Constructs a new column with a custom renderer.
  2614. *
  2615. * @param renderer
  2616. * The renderer to use for rendering the cells
  2617. * @param caption
  2618. * The header caption for this column
  2619. *
  2620. * @throws IllegalArgumentException
  2621. * if given Renderer or header caption is null
  2622. */
  2623. public Column(String caption, Renderer<? super C> renderer)
  2624. throws IllegalArgumentException {
  2625. this(renderer);
  2626. setHeaderCaption(caption);
  2627. }
  2628. /**
  2629. * Internally used by the grid to set itself
  2630. *
  2631. * @param grid
  2632. */
  2633. private void setGrid(Grid<T> grid) {
  2634. if (this.grid != null && grid != null) {
  2635. // Trying to replace grid
  2636. throw new IllegalStateException("Column already is attached "
  2637. + "to a grid. Remove the column first from the grid "
  2638. + "and then add it. (in: " + toString() + ")");
  2639. }
  2640. if (this.grid != null) {
  2641. this.grid.recalculateColumnWidths();
  2642. }
  2643. this.grid = grid;
  2644. if (this.grid != null) {
  2645. this.grid.recalculateColumnWidths();
  2646. updateHeader();
  2647. }
  2648. }
  2649. /**
  2650. * Sets a header caption for this column.
  2651. *
  2652. * @param caption
  2653. * The header caption for this column
  2654. * @return the column itself
  2655. *
  2656. * @throws IllegalArgumentException
  2657. * if given caption text is null
  2658. */
  2659. public Column<C, T> setHeaderCaption(String caption) {
  2660. if (caption == null) {
  2661. throw new IllegalArgumentException("Caption cannot be null.");
  2662. }
  2663. if (!this.headerCaption.equals(caption)) {
  2664. this.headerCaption = caption;
  2665. if (grid != null) {
  2666. updateHeader();
  2667. }
  2668. }
  2669. return this;
  2670. }
  2671. private void updateHeader() {
  2672. HeaderRow row = grid.getHeader().getDefaultRow();
  2673. if (row != null) {
  2674. row.getCell(this).setText(headerCaption);
  2675. }
  2676. }
  2677. /**
  2678. * Returns the data that should be rendered into the cell. By default
  2679. * returning Strings and Widgets are supported. If the return type is a
  2680. * String then it will be treated as preformatted text.
  2681. * <p>
  2682. * To support other types you will need to pass a custom renderer to the
  2683. * column via the column constructor.
  2684. *
  2685. * @param row
  2686. * The row object that provides the cell content.
  2687. *
  2688. * @return The cell content
  2689. */
  2690. public abstract C getValue(T row);
  2691. /**
  2692. * The renderer to render the cell with. By default renders the data as
  2693. * a String or adds the widget into the cell if the column type is of
  2694. * widget type.
  2695. *
  2696. * @return The renderer to render the cell content with
  2697. */
  2698. public Renderer<? super C> getRenderer() {
  2699. return bodyRenderer;
  2700. }
  2701. /**
  2702. * Sets a custom {@link Renderer} for this column.
  2703. *
  2704. * @param renderer
  2705. * The renderer to use for rendering the cells
  2706. * @return the column itself
  2707. *
  2708. * @throws IllegalArgumentException
  2709. * if given Renderer is null
  2710. */
  2711. public Column<C, T> setRenderer(Renderer<? super C> renderer)
  2712. throws IllegalArgumentException {
  2713. if (renderer == null) {
  2714. throw new IllegalArgumentException("Renderer cannot be null.");
  2715. }
  2716. bodyRenderer = renderer;
  2717. if (grid != null) {
  2718. grid.refreshBody();
  2719. }
  2720. return this;
  2721. }
  2722. /**
  2723. * Sets the pixel width of the column. Use a negative value for the grid
  2724. * to autosize column based on content and available space.
  2725. * <p>
  2726. * This action is done "finally", once the current execution loop
  2727. * returns. This is done to reduce overhead of unintentionally always
  2728. * recalculate all columns, when modifying several columns at once.
  2729. *
  2730. * @param pixels
  2731. * the width in pixels or negative for auto sizing
  2732. */
  2733. public Column<C, T> setWidth(double pixels) {
  2734. if (!WidgetUtil.pixelValuesEqual(widthUser, pixels)) {
  2735. widthUser = pixels;
  2736. scheduleColumnWidthRecalculator();
  2737. }
  2738. return this;
  2739. }
  2740. void doSetWidth(double pixels) {
  2741. if (grid != null) {
  2742. int index = grid.columns.indexOf(this);
  2743. ColumnConfiguration conf = grid.escalator
  2744. .getColumnConfiguration();
  2745. conf.setColumnWidth(index, pixels);
  2746. }
  2747. }
  2748. /**
  2749. * Returns the pixel width of the column as given by the user.
  2750. * <p>
  2751. * <em>Note:</em> If a negative value was given to
  2752. * {@link #setWidth(double)}, that same negative value is returned here.
  2753. *
  2754. * @return pixel width of the column, or a negative number if the column
  2755. * width has been automatically calculated.
  2756. * @see #setWidth(double)
  2757. * @see #getWidthActual()
  2758. */
  2759. public double getWidth() {
  2760. return widthUser;
  2761. }
  2762. /**
  2763. * Returns the effective pixel width of the column.
  2764. * <p>
  2765. * This differs from {@link #getWidth()} only when the column has been
  2766. * automatically resized.
  2767. *
  2768. * @return pixel width of the column.
  2769. */
  2770. public double getWidthActual() {
  2771. return grid.escalator.getColumnConfiguration()
  2772. .getColumnWidthActual(grid.columns.indexOf(this));
  2773. }
  2774. void reapplyWidth() {
  2775. scheduleColumnWidthRecalculator();
  2776. }
  2777. /**
  2778. * Enables sort indicators for the grid.
  2779. * <p>
  2780. * <b>Note:</b>The API can still sort the column even if this is set to
  2781. * <code>false</code>.
  2782. *
  2783. * @param sortable
  2784. * <code>true</code> when column sort indicators are visible.
  2785. * @return the column itself
  2786. */
  2787. public Column<C, T> setSortable(boolean sortable) {
  2788. if (this.sortable != sortable) {
  2789. this.sortable = sortable;
  2790. if (grid != null) {
  2791. grid.refreshHeader();
  2792. }
  2793. }
  2794. return this;
  2795. }
  2796. /**
  2797. * Are sort indicators shown for the column.
  2798. *
  2799. * @return <code>true</code> if the column is sortable
  2800. */
  2801. public boolean isSortable() {
  2802. return sortable;
  2803. }
  2804. @Override
  2805. public String toString() {
  2806. String details = "";
  2807. if (headerCaption != null && !headerCaption.isEmpty()) {
  2808. details += "header:\"" + headerCaption + "\" ";
  2809. } else {
  2810. details += "header:empty ";
  2811. }
  2812. if (grid != null) {
  2813. int index = grid.getColumns().indexOf(this);
  2814. if (index != -1) {
  2815. details += "attached:#" + index + " ";
  2816. } else {
  2817. details += "attached:unindexed ";
  2818. }
  2819. } else {
  2820. details += "detached ";
  2821. }
  2822. details += "sortable:" + sortable + " ";
  2823. return getClass().getSimpleName() + "[" + details.trim() + "]";
  2824. }
  2825. /**
  2826. * Sets the minimum width for this column.
  2827. * <p>
  2828. * This defines the minimum guaranteed pixel width of the column
  2829. * <em>when it is set to expand</em>.
  2830. * <p>
  2831. * This action is done "finally", once the current execution loop
  2832. * returns. This is done to reduce overhead of unintentionally always
  2833. * recalculate all columns, when modifying several columns at once.
  2834. *
  2835. * @param pixels
  2836. * the minimum width
  2837. * @return this column
  2838. */
  2839. public Column<C, T> setMinimumWidth(double pixels) {
  2840. final double maxwidth = getMaximumWidth();
  2841. if (pixels >= 0 && pixels > maxwidth && maxwidth >= 0) {
  2842. throw new IllegalArgumentException("New minimum width ("
  2843. + pixels + ") was greater than maximum width ("
  2844. + maxwidth + ")");
  2845. }
  2846. if (minimumWidthPx != pixels) {
  2847. minimumWidthPx = pixels;
  2848. scheduleColumnWidthRecalculator();
  2849. }
  2850. return this;
  2851. }
  2852. /**
  2853. * Sets the maximum width for this column.
  2854. * <p>
  2855. * This defines the maximum allowed pixel width of the column
  2856. * <em>when it is set to expand</em>.
  2857. * <p>
  2858. * This action is done "finally", once the current execution loop
  2859. * returns. This is done to reduce overhead of unintentionally always
  2860. * recalculate all columns, when modifying several columns at once.
  2861. *
  2862. * @param pixels
  2863. * the maximum width
  2864. * @param immediately
  2865. * <code>true</code> if the widths should be executed
  2866. * immediately (ignoring lazy loading completely), or
  2867. * <code>false</code> if the command should be run after a
  2868. * while (duplicate non-immediately invocations are ignored).
  2869. * @return this column
  2870. */
  2871. public Column<C, T> setMaximumWidth(double pixels) {
  2872. final double minwidth = getMinimumWidth();
  2873. if (pixels >= 0 && pixels < minwidth && minwidth >= 0) {
  2874. throw new IllegalArgumentException("New maximum width ("
  2875. + pixels + ") was less than minimum width (" + minwidth
  2876. + ")");
  2877. }
  2878. if (maximumWidthPx != pixels) {
  2879. maximumWidthPx = pixels;
  2880. scheduleColumnWidthRecalculator();
  2881. }
  2882. return this;
  2883. }
  2884. /**
  2885. * Sets the ratio with which the column expands.
  2886. * <p>
  2887. * By default, all columns expand equally (treated as if all of them had
  2888. * an expand ratio of 1). Once at least one column gets a defined expand
  2889. * ratio, the implicit expand ratio is removed, and only the defined
  2890. * expand ratios are taken into account.
  2891. * <p>
  2892. * If a column has a defined width ({@link #setWidth(double)}), it
  2893. * overrides this method's effects.
  2894. * <p>
  2895. * <em>Example:</em> A grid with three columns, with expand ratios 0, 1
  2896. * and 2, respectively. The column with a <strong>ratio of 0 is exactly
  2897. * as wide as its contents requires</strong>. The column with a ratio of
  2898. * 1 is as wide as it needs, <strong>plus a third of any excess
  2899. * space</strong>, bceause we have 3 parts total, and this column
  2900. * reservs only one of those. The column with a ratio of 2, is as wide
  2901. * as it needs to be, <strong>plus two thirds</strong> of the excess
  2902. * width.
  2903. * <p>
  2904. * This action is done "finally", once the current execution loop
  2905. * returns. This is done to reduce overhead of unintentionally always
  2906. * recalculate all columns, when modifying several columns at once.
  2907. *
  2908. * @param expandRatio
  2909. * the expand ratio of this column. {@code 0} to not have it
  2910. * expand at all. A negative number to clear the expand
  2911. * value.
  2912. * @return this column
  2913. */
  2914. public Column<C, T> setExpandRatio(int ratio) {
  2915. if (expandRatio != ratio) {
  2916. expandRatio = ratio;
  2917. scheduleColumnWidthRecalculator();
  2918. }
  2919. return this;
  2920. }
  2921. /**
  2922. * Clears the column's expand ratio.
  2923. * <p>
  2924. * Same as calling {@link #setExpandRatio(int) setExpandRatio(-1)}
  2925. *
  2926. * @return this column
  2927. */
  2928. public Column<C, T> clearExpandRatio() {
  2929. return setExpandRatio(-1);
  2930. }
  2931. /**
  2932. * Gets the minimum width for this column.
  2933. *
  2934. * @return the minimum width for this column
  2935. * @see #setMinimumWidth(double)
  2936. */
  2937. public double getMinimumWidth() {
  2938. return minimumWidthPx;
  2939. }
  2940. /**
  2941. * Gets the maximum width for this column.
  2942. *
  2943. * @return the maximum width for this column
  2944. * @see #setMaximumWidth(double)
  2945. */
  2946. public double getMaximumWidth() {
  2947. return maximumWidthPx;
  2948. }
  2949. /**
  2950. * Gets the expand ratio for this column.
  2951. *
  2952. * @return the expand ratio for this column
  2953. * @see #setExpandRatio(int)
  2954. */
  2955. public int getExpandRatio() {
  2956. return expandRatio;
  2957. }
  2958. /**
  2959. * Sets whether the values in this column should be editable by the user
  2960. * when the row editor is active. By default columns are editable.
  2961. *
  2962. * @param editable
  2963. * {@code true} to set this column editable, {@code false}
  2964. * otherwise
  2965. * @return this column
  2966. *
  2967. * @throws IllegalStateException
  2968. * if the editor is currently active
  2969. *
  2970. * @see Grid#editRow(int)
  2971. * @see Grid#isEditorActive()
  2972. */
  2973. public Column<C, T> setEditable(boolean editable) {
  2974. if (editable != this.editable && grid.isEditorActive()) {
  2975. throw new IllegalStateException(
  2976. "Cannot change column editable status while the editor is active");
  2977. }
  2978. this.editable = editable;
  2979. return this;
  2980. }
  2981. /**
  2982. * Returns whether the values in this column are editable by the user
  2983. * when the row editor is active.
  2984. *
  2985. * @return {@code true} if this column is editable, {@code false}
  2986. * otherwise
  2987. *
  2988. * @see #setEditable(boolean)
  2989. */
  2990. public boolean isEditable() {
  2991. return editable;
  2992. }
  2993. private void scheduleColumnWidthRecalculator() {
  2994. if (grid != null) {
  2995. grid.recalculateColumnWidths();
  2996. } else {
  2997. /*
  2998. * NOOP
  2999. *
  3000. * Since setGrid() will call reapplyWidths as the colum is
  3001. * attached to a grid, it will call setWidth, which, in turn,
  3002. * will call this method again. Therefore, it's guaranteed that
  3003. * the recalculation is scheduled eventually, once the column is
  3004. * attached to a grid.
  3005. */
  3006. }
  3007. }
  3008. }
  3009. protected class BodyUpdater implements EscalatorUpdater {
  3010. @Override
  3011. public void preAttach(Row row, Iterable<FlyweightCell> cellsToAttach) {
  3012. int rowIndex = row.getRow();
  3013. rowReference.set(rowIndex, getDataSource().getRow(rowIndex),
  3014. row.getElement());
  3015. for (FlyweightCell cell : cellsToAttach) {
  3016. Renderer<?> renderer = findRenderer(cell);
  3017. if (renderer instanceof ComplexRenderer) {
  3018. try {
  3019. rendererCellReference.set(cell,
  3020. getColumn(cell.getColumn()));
  3021. ((ComplexRenderer<?>) renderer)
  3022. .init(rendererCellReference);
  3023. } catch (RuntimeException e) {
  3024. getLogger().log(
  3025. Level.SEVERE,
  3026. "Error initing cell in column "
  3027. + cell.getColumn(), e);
  3028. }
  3029. }
  3030. }
  3031. }
  3032. @Override
  3033. public void postAttach(Row row, Iterable<FlyweightCell> attachedCells) {
  3034. for (FlyweightCell cell : attachedCells) {
  3035. Renderer<?> renderer = findRenderer(cell);
  3036. if (renderer instanceof WidgetRenderer) {
  3037. try {
  3038. WidgetRenderer<?, ?> widgetRenderer = (WidgetRenderer<?, ?>) renderer;
  3039. Widget widget = widgetRenderer.createWidget();
  3040. assert widget != null : "WidgetRenderer.createWidget() returned null. It should return a widget.";
  3041. assert widget.getParent() == null : "WidgetRenderer.createWidget() returned a widget which already is attached.";
  3042. assert cell.getElement().getChildCount() == 0 : "Cell content should be empty when adding Widget";
  3043. // Physical attach
  3044. cell.getElement().appendChild(widget.getElement());
  3045. // Logical attach
  3046. setParent(widget, Grid.this);
  3047. } catch (RuntimeException e) {
  3048. getLogger().log(
  3049. Level.SEVERE,
  3050. "Error attaching child widget in column "
  3051. + cell.getColumn(), e);
  3052. }
  3053. }
  3054. }
  3055. }
  3056. @Override
  3057. public void update(Row row, Iterable<FlyweightCell> cellsToUpdate) {
  3058. int rowIndex = row.getRow();
  3059. TableRowElement rowElement = row.getElement();
  3060. T rowData = dataSource.getRow(rowIndex);
  3061. boolean hasData = rowData != null;
  3062. /*
  3063. * TODO could be more efficient to build a list of all styles that
  3064. * should be used and update the element only once instead of
  3065. * attempting to update only the ones that have changed.
  3066. */
  3067. // Assign stylename for rows with data
  3068. boolean usedToHaveData = rowElement
  3069. .hasClassName(rowHasDataStyleName);
  3070. if (usedToHaveData != hasData) {
  3071. setStyleName(rowElement, rowHasDataStyleName, hasData);
  3072. }
  3073. boolean isEvenIndex = (row.getRow() % 2 == 0);
  3074. setStyleName(rowElement, rowStripeStyleName, !isEvenIndex);
  3075. rowReference.set(rowIndex, rowData, rowElement);
  3076. if (hasData) {
  3077. setStyleName(rowElement, rowSelectedStyleName,
  3078. isSelected(rowData));
  3079. if (rowStyleGenerator != null) {
  3080. try {
  3081. String rowStylename = rowStyleGenerator
  3082. .getStyle(rowReference);
  3083. setCustomStyleName(rowElement, rowStylename);
  3084. } catch (RuntimeException e) {
  3085. getLogger().log(
  3086. Level.SEVERE,
  3087. "Error generating styles for row "
  3088. + row.getRow(), e);
  3089. }
  3090. } else {
  3091. // Remove in case there was a generator previously
  3092. setCustomStyleName(rowElement, null);
  3093. }
  3094. } else if (usedToHaveData) {
  3095. setStyleName(rowElement, rowSelectedStyleName, false);
  3096. setCustomStyleName(rowElement, null);
  3097. }
  3098. cellFocusHandler.updateFocusedRowStyle(row);
  3099. for (FlyweightCell cell : cellsToUpdate) {
  3100. Column<?, T> column = getColumn(cell.getColumn());
  3101. assert column != null : "Column was not found from cell ("
  3102. + cell.getColumn() + "," + cell.getRow() + ")";
  3103. cellFocusHandler.updateFocusedCellStyle(cell,
  3104. escalator.getBody());
  3105. if (hasData && cellStyleGenerator != null) {
  3106. try {
  3107. cellReference.set(cell.getColumn(), column);
  3108. String generatedStyle = cellStyleGenerator
  3109. .getStyle(cellReference);
  3110. setCustomStyleName(cell.getElement(), generatedStyle);
  3111. } catch (RuntimeException e) {
  3112. getLogger().log(
  3113. Level.SEVERE,
  3114. "Error generating style for cell in column "
  3115. + cell.getColumn(), e);
  3116. }
  3117. } else if (hasData || usedToHaveData) {
  3118. setCustomStyleName(cell.getElement(), null);
  3119. }
  3120. Renderer renderer = column.getRenderer();
  3121. try {
  3122. rendererCellReference.set(cell, column);
  3123. if (renderer instanceof ComplexRenderer) {
  3124. // Hide cell content if needed
  3125. ComplexRenderer clxRenderer = (ComplexRenderer) renderer;
  3126. if (hasData) {
  3127. if (!usedToHaveData) {
  3128. // Prepare cell for rendering
  3129. clxRenderer.setContentVisible(
  3130. rendererCellReference, true);
  3131. }
  3132. Object value = column.getValue(rowData);
  3133. clxRenderer.render(rendererCellReference, value);
  3134. } else {
  3135. // Prepare cell for no data
  3136. clxRenderer.setContentVisible(
  3137. rendererCellReference, false);
  3138. }
  3139. } else if (hasData) {
  3140. // Simple renderers just render
  3141. Object value = column.getValue(rowData);
  3142. renderer.render(rendererCellReference, value);
  3143. } else {
  3144. // Clear cell if there is no data
  3145. cell.getElement().removeAllChildren();
  3146. }
  3147. } catch (RuntimeException e) {
  3148. getLogger().log(
  3149. Level.SEVERE,
  3150. "Error rendering cell in column "
  3151. + cell.getColumn(), e);
  3152. }
  3153. }
  3154. }
  3155. @Override
  3156. public void preDetach(Row row, Iterable<FlyweightCell> cellsToDetach) {
  3157. for (FlyweightCell cell : cellsToDetach) {
  3158. Renderer renderer = findRenderer(cell);
  3159. if (renderer instanceof WidgetRenderer) {
  3160. try {
  3161. Widget w = WidgetUtil.findWidget(cell.getElement()
  3162. .getFirstChildElement(), Widget.class);
  3163. if (w != null) {
  3164. // Logical detach
  3165. setParent(w, null);
  3166. // Physical detach
  3167. cell.getElement().removeChild(w.getElement());
  3168. }
  3169. } catch (RuntimeException e) {
  3170. getLogger().log(
  3171. Level.SEVERE,
  3172. "Error detaching widget in column "
  3173. + cell.getColumn(), e);
  3174. }
  3175. }
  3176. }
  3177. }
  3178. @Override
  3179. public void postDetach(Row row, Iterable<FlyweightCell> detachedCells) {
  3180. int rowIndex = row.getRow();
  3181. // Passing null row data since it might not exist in the data source
  3182. // any more
  3183. rowReference.set(rowIndex, null, row.getElement());
  3184. for (FlyweightCell cell : detachedCells) {
  3185. Renderer renderer = findRenderer(cell);
  3186. if (renderer instanceof ComplexRenderer) {
  3187. try {
  3188. rendererCellReference.set(cell,
  3189. getColumn(cell.getColumn()));
  3190. ((ComplexRenderer) renderer)
  3191. .destroy(rendererCellReference);
  3192. } catch (RuntimeException e) {
  3193. getLogger().log(
  3194. Level.SEVERE,
  3195. "Error destroying cell in column "
  3196. + cell.getColumn(), e);
  3197. }
  3198. }
  3199. }
  3200. }
  3201. }
  3202. protected class StaticSectionUpdater implements EscalatorUpdater {
  3203. private StaticSection<?> section;
  3204. private RowContainer container;
  3205. public StaticSectionUpdater(StaticSection<?> section,
  3206. RowContainer container) {
  3207. super();
  3208. this.section = section;
  3209. this.container = container;
  3210. }
  3211. @Override
  3212. public void update(Row row, Iterable<FlyweightCell> cellsToUpdate) {
  3213. StaticSection.StaticRow<?> staticRow = section.getRow(row.getRow());
  3214. final List<Column<?, T>> columns = getColumns();
  3215. setCustomStyleName(row.getElement(), staticRow.getStyleName());
  3216. for (FlyweightCell cell : cellsToUpdate) {
  3217. final StaticSection.StaticCell metadata = staticRow
  3218. .getCell(columns.get(cell.getColumn()));
  3219. // Decorate default row with sorting indicators
  3220. if (staticRow instanceof HeaderRow) {
  3221. addSortingIndicatorsToHeaderRow((HeaderRow) staticRow, cell);
  3222. }
  3223. // Assign colspan to cell before rendering
  3224. cell.setColSpan(metadata.getColspan());
  3225. TableCellElement element = cell.getElement();
  3226. switch (metadata.getType()) {
  3227. case TEXT:
  3228. element.setInnerText(metadata.getText());
  3229. break;
  3230. case HTML:
  3231. element.setInnerHTML(metadata.getHtml());
  3232. break;
  3233. case WIDGET:
  3234. preDetach(row, Arrays.asList(cell));
  3235. element.setInnerHTML("");
  3236. postAttach(row, Arrays.asList(cell));
  3237. break;
  3238. }
  3239. setCustomStyleName(element, metadata.getStyleName());
  3240. cellFocusHandler.updateFocusedCellStyle(cell, container);
  3241. }
  3242. }
  3243. private void addSortingIndicatorsToHeaderRow(HeaderRow headerRow,
  3244. FlyweightCell cell) {
  3245. cleanup(cell);
  3246. Column<?, ?> column = getColumn(cell.getColumn());
  3247. SortOrder sortingOrder = getSortOrder(column);
  3248. if (!headerRow.isDefault() || !column.isSortable()
  3249. || sortingOrder == null) {
  3250. // Only apply sorting indicators to sortable header columns in
  3251. // the default header row
  3252. return;
  3253. }
  3254. Element cellElement = cell.getElement();
  3255. if (SortDirection.ASCENDING == sortingOrder.getDirection()) {
  3256. cellElement.addClassName("sort-asc");
  3257. } else {
  3258. cellElement.addClassName("sort-desc");
  3259. }
  3260. int sortIndex = Grid.this.getSortOrder().indexOf(sortingOrder);
  3261. if (sortIndex > -1 && Grid.this.getSortOrder().size() > 1) {
  3262. // Show sort order indicator if column is
  3263. // sorted and other sorted columns also exists.
  3264. cellElement.setAttribute("sort-order",
  3265. String.valueOf(sortIndex + 1));
  3266. }
  3267. }
  3268. /**
  3269. * Finds the sort order for this column
  3270. */
  3271. private SortOrder getSortOrder(Column<?, ?> column) {
  3272. for (SortOrder order : Grid.this.getSortOrder()) {
  3273. if (order.getColumn() == column) {
  3274. return order;
  3275. }
  3276. }
  3277. return null;
  3278. }
  3279. private void cleanup(FlyweightCell cell) {
  3280. Element cellElement = cell.getElement();
  3281. cellElement.removeAttribute("sort-order");
  3282. cellElement.removeClassName("sort-desc");
  3283. cellElement.removeClassName("sort-asc");
  3284. }
  3285. @Override
  3286. public void preAttach(Row row, Iterable<FlyweightCell> cellsToAttach) {
  3287. }
  3288. @Override
  3289. public void postAttach(Row row, Iterable<FlyweightCell> attachedCells) {
  3290. StaticSection.StaticRow<?> gridRow = section.getRow(row.getRow());
  3291. List<Column<?, T>> columns = getColumns();
  3292. for (FlyweightCell cell : attachedCells) {
  3293. StaticSection.StaticCell metadata = gridRow.getCell(columns
  3294. .get(cell.getColumn()));
  3295. /*
  3296. * If the cell contains widgets that are not currently attach
  3297. * then attach them now.
  3298. */
  3299. if (GridStaticCellType.WIDGET.equals(metadata.getType())) {
  3300. final Widget widget = metadata.getWidget();
  3301. final Element cellElement = cell.getElement();
  3302. if (!widget.isAttached()) {
  3303. // Physical attach
  3304. cellElement.appendChild(widget.getElement());
  3305. // Logical attach
  3306. setParent(widget, Grid.this);
  3307. }
  3308. }
  3309. }
  3310. }
  3311. @Override
  3312. public void preDetach(Row row, Iterable<FlyweightCell> cellsToDetach) {
  3313. if (section.getRowCount() > row.getRow()) {
  3314. StaticSection.StaticRow<?> gridRow = section.getRow(row
  3315. .getRow());
  3316. List<Column<?, T>> columns = getColumns();
  3317. for (FlyweightCell cell : cellsToDetach) {
  3318. StaticSection.StaticCell metadata = gridRow.getCell(columns
  3319. .get(cell.getColumn()));
  3320. if (GridStaticCellType.WIDGET.equals(metadata.getType())
  3321. && metadata.getWidget().isAttached()) {
  3322. Widget widget = metadata.getWidget();
  3323. // Logical detach
  3324. setParent(widget, null);
  3325. // Physical detach
  3326. widget.getElement().removeFromParent();
  3327. }
  3328. }
  3329. }
  3330. }
  3331. @Override
  3332. public void postDetach(Row row, Iterable<FlyweightCell> detachedCells) {
  3333. }
  3334. };
  3335. /**
  3336. * Creates a new instance.
  3337. */
  3338. public Grid() {
  3339. initWidget(escalator);
  3340. getElement().setTabIndex(0);
  3341. cellFocusHandler = new CellFocusHandler();
  3342. setStylePrimaryName("v-grid");
  3343. escalator.getHeader().setEscalatorUpdater(createHeaderUpdater());
  3344. escalator.getBody().setEscalatorUpdater(createBodyUpdater());
  3345. escalator.getFooter().setEscalatorUpdater(createFooterUpdater());
  3346. header.setGrid(this);
  3347. HeaderRow defaultRow = header.appendRow();
  3348. header.setDefaultRow(defaultRow);
  3349. footer.setGrid(this);
  3350. editor.setGrid(this);
  3351. setSelectionMode(SelectionMode.SINGLE);
  3352. escalator.addScrollHandler(new ScrollHandler() {
  3353. @Override
  3354. public void onScroll(ScrollEvent event) {
  3355. fireEvent(new ScrollEvent());
  3356. }
  3357. });
  3358. escalator
  3359. .addRowVisibilityChangeHandler(new RowVisibilityChangeHandler() {
  3360. @Override
  3361. public void onRowVisibilityChange(
  3362. RowVisibilityChangeEvent event) {
  3363. if (dataSource != null && dataSource.size() != 0) {
  3364. dataIsBeingFetched = true;
  3365. dataSource.ensureAvailability(
  3366. event.getFirstVisibleRow(),
  3367. event.getVisibleRowCount());
  3368. }
  3369. }
  3370. });
  3371. // Default action on SelectionEvents. Refresh the body so changed
  3372. // become visible.
  3373. addSelectionHandler(new SelectionHandler<T>() {
  3374. @Override
  3375. public void onSelect(SelectionEvent<T> event) {
  3376. refreshBody();
  3377. }
  3378. });
  3379. // Sink header events and key events
  3380. sinkEvents(getHeader().getConsumedEvents());
  3381. sinkEvents(Arrays.asList(BrowserEvents.KEYDOWN, BrowserEvents.KEYUP,
  3382. BrowserEvents.KEYPRESS, BrowserEvents.DBLCLICK,
  3383. BrowserEvents.MOUSEDOWN));
  3384. // Make ENTER and SHIFT+ENTER in the header perform sorting
  3385. addHeaderKeyUpHandler(new HeaderKeyUpHandler() {
  3386. @Override
  3387. public void onKeyUp(GridKeyUpEvent event) {
  3388. if (event.getNativeKeyCode() != KeyCodes.KEY_ENTER) {
  3389. return;
  3390. }
  3391. sorter.sort(event.getFocusedCell().getColumn(),
  3392. event.isShiftKeyDown());
  3393. }
  3394. });
  3395. addDataAvailableHandler(new DataAvailableHandler() {
  3396. @Override
  3397. public void onDataAvailable(DataAvailableEvent event) {
  3398. dataIsBeingFetched = false;
  3399. }
  3400. });
  3401. }
  3402. @Override
  3403. public boolean isEnabled() {
  3404. return enabled;
  3405. }
  3406. @Override
  3407. public void setEnabled(boolean enabled) {
  3408. if (enabled == this.enabled) {
  3409. return;
  3410. }
  3411. this.enabled = enabled;
  3412. getElement().setTabIndex(enabled ? 0 : -1);
  3413. // Editor save and cancel buttons need to be disabled.
  3414. boolean editorOpen = editor.getState() != State.INACTIVE;
  3415. if (editorOpen) {
  3416. editor.setGridEnabled(enabled);
  3417. }
  3418. getEscalator().setScrollLocked(Direction.VERTICAL,
  3419. !enabled || editorOpen);
  3420. getEscalator().setScrollLocked(Direction.HORIZONTAL, !enabled);
  3421. }
  3422. @Override
  3423. public void setStylePrimaryName(String style) {
  3424. super.setStylePrimaryName(style);
  3425. escalator.setStylePrimaryName(style);
  3426. editor.setStylePrimaryName(style);
  3427. String rowStyle = getStylePrimaryName() + "-row";
  3428. rowHasDataStyleName = rowStyle + "-has-data";
  3429. rowSelectedStyleName = rowStyle + "-selected";
  3430. rowStripeStyleName = rowStyle + "-stripe";
  3431. cellFocusStyleName = getStylePrimaryName() + "-cell-focused";
  3432. rowFocusStyleName = getStylePrimaryName() + "-row-focused";
  3433. if (isAttached()) {
  3434. refreshHeader();
  3435. refreshBody();
  3436. refreshFooter();
  3437. }
  3438. }
  3439. /**
  3440. * Creates the escalator updater used to update the header rows in this
  3441. * grid. The updater is invoked when header rows or columns are added or
  3442. * removed, or the content of existing header cells is changed.
  3443. *
  3444. * @return the new header updater instance
  3445. *
  3446. * @see GridHeader
  3447. * @see Grid#getHeader()
  3448. */
  3449. protected EscalatorUpdater createHeaderUpdater() {
  3450. return new StaticSectionUpdater(header, escalator.getHeader());
  3451. }
  3452. /**
  3453. * Creates the escalator updater used to update the body rows in this grid.
  3454. * The updater is invoked when body rows or columns are added or removed,
  3455. * the content of body cells is changed, or the body is scrolled to expose
  3456. * previously hidden content.
  3457. *
  3458. * @return the new body updater instance
  3459. */
  3460. protected EscalatorUpdater createBodyUpdater() {
  3461. return new BodyUpdater();
  3462. }
  3463. /**
  3464. * Creates the escalator updater used to update the footer rows in this
  3465. * grid. The updater is invoked when header rows or columns are added or
  3466. * removed, or the content of existing header cells is changed.
  3467. *
  3468. * @return the new footer updater instance
  3469. *
  3470. * @see GridFooter
  3471. * @see #getFooter()
  3472. */
  3473. protected EscalatorUpdater createFooterUpdater() {
  3474. return new StaticSectionUpdater(footer, escalator.getFooter());
  3475. }
  3476. /**
  3477. * Refreshes header or footer rows on demand
  3478. *
  3479. * @param rows
  3480. * The row container
  3481. * @param firstRowIsVisible
  3482. * is the first row visible
  3483. * @param isHeader
  3484. * <code>true</code> if we refreshing the header, else assumed
  3485. * the footer
  3486. */
  3487. private void refreshRowContainer(RowContainer rows, StaticSection<?> section) {
  3488. // Add or Remove rows on demand
  3489. int rowDiff = section.getVisibleRowCount() - rows.getRowCount();
  3490. if (rowDiff > 0) {
  3491. rows.insertRows(0, rowDiff);
  3492. } else if (rowDiff < 0) {
  3493. rows.removeRows(0, -rowDiff);
  3494. }
  3495. // Refresh all the rows
  3496. if (rows.getRowCount() > 0) {
  3497. rows.refreshRows(0, rows.getRowCount());
  3498. }
  3499. }
  3500. /**
  3501. * Refreshes all header rows
  3502. */
  3503. void refreshHeader() {
  3504. refreshRowContainer(escalator.getHeader(), header);
  3505. }
  3506. /**
  3507. * Refreshes all body rows
  3508. */
  3509. private void refreshBody() {
  3510. escalator.getBody().refreshRows(0, escalator.getBody().getRowCount());
  3511. }
  3512. /**
  3513. * Refreshes all footer rows
  3514. */
  3515. void refreshFooter() {
  3516. refreshRowContainer(escalator.getFooter(), footer);
  3517. }
  3518. /**
  3519. * Adds columns as the last columns in the grid.
  3520. *
  3521. * @param columns
  3522. * the columns to add
  3523. */
  3524. public void addColumns(Column<?, T>... columns) {
  3525. int count = getColumnCount();
  3526. for (Column<?, T> column : columns) {
  3527. addColumn(column, count++);
  3528. }
  3529. }
  3530. /**
  3531. * Adds a column as the last column in the grid.
  3532. *
  3533. * @param column
  3534. * the column to add
  3535. * @return given column
  3536. */
  3537. public <C extends Column<?, T>> C addColumn(C column) {
  3538. addColumn(column, getColumnCount());
  3539. return column;
  3540. }
  3541. /**
  3542. * Inserts a column into a specific position in the grid.
  3543. *
  3544. * @param index
  3545. * the index where the column should be inserted into
  3546. * @param column
  3547. * the column to add
  3548. * @return given column
  3549. *
  3550. * @throws IllegalStateException
  3551. * if Grid's current selection model renders a selection column,
  3552. * and {@code index} is 0.
  3553. */
  3554. public <C extends Column<?, T>> C addColumn(C column, int index) {
  3555. if (column == selectionColumn) {
  3556. throw new IllegalArgumentException("The selection column many "
  3557. + "not be added manually");
  3558. } else if (selectionColumn != null && index == 0) {
  3559. throw new IllegalStateException("A column cannot be inserted "
  3560. + "before the selection column");
  3561. }
  3562. addColumnSkipSelectionColumnCheck(column, index);
  3563. return column;
  3564. }
  3565. private void addColumnSkipSelectionColumnCheck(Column<?, T> column,
  3566. int index) {
  3567. // Register column with grid
  3568. columns.add(index, column);
  3569. header.addColumn(column);
  3570. footer.addColumn(column);
  3571. // Register this grid instance with the column
  3572. ((Column<?, T>) column).setGrid(this);
  3573. // Add to escalator
  3574. escalator.getColumnConfiguration().insertColumns(index, 1);
  3575. // Reapply column width
  3576. column.reapplyWidth();
  3577. // Sink all renderer events
  3578. Set<String> events = new HashSet<String>();
  3579. events.addAll(getConsumedEventsForRenderer(column.getRenderer()));
  3580. sinkEvents(events);
  3581. }
  3582. private void sinkEvents(Collection<String> events) {
  3583. assert events != null;
  3584. int eventsToSink = 0;
  3585. for (String typeName : events) {
  3586. int typeInt = Event.getTypeInt(typeName);
  3587. if (typeInt < 0) {
  3588. // Type not recognized by typeInt
  3589. sinkBitlessEvent(typeName);
  3590. } else {
  3591. eventsToSink |= typeInt;
  3592. }
  3593. }
  3594. if (eventsToSink > 0) {
  3595. sinkEvents(eventsToSink);
  3596. }
  3597. }
  3598. private Renderer<?> findRenderer(FlyweightCell cell) {
  3599. Column<?, T> column = getColumn(cell.getColumn());
  3600. assert column != null : "Could not find column at index:"
  3601. + cell.getColumn();
  3602. return column.getRenderer();
  3603. }
  3604. /**
  3605. * Removes a column from the grid.
  3606. *
  3607. * @param column
  3608. * the column to remove
  3609. */
  3610. public void removeColumn(Column<?, T> column) {
  3611. if (column != null && column.equals(selectionColumn)) {
  3612. throw new IllegalArgumentException(
  3613. "The selection column may not be removed manually.");
  3614. }
  3615. removeColumnSkipSelectionColumnCheck(column);
  3616. }
  3617. private void removeColumnSkipSelectionColumnCheck(Column<?, T> column) {
  3618. int columnIndex = columns.indexOf(column);
  3619. // Remove from column configuration
  3620. escalator.getColumnConfiguration().removeColumns(columnIndex, 1);
  3621. updateFrozenColumns();
  3622. header.removeColumn(column);
  3623. footer.removeColumn(column);
  3624. // de-register column with grid
  3625. ((Column<?, T>) column).setGrid(null);
  3626. columns.remove(columnIndex);
  3627. }
  3628. /**
  3629. * Returns the amount of columns in the grid.
  3630. *
  3631. * @return The number of columns in the grid
  3632. */
  3633. public int getColumnCount() {
  3634. return columns.size();
  3635. }
  3636. /**
  3637. * Returns a list of columns in the grid.
  3638. *
  3639. * @return A unmodifiable list of the columns in the grid
  3640. */
  3641. public List<Column<?, T>> getColumns() {
  3642. return Collections
  3643. .unmodifiableList(new ArrayList<Column<?, T>>(columns));
  3644. }
  3645. /**
  3646. * Returns a column by its index in the grid.
  3647. *
  3648. * @param index
  3649. * the index of the column
  3650. * @return The column in the given index
  3651. * @throws IllegalArgumentException
  3652. * if the column index does not exist in the grid
  3653. */
  3654. public Column<?, T> getColumn(int index) throws IllegalArgumentException {
  3655. if (index < 0 || index >= columns.size()) {
  3656. throw new IllegalStateException("Column not found.");
  3657. }
  3658. return columns.get(index);
  3659. }
  3660. /**
  3661. * Returns current index of given column
  3662. *
  3663. * @param column
  3664. * column in grid
  3665. * @return column index, or <code>-1</code> if not in this Grid
  3666. */
  3667. protected int indexOfColumn(Column<?, T> column) {
  3668. return columns.indexOf(column);
  3669. }
  3670. /**
  3671. * Returns the header section of this grid. The default header contains a
  3672. * single row displaying the column captions.
  3673. *
  3674. * @return the header
  3675. */
  3676. protected Header getHeader() {
  3677. return header;
  3678. }
  3679. /**
  3680. * Gets the header row at given index.
  3681. *
  3682. * @param rowIndex
  3683. * 0 based index for row. Counted from top to bottom
  3684. * @return header row at given index
  3685. * @throws IllegalArgumentException
  3686. * if no row exists at given index
  3687. */
  3688. public HeaderRow getHeaderRow(int rowIndex) {
  3689. return header.getRow(rowIndex);
  3690. }
  3691. /**
  3692. * Inserts a new row at the given position to the header section. Shifts the
  3693. * row currently at that position and any subsequent rows down (adds one to
  3694. * their indices).
  3695. *
  3696. * @param index
  3697. * the position at which to insert the row
  3698. * @return the new row
  3699. *
  3700. * @throws IllegalArgumentException
  3701. * if the index is less than 0 or greater than row count
  3702. * @see #appendHeaderRow()
  3703. * @see #prependHeaderRow()
  3704. * @see #removeHeaderRow(HeaderRow)
  3705. * @see #removeHeaderRow(int)
  3706. */
  3707. public HeaderRow addHeaderRowAt(int index) {
  3708. return header.addRowAt(index);
  3709. }
  3710. /**
  3711. * Adds a new row at the bottom of the header section.
  3712. *
  3713. * @return the new row
  3714. * @see #prependHeaderRow()
  3715. * @see #addHeaderRowAt(int)
  3716. * @see #removeHeaderRow(HeaderRow)
  3717. * @see #removeHeaderRow(int)
  3718. */
  3719. public HeaderRow appendHeaderRow() {
  3720. return header.appendRow();
  3721. }
  3722. /**
  3723. * Returns the current default row of the header section. The default row is
  3724. * a special header row providing a user interface for sorting columns.
  3725. * Setting a header caption for column updates cells in the default header.
  3726. *
  3727. * @return the default row or null if no default row set
  3728. */
  3729. public HeaderRow getDefaultHeaderRow() {
  3730. return header.getDefaultRow();
  3731. }
  3732. /**
  3733. * Gets the row count for the header section.
  3734. *
  3735. * @return row count
  3736. */
  3737. public int getHeaderRowCount() {
  3738. return header.getRowCount();
  3739. }
  3740. /**
  3741. * Adds a new row at the top of the header section.
  3742. *
  3743. * @return the new row
  3744. * @see #appendHeaderRow()
  3745. * @see #addHeaderRowAt(int)
  3746. * @see #removeHeaderRow(HeaderRow)
  3747. * @see #removeHeaderRow(int)
  3748. */
  3749. public HeaderRow prependHeaderRow() {
  3750. return header.prependRow();
  3751. }
  3752. /**
  3753. * Removes the given row from the header section.
  3754. *
  3755. * @param row
  3756. * the row to be removed
  3757. *
  3758. * @throws IllegalArgumentException
  3759. * if the row does not exist in this section
  3760. * @see #removeHeaderRow(int)
  3761. * @see #addHeaderRowAt(int)
  3762. * @see #appendHeaderRow()
  3763. * @see #prependHeaderRow()
  3764. */
  3765. public void removeHeaderRow(HeaderRow row) {
  3766. header.removeRow(row);
  3767. }
  3768. /**
  3769. * Removes the row at the given position from the header section.
  3770. *
  3771. * @param index
  3772. * the position of the row
  3773. *
  3774. * @throws IllegalArgumentException
  3775. * if no row exists at given index
  3776. * @see #removeHeaderRow(HeaderRow)
  3777. * @see #addHeaderRowAt(int)
  3778. * @see #appendHeaderRow()
  3779. * @see #prependHeaderRow()
  3780. */
  3781. public void removeHeaderRow(int rowIndex) {
  3782. header.removeRow(rowIndex);
  3783. }
  3784. /**
  3785. * Sets the default row of the header. The default row is a special header
  3786. * row providing a user interface for sorting columns.
  3787. *
  3788. * @param row
  3789. * the new default row, or null for no default row
  3790. *
  3791. * @throws IllegalArgumentException
  3792. * header does not contain the row
  3793. */
  3794. public void setDefaultHeaderRow(HeaderRow row) {
  3795. header.setDefaultRow(row);
  3796. }
  3797. /**
  3798. * Sets the visibility of the header section.
  3799. *
  3800. * @param visible
  3801. * true to show header section, false to hide
  3802. */
  3803. public void setHeaderVisible(boolean visible) {
  3804. header.setVisible(visible);
  3805. }
  3806. /**
  3807. * Returns the visibility of the header section.
  3808. *
  3809. * @return true if visible, false otherwise.
  3810. */
  3811. public boolean isHeaderVisible() {
  3812. return header.isVisible();
  3813. }
  3814. /* Grid Footers */
  3815. /**
  3816. * Returns the footer section of this grid. The default footer is empty.
  3817. *
  3818. * @return the footer
  3819. */
  3820. protected Footer getFooter() {
  3821. return footer;
  3822. }
  3823. /**
  3824. * Gets the footer row at given index.
  3825. *
  3826. * @param rowIndex
  3827. * 0 based index for row. Counted from top to bottom
  3828. * @return footer row at given index
  3829. * @throws IllegalArgumentException
  3830. * if no row exists at given index
  3831. */
  3832. public FooterRow getFooterRow(int rowIndex) {
  3833. return footer.getRow(rowIndex);
  3834. }
  3835. /**
  3836. * Inserts a new row at the given position to the footer section. Shifts the
  3837. * row currently at that position and any subsequent rows down (adds one to
  3838. * their indices).
  3839. *
  3840. * @param index
  3841. * the position at which to insert the row
  3842. * @return the new row
  3843. *
  3844. * @throws IllegalArgumentException
  3845. * if the index is less than 0 or greater than row count
  3846. * @see #appendFooterRow()
  3847. * @see #prependFooterRow()
  3848. * @see #removeFooterRow(FooterRow)
  3849. * @see #removeFooterRow(int)
  3850. */
  3851. public FooterRow addFooterRowAt(int index) {
  3852. return footer.addRowAt(index);
  3853. }
  3854. /**
  3855. * Adds a new row at the bottom of the footer section.
  3856. *
  3857. * @return the new row
  3858. * @see #prependFooterRow()
  3859. * @see #addFooterRowAt(int)
  3860. * @see #removeFooterRow(FooterRow)
  3861. * @see #removeFooterRow(int)
  3862. */
  3863. public FooterRow appendFooterRow() {
  3864. return footer.appendRow();
  3865. }
  3866. /**
  3867. * Gets the row count for the footer.
  3868. *
  3869. * @return row count
  3870. */
  3871. public int getFooterRowCount() {
  3872. return footer.getRowCount();
  3873. }
  3874. /**
  3875. * Adds a new row at the top of the footer section.
  3876. *
  3877. * @return the new row
  3878. * @see #appendFooterRow()
  3879. * @see #addFooterRowAt(int)
  3880. * @see #removeFooterRow(FooterRow)
  3881. * @see #removeFooterRow(int)
  3882. */
  3883. public FooterRow prependFooterRow() {
  3884. return footer.prependRow();
  3885. }
  3886. /**
  3887. * Removes the given row from the footer section.
  3888. *
  3889. * @param row
  3890. * the row to be removed
  3891. *
  3892. * @throws IllegalArgumentException
  3893. * if the row does not exist in this section
  3894. * @see #removeFooterRow(int)
  3895. * @see #addFooterRowAt(int)
  3896. * @see #appendFooterRow()
  3897. * @see #prependFooterRow()
  3898. */
  3899. public void removeFooterRow(FooterRow row) {
  3900. footer.removeRow(row);
  3901. }
  3902. /**
  3903. * Removes the row at the given position from the footer section.
  3904. *
  3905. * @param index
  3906. * the position of the row
  3907. *
  3908. * @throws IllegalArgumentException
  3909. * if no row exists at given index
  3910. * @see #removeFooterRow(FooterRow)
  3911. * @see #addFooterRowAt(int)
  3912. * @see #appendFooterRow()
  3913. * @see #prependFooterRow()
  3914. */
  3915. public void removeFooterRow(int rowIndex) {
  3916. footer.removeRow(rowIndex);
  3917. }
  3918. /**
  3919. * Sets the visibility of the footer section.
  3920. *
  3921. * @param visible
  3922. * true to show footer section, false to hide
  3923. */
  3924. public void setFooterVisible(boolean visible) {
  3925. footer.setVisible(visible);
  3926. }
  3927. /**
  3928. * Returns the visibility of the footer section.
  3929. *
  3930. * @return true if visible, false otherwise.
  3931. */
  3932. public boolean isFooterVisible() {
  3933. return footer.isVisible();
  3934. }
  3935. protected Editor<T> getEditor() {
  3936. return editor;
  3937. }
  3938. protected Escalator getEscalator() {
  3939. return escalator;
  3940. }
  3941. /**
  3942. * {@inheritDoc}
  3943. * <p>
  3944. * <em>Note:</em> This method will change the widget's size in the browser
  3945. * only if {@link #getHeightMode()} returns {@link HeightMode#CSS}.
  3946. *
  3947. * @see #setHeightMode(HeightMode)
  3948. */
  3949. @Override
  3950. public void setHeight(String height) {
  3951. escalator.setHeight(height);
  3952. }
  3953. @Override
  3954. public void setWidth(String width) {
  3955. escalator.setWidth(width);
  3956. }
  3957. /**
  3958. * Sets the data source used by this grid.
  3959. *
  3960. * @param dataSource
  3961. * the data source to use, not null
  3962. * @throws IllegalArgumentException
  3963. * if <code>dataSource</code> is <code>null</code>
  3964. */
  3965. public void setDataSource(final DataSource<T> dataSource)
  3966. throws IllegalArgumentException {
  3967. if (dataSource == null) {
  3968. throw new IllegalArgumentException("dataSource can't be null.");
  3969. }
  3970. selectionModel.reset();
  3971. if (this.dataSource != null) {
  3972. this.dataSource.setDataChangeHandler(null);
  3973. }
  3974. this.dataSource = dataSource;
  3975. dataSource.setDataChangeHandler(new DataChangeHandler() {
  3976. @Override
  3977. public void dataUpdated(int firstIndex, int numberOfItems) {
  3978. escalator.getBody().refreshRows(firstIndex, numberOfItems);
  3979. }
  3980. @Override
  3981. public void dataRemoved(int firstIndex, int numberOfItems) {
  3982. escalator.getBody().removeRows(firstIndex, numberOfItems);
  3983. Range removed = Range.withLength(firstIndex, numberOfItems);
  3984. cellFocusHandler.rowsRemovedFromBody(removed);
  3985. }
  3986. @Override
  3987. public void dataAdded(int firstIndex, int numberOfItems) {
  3988. escalator.getBody().insertRows(firstIndex, numberOfItems);
  3989. Range added = Range.withLength(firstIndex, numberOfItems);
  3990. cellFocusHandler.rowsAddedToBody(added);
  3991. }
  3992. @Override
  3993. public void dataAvailable(int firstIndex, int numberOfItems) {
  3994. currentDataAvailable = Range.withLength(firstIndex,
  3995. numberOfItems);
  3996. fireEvent(new DataAvailableEvent(currentDataAvailable));
  3997. }
  3998. @Override
  3999. public void resetDataAndSize(int newSize) {
  4000. RowContainer body = escalator.getBody();
  4001. int oldSize = body.getRowCount();
  4002. if (newSize > oldSize) {
  4003. body.insertRows(oldSize, newSize - oldSize);
  4004. } else if (newSize < oldSize) {
  4005. body.removeRows(newSize, oldSize - newSize);
  4006. }
  4007. if (newSize > 0) {
  4008. dataIsBeingFetched = true;
  4009. Range visibleRowRange = escalator.getVisibleRowRange();
  4010. dataSource.ensureAvailability(visibleRowRange.getStart(),
  4011. visibleRowRange.length());
  4012. }
  4013. assert body.getRowCount() == newSize;
  4014. }
  4015. });
  4016. int previousRowCount = escalator.getBody().getRowCount();
  4017. if (previousRowCount != 0) {
  4018. escalator.getBody().removeRows(0, previousRowCount);
  4019. }
  4020. setEscalatorSizeFromDataSource();
  4021. }
  4022. private void setEscalatorSizeFromDataSource() {
  4023. assert escalator.getBody().getRowCount() == 0;
  4024. int size = dataSource.size();
  4025. if (size == -1 && isAttached()) {
  4026. // Exact size is not yet known, start with some reasonable guess
  4027. // just to get an initial backend request going
  4028. size = getEscalator().getMaxVisibleRowCount();
  4029. }
  4030. if (size > 0) {
  4031. escalator.getBody().insertRows(0, size);
  4032. }
  4033. }
  4034. /**
  4035. * Gets the {@Link DataSource} for this Grid.
  4036. *
  4037. * @return the data source used by this grid
  4038. */
  4039. public DataSource<T> getDataSource() {
  4040. return dataSource;
  4041. }
  4042. /**
  4043. * Sets the number of frozen columns in this grid. Setting the count to 0
  4044. * means that no data columns will be frozen, but the built-in selection
  4045. * checkbox column will still be frozen if it's in use. Setting the count to
  4046. * -1 will also disable the selection column.
  4047. * <p>
  4048. * The default value is 0.
  4049. *
  4050. * @param numberOfColumns
  4051. * the number of columns that should be frozen
  4052. *
  4053. * @throws IllegalArgumentException
  4054. * if the column count is < -1 or > the number of visible
  4055. * columns
  4056. */
  4057. public void setFrozenColumnCount(int numberOfColumns) {
  4058. if (numberOfColumns < -1 || numberOfColumns > getColumnCount()) {
  4059. throw new IllegalArgumentException(
  4060. "count must be between -1 and the current number of columns ("
  4061. + getColumnCount() + ")");
  4062. }
  4063. this.frozenColumnCount = numberOfColumns;
  4064. updateFrozenColumns();
  4065. }
  4066. private void updateFrozenColumns() {
  4067. int numberOfColumns = frozenColumnCount;
  4068. if (numberOfColumns == -1) {
  4069. numberOfColumns = 0;
  4070. } else if (selectionColumn != null) {
  4071. numberOfColumns++;
  4072. }
  4073. escalator.getColumnConfiguration()
  4074. .setFrozenColumnCount(numberOfColumns);
  4075. }
  4076. /**
  4077. * Gets the number of frozen columns in this grid. 0 means that no data
  4078. * columns will be frozen, but the built-in selection checkbox column will
  4079. * still be frozen if it's in use. -1 means that not even the selection
  4080. * column is frozen.
  4081. *
  4082. * @return the number of frozen columns
  4083. */
  4084. public int getFrozenColumnCount() {
  4085. return frozenColumnCount;
  4086. }
  4087. public HandlerRegistration addRowVisibilityChangeHandler(
  4088. RowVisibilityChangeHandler handler) {
  4089. /*
  4090. * Reusing Escalator's RowVisibilityChangeHandler, since a scroll
  4091. * concept is too abstract. e.g. the event needs to be re-sent when the
  4092. * widget is resized.
  4093. */
  4094. return escalator.addRowVisibilityChangeHandler(handler);
  4095. }
  4096. /**
  4097. * Scrolls to a certain row, using {@link ScrollDestination#ANY}.
  4098. *
  4099. * @param rowIndex
  4100. * zero-based index of the row to scroll to.
  4101. * @throws IllegalArgumentException
  4102. * if rowIndex is below zero, or above the maximum value
  4103. * supported by the data source.
  4104. */
  4105. public void scrollToRow(int rowIndex) throws IllegalArgumentException {
  4106. scrollToRow(rowIndex, ScrollDestination.ANY,
  4107. GridConstants.DEFAULT_PADDING);
  4108. }
  4109. /**
  4110. * Scrolls to a certain row, using user-specified scroll destination.
  4111. *
  4112. * @param rowIndex
  4113. * zero-based index of the row to scroll to.
  4114. * @param destination
  4115. * desired destination placement of scrolled-to-row. See
  4116. * {@link ScrollDestination} for more information.
  4117. * @throws IllegalArgumentException
  4118. * if rowIndex is below zero, or above the maximum value
  4119. * supported by the data source.
  4120. */
  4121. public void scrollToRow(int rowIndex, ScrollDestination destination)
  4122. throws IllegalArgumentException {
  4123. scrollToRow(rowIndex, destination,
  4124. destination == ScrollDestination.MIDDLE ? 0
  4125. : GridConstants.DEFAULT_PADDING);
  4126. }
  4127. /**
  4128. * Scrolls to a certain row using only user-specified parameters.
  4129. *
  4130. * @param rowIndex
  4131. * zero-based index of the row to scroll to.
  4132. * @param destination
  4133. * desired destination placement of scrolled-to-row. See
  4134. * {@link ScrollDestination} for more information.
  4135. * @param paddingPx
  4136. * number of pixels to overscroll. Behavior depends on
  4137. * destination.
  4138. * @throws IllegalArgumentException
  4139. * if {@code destination} is {@link ScrollDestination#MIDDLE}
  4140. * and padding is nonzero, because having a padding on a
  4141. * centered row is undefined behavior, or if rowIndex is below
  4142. * zero or above the row count of the data source.
  4143. */
  4144. private void scrollToRow(int rowIndex, ScrollDestination destination,
  4145. int paddingPx) throws IllegalArgumentException {
  4146. int maxsize = escalator.getBody().getRowCount() - 1;
  4147. if (rowIndex < 0) {
  4148. throw new IllegalArgumentException("Row index (" + rowIndex
  4149. + ") is below zero!");
  4150. }
  4151. if (rowIndex > maxsize) {
  4152. throw new IllegalArgumentException("Row index (" + rowIndex
  4153. + ") is above maximum (" + maxsize + ")!");
  4154. }
  4155. escalator.scrollToRow(rowIndex, destination, paddingPx);
  4156. }
  4157. /**
  4158. * Scrolls to the beginning of the very first row.
  4159. */
  4160. public void scrollToStart() {
  4161. scrollToRow(0, ScrollDestination.START);
  4162. }
  4163. /**
  4164. * Scrolls to the end of the very last row.
  4165. */
  4166. public void scrollToEnd() {
  4167. scrollToRow(escalator.getBody().getRowCount() - 1,
  4168. ScrollDestination.END);
  4169. }
  4170. /**
  4171. * Sets the vertical scroll offset.
  4172. *
  4173. * @param px
  4174. * the number of pixels this grid should be scrolled down
  4175. */
  4176. public void setScrollTop(double px) {
  4177. escalator.setScrollTop(px);
  4178. }
  4179. /**
  4180. * Gets the vertical scroll offset
  4181. *
  4182. * @return the number of pixels this grid is scrolled down
  4183. */
  4184. public double getScrollTop() {
  4185. return escalator.getScrollTop();
  4186. }
  4187. /**
  4188. * Gets the horizontal scroll offset
  4189. *
  4190. * @return the number of pixels this grid is scrolled to the right
  4191. */
  4192. public double getScrollLeft() {
  4193. return escalator.getScrollLeft();
  4194. }
  4195. private static final Logger getLogger() {
  4196. return Logger.getLogger(Grid.class.getName());
  4197. }
  4198. /**
  4199. * Sets the number of rows that should be visible in Grid's body, while
  4200. * {@link #getHeightMode()} is {@link HeightMode#ROW}.
  4201. * <p>
  4202. * If Grid is currently not in {@link HeightMode#ROW}, the given value is
  4203. * remembered, and applied once the mode is applied.
  4204. *
  4205. * @param rows
  4206. * The height in terms of number of rows displayed in Grid's
  4207. * body. If Grid doesn't contain enough rows, white space is
  4208. * displayed instead.
  4209. * @throws IllegalArgumentException
  4210. * if {@code rows} is zero or less
  4211. * @throws IllegalArgumentException
  4212. * if {@code rows} is {@link Double#isInifinite(double)
  4213. * infinite}
  4214. * @throws IllegalArgumentException
  4215. * if {@code rows} is {@link Double#isNaN(double) NaN}
  4216. *
  4217. * @see #setHeightMode(HeightMode)
  4218. */
  4219. public void setHeightByRows(double rows) throws IllegalArgumentException {
  4220. escalator.setHeightByRows(rows);
  4221. }
  4222. /**
  4223. * Gets the amount of rows in Grid's body that are shown, while
  4224. * {@link #getHeightMode()} is {@link HeightMode#ROW}.
  4225. * <p>
  4226. * By default, it is {@value Escalator#DEFAULT_HEIGHT_BY_ROWS}.
  4227. *
  4228. * @return the amount of rows that should be shown in Grid's body, while in
  4229. * {@link HeightMode#ROW}.
  4230. * @see #setHeightByRows(double)
  4231. */
  4232. public double getHeightByRows() {
  4233. return escalator.getHeightByRows();
  4234. }
  4235. /**
  4236. * Defines the mode in which the Grid widget's height is calculated.
  4237. * <p>
  4238. * If {@link HeightMode#CSS} is given, Grid will respect the values given
  4239. * via {@link #setHeight(String)}, and behave as a traditional Widget.
  4240. * <p>
  4241. * If {@link HeightMode#ROW} is given, Grid will make sure that the body
  4242. * will display as many rows as {@link #getHeightByRows()} defines.
  4243. * <em>Note:</em> If headers/footers are inserted or removed, the widget
  4244. * will resize itself to still display the required amount of rows in its
  4245. * body. It also takes the horizontal scrollbar into account.
  4246. *
  4247. * @param heightMode
  4248. * the mode in to which Grid should be set
  4249. */
  4250. public void setHeightMode(HeightMode heightMode) {
  4251. /*
  4252. * This method is a workaround for the fact that Vaadin re-applies
  4253. * widget dimensions (height/width) on each state change event. The
  4254. * original design was to have setHeight an setHeightByRow be equals,
  4255. * and whichever was called the latest was considered in effect.
  4256. *
  4257. * But, because of Vaadin always calling setHeight on the widget, this
  4258. * approach doesn't work.
  4259. */
  4260. escalator.setHeightMode(heightMode);
  4261. }
  4262. /**
  4263. * Returns the current {@link HeightMode} the Grid is in.
  4264. * <p>
  4265. * Defaults to {@link HeightMode#CSS}.
  4266. *
  4267. * @return the current HeightMode
  4268. */
  4269. public HeightMode getHeightMode() {
  4270. return escalator.getHeightMode();
  4271. }
  4272. private Set<String> getConsumedEventsForRenderer(Renderer<?> renderer) {
  4273. Set<String> events = new HashSet<String>();
  4274. if (renderer instanceof ComplexRenderer) {
  4275. Collection<String> consumedEvents = ((ComplexRenderer<?>) renderer)
  4276. .getConsumedEvents();
  4277. if (consumedEvents != null) {
  4278. events.addAll(consumedEvents);
  4279. }
  4280. }
  4281. return events;
  4282. }
  4283. @Override
  4284. public void onBrowserEvent(Event event) {
  4285. if (!isEnabled()) {
  4286. return;
  4287. }
  4288. EventTarget target = event.getEventTarget();
  4289. if (!Element.is(target)) {
  4290. return;
  4291. }
  4292. Element e = Element.as(target);
  4293. RowContainer container = escalator.findRowContainer(e);
  4294. Cell cell;
  4295. String eventType = event.getType();
  4296. if (container == null) {
  4297. if (eventType.equals(BrowserEvents.KEYDOWN)
  4298. || eventType.equals(BrowserEvents.KEYUP)
  4299. || eventType.equals(BrowserEvents.KEYPRESS)) {
  4300. cell = cellFocusHandler.getFocusedCell();
  4301. container = cellFocusHandler.containerWithFocus;
  4302. } else {
  4303. // Click in a location that does not contain cells.
  4304. return;
  4305. }
  4306. } else {
  4307. cell = container.getCell(e);
  4308. if (eventType.equals(BrowserEvents.MOUSEDOWN)) {
  4309. cellOnPrevMouseDown = cell;
  4310. } else if (cell == null && eventType.equals(BrowserEvents.CLICK)) {
  4311. /*
  4312. * Chrome has an interesting idea on click targets (see
  4313. * cellOnPrevMouseDown javadoc). Firefox, on the other hand, has
  4314. * the mousedown target as the click target.
  4315. */
  4316. cell = cellOnPrevMouseDown;
  4317. }
  4318. }
  4319. assert cell != null : "received " + eventType
  4320. + "-event with a null cell target";
  4321. eventCell.set(cell);
  4322. // Editor can steal focus from Grid and is still handled
  4323. if (handleEditorEvent(event, container)) {
  4324. return;
  4325. }
  4326. // Fire GridKeyEvents and GridClickEvents. Pass the event to escalator.
  4327. super.onBrowserEvent(event);
  4328. if (!isElementInChildWidget(e)) {
  4329. // Sorting through header Click / KeyUp
  4330. if (handleHeaderDefaultRowEvent(event, container)) {
  4331. return;
  4332. }
  4333. if (handleRendererEvent(event, container)) {
  4334. return;
  4335. }
  4336. if (handleNavigationEvent(event, container)) {
  4337. return;
  4338. }
  4339. if (handleCellFocusEvent(event, container)) {
  4340. return;
  4341. }
  4342. }
  4343. }
  4344. private boolean isElementInChildWidget(Element e) {
  4345. Widget w = WidgetUtil.findWidget(e, null);
  4346. if (w == this) {
  4347. return false;
  4348. }
  4349. /*
  4350. * If e is directly inside this grid, but the grid is wrapped in a
  4351. * Composite, findWidget is not going to find this, only the wrapper.
  4352. * Thus we need to check its parents to see if we encounter this; if we
  4353. * don't, the found widget is actually a parent of this, so we should
  4354. * return false.
  4355. */
  4356. while (w != null && w != this) {
  4357. w = w.getParent();
  4358. }
  4359. return w != null;
  4360. }
  4361. private boolean handleEditorEvent(Event event, RowContainer container) {
  4362. if (editor.getState() != Editor.State.INACTIVE) {
  4363. if (event.getTypeInt() == Event.ONKEYDOWN
  4364. && event.getKeyCode() == Editor.KEYCODE_HIDE) {
  4365. editor.cancel();
  4366. }
  4367. return true;
  4368. }
  4369. if (container == escalator.getBody() && editor.isEnabled()) {
  4370. if (event.getTypeInt() == Event.ONDBLCLICK) {
  4371. editor.editRow(eventCell.getRowIndex());
  4372. return true;
  4373. } else if (event.getTypeInt() == Event.ONKEYDOWN
  4374. && event.getKeyCode() == Editor.KEYCODE_SHOW) {
  4375. editor.editRow(cellFocusHandler.rowWithFocus);
  4376. return true;
  4377. }
  4378. }
  4379. return false;
  4380. }
  4381. private boolean handleRendererEvent(Event event, RowContainer container) {
  4382. if (container == escalator.getBody()) {
  4383. Column<?, T> gridColumn = eventCell.getColumn();
  4384. boolean enterKey = event.getType().equals(BrowserEvents.KEYDOWN)
  4385. && event.getKeyCode() == KeyCodes.KEY_ENTER;
  4386. boolean doubleClick = event.getType()
  4387. .equals(BrowserEvents.DBLCLICK);
  4388. if (gridColumn.getRenderer() instanceof ComplexRenderer) {
  4389. ComplexRenderer<?> cplxRenderer = (ComplexRenderer<?>) gridColumn
  4390. .getRenderer();
  4391. if (cplxRenderer.getConsumedEvents().contains(event.getType())) {
  4392. if (cplxRenderer.onBrowserEvent(eventCell, event)) {
  4393. return true;
  4394. }
  4395. }
  4396. // Calls onActivate if KeyDown and Enter or double click
  4397. if ((enterKey || doubleClick)
  4398. && cplxRenderer.onActivate(eventCell)) {
  4399. return true;
  4400. }
  4401. }
  4402. }
  4403. return false;
  4404. }
  4405. private boolean handleCellFocusEvent(Event event, RowContainer container) {
  4406. Collection<String> navigation = cellFocusHandler.getNavigationEvents();
  4407. if (navigation.contains(event.getType())) {
  4408. cellFocusHandler.handleNavigationEvent(event, eventCell);
  4409. }
  4410. return false;
  4411. }
  4412. private boolean handleNavigationEvent(Event event, RowContainer unused) {
  4413. if (!event.getType().equals(BrowserEvents.KEYDOWN)) {
  4414. // Only handle key downs
  4415. return false;
  4416. }
  4417. int newRow = -1;
  4418. RowContainer container = escalator.getBody();
  4419. switch (event.getKeyCode()) {
  4420. case KeyCodes.KEY_HOME:
  4421. if (container.getRowCount() > 0) {
  4422. newRow = 0;
  4423. }
  4424. break;
  4425. case KeyCodes.KEY_END:
  4426. if (container.getRowCount() > 0) {
  4427. newRow = container.getRowCount() - 1;
  4428. }
  4429. break;
  4430. case KeyCodes.KEY_PAGEUP: {
  4431. Range range = escalator.getVisibleRowRange();
  4432. if (!range.isEmpty()) {
  4433. int firstIndex = getFirstVisibleRowIndex();
  4434. newRow = firstIndex - range.length();
  4435. if (newRow < 0) {
  4436. newRow = 0;
  4437. }
  4438. }
  4439. break;
  4440. }
  4441. case KeyCodes.KEY_PAGEDOWN: {
  4442. Range range = escalator.getVisibleRowRange();
  4443. if (!range.isEmpty()) {
  4444. int lastIndex = getLastVisibleRowIndex();
  4445. newRow = lastIndex + range.length();
  4446. if (newRow >= container.getRowCount()) {
  4447. newRow = container.getRowCount() - 1;
  4448. }
  4449. }
  4450. break;
  4451. }
  4452. default:
  4453. return false;
  4454. }
  4455. scrollToRow(newRow);
  4456. return true;
  4457. }
  4458. private Point rowEventTouchStartingPoint;
  4459. private CellStyleGenerator<T> cellStyleGenerator;
  4460. private RowStyleGenerator<T> rowStyleGenerator;
  4461. private RowReference<T> rowReference = new RowReference<T>(this);
  4462. private CellReference<T> cellReference = new CellReference<T>(rowReference);
  4463. private RendererCellReference rendererCellReference = new RendererCellReference(
  4464. (RowReference<Object>) rowReference);
  4465. private boolean handleHeaderDefaultRowEvent(Event event,
  4466. RowContainer container) {
  4467. if (container != escalator.getHeader()) {
  4468. return false;
  4469. }
  4470. if (!getHeader().getRow(eventCell.getRowIndex()).isDefault()) {
  4471. return false;
  4472. }
  4473. if (!eventCell.getColumn().isSortable()) {
  4474. // Only handle sorting events if the column is sortable
  4475. return false;
  4476. }
  4477. if (BrowserEvents.MOUSEDOWN.equals(event.getType())
  4478. && event.getShiftKey()) {
  4479. // Don't select text when shift clicking on a header.
  4480. event.preventDefault();
  4481. }
  4482. if (BrowserEvents.TOUCHSTART.equals(event.getType())) {
  4483. if (event.getTouches().length() > 1) {
  4484. return false;
  4485. }
  4486. event.preventDefault();
  4487. Touch touch = event.getChangedTouches().get(0);
  4488. rowEventTouchStartingPoint = new Point(touch.getClientX(),
  4489. touch.getClientY());
  4490. sorter.sortAfterDelay(GridConstants.LONG_TAP_DELAY, true);
  4491. return true;
  4492. } else if (BrowserEvents.TOUCHMOVE.equals(event.getType())) {
  4493. if (event.getTouches().length() > 1) {
  4494. return false;
  4495. }
  4496. event.preventDefault();
  4497. Touch touch = event.getChangedTouches().get(0);
  4498. double diffX = Math.abs(touch.getClientX()
  4499. - rowEventTouchStartingPoint.getX());
  4500. double diffY = Math.abs(touch.getClientY()
  4501. - rowEventTouchStartingPoint.getY());
  4502. // Cancel long tap if finger strays too far from
  4503. // starting point
  4504. if (diffX > GridConstants.LONG_TAP_THRESHOLD
  4505. || diffY > GridConstants.LONG_TAP_THRESHOLD) {
  4506. sorter.cancelDelayedSort();
  4507. }
  4508. return true;
  4509. } else if (BrowserEvents.TOUCHEND.equals(event.getType())) {
  4510. if (event.getTouches().length() > 1) {
  4511. return false;
  4512. }
  4513. if (sorter.isDelayedSortScheduled()) {
  4514. // Not a long tap yet, perform single sort
  4515. sorter.cancelDelayedSort();
  4516. sorter.sort(eventCell.getColumn(), false);
  4517. }
  4518. return true;
  4519. } else if (BrowserEvents.TOUCHCANCEL.equals(event.getType())) {
  4520. if (event.getTouches().length() > 1) {
  4521. return false;
  4522. }
  4523. sorter.cancelDelayedSort();
  4524. return true;
  4525. } else if (BrowserEvents.CLICK.equals(event.getType())) {
  4526. sorter.sort(eventCell.getColumn(), event.getShiftKey());
  4527. // Click events should go onward to cell focus logic
  4528. return false;
  4529. } else {
  4530. return false;
  4531. }
  4532. }
  4533. @Override
  4534. public com.google.gwt.user.client.Element getSubPartElement(String subPart) {
  4535. // Parse SubPart string to type and indices
  4536. String[] splitArgs = subPart.split("\\[");
  4537. String type = splitArgs[0];
  4538. int[] indices = new int[splitArgs.length - 1];
  4539. for (int i = 0; i < indices.length; ++i) {
  4540. String tmp = splitArgs[i + 1];
  4541. indices[i] = Integer.parseInt(tmp.substring(0, tmp.length() - 1));
  4542. }
  4543. // Get correct RowContainer for type from Escalator
  4544. RowContainer container = null;
  4545. if (type.equalsIgnoreCase("header")) {
  4546. container = escalator.getHeader();
  4547. } else if (type.equalsIgnoreCase("cell")) {
  4548. // If wanted row is not visible, we need to scroll there.
  4549. Range visibleRowRange = escalator.getVisibleRowRange();
  4550. if (indices.length > 0 && !visibleRowRange.contains(indices[0])) {
  4551. try {
  4552. scrollToRow(indices[0]);
  4553. } catch (IllegalArgumentException e) {
  4554. getLogger().log(Level.SEVERE, e.getMessage());
  4555. }
  4556. // Scrolling causes a lazy loading event. No element can
  4557. // currently be retrieved.
  4558. return null;
  4559. }
  4560. container = escalator.getBody();
  4561. } else if (type.equalsIgnoreCase("footer")) {
  4562. container = escalator.getFooter();
  4563. } else if (type.equalsIgnoreCase("editor")) {
  4564. if (editor.getState() != State.ACTIVE) {
  4565. // Editor is not there.
  4566. return null;
  4567. }
  4568. if (indices.length == 0) {
  4569. return DOM.asOld(editor.editorOverlay);
  4570. } else if (indices.length == 1 && indices[0] < columns.size()) {
  4571. escalator.scrollToColumn(indices[0], ScrollDestination.ANY, 0);
  4572. return editor.getWidget(columns.get(indices[0])).getElement();
  4573. } else {
  4574. return null;
  4575. }
  4576. }
  4577. if (null != container) {
  4578. if (indices.length == 0) {
  4579. // No indexing. Just return the wanted container element
  4580. return DOM.asOld(container.getElement());
  4581. } else {
  4582. try {
  4583. return DOM.asOld(getSubPart(container, indices));
  4584. } catch (Exception e) {
  4585. getLogger().log(Level.SEVERE, e.getMessage());
  4586. }
  4587. }
  4588. }
  4589. return null;
  4590. }
  4591. private Element getSubPart(RowContainer container, int[] indices) {
  4592. Element targetElement = container.getRowElement(indices[0]);
  4593. // Scroll wanted column to view if able
  4594. if (indices.length > 1 && targetElement != null) {
  4595. if (escalator.getColumnConfiguration().getFrozenColumnCount() <= indices[1]) {
  4596. escalator.scrollToColumn(indices[1], ScrollDestination.ANY, 0);
  4597. }
  4598. targetElement = getCellFromRow(TableRowElement.as(targetElement),
  4599. indices[1]);
  4600. for (int i = 2; i < indices.length && targetElement != null; ++i) {
  4601. targetElement = (Element) targetElement.getChild(indices[i]);
  4602. }
  4603. }
  4604. return targetElement;
  4605. }
  4606. private Element getCellFromRow(TableRowElement rowElement, int index) {
  4607. int childCount = rowElement.getCells().getLength();
  4608. if (index < 0 || index >= childCount) {
  4609. return null;
  4610. }
  4611. TableCellElement currentCell = null;
  4612. boolean indexInColspan = false;
  4613. int i = 0;
  4614. while (!indexInColspan) {
  4615. currentCell = rowElement.getCells().getItem(i);
  4616. // Calculate if this is the cell we are looking for
  4617. int colSpan = currentCell.getColSpan();
  4618. indexInColspan = index < colSpan + i;
  4619. // Increment by colspan to skip over hidden cells
  4620. i += colSpan;
  4621. }
  4622. return currentCell;
  4623. }
  4624. @Override
  4625. public String getSubPartName(com.google.gwt.user.client.Element subElement) {
  4626. // Containers and matching SubPart types
  4627. List<RowContainer> containers = Arrays.asList(escalator.getHeader(),
  4628. escalator.getBody(), escalator.getFooter());
  4629. List<String> containerType = Arrays.asList("header", "cell", "footer");
  4630. for (int i = 0; i < containers.size(); ++i) {
  4631. RowContainer container = containers.get(i);
  4632. boolean containerRow = (subElement.getTagName().equalsIgnoreCase(
  4633. "tr") && subElement.getParentElement() == container
  4634. .getElement());
  4635. if (containerRow) {
  4636. // Wanted SubPart is row that is a child of containers root
  4637. // To get indices, we use a cell that is a child of this row
  4638. subElement = DOM.asOld(subElement.getFirstChildElement());
  4639. }
  4640. Cell cell = container.getCell(subElement);
  4641. if (cell != null) {
  4642. // Skip the column index if subElement was a child of root
  4643. return containerType.get(i) + "[" + cell.getRow()
  4644. + (containerRow ? "]" : "][" + cell.getColumn() + "]");
  4645. }
  4646. }
  4647. // Check if subelement is part of editor.
  4648. if (editor.getState() == State.ACTIVE) {
  4649. if (editor.editorOverlay.isOrHasChild(subElement)) {
  4650. int i = 0;
  4651. for (Column<?, T> column : columns) {
  4652. if (editor.getWidget(column).getElement()
  4653. .isOrHasChild(subElement)) {
  4654. return "editor[" + i + "]";
  4655. }
  4656. ++i;
  4657. }
  4658. return "editor";
  4659. }
  4660. }
  4661. return null;
  4662. }
  4663. private void setSelectColumnRenderer(
  4664. final Renderer<Boolean> selectColumnRenderer) {
  4665. if (this.selectColumnRenderer == selectColumnRenderer) {
  4666. return;
  4667. }
  4668. if (this.selectColumnRenderer != null) {
  4669. if (this.selectColumnRenderer instanceof ComplexRenderer) {
  4670. // End of Life for the old selection column renderer.
  4671. ((ComplexRenderer<?>) this.selectColumnRenderer).destroy();
  4672. }
  4673. // Clear field so frozen column logic in the remove method knows
  4674. // what to do
  4675. Column<?, T> colToRemove = selectionColumn;
  4676. selectionColumn = null;
  4677. removeColumnSkipSelectionColumnCheck(colToRemove);
  4678. cellFocusHandler.offsetRangeBy(-1);
  4679. }
  4680. this.selectColumnRenderer = selectColumnRenderer;
  4681. if (selectColumnRenderer != null) {
  4682. cellFocusHandler.offsetRangeBy(1);
  4683. selectionColumn = new SelectionColumn(selectColumnRenderer);
  4684. addColumnSkipSelectionColumnCheck(selectionColumn, 0);
  4685. selectionColumn.initDone();
  4686. } else {
  4687. selectionColumn = null;
  4688. refreshBody();
  4689. }
  4690. updateFrozenColumns();
  4691. }
  4692. /**
  4693. * Sets the current selection model.
  4694. * <p>
  4695. * This function will call {@link SelectionModel#setGrid(Grid)}.
  4696. *
  4697. * @param selectionModel
  4698. * a selection model implementation.
  4699. * @throws IllegalArgumentException
  4700. * if selection model argument is null
  4701. */
  4702. public void setSelectionModel(SelectionModel<T> selectionModel) {
  4703. if (selectionModel == null) {
  4704. throw new IllegalArgumentException("Selection model can't be null");
  4705. }
  4706. if (this.selectionModel != null) {
  4707. // Detach selection model from Grid.
  4708. this.selectionModel.setGrid(null);
  4709. }
  4710. this.selectionModel = selectionModel;
  4711. selectionModel.setGrid(this);
  4712. setSelectColumnRenderer(this.selectionModel
  4713. .getSelectionColumnRenderer());
  4714. }
  4715. /**
  4716. * Gets a reference to the current selection model.
  4717. *
  4718. * @return the currently used SelectionModel instance.
  4719. */
  4720. public SelectionModel<T> getSelectionModel() {
  4721. return selectionModel;
  4722. }
  4723. /**
  4724. * Sets current selection mode.
  4725. * <p>
  4726. * This is a shorthand method for {@link Grid#setSelectionModel}.
  4727. *
  4728. * @param mode
  4729. * a selection mode value
  4730. * @see {@link SelectionMode}.
  4731. */
  4732. public void setSelectionMode(SelectionMode mode) {
  4733. SelectionModel<T> model = mode.createModel();
  4734. setSelectionModel(model);
  4735. }
  4736. /**
  4737. * Test if a row is selected.
  4738. *
  4739. * @param row
  4740. * a row object
  4741. * @return true, if the current selection model considers the provided row
  4742. * object selected.
  4743. */
  4744. public boolean isSelected(T row) {
  4745. return selectionModel.isSelected(row);
  4746. }
  4747. /**
  4748. * Select a row using the current selection model.
  4749. * <p>
  4750. * Only selection models implementing {@link SelectionModel.Single} and
  4751. * {@link SelectionModel.Multi} are supported; for anything else, an
  4752. * exception will be thrown.
  4753. *
  4754. * @param row
  4755. * a row object
  4756. * @return <code>true</code> iff the current selection changed
  4757. * @throws IllegalStateException
  4758. * if the current selection model is not an instance of
  4759. * {@link SelectionModel.Single} or {@link SelectionModel.Multi}
  4760. */
  4761. @SuppressWarnings("unchecked")
  4762. public boolean select(T row) {
  4763. if (selectionModel instanceof SelectionModel.Single<?>) {
  4764. return ((SelectionModel.Single<T>) selectionModel).select(row);
  4765. } else if (selectionModel instanceof SelectionModel.Multi<?>) {
  4766. return ((SelectionModel.Multi<T>) selectionModel).select(row);
  4767. } else {
  4768. throw new IllegalStateException("Unsupported selection model");
  4769. }
  4770. }
  4771. /**
  4772. * Deselect a row using the current selection model.
  4773. * <p>
  4774. * Only selection models implementing {@link SelectionModel.Single} and
  4775. * {@link SelectionModel.Multi} are supported; for anything else, an
  4776. * exception will be thrown.
  4777. *
  4778. * @param row
  4779. * a row object
  4780. * @return <code>true</code> iff the current selection changed
  4781. * @throws IllegalStateException
  4782. * if the current selection model is not an instance of
  4783. * {@link SelectionModel.Single} or {@link SelectionModel.Multi}
  4784. */
  4785. @SuppressWarnings("unchecked")
  4786. public boolean deselect(T row) {
  4787. if (selectionModel instanceof SelectionModel.Single<?>) {
  4788. return ((SelectionModel.Single<T>) selectionModel).deselect(row);
  4789. } else if (selectionModel instanceof SelectionModel.Multi<?>) {
  4790. return ((SelectionModel.Multi<T>) selectionModel).deselect(row);
  4791. } else {
  4792. throw new IllegalStateException("Unsupported selection model");
  4793. }
  4794. }
  4795. /**
  4796. * Gets last selected row from the current SelectionModel.
  4797. * <p>
  4798. * Only selection models implementing {@link SelectionModel.Single} are
  4799. * valid for this method; for anything else, use the
  4800. * {@link Grid#getSelectedRows()} method.
  4801. *
  4802. * @return a selected row reference, or null, if no row is selected
  4803. * @throws IllegalStateException
  4804. * if the current selection model is not an instance of
  4805. * {@link SelectionModel.Single}
  4806. */
  4807. public T getSelectedRow() {
  4808. if (selectionModel instanceof SelectionModel.Single<?>) {
  4809. return ((SelectionModel.Single<T>) selectionModel).getSelectedRow();
  4810. } else {
  4811. throw new IllegalStateException(
  4812. "Unsupported selection model; can not get single selected row");
  4813. }
  4814. }
  4815. /**
  4816. * Gets currently selected rows from the current selection model.
  4817. *
  4818. * @return a non-null collection containing all currently selected rows.
  4819. */
  4820. public Collection<T> getSelectedRows() {
  4821. return selectionModel.getSelectedRows();
  4822. }
  4823. @Override
  4824. public HandlerRegistration addSelectionHandler(
  4825. final SelectionHandler<T> handler) {
  4826. return addHandler(handler, SelectionEvent.getType());
  4827. }
  4828. /**
  4829. * Sets the current sort order using the fluid Sort API. Read the
  4830. * documentation for {@link Sort} for more information.
  4831. *
  4832. * @param s
  4833. * a sort instance
  4834. */
  4835. public void sort(Sort s) {
  4836. setSortOrder(s.build());
  4837. }
  4838. /**
  4839. * Sorts the Grid data in ascending order along one column.
  4840. *
  4841. * @param column
  4842. * a grid column reference
  4843. */
  4844. public <C> void sort(Column<C, T> column) {
  4845. sort(column, SortDirection.ASCENDING);
  4846. }
  4847. /**
  4848. * Sorts the Grid data along one column.
  4849. *
  4850. * @param column
  4851. * a grid column reference
  4852. * @param direction
  4853. * a sort direction value
  4854. */
  4855. public <C> void sort(Column<C, T> column, SortDirection direction) {
  4856. sort(Sort.by(column, direction));
  4857. }
  4858. /**
  4859. * Sets the sort order to use. Setting this causes the Grid to re-sort
  4860. * itself.
  4861. *
  4862. * @param order
  4863. * a sort order list. If set to null, the sort order is cleared.
  4864. */
  4865. public void setSortOrder(List<SortOrder> order) {
  4866. setSortOrder(order, false);
  4867. }
  4868. private void setSortOrder(List<SortOrder> order, boolean userOriginated) {
  4869. if (order != sortOrder) {
  4870. sortOrder.clear();
  4871. if (order != null) {
  4872. sortOrder.addAll(order);
  4873. }
  4874. }
  4875. sort(userOriginated);
  4876. }
  4877. /**
  4878. * Get a copy of the current sort order array.
  4879. *
  4880. * @return a copy of the current sort order array
  4881. */
  4882. public List<SortOrder> getSortOrder() {
  4883. return Collections.unmodifiableList(sortOrder);
  4884. }
  4885. /**
  4886. * Finds the sorting order for this column
  4887. */
  4888. private SortOrder getSortOrder(Column<?, ?> column) {
  4889. for (SortOrder order : getSortOrder()) {
  4890. if (order.getColumn() == column) {
  4891. return order;
  4892. }
  4893. }
  4894. return null;
  4895. }
  4896. /**
  4897. * Register a GWT event handler for a sorting event. This handler gets
  4898. * called whenever this Grid needs its data source to provide data sorted in
  4899. * a specific order.
  4900. *
  4901. * @param handler
  4902. * a sort event handler
  4903. * @return the registration for the event
  4904. */
  4905. public HandlerRegistration addSortHandler(SortHandler<T> handler) {
  4906. return addHandler(handler, SortEvent.getType());
  4907. }
  4908. /**
  4909. * Register a GWT event handler for a select all event. This handler gets
  4910. * called whenever Grid needs all rows selected.
  4911. *
  4912. * @param handler
  4913. * a select all event handler
  4914. */
  4915. public HandlerRegistration addSelectAllHandler(SelectAllHandler<T> handler) {
  4916. return addHandler(handler, SelectAllEvent.getType());
  4917. }
  4918. /**
  4919. * Register a GWT event handler for a data available event. This handler
  4920. * gets called whenever the {@link DataSource} for this Grid has new data
  4921. * available.
  4922. * <p>
  4923. * This handle will be fired with the current available data after
  4924. * registration is done.
  4925. *
  4926. * @param handler
  4927. * a data available event handler
  4928. * @return the registartion for the event
  4929. */
  4930. public HandlerRegistration addDataAvailableHandler(
  4931. final DataAvailableHandler handler) {
  4932. // Deferred call to handler with current row range
  4933. Scheduler.get().scheduleFinally(new ScheduledCommand() {
  4934. @Override
  4935. public void execute() {
  4936. if (!dataIsBeingFetched) {
  4937. handler.onDataAvailable(new DataAvailableEvent(
  4938. currentDataAvailable));
  4939. }
  4940. }
  4941. });
  4942. return addHandler(handler, DataAvailableEvent.TYPE);
  4943. }
  4944. /**
  4945. * Register a BodyKeyDownHandler to this Grid. The event for this handler is
  4946. * fired when a KeyDown event occurs while cell focus is in the Body of this
  4947. * Grid.
  4948. *
  4949. * @param handler
  4950. * the key handler to register
  4951. * @return the registration for the event
  4952. */
  4953. public HandlerRegistration addBodyKeyDownHandler(BodyKeyDownHandler handler) {
  4954. return addHandler(handler, keyDown.getAssociatedType());
  4955. }
  4956. /**
  4957. * Register a BodyKeyUpHandler to this Grid. The event for this handler is
  4958. * fired when a KeyUp event occurs while cell focus is in the Body of this
  4959. * Grid.
  4960. *
  4961. * @param handler
  4962. * the key handler to register
  4963. * @return the registration for the event
  4964. */
  4965. public HandlerRegistration addBodyKeyUpHandler(BodyKeyUpHandler handler) {
  4966. return addHandler(handler, keyUp.getAssociatedType());
  4967. }
  4968. /**
  4969. * Register a BodyKeyPressHandler to this Grid. The event for this handler
  4970. * is fired when a KeyPress event occurs while cell focus is in the Body of
  4971. * this Grid.
  4972. *
  4973. * @param handler
  4974. * the key handler to register
  4975. * @return the registration for the event
  4976. */
  4977. public HandlerRegistration addBodyKeyPressHandler(
  4978. BodyKeyPressHandler handler) {
  4979. return addHandler(handler, keyPress.getAssociatedType());
  4980. }
  4981. /**
  4982. * Register a HeaderKeyDownHandler to this Grid. The event for this handler
  4983. * is fired when a KeyDown event occurs while cell focus is in the Header of
  4984. * this Grid.
  4985. *
  4986. * @param handler
  4987. * the key handler to register
  4988. * @return the registration for the event
  4989. */
  4990. public HandlerRegistration addHeaderKeyDownHandler(
  4991. HeaderKeyDownHandler handler) {
  4992. return addHandler(handler, keyDown.getAssociatedType());
  4993. }
  4994. /**
  4995. * Register a HeaderKeyUpHandler to this Grid. The event for this handler is
  4996. * fired when a KeyUp event occurs while cell focus is in the Header of this
  4997. * Grid.
  4998. *
  4999. * @param handler
  5000. * the key handler to register
  5001. * @return the registration for the event
  5002. */
  5003. public HandlerRegistration addHeaderKeyUpHandler(HeaderKeyUpHandler handler) {
  5004. return addHandler(handler, keyUp.getAssociatedType());
  5005. }
  5006. /**
  5007. * Register a HeaderKeyPressHandler to this Grid. The event for this handler
  5008. * is fired when a KeyPress event occurs while cell focus is in the Header
  5009. * of this Grid.
  5010. *
  5011. * @param handler
  5012. * the key handler to register
  5013. * @return the registration for the event
  5014. */
  5015. public HandlerRegistration addHeaderKeyPressHandler(
  5016. HeaderKeyPressHandler handler) {
  5017. return addHandler(handler, keyPress.getAssociatedType());
  5018. }
  5019. /**
  5020. * Register a FooterKeyDownHandler to this Grid. The event for this handler
  5021. * is fired when a KeyDown event occurs while cell focus is in the Footer of
  5022. * this Grid.
  5023. *
  5024. * @param handler
  5025. * the key handler to register
  5026. * @return the registration for the event
  5027. */
  5028. public HandlerRegistration addFooterKeyDownHandler(
  5029. FooterKeyDownHandler handler) {
  5030. return addHandler(handler, keyDown.getAssociatedType());
  5031. }
  5032. /**
  5033. * Register a FooterKeyUpHandler to this Grid. The event for this handler is
  5034. * fired when a KeyUp event occurs while cell focus is in the Footer of this
  5035. * Grid.
  5036. *
  5037. * @param handler
  5038. * the key handler to register
  5039. * @return the registration for the event
  5040. */
  5041. public HandlerRegistration addFooterKeyUpHandler(FooterKeyUpHandler handler) {
  5042. return addHandler(handler, keyUp.getAssociatedType());
  5043. }
  5044. /**
  5045. * Register a FooterKeyPressHandler to this Grid. The event for this handler
  5046. * is fired when a KeyPress event occurs while cell focus is in the Footer
  5047. * of this Grid.
  5048. *
  5049. * @param handler
  5050. * the key handler to register
  5051. * @return the registration for the event
  5052. */
  5053. public HandlerRegistration addFooterKeyPressHandler(
  5054. FooterKeyPressHandler handler) {
  5055. return addHandler(handler, keyPress.getAssociatedType());
  5056. }
  5057. /**
  5058. * Register a BodyClickHandler to this Grid. The event for this handler is
  5059. * fired when a Click event occurs in the Body of this Grid.
  5060. *
  5061. * @param handler
  5062. * the click handler to register
  5063. * @return the registration for the event
  5064. */
  5065. public HandlerRegistration addBodyClickHandler(BodyClickHandler handler) {
  5066. return addHandler(handler, clickEvent.getAssociatedType());
  5067. }
  5068. /**
  5069. * Register a HeaderClickHandler to this Grid. The event for this handler is
  5070. * fired when a Click event occurs in the Header of this Grid.
  5071. *
  5072. * @param handler
  5073. * the click handler to register
  5074. * @return the registration for the event
  5075. */
  5076. public HandlerRegistration addHeaderClickHandler(HeaderClickHandler handler) {
  5077. return addHandler(handler, clickEvent.getAssociatedType());
  5078. }
  5079. /**
  5080. * Register a FooterClickHandler to this Grid. The event for this handler is
  5081. * fired when a Click event occurs in the Footer of this Grid.
  5082. *
  5083. * @param handler
  5084. * the click handler to register
  5085. * @return the registration for the event
  5086. */
  5087. public HandlerRegistration addFooterClickHandler(FooterClickHandler handler) {
  5088. return addHandler(handler, clickEvent.getAssociatedType());
  5089. }
  5090. /**
  5091. * Register a BodyDoubleClickHandler to this Grid. The event for this
  5092. * handler is fired when a double click event occurs in the Body of this
  5093. * Grid.
  5094. *
  5095. * @param handler
  5096. * the double click handler to register
  5097. * @return the registration for the event
  5098. */
  5099. public HandlerRegistration addBodyDoubleClickHandler(
  5100. BodyDoubleClickHandler handler) {
  5101. return addHandler(handler, doubleClickEvent.getAssociatedType());
  5102. }
  5103. /**
  5104. * Register a HeaderDoubleClickHandler to this Grid. The event for this
  5105. * handler is fired when a double click event occurs in the Header of this
  5106. * Grid.
  5107. *
  5108. * @param handler
  5109. * the double click handler to register
  5110. * @return the registration for the event
  5111. */
  5112. public HandlerRegistration addHeaderDoubleClickHandler(
  5113. HeaderDoubleClickHandler handler) {
  5114. return addHandler(handler, doubleClickEvent.getAssociatedType());
  5115. }
  5116. /**
  5117. * Register a FooterDoubleClickHandler to this Grid. The event for this
  5118. * handler is fired when a double click event occurs in the Footer of this
  5119. * Grid.
  5120. *
  5121. * @param handler
  5122. * the double click handler to register
  5123. * @return the registration for the event
  5124. */
  5125. public HandlerRegistration addFooterDoubleClickHandler(
  5126. FooterDoubleClickHandler handler) {
  5127. return addHandler(handler, doubleClickEvent.getAssociatedType());
  5128. }
  5129. /**
  5130. * Apply sorting to data source.
  5131. */
  5132. private void sort(boolean userOriginated) {
  5133. refreshHeader();
  5134. fireEvent(new SortEvent<T>(this,
  5135. Collections.unmodifiableList(sortOrder), userOriginated));
  5136. }
  5137. private int getLastVisibleRowIndex() {
  5138. int lastRowIndex = escalator.getVisibleRowRange().getEnd();
  5139. int footerTop = escalator.getFooter().getElement().getAbsoluteTop();
  5140. Element lastRow;
  5141. do {
  5142. lastRow = escalator.getBody().getRowElement(--lastRowIndex);
  5143. } while (lastRow.getAbsoluteTop() > footerTop);
  5144. return lastRowIndex;
  5145. }
  5146. private int getFirstVisibleRowIndex() {
  5147. int firstRowIndex = escalator.getVisibleRowRange().getStart();
  5148. int headerBottom = escalator.getHeader().getElement()
  5149. .getAbsoluteBottom();
  5150. Element firstRow = escalator.getBody().getRowElement(firstRowIndex);
  5151. while (firstRow.getAbsoluteBottom() < headerBottom) {
  5152. firstRow = escalator.getBody().getRowElement(++firstRowIndex);
  5153. }
  5154. return firstRowIndex;
  5155. }
  5156. /**
  5157. * Adds a scroll handler to this grid
  5158. *
  5159. * @param handler
  5160. * the scroll handler to add
  5161. * @return a handler registration for the registered scroll handler
  5162. */
  5163. public HandlerRegistration addScrollHandler(ScrollHandler handler) {
  5164. return addHandler(handler, ScrollEvent.TYPE);
  5165. }
  5166. @Override
  5167. public boolean isWorkPending() {
  5168. return escalator.isWorkPending() || dataIsBeingFetched
  5169. || autoColumnWidthsRecalculator.isScheduled();
  5170. }
  5171. /**
  5172. * Sets a new column order for the grid. All columns which are not ordered
  5173. * here will remain in the order they were before as the last columns of
  5174. * grid.
  5175. *
  5176. * @param orderedColumns
  5177. * array of columns in wanted order
  5178. */
  5179. public void setColumnOrder(Column<?, T>... orderedColumns) {
  5180. ColumnConfiguration conf = getEscalator().getColumnConfiguration();
  5181. // Trigger ComplexRenderer.destroy for old content
  5182. conf.removeColumns(0, conf.getColumnCount());
  5183. List<Column<?, T>> newOrder = new ArrayList<Column<?, T>>();
  5184. if (selectionColumn != null) {
  5185. newOrder.add(selectionColumn);
  5186. }
  5187. int i = 0;
  5188. for (Column<?, T> column : orderedColumns) {
  5189. if (columns.contains(column)) {
  5190. newOrder.add(column);
  5191. ++i;
  5192. } else {
  5193. throw new IllegalArgumentException("Given column at index " + i
  5194. + " does not exist in Grid");
  5195. }
  5196. }
  5197. if (columns.size() != newOrder.size()) {
  5198. columns.removeAll(newOrder);
  5199. newOrder.addAll(columns);
  5200. }
  5201. columns = newOrder;
  5202. // Do ComplexRenderer.init and render new content
  5203. conf.insertColumns(0, columns.size());
  5204. // Update column widths.
  5205. for (Column<?, T> column : columns) {
  5206. column.reapplyWidth();
  5207. }
  5208. // Recalculate all the colspans
  5209. for (HeaderRow row : header.getRows()) {
  5210. row.calculateColspans();
  5211. }
  5212. for (FooterRow row : footer.getRows()) {
  5213. row.calculateColspans();
  5214. }
  5215. }
  5216. /**
  5217. * Sets the style generator that is used for generating styles for cells
  5218. *
  5219. * @param cellStyleGenerator
  5220. * the cell style generator to set, or <code>null</code> to
  5221. * remove a previously set generator
  5222. */
  5223. public void setCellStyleGenerator(CellStyleGenerator<T> cellStyleGenerator) {
  5224. this.cellStyleGenerator = cellStyleGenerator;
  5225. refreshBody();
  5226. }
  5227. /**
  5228. * Gets the style generator that is used for generating styles for cells
  5229. *
  5230. * @return the cell style generator, or <code>null</code> if no generator is
  5231. * set
  5232. */
  5233. public CellStyleGenerator<T> getCellStyleGenerator() {
  5234. return cellStyleGenerator;
  5235. }
  5236. /**
  5237. * Sets the style generator that is used for generating styles for rows
  5238. *
  5239. * @param rowStyleGenerator
  5240. * the row style generator to set, or <code>null</code> to remove
  5241. * a previously set generator
  5242. */
  5243. public void setRowStyleGenerator(RowStyleGenerator<T> rowStyleGenerator) {
  5244. this.rowStyleGenerator = rowStyleGenerator;
  5245. refreshBody();
  5246. }
  5247. /**
  5248. * Gets the style generator that is used for generating styles for rows
  5249. *
  5250. * @return the row style generator, or <code>null</code> if no generator is
  5251. * set
  5252. */
  5253. public RowStyleGenerator<T> getRowStyleGenerator() {
  5254. return rowStyleGenerator;
  5255. }
  5256. private static void setCustomStyleName(Element element, String styleName) {
  5257. assert element != null;
  5258. String oldStyleName = element
  5259. .getPropertyString(CUSTOM_STYLE_PROPERTY_NAME);
  5260. if (!SharedUtil.equals(oldStyleName, styleName)) {
  5261. if (oldStyleName != null) {
  5262. element.removeClassName(oldStyleName);
  5263. }
  5264. if (styleName != null) {
  5265. element.addClassName(styleName);
  5266. }
  5267. element.setPropertyString(CUSTOM_STYLE_PROPERTY_NAME, styleName);
  5268. }
  5269. }
  5270. /**
  5271. * Opens the editor over the row with the given index.
  5272. *
  5273. * @param rowIndex
  5274. * the index of the row to be edited
  5275. *
  5276. * @throws IllegalStateException
  5277. * if the editor is not enabled
  5278. * @throws IllegalStateException
  5279. * if the editor is already in edit mode
  5280. */
  5281. public void editRow(int rowIndex) {
  5282. editor.editRow(rowIndex);
  5283. }
  5284. /**
  5285. * Returns whether the editor is currently open on some row.
  5286. *
  5287. * @return {@code true} if the editor is active, {@code false} otherwise.
  5288. */
  5289. public boolean isEditorActive() {
  5290. return editor.getState() != State.INACTIVE;
  5291. }
  5292. /**
  5293. * Saves any unsaved changes in the editor to the data source.
  5294. *
  5295. * @throws IllegalStateException
  5296. * if the editor is not enabled
  5297. * @throws IllegalStateException
  5298. * if the editor is not in edit mode
  5299. */
  5300. public void saveEditor() {
  5301. editor.save();
  5302. }
  5303. /**
  5304. * Cancels the currently active edit and hides the editor. Any changes that
  5305. * are not {@link #saveEditor() saved} are lost.
  5306. *
  5307. * @throws IllegalStateException
  5308. * if the editor is not enabled
  5309. * @throws IllegalStateException
  5310. * if the editor is not in edit mode
  5311. */
  5312. public void cancelEditor() {
  5313. editor.cancel();
  5314. }
  5315. /**
  5316. * Returns the handler responsible for binding data and editor widgets to
  5317. * the editor.
  5318. *
  5319. * @return the editor handler or null if not set
  5320. */
  5321. public EditorHandler<T> getEditorHandler() {
  5322. return editor.getHandler();
  5323. }
  5324. /**
  5325. * Sets the handler responsible for binding data and editor widgets to the
  5326. * editor.
  5327. *
  5328. * @param rowHandler
  5329. * the new editor handler
  5330. *
  5331. * @throws IllegalStateException
  5332. * if the editor is currently in edit mode
  5333. */
  5334. public void setEditorHandler(EditorHandler<T> handler) {
  5335. editor.setHandler(handler);
  5336. }
  5337. /**
  5338. * Returns the enabled state of the editor.
  5339. *
  5340. * @return true if editing is enabled, false otherwise
  5341. */
  5342. public boolean isEditorEnabled() {
  5343. return editor.isEnabled();
  5344. }
  5345. /**
  5346. * Sets the enabled state of the editor.
  5347. *
  5348. * @param enabled
  5349. * true to enable editing, false to disable
  5350. *
  5351. * @throws IllegalStateException
  5352. * if in edit mode and trying to disable
  5353. * @throws IllegalStateException
  5354. * if the editor handler is not set
  5355. */
  5356. public void setEditorEnabled(boolean enabled) {
  5357. editor.setEnabled(enabled);
  5358. }
  5359. /**
  5360. * Returns the editor widget associated with the given column. If the editor
  5361. * is not active, returns null.
  5362. *
  5363. * @param column
  5364. * the column
  5365. * @return the widget if the editor is open, null otherwise
  5366. */
  5367. public Widget getEditorWidget(Column<?, T> column) {
  5368. return editor.getWidget(column);
  5369. }
  5370. /**
  5371. * Sets the caption on the save button in the Grid editor.
  5372. *
  5373. * @param saveCaption
  5374. * the caption to set
  5375. * @throws IllegalArgumentException
  5376. * if {@code saveCaption} is {@code null}
  5377. */
  5378. public void setEditorSaveCaption(String saveCaption)
  5379. throws IllegalArgumentException {
  5380. editor.setSaveCaption(saveCaption);
  5381. }
  5382. /**
  5383. * Gets the current caption on the save button in the Grid editor.
  5384. *
  5385. * @return the current caption on the save button
  5386. */
  5387. public String getEditorSaveCaption() {
  5388. return editor.getSaveCaption();
  5389. }
  5390. /**
  5391. * Sets the caption on the cancel button in the Grid editor.
  5392. *
  5393. * @param cancelCaption
  5394. * the caption to set
  5395. * @throws IllegalArgumentException
  5396. * if {@code cancelCaption} is {@code null}
  5397. */
  5398. public void setEditorCancelCaption(String cancelCaption)
  5399. throws IllegalArgumentException {
  5400. editor.setCancelCaption(cancelCaption);
  5401. }
  5402. /**
  5403. * Gets the caption on the cancel button in the Grid editor.
  5404. *
  5405. * @return the current caption on the cancel button
  5406. */
  5407. public String getEditorCancelCaption() {
  5408. return editor.getCancelCaption();
  5409. }
  5410. @Override
  5411. protected void onAttach() {
  5412. super.onAttach();
  5413. if (getEscalator().getBody().getRowCount() == 0 && dataSource != null) {
  5414. setEscalatorSizeFromDataSource();
  5415. }
  5416. }
  5417. @Override
  5418. public void onResize() {
  5419. super.onResize();
  5420. /*
  5421. * Delay calculation to be deferred so Escalator can do it's magic.
  5422. */
  5423. Scheduler.get().scheduleDeferred(new ScheduledCommand() {
  5424. @Override
  5425. public void execute() {
  5426. if (!autoColumnWidthsRecalculator.isScheduled()) {
  5427. autoColumnWidthsRecalculator.schedule();
  5428. }
  5429. }
  5430. });
  5431. }
  5432. /**
  5433. * Grid does not support adding Widgets this way.
  5434. * <p>
  5435. * This method is implemented only because removing widgets from Grid (added
  5436. * via e.g. {@link Renderer}s) requires the {@link HasWidgets} interface.
  5437. *
  5438. * @param w
  5439. * irrelevant
  5440. * @throws UnsupportedOperationException
  5441. * always
  5442. */
  5443. @Override
  5444. @Deprecated
  5445. public void add(Widget w) {
  5446. throw new UnsupportedOperationException(
  5447. "Cannot add widgets to Grid with this method");
  5448. }
  5449. /**
  5450. * Grid does not support clearing Widgets this way.
  5451. * <p>
  5452. * This method is implemented only because removing widgets from Grid (added
  5453. * via e.g. {@link Renderer}s) requires the {@link HasWidgets} interface.
  5454. *
  5455. * @throws UnsupportedOperationException
  5456. * always
  5457. */
  5458. @Override
  5459. @Deprecated
  5460. public void clear() {
  5461. throw new UnsupportedOperationException(
  5462. "Cannot clear widgets from Grid this way");
  5463. }
  5464. /**
  5465. * Grid does not support iterating through Widgets this way.
  5466. * <p>
  5467. * This method is implemented only because removing widgets from Grid (added
  5468. * via e.g. {@link Renderer}s) requires the {@link HasWidgets} interface.
  5469. *
  5470. * @return never
  5471. * @throws UnsupportedOperationException
  5472. * always
  5473. */
  5474. @Override
  5475. @Deprecated
  5476. public Iterator<Widget> iterator() {
  5477. throw new UnsupportedOperationException(
  5478. "Cannot iterate through widgets in Grid this way");
  5479. }
  5480. /**
  5481. * Grid does not support removing Widgets this way.
  5482. * <p>
  5483. * This method is implemented only because removing widgets from Grid (added
  5484. * via e.g. {@link Renderer}s) requires the {@link HasWidgets} interface.
  5485. *
  5486. * @return always <code>false</code>
  5487. */
  5488. @Override
  5489. @Deprecated
  5490. public boolean remove(Widget w) {
  5491. /*
  5492. * This is the method that is the sole reason to have Grid implement
  5493. * HasWidget - when Vaadin removes a Component from the hierarchy, the
  5494. * corresponding Widget will call removeFromParent() on itself. GWT will
  5495. * check there that its parent (i.e. Grid) implements HasWidgets, and
  5496. * will call this remove(Widget) method.
  5497. *
  5498. * tl;dr: all this song and dance to make sure GWT's sanity checks
  5499. * aren't triggered, even though they effectively do nothing interesting
  5500. * from Grid's perspective.
  5501. */
  5502. return false;
  5503. }
  5504. /**
  5505. * Accesses the package private method Widget#setParent()
  5506. *
  5507. * @param widget
  5508. * The widget to access
  5509. * @param parent
  5510. * The parent to set
  5511. */
  5512. private static native final void setParent(Widget widget, Grid<?> parent)
  5513. /*-{
  5514. widget.@com.google.gwt.user.client.ui.Widget::setParent(Lcom/google/gwt/user/client/ui/Widget;)(parent);
  5515. }-*/;
  5516. /**
  5517. * Resets all cached pixel sizes and reads new values from the DOM. This
  5518. * methods should be used e.g. when styles affecting the dimensions of
  5519. * elements in this grid have been changed.
  5520. */
  5521. public void resetSizesFromDom() {
  5522. getEscalator().resetSizesFromDom();
  5523. }
  5524. /**
  5525. * Requests that the column widths should be recalculated.
  5526. * <p>
  5527. * The actual recalculation is not necessarily done immediately so you
  5528. * cannot rely on the columns being the correct width after the call
  5529. * returns.
  5530. *
  5531. * @since
  5532. */
  5533. public void recalculateColumnWidths() {
  5534. autoColumnWidthsRecalculator.schedule();
  5535. }
  5536. }