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 218KB

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