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.

VScrollTable.java 295KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032
  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.ui;
  17. import java.util.ArrayList;
  18. import java.util.Collection;
  19. import java.util.HashMap;
  20. import java.util.HashSet;
  21. import java.util.Iterator;
  22. import java.util.LinkedList;
  23. import java.util.List;
  24. import java.util.Map;
  25. import java.util.Set;
  26. import com.google.gwt.core.client.JavaScriptObject;
  27. import com.google.gwt.core.client.Scheduler;
  28. import com.google.gwt.core.client.Scheduler.ScheduledCommand;
  29. import com.google.gwt.dom.client.Document;
  30. import com.google.gwt.dom.client.Element;
  31. import com.google.gwt.dom.client.NativeEvent;
  32. import com.google.gwt.dom.client.Node;
  33. import com.google.gwt.dom.client.NodeList;
  34. import com.google.gwt.dom.client.Style;
  35. import com.google.gwt.dom.client.Style.Display;
  36. import com.google.gwt.dom.client.Style.Overflow;
  37. import com.google.gwt.dom.client.Style.Position;
  38. import com.google.gwt.dom.client.Style.TextAlign;
  39. import com.google.gwt.dom.client.Style.Unit;
  40. import com.google.gwt.dom.client.Style.Visibility;
  41. import com.google.gwt.dom.client.TableCellElement;
  42. import com.google.gwt.dom.client.TableRowElement;
  43. import com.google.gwt.dom.client.TableSectionElement;
  44. import com.google.gwt.dom.client.Touch;
  45. import com.google.gwt.event.dom.client.BlurEvent;
  46. import com.google.gwt.event.dom.client.BlurHandler;
  47. import com.google.gwt.event.dom.client.FocusEvent;
  48. import com.google.gwt.event.dom.client.FocusHandler;
  49. import com.google.gwt.event.dom.client.KeyCodes;
  50. import com.google.gwt.event.dom.client.KeyDownEvent;
  51. import com.google.gwt.event.dom.client.KeyDownHandler;
  52. import com.google.gwt.event.dom.client.KeyPressEvent;
  53. import com.google.gwt.event.dom.client.KeyPressHandler;
  54. import com.google.gwt.event.dom.client.KeyUpEvent;
  55. import com.google.gwt.event.dom.client.KeyUpHandler;
  56. import com.google.gwt.event.dom.client.ScrollEvent;
  57. import com.google.gwt.event.dom.client.ScrollHandler;
  58. import com.google.gwt.event.logical.shared.CloseEvent;
  59. import com.google.gwt.event.logical.shared.CloseHandler;
  60. import com.google.gwt.event.shared.HandlerRegistration;
  61. import com.google.gwt.regexp.shared.MatchResult;
  62. import com.google.gwt.regexp.shared.RegExp;
  63. import com.google.gwt.user.client.Command;
  64. import com.google.gwt.user.client.DOM;
  65. import com.google.gwt.user.client.Event;
  66. import com.google.gwt.user.client.Timer;
  67. import com.google.gwt.user.client.Window;
  68. import com.google.gwt.user.client.ui.FlowPanel;
  69. import com.google.gwt.user.client.ui.HasWidgets;
  70. import com.google.gwt.user.client.ui.Panel;
  71. import com.google.gwt.user.client.ui.PopupPanel;
  72. import com.google.gwt.user.client.ui.UIObject;
  73. import com.google.gwt.user.client.ui.Widget;
  74. import com.vaadin.client.ApplicationConnection;
  75. import com.vaadin.client.BrowserInfo;
  76. import com.vaadin.client.ComponentConnector;
  77. import com.vaadin.client.ConnectorMap;
  78. import com.vaadin.client.Focusable;
  79. import com.vaadin.client.MouseEventDetailsBuilder;
  80. import com.vaadin.client.StyleConstants;
  81. import com.vaadin.client.TooltipInfo;
  82. import com.vaadin.client.UIDL;
  83. import com.vaadin.client.Util;
  84. import com.vaadin.client.VConsole;
  85. import com.vaadin.client.VTooltip;
  86. import com.vaadin.client.ui.VScrollTable.VScrollTableBody.VScrollTableRow;
  87. import com.vaadin.client.ui.dd.DDUtil;
  88. import com.vaadin.client.ui.dd.VAbstractDropHandler;
  89. import com.vaadin.client.ui.dd.VAcceptCallback;
  90. import com.vaadin.client.ui.dd.VDragAndDropManager;
  91. import com.vaadin.client.ui.dd.VDragEvent;
  92. import com.vaadin.client.ui.dd.VHasDropHandler;
  93. import com.vaadin.client.ui.dd.VTransferable;
  94. import com.vaadin.shared.AbstractComponentState;
  95. import com.vaadin.shared.MouseEventDetails;
  96. import com.vaadin.shared.ui.dd.VerticalDropLocation;
  97. import com.vaadin.shared.ui.table.TableConstants;
  98. /**
  99. * VScrollTable
  100. *
  101. * VScrollTable is a FlowPanel having two widgets in it: * TableHead component *
  102. * ScrollPanel
  103. *
  104. * TableHead contains table's header and widgets + logic for resizing,
  105. * reordering and hiding columns.
  106. *
  107. * ScrollPanel contains VScrollTableBody object which handles content. To save
  108. * some bandwidth and to improve clients responsiveness with loads of data, in
  109. * VScrollTableBody all rows are not necessary rendered. There are "spacers" in
  110. * VScrollTableBody to use the exact same space as non-rendered rows would use.
  111. * This way we can use seamlessly traditional scrollbars and scrolling to fetch
  112. * more rows instead of "paging".
  113. *
  114. * In VScrollTable we listen to scroll events. On horizontal scrolling we also
  115. * update TableHeads scroll position which has its scrollbars hidden. On
  116. * vertical scroll events we will check if we are reaching the end of area where
  117. * we have rows rendered and
  118. *
  119. * TODO implement unregistering for child components in Cells
  120. */
  121. public class VScrollTable extends FlowPanel implements HasWidgets,
  122. ScrollHandler, VHasDropHandler, FocusHandler, BlurHandler, Focusable,
  123. ActionOwner, SubPartAware {
  124. /**
  125. * Simple interface for parts of the table capable of owning a context menu.
  126. *
  127. * @author Vaadin Ltd
  128. */
  129. private interface ContextMenuOwner {
  130. public void showContextMenu(Event event);
  131. }
  132. /**
  133. * Handles showing context menu on "long press" from a touch screen.
  134. *
  135. * @author Vaadin Ltd
  136. */
  137. private class TouchContextProvider {
  138. private static final int TOUCH_CONTEXT_MENU_TIMEOUT = 500;
  139. private Timer contextTouchTimeout;
  140. private Event touchStart;
  141. private int touchStartY;
  142. private int touchStartX;
  143. private ContextMenuOwner target;
  144. /**
  145. * Initializes a handler for a certain context menu owner.
  146. *
  147. * @param target
  148. * the owner of the context menu
  149. */
  150. public TouchContextProvider(ContextMenuOwner target) {
  151. this.target = target;
  152. }
  153. /**
  154. * Cancels the current context touch timeout.
  155. */
  156. public void cancel() {
  157. if (contextTouchTimeout != null) {
  158. contextTouchTimeout.cancel();
  159. contextTouchTimeout = null;
  160. }
  161. touchStart = null;
  162. }
  163. /**
  164. * A function to handle touch context events in a table.
  165. *
  166. * @param event
  167. * browser event to handle
  168. */
  169. public void handleTouchEvent(final Event event) {
  170. int type = event.getTypeInt();
  171. switch (type) {
  172. case Event.ONCONTEXTMENU:
  173. target.showContextMenu(event);
  174. break;
  175. case Event.ONTOUCHSTART:
  176. // save position to fields, touches in events are same
  177. // instance during the operation.
  178. touchStart = event;
  179. Touch touch = event.getChangedTouches().get(0);
  180. touchStartX = touch.getClientX();
  181. touchStartY = touch.getClientY();
  182. if (contextTouchTimeout == null) {
  183. contextTouchTimeout = new Timer() {
  184. @Override
  185. public void run() {
  186. if (touchStart != null) {
  187. // Open the context menu if finger
  188. // is held in place long enough.
  189. target.showContextMenu(touchStart);
  190. event.preventDefault();
  191. touchStart = null;
  192. }
  193. }
  194. };
  195. }
  196. contextTouchTimeout.schedule(TOUCH_CONTEXT_MENU_TIMEOUT);
  197. break;
  198. case Event.ONTOUCHCANCEL:
  199. case Event.ONTOUCHEND:
  200. cancel();
  201. break;
  202. case Event.ONTOUCHMOVE:
  203. if (isSignificantMove(event)) {
  204. // Moved finger before the context menu timer
  205. // expired, so let the browser handle the event.
  206. cancel();
  207. }
  208. }
  209. }
  210. /**
  211. * Calculates how many pixels away the user's finger has traveled. This
  212. * reduces the chance of small non-intentional movements from canceling
  213. * the long press detection.
  214. *
  215. * @param event
  216. * the Event for which to check the move distance
  217. * @return true if this is considered an intentional move by the user
  218. */
  219. protected boolean isSignificantMove(Event event) {
  220. if (touchStart == null) {
  221. // no touch start
  222. return false;
  223. }
  224. // Calculate the distance between touch start and the current touch
  225. // position
  226. Touch touch = event.getChangedTouches().get(0);
  227. int deltaX = touch.getClientX() - touchStartX;
  228. int deltaY = touch.getClientY() - touchStartY;
  229. int delta = deltaX * deltaX + deltaY * deltaY;
  230. // Compare to the square of the significant move threshold to remove
  231. // the need for a square root
  232. if (delta > TouchScrollDelegate.SIGNIFICANT_MOVE_THRESHOLD
  233. * TouchScrollDelegate.SIGNIFICANT_MOVE_THRESHOLD) {
  234. return true;
  235. }
  236. return false;
  237. }
  238. }
  239. public static final String STYLENAME = "v-table";
  240. public enum SelectMode {
  241. NONE(0), SINGLE(1), MULTI(2);
  242. private int id;
  243. private SelectMode(int id) {
  244. this.id = id;
  245. }
  246. public int getId() {
  247. return id;
  248. }
  249. }
  250. private static final String ROW_HEADER_COLUMN_KEY = "0";
  251. private static final double CACHE_RATE_DEFAULT = 2;
  252. /**
  253. * The default multi select mode where simple left clicks only selects one
  254. * item, CTRL+left click selects multiple items and SHIFT-left click selects
  255. * a range of items.
  256. */
  257. private static final int MULTISELECT_MODE_DEFAULT = 0;
  258. /**
  259. * The simple multiselect mode is what the table used to have before
  260. * ctrl/shift selections were added. That is that when this is set clicking
  261. * on an item selects/deselects the item and no ctrl/shift selections are
  262. * available.
  263. */
  264. private static final int MULTISELECT_MODE_SIMPLE = 1;
  265. /**
  266. * multiple of pagelength which component will cache when requesting more
  267. * rows
  268. */
  269. private double cache_rate = CACHE_RATE_DEFAULT;
  270. /**
  271. * fraction of pageLenght which can be scrolled without making new request
  272. */
  273. private double cache_react_rate = 0.75 * cache_rate;
  274. public static final char ALIGN_CENTER = 'c';
  275. public static final char ALIGN_LEFT = 'b';
  276. public static final char ALIGN_RIGHT = 'e';
  277. private static final int CHARCODE_SPACE = 32;
  278. private int firstRowInViewPort = 0;
  279. private int pageLength = 15;
  280. private int lastRequestedFirstvisible = 0; // to detect "serverside scroll"
  281. private int firstvisibleOnLastPage = -1; // To detect if the first visible
  282. // is on the last page
  283. /** For internal use only. May be removed or replaced in the future. */
  284. public boolean showRowHeaders = false;
  285. private String[] columnOrder;
  286. protected ApplicationConnection client;
  287. /** For internal use only. May be removed or replaced in the future. */
  288. public String paintableId;
  289. /** For internal use only. May be removed or replaced in the future. */
  290. public boolean immediate;
  291. private boolean nullSelectionAllowed = true;
  292. private SelectMode selectMode = SelectMode.NONE;
  293. public final HashSet<String> selectedRowKeys = new HashSet<String>();
  294. /*
  295. * When scrolling and selecting at the same time, the selections are not in
  296. * sync with the server while retrieving new rows (until key is released).
  297. */
  298. private HashSet<Object> unSyncedselectionsBeforeRowFetch;
  299. /*
  300. * These are used when jumping between pages when pressing Home and End
  301. */
  302. /** For internal use only. May be removed or replaced in the future. */
  303. public boolean selectLastItemInNextRender = false;
  304. /** For internal use only. May be removed or replaced in the future. */
  305. public boolean selectFirstItemInNextRender = false;
  306. /** For internal use only. May be removed or replaced in the future. */
  307. public boolean focusFirstItemInNextRender = false;
  308. /** For internal use only. May be removed or replaced in the future. */
  309. public boolean focusLastItemInNextRender = false;
  310. /**
  311. * The currently focused row.
  312. * <p>
  313. * For internal use only. May be removed or replaced in the future.
  314. */
  315. public VScrollTableRow focusedRow;
  316. /**
  317. * Helper to store selection range start in when using the keyboard
  318. * <p>
  319. * For internal use only. May be removed or replaced in the future.
  320. */
  321. public VScrollTableRow selectionRangeStart;
  322. /**
  323. * Flag for notifying when the selection has changed and should be sent to
  324. * the server
  325. * <p>
  326. * For internal use only. May be removed or replaced in the future.
  327. */
  328. public boolean selectionChanged = false;
  329. /*
  330. * The speed (in pixels) which the scrolling scrolls vertically/horizontally
  331. */
  332. private int scrollingVelocity = 10;
  333. private Timer scrollingVelocityTimer = null;
  334. /** For internal use only. May be removed or replaced in the future. */
  335. public String[] bodyActionKeys;
  336. private boolean enableDebug = false;
  337. private static final boolean hasNativeTouchScrolling = BrowserInfo.get()
  338. .isTouchDevice()
  339. && !BrowserInfo.get().requiresTouchScrollDelegate();
  340. private Set<String> noncollapsibleColumns;
  341. /**
  342. * The last known row height used to preserve the height of a table with
  343. * custom row heights and a fixed page length after removing the last row
  344. * from the table.
  345. *
  346. * A new VScrollTableBody instance is created every time the number of rows
  347. * changes causing {@link VScrollTableBody#rowHeight} to be discarded and
  348. * the height recalculated by {@link VScrollTableBody#getRowHeight(boolean)}
  349. * to avoid some rounding problems, e.g. round(2 * 19.8) / 2 = 20 but
  350. * round(3 * 19.8) / 3 = 19.66.
  351. */
  352. private double lastKnownRowHeight = Double.NaN;
  353. /**
  354. * Remember scroll position when getting detached to properly scroll back to
  355. * the location that there is data for if getting attached again.
  356. */
  357. private int detachedScrollPosition = 0;
  358. /**
  359. * Represents a select range of rows
  360. */
  361. private class SelectionRange {
  362. private VScrollTableRow startRow;
  363. private final int length;
  364. /**
  365. * Constuctor.
  366. */
  367. public SelectionRange(VScrollTableRow row1, VScrollTableRow row2) {
  368. VScrollTableRow endRow;
  369. if (row2.isBefore(row1)) {
  370. startRow = row2;
  371. endRow = row1;
  372. } else {
  373. startRow = row1;
  374. endRow = row2;
  375. }
  376. length = endRow.getIndex() - startRow.getIndex() + 1;
  377. }
  378. public SelectionRange(VScrollTableRow row, int length) {
  379. startRow = row;
  380. this.length = length;
  381. }
  382. /*
  383. * (non-Javadoc)
  384. *
  385. * @see java.lang.Object#toString()
  386. */
  387. @Override
  388. public String toString() {
  389. return startRow.getKey() + "-" + length;
  390. }
  391. private boolean inRange(VScrollTableRow row) {
  392. return row.getIndex() >= startRow.getIndex()
  393. && row.getIndex() < startRow.getIndex() + length;
  394. }
  395. public Collection<SelectionRange> split(VScrollTableRow row) {
  396. assert row.isAttached();
  397. ArrayList<SelectionRange> ranges = new ArrayList<SelectionRange>(2);
  398. int endOfFirstRange = row.getIndex() - 1;
  399. if (endOfFirstRange >= startRow.getIndex()) {
  400. // create range of first part unless its length is < 1
  401. ranges.add(new SelectionRange(startRow, endOfFirstRange
  402. - startRow.getIndex() + 1));
  403. }
  404. int startOfSecondRange = row.getIndex() + 1;
  405. if (getEndIndex() >= startOfSecondRange) {
  406. // create range of second part unless its length is < 1
  407. VScrollTableRow startOfRange = scrollBody
  408. .getRowByRowIndex(startOfSecondRange);
  409. if (startOfRange != null) {
  410. ranges.add(new SelectionRange(startOfRange, getEndIndex()
  411. - startOfSecondRange + 1));
  412. }
  413. }
  414. return ranges;
  415. }
  416. private int getEndIndex() {
  417. return startRow.getIndex() + length - 1;
  418. }
  419. }
  420. private final HashSet<SelectionRange> selectedRowRanges = new HashSet<SelectionRange>();
  421. /** For internal use only. May be removed or replaced in the future. */
  422. public boolean initializedAndAttached = false;
  423. /**
  424. * Flag to indicate if a column width recalculation is needed due update.
  425. * <p>
  426. * For internal use only. May be removed or replaced in the future.
  427. */
  428. public boolean headerChangedDuringUpdate = false;
  429. /** For internal use only. May be removed or replaced in the future. */
  430. public final TableHead tHead = new TableHead();
  431. /** For internal use only. May be removed or replaced in the future. */
  432. public final TableFooter tFoot = new TableFooter();
  433. /** Handles context menu for table body */
  434. private ContextMenuOwner contextMenuOwner = new ContextMenuOwner() {
  435. @Override
  436. public void showContextMenu(Event event) {
  437. int left = Util.getTouchOrMouseClientX(event);
  438. int top = Util.getTouchOrMouseClientY(event);
  439. boolean menuShown = handleBodyContextMenu(left, top);
  440. if (menuShown) {
  441. event.stopPropagation();
  442. event.preventDefault();
  443. }
  444. }
  445. };
  446. /** Handles touch events to display a context menu for table body */
  447. private TouchContextProvider touchContextProvider = new TouchContextProvider(
  448. contextMenuOwner);
  449. /** For internal use only. May be removed or replaced in the future. */
  450. public final FocusableScrollPanel scrollBodyPanel = new FocusableScrollPanel(
  451. true) {
  452. @Override
  453. public void onBrowserEvent(Event event) {
  454. super.onBrowserEvent(event);
  455. touchContextProvider.handleTouchEvent(event);
  456. };
  457. };
  458. private KeyPressHandler navKeyPressHandler = new KeyPressHandler() {
  459. @Override
  460. public void onKeyPress(KeyPressEvent keyPressEvent) {
  461. // This is used for Firefox only, since Firefox auto-repeat
  462. // works correctly only if we use a key press handler, other
  463. // browsers handle it correctly when using a key down handler
  464. if (!BrowserInfo.get().isGecko()) {
  465. return;
  466. }
  467. NativeEvent event = keyPressEvent.getNativeEvent();
  468. if (!enabled) {
  469. // Cancel default keyboard events on a disabled Table
  470. // (prevents scrolling)
  471. event.preventDefault();
  472. } else if (hasFocus) {
  473. // Key code in Firefox/onKeyPress is present only for
  474. // special keys, otherwise 0 is returned
  475. int keyCode = event.getKeyCode();
  476. if (keyCode == 0 && event.getCharCode() == ' ') {
  477. // Provide a keyCode for space to be compatible with
  478. // FireFox keypress event
  479. keyCode = CHARCODE_SPACE;
  480. }
  481. if (handleNavigation(keyCode,
  482. event.getCtrlKey() || event.getMetaKey(),
  483. event.getShiftKey())) {
  484. event.preventDefault();
  485. }
  486. startScrollingVelocityTimer();
  487. }
  488. }
  489. };
  490. private KeyUpHandler navKeyUpHandler = new KeyUpHandler() {
  491. @Override
  492. public void onKeyUp(KeyUpEvent keyUpEvent) {
  493. NativeEvent event = keyUpEvent.getNativeEvent();
  494. int keyCode = event.getKeyCode();
  495. if (!isFocusable()) {
  496. cancelScrollingVelocityTimer();
  497. } else if (isNavigationKey(keyCode)) {
  498. if (keyCode == getNavigationDownKey()
  499. || keyCode == getNavigationUpKey()) {
  500. /*
  501. * in multiselect mode the server may still have value from
  502. * previous page. Clear it unless doing multiselection or
  503. * just moving focus.
  504. */
  505. if (!event.getShiftKey() && !event.getCtrlKey()) {
  506. instructServerToForgetPreviousSelections();
  507. }
  508. sendSelectedRows();
  509. }
  510. cancelScrollingVelocityTimer();
  511. navKeyDown = false;
  512. }
  513. }
  514. };
  515. private KeyDownHandler navKeyDownHandler = new KeyDownHandler() {
  516. @Override
  517. public void onKeyDown(KeyDownEvent keyDownEvent) {
  518. NativeEvent event = keyDownEvent.getNativeEvent();
  519. // This is not used for Firefox
  520. if (BrowserInfo.get().isGecko()) {
  521. return;
  522. }
  523. if (!enabled) {
  524. // Cancel default keyboard events on a disabled Table
  525. // (prevents scrolling)
  526. event.preventDefault();
  527. } else if (hasFocus) {
  528. if (handleNavigation(event.getKeyCode(), event.getCtrlKey()
  529. || event.getMetaKey(), event.getShiftKey())) {
  530. navKeyDown = true;
  531. event.preventDefault();
  532. }
  533. startScrollingVelocityTimer();
  534. }
  535. }
  536. };
  537. /** For internal use only. May be removed or replaced in the future. */
  538. public int totalRows;
  539. private Set<String> collapsedColumns;
  540. /** For internal use only. May be removed or replaced in the future. */
  541. public final RowRequestHandler rowRequestHandler;
  542. /** For internal use only. May be removed or replaced in the future. */
  543. public VScrollTableBody scrollBody;
  544. private int firstvisible = 0;
  545. private boolean sortAscending;
  546. private String sortColumn;
  547. private String oldSortColumn;
  548. private boolean columnReordering;
  549. /**
  550. * This map contains captions and icon urls for actions like: * "33_c" ->
  551. * "Edit" * "33_i" -> "http://dom.com/edit.png"
  552. */
  553. private final HashMap<Object, String> actionMap = new HashMap<Object, String>();
  554. private String[] visibleColOrder;
  555. private boolean initialContentReceived = false;
  556. private Element scrollPositionElement;
  557. /** For internal use only. May be removed or replaced in the future. */
  558. public boolean enabled;
  559. /** For internal use only. May be removed or replaced in the future. */
  560. public boolean showColHeaders;
  561. /** For internal use only. May be removed or replaced in the future. */
  562. public boolean showColFooters;
  563. /** flag to indicate that table body has changed */
  564. private boolean isNewBody = true;
  565. /**
  566. * Read from the "recalcWidths" -attribute. When it is true, the table will
  567. * recalculate the widths for columns - desirable in some cases. For #1983,
  568. * marked experimental. See also variable <code>refreshContentWidths</code>
  569. * in method {@link TableHead#updateCellsFromUIDL(UIDL)}.
  570. * <p>
  571. * For internal use only. May be removed or replaced in the future.
  572. */
  573. public boolean recalcWidths = false;
  574. /** For internal use only. May be removed or replaced in the future. */
  575. public boolean rendering = false;
  576. private boolean hasFocus = false;
  577. private int dragmode;
  578. private int multiselectmode;
  579. /** For internal use only. May be removed or replaced in the future. */
  580. public int tabIndex;
  581. private TouchScrollDelegate touchScrollDelegate;
  582. /** For internal use only. May be removed or replaced in the future. */
  583. public int lastRenderedHeight;
  584. /**
  585. * Values (serverCacheFirst+serverCacheLast) sent by server that tells which
  586. * rows (indexes) are in the server side cache (page buffer). -1 means
  587. * unknown. The server side cache row MUST MATCH the client side cache rows.
  588. *
  589. * If the client side cache contains additional rows with e.g. buttons, it
  590. * will cause out of sync when such a button is pressed.
  591. *
  592. * If the server side cache contains additional rows with e.g. buttons,
  593. * scrolling in the client will cause empty buttons to be rendered
  594. * (cached=true request for non-existing components)
  595. *
  596. * For internal use only. May be removed or replaced in the future.
  597. */
  598. public int serverCacheFirst = -1;
  599. public int serverCacheLast = -1;
  600. /**
  601. * In several cases TreeTable depends on the scrollBody.lastRendered being
  602. * 'out of sync' while the update is being done. In those cases the sanity
  603. * check must be performed afterwards.
  604. */
  605. public boolean postponeSanityCheckForLastRendered;
  606. /** For internal use only. May be removed or replaced in the future. */
  607. public boolean sizeNeedsInit = true;
  608. /**
  609. * Used to recall the position of an open context menu if we need to close
  610. * and reopen it during a row update.
  611. * <p>
  612. * For internal use only. May be removed or replaced in the future.
  613. */
  614. public class ContextMenuDetails implements CloseHandler<PopupPanel> {
  615. public String rowKey;
  616. public int left;
  617. public int top;
  618. HandlerRegistration closeRegistration;
  619. public ContextMenuDetails(VContextMenu menu, String rowKey, int left,
  620. int top) {
  621. this.rowKey = rowKey;
  622. this.left = left;
  623. this.top = top;
  624. closeRegistration = menu.addCloseHandler(this);
  625. }
  626. @Override
  627. public void onClose(CloseEvent<PopupPanel> event) {
  628. contextMenu = null;
  629. closeRegistration.removeHandler();
  630. }
  631. }
  632. /** For internal use only. May be removed or replaced in the future. */
  633. public ContextMenuDetails contextMenu = null;
  634. private boolean hadScrollBars = false;
  635. private HandlerRegistration addCloseHandler;
  636. public VScrollTable() {
  637. setMultiSelectMode(MULTISELECT_MODE_DEFAULT);
  638. scrollBodyPanel.addFocusHandler(this);
  639. scrollBodyPanel.addBlurHandler(this);
  640. scrollBodyPanel.addScrollHandler(this);
  641. /*
  642. * Firefox auto-repeat works correctly only if we use a key press
  643. * handler, other browsers handle it correctly when using a key down
  644. * handler
  645. */
  646. if (BrowserInfo.get().isGecko()) {
  647. scrollBodyPanel.addKeyPressHandler(navKeyPressHandler);
  648. } else {
  649. scrollBodyPanel.addKeyDownHandler(navKeyDownHandler);
  650. }
  651. scrollBodyPanel.addKeyUpHandler(navKeyUpHandler);
  652. scrollBodyPanel.sinkEvents(Event.TOUCHEVENTS | Event.ONCONTEXTMENU);
  653. setStyleName(STYLENAME);
  654. add(tHead);
  655. add(scrollBodyPanel);
  656. add(tFoot);
  657. rowRequestHandler = new RowRequestHandler();
  658. }
  659. @Override
  660. public void setStyleName(String style) {
  661. updateStyleNames(style, false);
  662. }
  663. @Override
  664. public void setStylePrimaryName(String style) {
  665. updateStyleNames(style, true);
  666. }
  667. private void updateStyleNames(String newStyle, boolean isPrimary) {
  668. scrollBodyPanel
  669. .removeStyleName(getStylePrimaryName() + "-body-wrapper");
  670. scrollBodyPanel.removeStyleName(getStylePrimaryName() + "-body");
  671. if (scrollBody != null) {
  672. scrollBody.removeStyleName(getStylePrimaryName()
  673. + "-body-noselection");
  674. }
  675. if (isPrimary) {
  676. super.setStylePrimaryName(newStyle);
  677. } else {
  678. super.setStyleName(newStyle);
  679. }
  680. scrollBodyPanel.addStyleName(getStylePrimaryName() + "-body-wrapper");
  681. scrollBodyPanel.addStyleName(getStylePrimaryName() + "-body");
  682. tHead.updateStyleNames(getStylePrimaryName());
  683. tFoot.updateStyleNames(getStylePrimaryName());
  684. if (scrollBody != null) {
  685. scrollBody.updateStyleNames(getStylePrimaryName());
  686. }
  687. }
  688. public void init(ApplicationConnection client) {
  689. this.client = client;
  690. // Add a handler to clear saved context menu details when the menu
  691. // closes. See #8526.
  692. addCloseHandler = client.getContextMenu().addCloseHandler(
  693. new CloseHandler<PopupPanel>() {
  694. @Override
  695. public void onClose(CloseEvent<PopupPanel> event) {
  696. contextMenu = null;
  697. }
  698. });
  699. }
  700. /**
  701. * Handles a context menu event on table body.
  702. *
  703. * @param left
  704. * left position of the context menu
  705. * @param top
  706. * top position of the context menu
  707. * @return true if a context menu was shown, otherwise false
  708. */
  709. private boolean handleBodyContextMenu(int left, int top) {
  710. if (enabled && bodyActionKeys != null) {
  711. top += Window.getScrollTop();
  712. left += Window.getScrollLeft();
  713. client.getContextMenu().showAt(this, left, top);
  714. return true;
  715. }
  716. return false;
  717. }
  718. /**
  719. * Fires a column resize event which sends the resize information to the
  720. * server.
  721. *
  722. * @param columnId
  723. * The columnId of the column which was resized
  724. * @param originalWidth
  725. * The width in pixels of the column before the resize event
  726. * @param newWidth
  727. * The width in pixels of the column after the resize event
  728. */
  729. private void fireColumnResizeEvent(String columnId, int originalWidth,
  730. int newWidth) {
  731. client.updateVariable(paintableId, "columnResizeEventColumn", columnId,
  732. false);
  733. client.updateVariable(paintableId, "columnResizeEventPrev",
  734. originalWidth, false);
  735. client.updateVariable(paintableId, "columnResizeEventCurr", newWidth,
  736. immediate);
  737. }
  738. /**
  739. * Non-immediate variable update of column widths for a collection of
  740. * columns.
  741. *
  742. * @param columns
  743. * the columns to trigger the events for.
  744. */
  745. private void sendColumnWidthUpdates(Collection<HeaderCell> columns) {
  746. String[] newSizes = new String[columns.size()];
  747. int ix = 0;
  748. for (HeaderCell cell : columns) {
  749. newSizes[ix++] = cell.getColKey() + ":" + cell.getWidth();
  750. }
  751. client.updateVariable(paintableId, "columnWidthUpdates", newSizes,
  752. false);
  753. }
  754. /**
  755. * Moves the focus one step down
  756. *
  757. * @return Returns true if succeeded
  758. */
  759. private boolean moveFocusDown() {
  760. return moveFocusDown(0);
  761. }
  762. /**
  763. * Moves the focus down by 1+offset rows
  764. *
  765. * @return Returns true if succeeded, else false if the selection could not
  766. * be move downwards
  767. */
  768. private boolean moveFocusDown(int offset) {
  769. if (isSelectable()) {
  770. if (focusedRow == null && scrollBody.iterator().hasNext()) {
  771. // FIXME should focus first visible from top, not first rendered
  772. // ??
  773. return setRowFocus((VScrollTableRow) scrollBody.iterator()
  774. .next());
  775. } else {
  776. VScrollTableRow next = getNextRow(focusedRow, offset);
  777. if (next != null) {
  778. return setRowFocus(next);
  779. }
  780. }
  781. }
  782. return false;
  783. }
  784. /**
  785. * Moves the selection one step up
  786. *
  787. * @return Returns true if succeeded
  788. */
  789. private boolean moveFocusUp() {
  790. return moveFocusUp(0);
  791. }
  792. /**
  793. * Moves the focus row upwards
  794. *
  795. * @return Returns true if succeeded, else false if the selection could not
  796. * be move upwards
  797. *
  798. */
  799. private boolean moveFocusUp(int offset) {
  800. if (isSelectable()) {
  801. if (focusedRow == null && scrollBody.iterator().hasNext()) {
  802. // FIXME logic is exactly the same as in moveFocusDown, should
  803. // be the opposite??
  804. return setRowFocus((VScrollTableRow) scrollBody.iterator()
  805. .next());
  806. } else {
  807. VScrollTableRow prev = getPreviousRow(focusedRow, offset);
  808. if (prev != null) {
  809. return setRowFocus(prev);
  810. } else {
  811. VConsole.log("no previous available");
  812. }
  813. }
  814. }
  815. return false;
  816. }
  817. /**
  818. * Selects a row where the current selection head is
  819. *
  820. * @param ctrlSelect
  821. * Is the selection a ctrl+selection
  822. * @param shiftSelect
  823. * Is the selection a shift+selection
  824. * @return Returns truw
  825. */
  826. private void selectFocusedRow(boolean ctrlSelect, boolean shiftSelect) {
  827. if (focusedRow != null) {
  828. // Arrows moves the selection and clears previous selections
  829. if (isSelectable() && !ctrlSelect && !shiftSelect) {
  830. deselectAll();
  831. focusedRow.toggleSelection();
  832. selectionRangeStart = focusedRow;
  833. } else if (isSelectable() && ctrlSelect && !shiftSelect) {
  834. // Ctrl+arrows moves selection head
  835. selectionRangeStart = focusedRow;
  836. // No selection, only selection head is moved
  837. } else if (isMultiSelectModeAny() && !ctrlSelect && shiftSelect) {
  838. // Shift+arrows selection selects a range
  839. focusedRow.toggleShiftSelection(shiftSelect);
  840. }
  841. }
  842. }
  843. /**
  844. * Sends the selection to the server if changed since the last update/visit.
  845. */
  846. protected void sendSelectedRows() {
  847. sendSelectedRows(immediate);
  848. }
  849. /**
  850. * Sends the selection to the server if it has been changed since the last
  851. * update/visit.
  852. *
  853. * @param immediately
  854. * set to true to immediately send the rows
  855. */
  856. protected void sendSelectedRows(boolean immediately) {
  857. // Don't send anything if selection has not changed
  858. if (!selectionChanged) {
  859. return;
  860. }
  861. // Reset selection changed flag
  862. selectionChanged = false;
  863. // Note: changing the immediateness of this might require changes to
  864. // "clickEvent" immediateness also.
  865. if (isMultiSelectModeDefault()) {
  866. // Convert ranges to a set of strings
  867. Set<String> ranges = new HashSet<String>();
  868. for (SelectionRange range : selectedRowRanges) {
  869. ranges.add(range.toString());
  870. }
  871. // Send the selected row ranges
  872. client.updateVariable(paintableId, "selectedRanges",
  873. ranges.toArray(new String[selectedRowRanges.size()]), false);
  874. selectedRowRanges.clear();
  875. // clean selectedRowKeys so that they don't contain excess values
  876. for (Iterator<String> iterator = selectedRowKeys.iterator(); iterator
  877. .hasNext();) {
  878. String key = iterator.next();
  879. VScrollTableRow renderedRowByKey = getRenderedRowByKey(key);
  880. if (renderedRowByKey != null) {
  881. for (SelectionRange range : selectedRowRanges) {
  882. if (range.inRange(renderedRowByKey)) {
  883. iterator.remove();
  884. }
  885. }
  886. } else {
  887. // orphaned selected key, must be in a range, ignore
  888. iterator.remove();
  889. }
  890. }
  891. }
  892. // Send the selected rows
  893. client.updateVariable(paintableId, "selected",
  894. selectedRowKeys.toArray(new String[selectedRowKeys.size()]),
  895. immediately);
  896. }
  897. /**
  898. * Get the key that moves the selection head upwards. By default it is the
  899. * up arrow key but by overriding this you can change the key to whatever
  900. * you want.
  901. *
  902. * @return The keycode of the key
  903. */
  904. protected int getNavigationUpKey() {
  905. return KeyCodes.KEY_UP;
  906. }
  907. /**
  908. * Get the key that moves the selection head downwards. By default it is the
  909. * down arrow key but by overriding this you can change the key to whatever
  910. * you want.
  911. *
  912. * @return The keycode of the key
  913. */
  914. protected int getNavigationDownKey() {
  915. return KeyCodes.KEY_DOWN;
  916. }
  917. /**
  918. * Get the key that scrolls to the left in the table. By default it is the
  919. * left arrow key but by overriding this you can change the key to whatever
  920. * you want.
  921. *
  922. * @return The keycode of the key
  923. */
  924. protected int getNavigationLeftKey() {
  925. return KeyCodes.KEY_LEFT;
  926. }
  927. /**
  928. * Get the key that scroll to the right on the table. By default it is the
  929. * right arrow key but by overriding this you can change the key to whatever
  930. * you want.
  931. *
  932. * @return The keycode of the key
  933. */
  934. protected int getNavigationRightKey() {
  935. return KeyCodes.KEY_RIGHT;
  936. }
  937. /**
  938. * Get the key that selects an item in the table. By default it is the space
  939. * bar key but by overriding this you can change the key to whatever you
  940. * want.
  941. *
  942. * @return
  943. */
  944. protected int getNavigationSelectKey() {
  945. return CHARCODE_SPACE;
  946. }
  947. /**
  948. * Get the key the moves the selection one page up in the table. By default
  949. * this is the Page Up key but by overriding this you can change the key to
  950. * whatever you want.
  951. *
  952. * @return
  953. */
  954. protected int getNavigationPageUpKey() {
  955. return KeyCodes.KEY_PAGEUP;
  956. }
  957. /**
  958. * Get the key the moves the selection one page down in the table. By
  959. * default this is the Page Down key but by overriding this you can change
  960. * the key to whatever you want.
  961. *
  962. * @return
  963. */
  964. protected int getNavigationPageDownKey() {
  965. return KeyCodes.KEY_PAGEDOWN;
  966. }
  967. /**
  968. * Get the key the moves the selection to the beginning of the table. By
  969. * default this is the Home key but by overriding this you can change the
  970. * key to whatever you want.
  971. *
  972. * @return
  973. */
  974. protected int getNavigationStartKey() {
  975. return KeyCodes.KEY_HOME;
  976. }
  977. /**
  978. * Get the key the moves the selection to the end of the table. By default
  979. * this is the End key but by overriding this you can change the key to
  980. * whatever you want.
  981. *
  982. * @return
  983. */
  984. protected int getNavigationEndKey() {
  985. return KeyCodes.KEY_END;
  986. }
  987. /** For internal use only. May be removed or replaced in the future. */
  988. public void initializeRows(UIDL uidl, UIDL rowData) {
  989. if (scrollBody != null) {
  990. scrollBody.removeFromParent();
  991. }
  992. // Without this call the scroll position is messed up in IE even after
  993. // the lazy scroller has set the scroll position to the first visible
  994. // item
  995. scrollBodyPanel.getScrollPosition();
  996. scrollBody = createScrollBody();
  997. scrollBody.renderInitialRows(rowData, uidl.getIntAttribute("firstrow"),
  998. uidl.getIntAttribute("rows"));
  999. scrollBodyPanel.add(scrollBody);
  1000. // New body starts scrolled to the left, make sure the header and footer
  1001. // are also scrolled to the left
  1002. tHead.setHorizontalScrollPosition(0);
  1003. tFoot.setHorizontalScrollPosition(0);
  1004. initialContentReceived = true;
  1005. sizeNeedsInit = true;
  1006. scrollBody.restoreRowVisibility();
  1007. }
  1008. /** For internal use only. May be removed or replaced in the future. */
  1009. public void updateColumnProperties(UIDL uidl) {
  1010. updateColumnOrder(uidl);
  1011. updateCollapsedColumns(uidl);
  1012. UIDL vc = uidl.getChildByTagName("visiblecolumns");
  1013. if (vc != null) {
  1014. tHead.updateCellsFromUIDL(vc);
  1015. tFoot.updateCellsFromUIDL(vc);
  1016. }
  1017. updateHeader(uidl.getStringArrayAttribute("vcolorder"));
  1018. updateFooter(uidl.getStringArrayAttribute("vcolorder"));
  1019. if (uidl.hasVariable("noncollapsiblecolumns")) {
  1020. noncollapsibleColumns = uidl
  1021. .getStringArrayVariableAsSet("noncollapsiblecolumns");
  1022. }
  1023. }
  1024. private void updateCollapsedColumns(UIDL uidl) {
  1025. if (uidl.hasVariable("collapsedcolumns")) {
  1026. tHead.setColumnCollapsingAllowed(true);
  1027. collapsedColumns = uidl
  1028. .getStringArrayVariableAsSet("collapsedcolumns");
  1029. } else {
  1030. tHead.setColumnCollapsingAllowed(false);
  1031. }
  1032. }
  1033. private void updateColumnOrder(UIDL uidl) {
  1034. if (uidl.hasVariable("columnorder")) {
  1035. columnReordering = true;
  1036. columnOrder = uidl.getStringArrayVariable("columnorder");
  1037. } else {
  1038. columnReordering = false;
  1039. columnOrder = null;
  1040. }
  1041. }
  1042. /** For internal use only. May be removed or replaced in the future. */
  1043. public boolean selectSelectedRows(UIDL uidl) {
  1044. boolean keyboardSelectionOverRowFetchInProgress = false;
  1045. if (uidl.hasVariable("selected")) {
  1046. final Set<String> selectedKeys = uidl
  1047. .getStringArrayVariableAsSet("selected");
  1048. removeUnselectedRowKeys(selectedKeys);
  1049. if (scrollBody != null) {
  1050. Iterator<Widget> iterator = scrollBody.iterator();
  1051. while (iterator.hasNext()) {
  1052. /*
  1053. * Make the focus reflect to the server side state unless we
  1054. * are currently selecting multiple rows with keyboard.
  1055. */
  1056. VScrollTableRow row = (VScrollTableRow) iterator.next();
  1057. boolean selected = selectedKeys.contains(row.getKey());
  1058. if (!selected
  1059. && unSyncedselectionsBeforeRowFetch != null
  1060. && unSyncedselectionsBeforeRowFetch.contains(row
  1061. .getKey())) {
  1062. selected = true;
  1063. keyboardSelectionOverRowFetchInProgress = true;
  1064. }
  1065. if (selected) {
  1066. if (focusedRow == null
  1067. || !selectedRowKeys.contains(focusedRow
  1068. .getKey())) {
  1069. /*
  1070. * The focus is no longer on a selected row. Move
  1071. * focus to the selected row. (#10522)
  1072. *
  1073. * Don't do this for multiselect (#13341).
  1074. *
  1075. * Checking the selection mode here instead of in
  1076. * setRowFocus allows keyboard shift+downarrow
  1077. * selection to work as expected.
  1078. */
  1079. if (isSingleSelectMode()) {
  1080. setRowFocus(row);
  1081. }
  1082. }
  1083. }
  1084. if (selected != row.isSelected()) {
  1085. row.toggleSelection();
  1086. if (!isSingleSelectMode() && !selected) {
  1087. // Update selection range in case a row is
  1088. // unselected from the middle of a range - #8076
  1089. removeRowFromUnsentSelectionRanges(row);
  1090. }
  1091. }
  1092. }
  1093. }
  1094. }
  1095. unSyncedselectionsBeforeRowFetch = null;
  1096. return keyboardSelectionOverRowFetchInProgress;
  1097. }
  1098. private void removeUnselectedRowKeys(final Set<String> selectedKeys) {
  1099. List<String> unselectedKeys = new ArrayList<String>(0);
  1100. for (String key : selectedRowKeys) {
  1101. if (!selectedKeys.contains(key)) {
  1102. unselectedKeys.add(key);
  1103. }
  1104. }
  1105. selectedRowKeys.removeAll(unselectedKeys);
  1106. }
  1107. /** For internal use only. May be removed or replaced in the future. */
  1108. public void updateSortingProperties(UIDL uidl) {
  1109. oldSortColumn = sortColumn;
  1110. if (uidl.hasVariable("sortascending")) {
  1111. sortAscending = uidl.getBooleanVariable("sortascending");
  1112. sortColumn = uidl.getStringVariable("sortcolumn");
  1113. }
  1114. }
  1115. /** For internal use only. May be removed or replaced in the future. */
  1116. public void resizeSortedColumnForSortIndicator() {
  1117. // Force recalculation of the captionContainer element inside the header
  1118. // cell to accomodate for the size of the sort arrow.
  1119. HeaderCell sortedHeader = tHead.getHeaderCell(sortColumn);
  1120. if (sortedHeader != null) {
  1121. // Mark header as sorted now. Any earlier marking would lead to
  1122. // columns with wrong sizes
  1123. sortedHeader.setSorted(true);
  1124. tHead.resizeCaptionContainer(sortedHeader);
  1125. }
  1126. // Also recalculate the width of the captionContainer element in the
  1127. // previously sorted header, since this now has more room.
  1128. HeaderCell oldSortedHeader = tHead.getHeaderCell(oldSortColumn);
  1129. if (oldSortedHeader != null) {
  1130. tHead.resizeCaptionContainer(oldSortedHeader);
  1131. }
  1132. }
  1133. private boolean lazyScrollerIsActive;
  1134. private void disableLazyScroller() {
  1135. lazyScrollerIsActive = false;
  1136. scrollBodyPanel.getElement().getStyle().clearOverflowX();
  1137. scrollBodyPanel.getElement().getStyle().clearOverflowY();
  1138. }
  1139. private void enableLazyScroller() {
  1140. Scheduler.get().scheduleDeferred(lazyScroller);
  1141. lazyScrollerIsActive = true;
  1142. // prevent scrolling to jump in IE11
  1143. scrollBodyPanel.getElement().getStyle().setOverflowX(Overflow.HIDDEN);
  1144. scrollBodyPanel.getElement().getStyle().setOverflowY(Overflow.HIDDEN);
  1145. }
  1146. private boolean isLazyScrollerActive() {
  1147. return lazyScrollerIsActive;
  1148. }
  1149. private ScheduledCommand lazyScroller = new ScheduledCommand() {
  1150. @Override
  1151. public void execute() {
  1152. if (firstvisible >= 0) {
  1153. firstRowInViewPort = firstvisible;
  1154. if (firstvisibleOnLastPage > -1) {
  1155. scrollBodyPanel
  1156. .setScrollPosition(measureRowHeightOffset(firstvisibleOnLastPage));
  1157. } else {
  1158. scrollBodyPanel
  1159. .setScrollPosition(measureRowHeightOffset(firstvisible));
  1160. }
  1161. }
  1162. disableLazyScroller();
  1163. }
  1164. };
  1165. /** For internal use only. May be removed or replaced in the future. */
  1166. public void updateFirstVisibleAndScrollIfNeeded(UIDL uidl) {
  1167. firstvisible = uidl.hasVariable("firstvisible") ? uidl
  1168. .getIntVariable("firstvisible") : 0;
  1169. firstvisibleOnLastPage = uidl.hasVariable("firstvisibleonlastpage") ? uidl
  1170. .getIntVariable("firstvisibleonlastpage") : -1;
  1171. if (firstvisible != lastRequestedFirstvisible && scrollBody != null) {
  1172. // Update lastRequestedFirstvisible right away here
  1173. // (don't rely on update in the timer which could be cancelled).
  1174. lastRequestedFirstvisible = firstRowInViewPort;
  1175. // Only scroll if the first visible changes from the server side.
  1176. // Else we might unintentionally scroll even when the scroll
  1177. // position has not changed.
  1178. enableLazyScroller();
  1179. }
  1180. }
  1181. protected int measureRowHeightOffset(int rowIx) {
  1182. return (int) (rowIx * scrollBody.getRowHeight());
  1183. }
  1184. /** For internal use only. May be removed or replaced in the future. */
  1185. public void updatePageLength(UIDL uidl) {
  1186. int oldPageLength = pageLength;
  1187. if (uidl.hasAttribute("pagelength")) {
  1188. pageLength = uidl.getIntAttribute("pagelength");
  1189. } else {
  1190. // pagelenght is "0" meaning scrolling is turned off
  1191. pageLength = totalRows;
  1192. }
  1193. if (oldPageLength != pageLength && initializedAndAttached) {
  1194. // page length changed, need to update size
  1195. sizeNeedsInit = true;
  1196. }
  1197. }
  1198. /** For internal use only. May be removed or replaced in the future. */
  1199. public void updateSelectionProperties(UIDL uidl,
  1200. AbstractComponentState state, boolean readOnly) {
  1201. setMultiSelectMode(uidl.hasAttribute("multiselectmode") ? uidl
  1202. .getIntAttribute("multiselectmode") : MULTISELECT_MODE_DEFAULT);
  1203. nullSelectionAllowed = uidl.hasAttribute("nsa") ? uidl
  1204. .getBooleanAttribute("nsa") : true;
  1205. if (uidl.hasAttribute("selectmode")) {
  1206. if (readOnly) {
  1207. selectMode = SelectMode.NONE;
  1208. } else if (uidl.getStringAttribute("selectmode").equals("multi")) {
  1209. selectMode = SelectMode.MULTI;
  1210. } else if (uidl.getStringAttribute("selectmode").equals("single")) {
  1211. selectMode = SelectMode.SINGLE;
  1212. } else {
  1213. selectMode = SelectMode.NONE;
  1214. }
  1215. }
  1216. }
  1217. /** For internal use only. May be removed or replaced in the future. */
  1218. public void updateDragMode(UIDL uidl) {
  1219. dragmode = uidl.hasAttribute("dragmode") ? uidl
  1220. .getIntAttribute("dragmode") : 0;
  1221. if (BrowserInfo.get().isIE()) {
  1222. if (dragmode > 0) {
  1223. getElement().setPropertyJSO("onselectstart",
  1224. getPreventTextSelectionIEHack());
  1225. } else {
  1226. getElement().setPropertyJSO("onselectstart", null);
  1227. }
  1228. }
  1229. }
  1230. /** For internal use only. May be removed or replaced in the future. */
  1231. public void updateTotalRows(UIDL uidl) {
  1232. int newTotalRows = uidl.getIntAttribute("totalrows");
  1233. if (newTotalRows != getTotalRows()) {
  1234. if (scrollBody != null) {
  1235. if (getTotalRows() == 0) {
  1236. tHead.clear();
  1237. tFoot.clear();
  1238. }
  1239. initializedAndAttached = false;
  1240. initialContentReceived = false;
  1241. isNewBody = true;
  1242. }
  1243. setTotalRows(newTotalRows);
  1244. }
  1245. }
  1246. protected void setTotalRows(int newTotalRows) {
  1247. totalRows = newTotalRows;
  1248. }
  1249. public int getTotalRows() {
  1250. return totalRows;
  1251. }
  1252. /**
  1253. * Returns the extra space that is given to the header column when column
  1254. * width is determined by header text.
  1255. *
  1256. * @return extra space in pixels
  1257. */
  1258. private int getHeaderPadding() {
  1259. return scrollBody.getCellExtraWidth();
  1260. }
  1261. /**
  1262. * This method exists for the needs of {@link VTreeTable} only. Not part of
  1263. * the official API, <b>extend at your own risk</b>. May be removed or
  1264. * replaced in the future.
  1265. *
  1266. * @return index of TreeTable's hierarchy column, or -1 if not applicable
  1267. */
  1268. protected int getHierarchyColumnIndex() {
  1269. return -1;
  1270. }
  1271. /**
  1272. * For internal use only. May be removed or replaced in the future.
  1273. */
  1274. public void updateMaxIndent() {
  1275. int oldIndent = scrollBody.getMaxIndent();
  1276. scrollBody.calculateMaxIndent();
  1277. if (oldIndent != scrollBody.getMaxIndent()) {
  1278. // indent updated, headers might need adjusting
  1279. triggerLazyColumnAdjustment(true);
  1280. }
  1281. }
  1282. /** For internal use only. May be removed or replaced in the future. */
  1283. public void focusRowFromBody() {
  1284. if (selectedRowKeys.size() == 1) {
  1285. // try to focus a row currently selected and in viewport
  1286. String selectedRowKey = selectedRowKeys.iterator().next();
  1287. if (selectedRowKey != null) {
  1288. VScrollTableRow renderedRow = getRenderedRowByKey(selectedRowKey);
  1289. if (renderedRow == null || !renderedRow.isInViewPort()) {
  1290. setRowFocus(scrollBody.getRowByRowIndex(firstRowInViewPort));
  1291. } else {
  1292. setRowFocus(renderedRow);
  1293. }
  1294. }
  1295. } else {
  1296. // multiselect mode
  1297. setRowFocus(scrollBody.getRowByRowIndex(firstRowInViewPort));
  1298. }
  1299. }
  1300. protected VScrollTableBody createScrollBody() {
  1301. return new VScrollTableBody();
  1302. }
  1303. /**
  1304. * Selects the last row visible in the table
  1305. * <p>
  1306. * For internal use only. May be removed or replaced in the future.
  1307. *
  1308. * @param focusOnly
  1309. * Should the focus only be moved to the last row
  1310. */
  1311. public void selectLastRenderedRowInViewPort(boolean focusOnly) {
  1312. int index = firstRowInViewPort + getFullyVisibleRowCount();
  1313. VScrollTableRow lastRowInViewport = scrollBody.getRowByRowIndex(index);
  1314. if (lastRowInViewport == null) {
  1315. // this should not happen in normal situations (white space at the
  1316. // end of viewport). Select the last rendered as a fallback.
  1317. lastRowInViewport = scrollBody.getRowByRowIndex(scrollBody
  1318. .getLastRendered());
  1319. if (lastRowInViewport == null) {
  1320. return; // empty table
  1321. }
  1322. }
  1323. setRowFocus(lastRowInViewport);
  1324. if (!focusOnly) {
  1325. selectFocusedRow(false, multiselectPending);
  1326. sendSelectedRows();
  1327. }
  1328. }
  1329. /**
  1330. * Selects the first row visible in the table
  1331. * <p>
  1332. * For internal use only. May be removed or replaced in the future.
  1333. *
  1334. * @param focusOnly
  1335. * Should the focus only be moved to the first row
  1336. */
  1337. public void selectFirstRenderedRowInViewPort(boolean focusOnly) {
  1338. int index = firstRowInViewPort;
  1339. VScrollTableRow firstInViewport = scrollBody.getRowByRowIndex(index);
  1340. if (firstInViewport == null) {
  1341. // this should not happen in normal situations
  1342. return;
  1343. }
  1344. setRowFocus(firstInViewport);
  1345. if (!focusOnly) {
  1346. selectFocusedRow(false, multiselectPending);
  1347. sendSelectedRows();
  1348. }
  1349. }
  1350. /** For internal use only. May be removed or replaced in the future. */
  1351. public void setCacheRateFromUIDL(UIDL uidl) {
  1352. setCacheRate(uidl.hasAttribute("cr") ? uidl.getDoubleAttribute("cr")
  1353. : CACHE_RATE_DEFAULT);
  1354. }
  1355. private void setCacheRate(double d) {
  1356. if (cache_rate != d) {
  1357. cache_rate = d;
  1358. cache_react_rate = 0.75 * d;
  1359. }
  1360. }
  1361. /** For internal use only. May be removed or replaced in the future. */
  1362. public void updateActionMap(UIDL mainUidl) {
  1363. UIDL actionsUidl = mainUidl.getChildByTagName("actions");
  1364. if (actionsUidl == null) {
  1365. return;
  1366. }
  1367. final Iterator<?> it = actionsUidl.getChildIterator();
  1368. while (it.hasNext()) {
  1369. final UIDL action = (UIDL) it.next();
  1370. final String key = action.getStringAttribute("key");
  1371. final String caption = action.getStringAttribute("caption");
  1372. actionMap.put(key + "_c", caption);
  1373. if (action.hasAttribute("icon")) {
  1374. // TODO need some uri handling ??
  1375. actionMap.put(key + "_i", client.translateVaadinUri(action
  1376. .getStringAttribute("icon")));
  1377. } else {
  1378. actionMap.remove(key + "_i");
  1379. }
  1380. }
  1381. }
  1382. public String getActionCaption(String actionKey) {
  1383. return actionMap.get(actionKey + "_c");
  1384. }
  1385. public String getActionIcon(String actionKey) {
  1386. return actionMap.get(actionKey + "_i");
  1387. }
  1388. private void updateHeader(String[] strings) {
  1389. if (strings == null) {
  1390. return;
  1391. }
  1392. int visibleCols = strings.length;
  1393. int colIndex = 0;
  1394. if (showRowHeaders) {
  1395. tHead.enableColumn(ROW_HEADER_COLUMN_KEY, colIndex);
  1396. visibleCols++;
  1397. visibleColOrder = new String[visibleCols];
  1398. visibleColOrder[colIndex] = ROW_HEADER_COLUMN_KEY;
  1399. colIndex++;
  1400. } else {
  1401. visibleColOrder = new String[visibleCols];
  1402. tHead.removeCell(ROW_HEADER_COLUMN_KEY);
  1403. }
  1404. int i;
  1405. for (i = 0; i < strings.length; i++) {
  1406. final String cid = strings[i];
  1407. visibleColOrder[colIndex] = cid;
  1408. tHead.enableColumn(cid, colIndex);
  1409. colIndex++;
  1410. }
  1411. tHead.setVisible(showColHeaders);
  1412. setContainerHeight();
  1413. }
  1414. /**
  1415. * Updates footers.
  1416. * <p>
  1417. * Update headers whould be called before this method is called!
  1418. * </p>
  1419. *
  1420. * @param strings
  1421. */
  1422. private void updateFooter(String[] strings) {
  1423. if (strings == null) {
  1424. return;
  1425. }
  1426. // Add dummy column if row headers are present
  1427. int colIndex = 0;
  1428. if (showRowHeaders) {
  1429. tFoot.enableColumn(ROW_HEADER_COLUMN_KEY, colIndex);
  1430. colIndex++;
  1431. } else {
  1432. tFoot.removeCell(ROW_HEADER_COLUMN_KEY);
  1433. }
  1434. int i;
  1435. for (i = 0; i < strings.length; i++) {
  1436. final String cid = strings[i];
  1437. tFoot.enableColumn(cid, colIndex);
  1438. colIndex++;
  1439. }
  1440. tFoot.setVisible(showColFooters);
  1441. }
  1442. /**
  1443. * For internal use only. May be removed or replaced in the future.
  1444. *
  1445. * @param uidl
  1446. * which contains row data
  1447. * @param firstRow
  1448. * first row in data set
  1449. * @param reqRows
  1450. * amount of rows in data set
  1451. */
  1452. public void updateBody(UIDL uidl, int firstRow, int reqRows) {
  1453. int oldIndent = scrollBody.getMaxIndent();
  1454. if (uidl == null || reqRows < 1) {
  1455. // container is empty, remove possibly existing rows
  1456. if (firstRow <= 0) {
  1457. postponeSanityCheckForLastRendered = true;
  1458. while (scrollBody.getLastRendered() > scrollBody
  1459. .getFirstRendered()) {
  1460. scrollBody.unlinkRow(false);
  1461. }
  1462. postponeSanityCheckForLastRendered = false;
  1463. scrollBody.unlinkRow(false);
  1464. }
  1465. return;
  1466. }
  1467. scrollBody.renderRows(uidl, firstRow, reqRows);
  1468. discardRowsOutsideCacheWindow();
  1469. scrollBody.calculateMaxIndent();
  1470. if (oldIndent != scrollBody.getMaxIndent()) {
  1471. // indent updated, headers might need adjusting
  1472. headerChangedDuringUpdate = true;
  1473. }
  1474. }
  1475. /** For internal use only. May be removed or replaced in the future. */
  1476. public void updateRowsInBody(UIDL partialRowUpdates) {
  1477. if (partialRowUpdates == null) {
  1478. return;
  1479. }
  1480. int firstRowIx = partialRowUpdates.getIntAttribute("firsturowix");
  1481. int count = partialRowUpdates.getIntAttribute("numurows");
  1482. scrollBody.unlinkRows(firstRowIx, count);
  1483. scrollBody.insertRows(partialRowUpdates, firstRowIx, count);
  1484. }
  1485. /**
  1486. * Updates the internal cache by unlinking rows that fall outside of the
  1487. * caching window.
  1488. */
  1489. protected void discardRowsOutsideCacheWindow() {
  1490. int firstRowToKeep = (int) (firstRowInViewPort - pageLength
  1491. * cache_rate);
  1492. int lastRowToKeep = (int) (firstRowInViewPort + pageLength + pageLength
  1493. * cache_rate);
  1494. // sanity checks:
  1495. if (firstRowToKeep < 0) {
  1496. firstRowToKeep = 0;
  1497. }
  1498. if (lastRowToKeep > totalRows) {
  1499. lastRowToKeep = totalRows - 1;
  1500. }
  1501. debug("Client side calculated cache rows to keep: " + firstRowToKeep
  1502. + "-" + lastRowToKeep);
  1503. if (serverCacheFirst != -1) {
  1504. firstRowToKeep = serverCacheFirst;
  1505. lastRowToKeep = serverCacheLast;
  1506. debug("Server cache rows that override: " + serverCacheFirst + "-"
  1507. + serverCacheLast);
  1508. if (firstRowToKeep < scrollBody.getFirstRendered()
  1509. || lastRowToKeep > scrollBody.getLastRendered()) {
  1510. debug("*** Server wants us to keep " + serverCacheFirst + "-"
  1511. + serverCacheLast + " but we only have rows "
  1512. + scrollBody.getFirstRendered() + "-"
  1513. + scrollBody.getLastRendered() + " rendered!");
  1514. }
  1515. }
  1516. discardRowsOutsideOf(firstRowToKeep, lastRowToKeep);
  1517. scrollBody.fixSpacers();
  1518. scrollBody.restoreRowVisibility();
  1519. }
  1520. private void discardRowsOutsideOf(int optimalFirstRow, int optimalLastRow) {
  1521. /*
  1522. * firstDiscarded and lastDiscarded are only calculated for debug
  1523. * purposes
  1524. */
  1525. int firstDiscarded = -1, lastDiscarded = -1;
  1526. boolean cont = true;
  1527. while (cont && scrollBody.getLastRendered() > optimalFirstRow
  1528. && scrollBody.getFirstRendered() < optimalFirstRow) {
  1529. if (firstDiscarded == -1) {
  1530. firstDiscarded = scrollBody.getFirstRendered();
  1531. }
  1532. // removing row from start
  1533. cont = scrollBody.unlinkRow(true);
  1534. }
  1535. if (firstDiscarded != -1) {
  1536. lastDiscarded = scrollBody.getFirstRendered() - 1;
  1537. debug("Discarded rows " + firstDiscarded + "-" + lastDiscarded);
  1538. }
  1539. firstDiscarded = lastDiscarded = -1;
  1540. cont = true;
  1541. while (cont && scrollBody.getLastRendered() > optimalLastRow) {
  1542. if (lastDiscarded == -1) {
  1543. lastDiscarded = scrollBody.getLastRendered();
  1544. }
  1545. // removing row from the end
  1546. cont = scrollBody.unlinkRow(false);
  1547. }
  1548. if (lastDiscarded != -1) {
  1549. firstDiscarded = scrollBody.getLastRendered() + 1;
  1550. debug("Discarded rows " + firstDiscarded + "-" + lastDiscarded);
  1551. }
  1552. debug("Now in cache: " + scrollBody.getFirstRendered() + "-"
  1553. + scrollBody.getLastRendered());
  1554. }
  1555. /**
  1556. * Inserts rows in the table body or removes them from the table body based
  1557. * on the commands in the UIDL.
  1558. * <p>
  1559. * For internal use only. May be removed or replaced in the future.
  1560. *
  1561. * @param partialRowAdditions
  1562. * the UIDL containing row updates.
  1563. */
  1564. public void addAndRemoveRows(UIDL partialRowAdditions) {
  1565. if (partialRowAdditions == null) {
  1566. return;
  1567. }
  1568. if (partialRowAdditions.hasAttribute("hide")) {
  1569. scrollBody.unlinkAndReindexRows(
  1570. partialRowAdditions.getIntAttribute("firstprowix"),
  1571. partialRowAdditions.getIntAttribute("numprows"));
  1572. scrollBody.ensureCacheFilled();
  1573. } else {
  1574. if (partialRowAdditions.hasAttribute("delbelow")) {
  1575. scrollBody.insertRowsDeleteBelow(partialRowAdditions,
  1576. partialRowAdditions.getIntAttribute("firstprowix"),
  1577. partialRowAdditions.getIntAttribute("numprows"));
  1578. } else {
  1579. scrollBody.insertAndReindexRows(partialRowAdditions,
  1580. partialRowAdditions.getIntAttribute("firstprowix"),
  1581. partialRowAdditions.getIntAttribute("numprows"));
  1582. }
  1583. }
  1584. discardRowsOutsideCacheWindow();
  1585. }
  1586. /**
  1587. * Gives correct column index for given column key ("cid" in UIDL).
  1588. *
  1589. * @param colKey
  1590. * @return column index of visible columns, -1 if column not visible
  1591. */
  1592. private int getColIndexByKey(String colKey) {
  1593. // return 0 if asked for rowHeaders
  1594. if (ROW_HEADER_COLUMN_KEY.equals(colKey)) {
  1595. return 0;
  1596. }
  1597. for (int i = 0; i < visibleColOrder.length; i++) {
  1598. if (visibleColOrder[i].equals(colKey)) {
  1599. return i;
  1600. }
  1601. }
  1602. return -1;
  1603. }
  1604. private boolean isMultiSelectModeSimple() {
  1605. return selectMode == SelectMode.MULTI
  1606. && multiselectmode == MULTISELECT_MODE_SIMPLE;
  1607. }
  1608. private boolean isSingleSelectMode() {
  1609. return selectMode == SelectMode.SINGLE;
  1610. }
  1611. private boolean isMultiSelectModeAny() {
  1612. return selectMode == SelectMode.MULTI;
  1613. }
  1614. private boolean isMultiSelectModeDefault() {
  1615. return selectMode == SelectMode.MULTI
  1616. && multiselectmode == MULTISELECT_MODE_DEFAULT;
  1617. }
  1618. private void setMultiSelectMode(int multiselectmode) {
  1619. if (BrowserInfo.get().isTouchDevice()) {
  1620. // Always use the simple mode for touch devices that do not have
  1621. // shift/ctrl keys
  1622. this.multiselectmode = MULTISELECT_MODE_SIMPLE;
  1623. } else {
  1624. this.multiselectmode = multiselectmode;
  1625. }
  1626. }
  1627. /** For internal use only. May be removed or replaced in the future. */
  1628. public boolean isSelectable() {
  1629. return selectMode.getId() > SelectMode.NONE.getId();
  1630. }
  1631. private boolean isCollapsedColumn(String colKey) {
  1632. if (collapsedColumns == null) {
  1633. return false;
  1634. }
  1635. if (collapsedColumns.contains(colKey)) {
  1636. return true;
  1637. }
  1638. return false;
  1639. }
  1640. private String getColKeyByIndex(int index) {
  1641. return tHead.getHeaderCell(index).getColKey();
  1642. }
  1643. /**
  1644. * Note: not part of the official API, extend at your own risk. May be
  1645. * removed or replaced in the future.
  1646. *
  1647. * Sets the indicated column's width for headers and scrollBody alike.
  1648. *
  1649. * @param colIndex
  1650. * index of the modified column
  1651. * @param w
  1652. * new width (may be subject to modifications if doesn't meet
  1653. * minimum requirements)
  1654. * @param isDefinedWidth
  1655. * disables expand ratio if set true
  1656. */
  1657. protected void setColWidth(int colIndex, int w, boolean isDefinedWidth) {
  1658. final HeaderCell hcell = tHead.getHeaderCell(colIndex);
  1659. // Make sure that the column grows to accommodate the sort indicator if
  1660. // necessary.
  1661. // get min width with no indent or padding
  1662. int minWidth = hcell.getMinWidth(false, false);
  1663. if (w < minWidth) {
  1664. w = minWidth;
  1665. }
  1666. // Set header column width WITHOUT INDENT
  1667. hcell.setWidth(w, isDefinedWidth);
  1668. // Set footer column width likewise
  1669. FooterCell fcell = tFoot.getFooterCell(colIndex);
  1670. fcell.setWidth(w, isDefinedWidth);
  1671. // Ensure indicators have been taken into account
  1672. tHead.resizeCaptionContainer(hcell);
  1673. // Make sure that the body column grows to accommodate the indent if
  1674. // necessary.
  1675. // get min width with indent, no padding
  1676. minWidth = hcell.getMinWidth(true, false);
  1677. if (w < minWidth) {
  1678. w = minWidth;
  1679. }
  1680. // Set body column width
  1681. scrollBody.setColWidth(colIndex, w);
  1682. }
  1683. private int getColWidth(String colKey) {
  1684. return tHead.getHeaderCell(colKey).getWidthWithIndent();
  1685. }
  1686. /**
  1687. * Get a rendered row by its key
  1688. *
  1689. * @param key
  1690. * The key to search with
  1691. * @return
  1692. */
  1693. public VScrollTableRow getRenderedRowByKey(String key) {
  1694. if (scrollBody != null) {
  1695. final Iterator<Widget> it = scrollBody.iterator();
  1696. VScrollTableRow r = null;
  1697. while (it.hasNext()) {
  1698. r = (VScrollTableRow) it.next();
  1699. if (r.getKey().equals(key)) {
  1700. return r;
  1701. }
  1702. }
  1703. }
  1704. return null;
  1705. }
  1706. /**
  1707. * Returns the next row to the given row
  1708. *
  1709. * @param row
  1710. * The row to calculate from
  1711. *
  1712. * @return The next row or null if no row exists
  1713. */
  1714. private VScrollTableRow getNextRow(VScrollTableRow row, int offset) {
  1715. final Iterator<Widget> it = scrollBody.iterator();
  1716. VScrollTableRow r = null;
  1717. while (it.hasNext()) {
  1718. r = (VScrollTableRow) it.next();
  1719. if (r == row) {
  1720. r = null;
  1721. while (offset >= 0 && it.hasNext()) {
  1722. r = (VScrollTableRow) it.next();
  1723. offset--;
  1724. }
  1725. return r;
  1726. }
  1727. }
  1728. return null;
  1729. }
  1730. /**
  1731. * Returns the previous row from the given row
  1732. *
  1733. * @param row
  1734. * The row to calculate from
  1735. * @return The previous row or null if no row exists
  1736. */
  1737. private VScrollTableRow getPreviousRow(VScrollTableRow row, int offset) {
  1738. final Iterator<Widget> it = scrollBody.iterator();
  1739. final Iterator<Widget> offsetIt = scrollBody.iterator();
  1740. VScrollTableRow r = null;
  1741. VScrollTableRow prev = null;
  1742. while (it.hasNext()) {
  1743. r = (VScrollTableRow) it.next();
  1744. if (offset < 0) {
  1745. prev = (VScrollTableRow) offsetIt.next();
  1746. }
  1747. if (r == row) {
  1748. return prev;
  1749. }
  1750. offset--;
  1751. }
  1752. return null;
  1753. }
  1754. protected void reOrderColumn(String columnKey, int newIndex) {
  1755. final int oldIndex = getColIndexByKey(columnKey);
  1756. // Change header order
  1757. tHead.moveCell(oldIndex, newIndex);
  1758. // Change body order
  1759. scrollBody.moveCol(oldIndex, newIndex);
  1760. // Change footer order
  1761. tFoot.moveCell(oldIndex, newIndex);
  1762. /*
  1763. * Build new columnOrder and update it to server Note that columnOrder
  1764. * also contains collapsed columns so we cannot directly build it from
  1765. * cells vector Loop the old columnOrder and append in order to new
  1766. * array unless on moved columnKey. On new index also put the moved key
  1767. * i == index on columnOrder, j == index on newOrder
  1768. */
  1769. final String oldKeyOnNewIndex = visibleColOrder[newIndex];
  1770. if (showRowHeaders) {
  1771. newIndex--; // columnOrder don't have rowHeader
  1772. }
  1773. // add back hidden rows,
  1774. for (int i = 0; i < columnOrder.length; i++) {
  1775. if (columnOrder[i].equals(oldKeyOnNewIndex)) {
  1776. break; // break loop at target
  1777. }
  1778. if (isCollapsedColumn(columnOrder[i])) {
  1779. newIndex++;
  1780. }
  1781. }
  1782. // finally we can build the new columnOrder for server
  1783. final String[] newOrder = new String[columnOrder.length];
  1784. for (int i = 0, j = 0; j < newOrder.length; i++) {
  1785. if (j == newIndex) {
  1786. newOrder[j] = columnKey;
  1787. j++;
  1788. }
  1789. if (i == columnOrder.length) {
  1790. break;
  1791. }
  1792. if (columnOrder[i].equals(columnKey)) {
  1793. continue;
  1794. }
  1795. newOrder[j] = columnOrder[i];
  1796. j++;
  1797. }
  1798. columnOrder = newOrder;
  1799. // also update visibleColumnOrder
  1800. int i = showRowHeaders ? 1 : 0;
  1801. for (int j = 0; j < newOrder.length; j++) {
  1802. final String cid = newOrder[j];
  1803. if (!isCollapsedColumn(cid)) {
  1804. visibleColOrder[i++] = cid;
  1805. }
  1806. }
  1807. client.updateVariable(paintableId, "columnorder", columnOrder, false);
  1808. if (client.hasEventListeners(this,
  1809. TableConstants.COLUMN_REORDER_EVENT_ID)) {
  1810. client.sendPendingVariableChanges();
  1811. }
  1812. }
  1813. @Override
  1814. protected void onDetach() {
  1815. detachedScrollPosition = scrollBodyPanel.getScrollPosition();
  1816. rowRequestHandler.cancel();
  1817. super.onDetach();
  1818. // ensure that scrollPosElement will be detached
  1819. if (scrollPositionElement != null) {
  1820. final Element parent = DOM.getParent(scrollPositionElement);
  1821. if (parent != null) {
  1822. DOM.removeChild(parent, scrollPositionElement);
  1823. }
  1824. }
  1825. }
  1826. @Override
  1827. public void onAttach() {
  1828. super.onAttach();
  1829. scrollBodyPanel.setScrollPosition(detachedScrollPosition);
  1830. }
  1831. /**
  1832. * Run only once when component is attached and received its initial
  1833. * content. This function:
  1834. *
  1835. * * Syncs headers and bodys "natural widths and saves the values.
  1836. *
  1837. * * Sets proper width and height
  1838. *
  1839. * * Makes deferred request to get some cache rows
  1840. *
  1841. * For internal use only. May be removed or replaced in the future.
  1842. */
  1843. public void sizeInit() {
  1844. sizeNeedsInit = false;
  1845. scrollBody.setContainerHeight();
  1846. /*
  1847. * We will use browsers table rendering algorithm to find proper column
  1848. * widths. If content and header take less space than available, we will
  1849. * divide extra space relatively to each column which has not width set.
  1850. *
  1851. * Overflow pixels are added to last column.
  1852. */
  1853. Iterator<Widget> headCells = tHead.iterator();
  1854. Iterator<Widget> footCells = tFoot.iterator();
  1855. int i = 0;
  1856. int totalExplicitColumnsWidths = 0;
  1857. int total = 0;
  1858. float expandRatioDivider = 0;
  1859. final int[] widths = new int[tHead.visibleCells.size()];
  1860. tHead.enableBrowserIntelligence();
  1861. tFoot.enableBrowserIntelligence();
  1862. int hierarchyColumnIndent = scrollBody != null ? scrollBody
  1863. .getMaxIndent() : 0;
  1864. HeaderCell hierarchyHeaderWithExpandRatio = null;
  1865. // first loop: collect natural widths
  1866. while (headCells.hasNext()) {
  1867. final HeaderCell hCell = (HeaderCell) headCells.next();
  1868. final FooterCell fCell = (FooterCell) footCells.next();
  1869. boolean needsIndent = hierarchyColumnIndent > 0
  1870. && hCell.isHierarchyColumn();
  1871. int w = hCell.getWidth();
  1872. if (hCell.isDefinedWidth()) {
  1873. // server has defined column width explicitly
  1874. if (needsIndent && w < hierarchyColumnIndent) {
  1875. // hierarchy indent overrides explicitly set width
  1876. w = hierarchyColumnIndent;
  1877. }
  1878. totalExplicitColumnsWidths += w;
  1879. } else {
  1880. if (hCell.getExpandRatio() > 0) {
  1881. expandRatioDivider += hCell.getExpandRatio();
  1882. w = 0;
  1883. if (needsIndent && w < hierarchyColumnIndent) {
  1884. hierarchyHeaderWithExpandRatio = hCell;
  1885. // don't add to widths here, because will be included in
  1886. // the expand ratio space if there's enough of it
  1887. }
  1888. } else {
  1889. // get and store greater of header width and column width,
  1890. // and store it as a minimum natural column width (these
  1891. // already contain the indent if any)
  1892. int headerWidth = hCell.getNaturalColumnWidth(i);
  1893. int footerWidth = fCell.getNaturalColumnWidth(i);
  1894. w = headerWidth > footerWidth ? headerWidth : footerWidth;
  1895. }
  1896. hCell.setNaturalMinimumColumnWidth(w);
  1897. fCell.setNaturalMinimumColumnWidth(w);
  1898. }
  1899. widths[i] = w;
  1900. total += w;
  1901. i++;
  1902. }
  1903. if (hierarchyHeaderWithExpandRatio != null) {
  1904. total += hierarchyColumnIndent;
  1905. }
  1906. tHead.disableBrowserIntelligence();
  1907. tFoot.disableBrowserIntelligence();
  1908. boolean willHaveScrollbarz = willHaveScrollbars();
  1909. // fix "natural" width if width not set
  1910. if (isDynamicWidth()) {
  1911. int w = total;
  1912. w += scrollBody.getCellExtraWidth() * visibleColOrder.length;
  1913. if (willHaveScrollbarz) {
  1914. w += Util.getNativeScrollbarSize();
  1915. }
  1916. setContentWidth(w);
  1917. }
  1918. int availW = scrollBody.getAvailableWidth();
  1919. if (BrowserInfo.get().isIE()) {
  1920. // Hey IE, are you really sure about this?
  1921. availW = scrollBody.getAvailableWidth();
  1922. }
  1923. availW -= scrollBody.getCellExtraWidth() * visibleColOrder.length;
  1924. if (willHaveScrollbarz) {
  1925. availW -= Util.getNativeScrollbarSize();
  1926. }
  1927. // TODO refactor this code to be the same as in resize timer
  1928. if (availW > total) {
  1929. // natural size is smaller than available space
  1930. int extraSpace = availW - total;
  1931. if (hierarchyHeaderWithExpandRatio != null) {
  1932. /*
  1933. * add the indent's space back to ensure each column gets an
  1934. * even share according to the expand ratios (note: if the
  1935. * allocated space isn't enough for the hierarchy column it
  1936. * shall be treated like a defined width column and the indent
  1937. * space gets removed from the extra space again)
  1938. */
  1939. extraSpace += hierarchyColumnIndent;
  1940. }
  1941. final int totalWidthR = total - totalExplicitColumnsWidths;
  1942. int checksum = 0;
  1943. if (extraSpace == 1) {
  1944. // We cannot divide one single pixel so we give it the first
  1945. // undefined column
  1946. // no need to worry about indent here
  1947. headCells = tHead.iterator();
  1948. i = 0;
  1949. checksum = availW;
  1950. while (headCells.hasNext()) {
  1951. HeaderCell hc = (HeaderCell) headCells.next();
  1952. if (!hc.isDefinedWidth()) {
  1953. widths[i]++;
  1954. break;
  1955. }
  1956. i++;
  1957. }
  1958. } else if (expandRatioDivider > 0) {
  1959. boolean setIndentToHierarchyHeader = false;
  1960. if (hierarchyHeaderWithExpandRatio != null) {
  1961. // ensure first that the hierarchyColumn gets at least the
  1962. // space allocated for indent
  1963. final int newSpace = Math
  1964. .round((extraSpace * (hierarchyHeaderWithExpandRatio
  1965. .getExpandRatio() / expandRatioDivider)));
  1966. if (newSpace < hierarchyColumnIndent) {
  1967. // not enough space for indent, remove indent from the
  1968. // extraSpace again and handle hierarchy column's header
  1969. // separately
  1970. setIndentToHierarchyHeader = true;
  1971. extraSpace -= hierarchyColumnIndent;
  1972. }
  1973. }
  1974. // visible columns have some active expand ratios, excess
  1975. // space is divided according to them
  1976. headCells = tHead.iterator();
  1977. i = 0;
  1978. while (headCells.hasNext()) {
  1979. HeaderCell hCell = (HeaderCell) headCells.next();
  1980. if (hCell.getExpandRatio() > 0) {
  1981. int w = widths[i];
  1982. if (setIndentToHierarchyHeader
  1983. && hierarchyHeaderWithExpandRatio.equals(hCell)) {
  1984. // hierarchy column's header is no longer part of
  1985. // the expansion divide and only gets indent
  1986. w += hierarchyColumnIndent;
  1987. } else {
  1988. final int newSpace = Math
  1989. .round((extraSpace * (hCell
  1990. .getExpandRatio() / expandRatioDivider)));
  1991. w += newSpace;
  1992. }
  1993. widths[i] = w;
  1994. }
  1995. checksum += widths[i];
  1996. i++;
  1997. }
  1998. } else if (totalWidthR > 0) {
  1999. // no expand ratios defined, we will share extra space
  2000. // relatively to "natural widths" among those without
  2001. // explicit width
  2002. // no need to worry about indent here, it's already included
  2003. headCells = tHead.iterator();
  2004. i = 0;
  2005. while (headCells.hasNext()) {
  2006. HeaderCell hCell = (HeaderCell) headCells.next();
  2007. if (!hCell.isDefinedWidth()) {
  2008. int w = widths[i];
  2009. final int newSpace = Math.round((float) extraSpace
  2010. * (float) w / totalWidthR);
  2011. w += newSpace;
  2012. widths[i] = w;
  2013. }
  2014. checksum += widths[i];
  2015. i++;
  2016. }
  2017. }
  2018. if (extraSpace > 0 && checksum != availW) {
  2019. /*
  2020. * There might be in some cases a rounding error of 1px when
  2021. * extra space is divided so if there is one then we give the
  2022. * first undefined column 1 more pixel
  2023. */
  2024. headCells = tHead.iterator();
  2025. i = 0;
  2026. while (headCells.hasNext()) {
  2027. HeaderCell hc = (HeaderCell) headCells.next();
  2028. if (!hc.isDefinedWidth()) {
  2029. widths[i] += availW - checksum;
  2030. break;
  2031. }
  2032. i++;
  2033. }
  2034. }
  2035. } else {
  2036. // body's size will be more than available and scrollbar will appear
  2037. }
  2038. // last loop: set possibly modified values or reset if new tBody
  2039. i = 0;
  2040. headCells = tHead.iterator();
  2041. while (headCells.hasNext()) {
  2042. final HeaderCell hCell = (HeaderCell) headCells.next();
  2043. if (isNewBody || hCell.getWidth() == -1) {
  2044. final int w = widths[i];
  2045. setColWidth(i, w, false);
  2046. }
  2047. i++;
  2048. }
  2049. initializedAndAttached = true;
  2050. updatePageLength();
  2051. /*
  2052. * Fix "natural" height if height is not set. This must be after width
  2053. * fixing so the components' widths have been adjusted.
  2054. */
  2055. if (isDynamicHeight()) {
  2056. /*
  2057. * We must force an update of the row height as this point as it
  2058. * might have been (incorrectly) calculated earlier
  2059. */
  2060. /*
  2061. * TreeTable updates stuff in a funky order, so we must set the
  2062. * height as zero here before doing the real update to make it
  2063. * realize that there is no content,
  2064. */
  2065. if (pageLength == totalRows && pageLength == 0) {
  2066. scrollBody.setHeight("0px");
  2067. }
  2068. int bodyHeight;
  2069. if (pageLength == totalRows) {
  2070. /*
  2071. * A hack to support variable height rows when paging is off.
  2072. * Generally this is not supported by scrolltable. We want to
  2073. * show all rows so the bodyHeight should be equal to the table
  2074. * height.
  2075. */
  2076. // int bodyHeight = scrollBody.getOffsetHeight();
  2077. bodyHeight = scrollBody.getRequiredHeight();
  2078. } else {
  2079. bodyHeight = (int) Math.round(scrollBody.getRowHeight(true)
  2080. * pageLength);
  2081. }
  2082. boolean needsSpaceForHorizontalSrollbar = (total > availW);
  2083. if (needsSpaceForHorizontalSrollbar) {
  2084. bodyHeight += Util.getNativeScrollbarSize();
  2085. }
  2086. scrollBodyPanel.setHeight(bodyHeight + "px");
  2087. Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement());
  2088. }
  2089. isNewBody = false;
  2090. if (firstvisible > 0) {
  2091. enableLazyScroller();
  2092. }
  2093. if (enabled) {
  2094. // Do we need cache rows
  2095. if (scrollBody.getLastRendered() + 1 < firstRowInViewPort
  2096. + pageLength + (int) cache_react_rate * pageLength) {
  2097. if (totalRows - 1 > scrollBody.getLastRendered()) {
  2098. // fetch cache rows
  2099. int firstInNewSet = scrollBody.getLastRendered() + 1;
  2100. int lastInNewSet = (int) (firstRowInViewPort + pageLength + cache_rate
  2101. * pageLength);
  2102. if (lastInNewSet > totalRows - 1) {
  2103. lastInNewSet = totalRows - 1;
  2104. }
  2105. rowRequestHandler.triggerRowFetch(firstInNewSet,
  2106. lastInNewSet - firstInNewSet + 1, 1);
  2107. }
  2108. }
  2109. }
  2110. /*
  2111. * Ensures the column alignments are correct at initial loading. <br/>
  2112. * (child components widths are correct)
  2113. */
  2114. Util.runWebkitOverflowAutoFixDeferred(scrollBodyPanel.getElement());
  2115. hadScrollBars = willHaveScrollbarz;
  2116. }
  2117. /**
  2118. * Note: this method is not part of official API although declared as
  2119. * protected. Extend at your own risk.
  2120. *
  2121. * @return true if content area will have scrollbars visible.
  2122. */
  2123. protected boolean willHaveScrollbars() {
  2124. if (isDynamicHeight()) {
  2125. if (pageLength < totalRows) {
  2126. return true;
  2127. }
  2128. } else {
  2129. int fakeheight = (int) Math.round(scrollBody.getRowHeight()
  2130. * totalRows);
  2131. int availableHeight = scrollBodyPanel.getElement().getPropertyInt(
  2132. "clientHeight");
  2133. if (fakeheight > availableHeight) {
  2134. return true;
  2135. }
  2136. }
  2137. return false;
  2138. }
  2139. private void announceScrollPosition() {
  2140. if (scrollPositionElement == null) {
  2141. scrollPositionElement = DOM.createDiv();
  2142. scrollPositionElement.setClassName(getStylePrimaryName()
  2143. + "-scrollposition");
  2144. scrollPositionElement.getStyle().setPosition(Position.ABSOLUTE);
  2145. scrollPositionElement.getStyle().setDisplay(Display.NONE);
  2146. getElement().appendChild(scrollPositionElement);
  2147. }
  2148. Style style = scrollPositionElement.getStyle();
  2149. style.setMarginLeft(getElement().getOffsetWidth() / 2 - 80, Unit.PX);
  2150. style.setMarginTop(-scrollBodyPanel.getOffsetHeight(), Unit.PX);
  2151. // indexes go from 1-totalRows, as rowheaders in index-mode indicate
  2152. int last = (firstRowInViewPort + pageLength);
  2153. if (last > totalRows) {
  2154. last = totalRows;
  2155. }
  2156. scrollPositionElement.setInnerHTML("<span>" + (firstRowInViewPort + 1)
  2157. + " &ndash; " + (last) + "..." + "</span>");
  2158. style.setDisplay(Display.BLOCK);
  2159. }
  2160. /** For internal use only. May be removed or replaced in the future. */
  2161. public void hideScrollPositionAnnotation() {
  2162. if (scrollPositionElement != null) {
  2163. scrollPositionElement.getStyle().setDisplay(Display.NONE);
  2164. }
  2165. }
  2166. /** For internal use only. May be removed or replaced in the future. */
  2167. public boolean isScrollPositionVisible() {
  2168. return scrollPositionElement != null
  2169. && !scrollPositionElement.getStyle().getDisplay()
  2170. .equals(Display.NONE.toString());
  2171. }
  2172. /** For internal use only. May be removed or replaced in the future. */
  2173. public class RowRequestHandler extends Timer {
  2174. private int reqFirstRow = 0;
  2175. private int reqRows = 0;
  2176. private boolean isRequestHandlerRunning = false;
  2177. public void triggerRowFetch(int first, int rows) {
  2178. setReqFirstRow(first);
  2179. setReqRows(rows);
  2180. deferRowFetch();
  2181. }
  2182. public void triggerRowFetch(int first, int rows, int delay) {
  2183. setReqFirstRow(first);
  2184. setReqRows(rows);
  2185. deferRowFetch(delay);
  2186. }
  2187. public void deferRowFetch() {
  2188. deferRowFetch(250);
  2189. }
  2190. public boolean isRequestHandlerRunning() {
  2191. return isRequestHandlerRunning;
  2192. }
  2193. public void deferRowFetch(int msec) {
  2194. isRequestHandlerRunning = true;
  2195. if (reqRows > 0 && reqFirstRow < totalRows) {
  2196. schedule(msec);
  2197. // tell scroll position to user if currently "visible" rows are
  2198. // not rendered
  2199. if (totalRows > pageLength
  2200. && ((firstRowInViewPort + pageLength > scrollBody
  2201. .getLastRendered()) || (firstRowInViewPort < scrollBody
  2202. .getFirstRendered()))) {
  2203. announceScrollPosition();
  2204. } else {
  2205. hideScrollPositionAnnotation();
  2206. }
  2207. }
  2208. }
  2209. public int getReqFirstRow() {
  2210. return reqFirstRow;
  2211. }
  2212. public void setReqFirstRow(int reqFirstRow) {
  2213. if (reqFirstRow < 0) {
  2214. this.reqFirstRow = 0;
  2215. } else if (reqFirstRow >= totalRows) {
  2216. this.reqFirstRow = totalRows - 1;
  2217. } else {
  2218. this.reqFirstRow = reqFirstRow;
  2219. }
  2220. }
  2221. public void setReqRows(int reqRows) {
  2222. if (reqRows < 0) {
  2223. this.reqRows = 0;
  2224. } else if (reqFirstRow + reqRows > totalRows) {
  2225. this.reqRows = totalRows - reqFirstRow;
  2226. } else {
  2227. this.reqRows = reqRows;
  2228. }
  2229. }
  2230. @Override
  2231. public void run() {
  2232. if (client.hasActiveRequest() || navKeyDown) {
  2233. // if client connection is busy, don't bother loading it more
  2234. VConsole.log("Postponed rowfetch");
  2235. schedule(250);
  2236. } else {
  2237. int firstRendered = scrollBody.getFirstRendered();
  2238. int lastRendered = scrollBody.getLastRendered();
  2239. if (lastRendered > totalRows) {
  2240. lastRendered = totalRows - 1;
  2241. }
  2242. boolean rendered = firstRendered >= 0 && lastRendered >= 0;
  2243. int firstToBeRendered = firstRendered;
  2244. if (reqFirstRow < firstToBeRendered) {
  2245. firstToBeRendered = reqFirstRow;
  2246. } else if (firstRowInViewPort - (int) (cache_rate * pageLength) > firstToBeRendered) {
  2247. firstToBeRendered = firstRowInViewPort
  2248. - (int) (cache_rate * pageLength);
  2249. if (firstToBeRendered < 0) {
  2250. firstToBeRendered = 0;
  2251. }
  2252. } else if (rendered && firstRendered + 1 < reqFirstRow
  2253. && lastRendered + 1 < reqFirstRow) {
  2254. // requested rows must fall within the requested rendering
  2255. // area
  2256. firstToBeRendered = reqFirstRow;
  2257. }
  2258. if (firstToBeRendered + reqRows < firstRendered) {
  2259. // must increase the required row count accordingly,
  2260. // otherwise may leave a gap and the rows beyond will get
  2261. // removed
  2262. setReqRows(firstRendered - firstToBeRendered);
  2263. }
  2264. int lastToBeRendered = lastRendered;
  2265. int lastReqRow = reqFirstRow + reqRows - 1;
  2266. if (lastReqRow > lastToBeRendered) {
  2267. lastToBeRendered = lastReqRow;
  2268. } else if (firstRowInViewPort + pageLength + pageLength
  2269. * cache_rate < lastToBeRendered) {
  2270. lastToBeRendered = (firstRowInViewPort + pageLength + (int) (pageLength * cache_rate));
  2271. if (lastToBeRendered >= totalRows) {
  2272. lastToBeRendered = totalRows - 1;
  2273. }
  2274. // due Safari 3.1 bug (see #2607), verify reqrows, original
  2275. // problem unknown, but this should catch the issue
  2276. if (lastReqRow > lastToBeRendered) {
  2277. setReqRows(lastToBeRendered - reqFirstRow);
  2278. }
  2279. } else if (rendered && lastRendered - 1 > lastReqRow
  2280. && firstRendered - 1 > lastReqRow) {
  2281. // requested rows must fall within the requested rendering
  2282. // area
  2283. lastToBeRendered = lastReqRow;
  2284. }
  2285. if (lastToBeRendered > totalRows) {
  2286. lastToBeRendered = totalRows - 1;
  2287. }
  2288. if (reqFirstRow < firstToBeRendered
  2289. || (reqFirstRow > firstToBeRendered && (reqFirstRow < firstRendered || reqFirstRow > lastRendered + 1))) {
  2290. setReqFirstRow(firstToBeRendered);
  2291. }
  2292. if (lastRendered < lastToBeRendered
  2293. && lastRendered + reqRows < lastToBeRendered) {
  2294. // must increase the required row count accordingly,
  2295. // otherwise may leave a gap and the rows after will get
  2296. // removed
  2297. setReqRows(lastToBeRendered - lastRendered);
  2298. } else if (lastToBeRendered >= firstRendered
  2299. && reqFirstRow + reqRows < firstRendered) {
  2300. setReqRows(lastToBeRendered - lastRendered);
  2301. }
  2302. client.updateVariable(paintableId, "firstToBeRendered",
  2303. firstToBeRendered, false);
  2304. client.updateVariable(paintableId, "lastToBeRendered",
  2305. lastToBeRendered, false);
  2306. // don't request server to update page first index in case it
  2307. // has not been changed
  2308. if (firstRowInViewPort != firstvisible) {
  2309. // remember which firstvisible we requested, in case the
  2310. // server has a differing opinion
  2311. lastRequestedFirstvisible = firstRowInViewPort;
  2312. client.updateVariable(paintableId, "firstvisible",
  2313. firstRowInViewPort, false);
  2314. }
  2315. client.updateVariable(paintableId, "reqfirstrow", reqFirstRow,
  2316. false);
  2317. client.updateVariable(paintableId, "reqrows", reqRows, true);
  2318. if (selectionChanged) {
  2319. unSyncedselectionsBeforeRowFetch = new HashSet<Object>(
  2320. selectedRowKeys);
  2321. }
  2322. isRequestHandlerRunning = false;
  2323. }
  2324. }
  2325. /**
  2326. * Sends request to refresh content at this position.
  2327. */
  2328. public void refreshContent() {
  2329. isRequestHandlerRunning = true;
  2330. int first = (int) (firstRowInViewPort - pageLength * cache_rate);
  2331. int reqRows = (int) (2 * pageLength * cache_rate + pageLength);
  2332. if (first < 0) {
  2333. reqRows = reqRows + first;
  2334. first = 0;
  2335. }
  2336. setReqFirstRow(first);
  2337. setReqRows(reqRows);
  2338. run();
  2339. }
  2340. }
  2341. public class HeaderCell extends Widget {
  2342. Element td = DOM.createTD();
  2343. Element captionContainer = DOM.createDiv();
  2344. Element sortIndicator = DOM.createDiv();
  2345. Element colResizeWidget = DOM.createDiv();
  2346. Element floatingCopyOfHeaderCell;
  2347. private boolean sortable = false;
  2348. private final String cid;
  2349. private boolean dragging;
  2350. private int dragStartX;
  2351. private int colIndex;
  2352. private int originalWidth;
  2353. private boolean isResizing;
  2354. private int headerX;
  2355. private boolean moved;
  2356. private int closestSlot;
  2357. private int width = -1;
  2358. private int naturalWidth = -1;
  2359. private char align = ALIGN_LEFT;
  2360. boolean definedWidth = false;
  2361. private float expandRatio = 0;
  2362. private boolean sorted;
  2363. public void setSortable(boolean b) {
  2364. sortable = b;
  2365. }
  2366. /**
  2367. * Makes room for the sorting indicator in case the column that the
  2368. * header cell belongs to is sorted. This is done by resizing the width
  2369. * of the caption container element by the correct amount
  2370. */
  2371. public void resizeCaptionContainer(int rightSpacing) {
  2372. int captionContainerWidth = width
  2373. - colResizeWidget.getOffsetWidth() - rightSpacing;
  2374. if (td.getClassName().contains("-asc")
  2375. || td.getClassName().contains("-desc")) {
  2376. // Leave room for the sort indicator
  2377. captionContainerWidth -= sortIndicator.getOffsetWidth();
  2378. }
  2379. if (captionContainerWidth < 0) {
  2380. rightSpacing += captionContainerWidth;
  2381. captionContainerWidth = 0;
  2382. }
  2383. captionContainer.getStyle().setPropertyPx("width",
  2384. captionContainerWidth);
  2385. // Apply/Remove spacing if defined
  2386. if (rightSpacing > 0) {
  2387. colResizeWidget.getStyle().setMarginLeft(rightSpacing, Unit.PX);
  2388. } else {
  2389. colResizeWidget.getStyle().clearMarginLeft();
  2390. }
  2391. }
  2392. public void setNaturalMinimumColumnWidth(int w) {
  2393. naturalWidth = w;
  2394. }
  2395. public HeaderCell(String colId, String headerText) {
  2396. cid = colId;
  2397. setText(headerText);
  2398. td.appendChild(colResizeWidget);
  2399. // ensure no clipping initially (problem on column additions)
  2400. captionContainer.getStyle().setOverflow(Overflow.VISIBLE);
  2401. td.appendChild(sortIndicator);
  2402. td.appendChild(captionContainer);
  2403. DOM.sinkEvents(td, Event.MOUSEEVENTS | Event.ONDBLCLICK
  2404. | Event.ONCONTEXTMENU | Event.TOUCHEVENTS);
  2405. setElement(td);
  2406. setAlign(ALIGN_LEFT);
  2407. }
  2408. protected void updateStyleNames(String primaryStyleName) {
  2409. colResizeWidget.setClassName(primaryStyleName + "-resizer");
  2410. sortIndicator.setClassName(primaryStyleName + "-sort-indicator");
  2411. captionContainer.setClassName(primaryStyleName
  2412. + "-caption-container");
  2413. if (sorted) {
  2414. if (sortAscending) {
  2415. setStyleName(primaryStyleName + "-header-cell-asc");
  2416. } else {
  2417. setStyleName(primaryStyleName + "-header-cell-desc");
  2418. }
  2419. } else {
  2420. setStyleName(primaryStyleName + "-header-cell");
  2421. }
  2422. final String ALIGN_PREFIX = primaryStyleName
  2423. + "-caption-container-align-";
  2424. switch (align) {
  2425. case ALIGN_CENTER:
  2426. captionContainer.addClassName(ALIGN_PREFIX + "center");
  2427. break;
  2428. case ALIGN_RIGHT:
  2429. captionContainer.addClassName(ALIGN_PREFIX + "right");
  2430. break;
  2431. default:
  2432. captionContainer.addClassName(ALIGN_PREFIX + "left");
  2433. break;
  2434. }
  2435. }
  2436. public void disableAutoWidthCalculation() {
  2437. definedWidth = true;
  2438. expandRatio = 0;
  2439. }
  2440. /**
  2441. * Sets width to the header cell. This width should not include any
  2442. * possible indent modifications that are present in
  2443. * {@link VScrollTableBody#getMaxIndent()}.
  2444. *
  2445. * @param w
  2446. * required width of the cell sans indentations
  2447. * @param ensureDefinedWidth
  2448. * disables expand ratio if required
  2449. */
  2450. public void setWidth(int w, boolean ensureDefinedWidth) {
  2451. if (ensureDefinedWidth) {
  2452. definedWidth = true;
  2453. // on column resize expand ratio becomes zero
  2454. expandRatio = 0;
  2455. }
  2456. if (width == -1) {
  2457. // go to default mode, clip content if necessary
  2458. captionContainer.getStyle().clearOverflow();
  2459. }
  2460. width = w;
  2461. if (w == -1) {
  2462. captionContainer.getStyle().clearWidth();
  2463. setWidth("");
  2464. } else {
  2465. tHead.resizeCaptionContainer(this);
  2466. /*
  2467. * if we already have tBody, set the header width properly, if
  2468. * not defer it. IE will fail with complex float in table header
  2469. * unless TD width is not explicitly set.
  2470. */
  2471. if (scrollBody != null) {
  2472. int maxIndent = scrollBody.getMaxIndent();
  2473. if (w < maxIndent && isHierarchyColumn()) {
  2474. w = maxIndent;
  2475. }
  2476. int tdWidth = w + scrollBody.getCellExtraWidth();
  2477. setWidth(tdWidth + "px");
  2478. } else {
  2479. Scheduler.get().scheduleDeferred(new Command() {
  2480. @Override
  2481. public void execute() {
  2482. int maxIndent = scrollBody.getMaxIndent();
  2483. int tdWidth = width;
  2484. if (tdWidth < maxIndent && isHierarchyColumn()) {
  2485. tdWidth = maxIndent;
  2486. }
  2487. tdWidth += scrollBody.getCellExtraWidth();
  2488. setWidth(tdWidth + "px");
  2489. }
  2490. });
  2491. }
  2492. }
  2493. }
  2494. public void setUndefinedWidth() {
  2495. definedWidth = false;
  2496. if (!isResizing) {
  2497. setWidth(-1, false);
  2498. }
  2499. }
  2500. /**
  2501. * Detects if width is fixed by developer on server side or resized to
  2502. * current width by user.
  2503. *
  2504. * @return true if defined, false if "natural" width
  2505. */
  2506. public boolean isDefinedWidth() {
  2507. return definedWidth && width >= 0;
  2508. }
  2509. /**
  2510. * This method exists for the needs of {@link VTreeTable} only.
  2511. *
  2512. * Returns the pixels width of the header cell. This includes the
  2513. * indent, if applicable.
  2514. *
  2515. * @return The width in pixels
  2516. */
  2517. protected int getWidthWithIndent() {
  2518. if (scrollBody != null && isHierarchyColumn()) {
  2519. int maxIndent = scrollBody.getMaxIndent();
  2520. if (maxIndent > width) {
  2521. return maxIndent;
  2522. }
  2523. }
  2524. return width;
  2525. }
  2526. /**
  2527. * Returns the pixels width of the header cell.
  2528. *
  2529. * @return The width in pixels
  2530. */
  2531. public int getWidth() {
  2532. return width;
  2533. }
  2534. /**
  2535. * This method exists for the needs of {@link VTreeTable} only.
  2536. *
  2537. * @return <code>true</code> if this is hierarcyColumn's header cell,
  2538. * <code>false</code> otherwise
  2539. */
  2540. private boolean isHierarchyColumn() {
  2541. int hierarchyColumnIndex = getHierarchyColumnIndex();
  2542. return hierarchyColumnIndex >= 0
  2543. && tHead.visibleCells.indexOf(this) == hierarchyColumnIndex;
  2544. }
  2545. public void setText(String headerText) {
  2546. DOM.setInnerHTML(captionContainer, headerText);
  2547. }
  2548. public String getColKey() {
  2549. return cid;
  2550. }
  2551. private void setSorted(boolean sorted) {
  2552. this.sorted = sorted;
  2553. updateStyleNames(VScrollTable.this.getStylePrimaryName());
  2554. }
  2555. /**
  2556. * Handle column reordering.
  2557. */
  2558. @Override
  2559. public void onBrowserEvent(Event event) {
  2560. if (enabled && event != null) {
  2561. if (isResizing
  2562. || event.getEventTarget().cast() == colResizeWidget) {
  2563. if (dragging
  2564. && (event.getTypeInt() == Event.ONMOUSEUP || event
  2565. .getTypeInt() == Event.ONTOUCHEND)) {
  2566. // Handle releasing column header on spacer #5318
  2567. handleCaptionEvent(event);
  2568. } else {
  2569. onResizeEvent(event);
  2570. }
  2571. } else {
  2572. /*
  2573. * Ensure focus before handling caption event. Otherwise
  2574. * variables changed from caption event may be before
  2575. * variables from other components that fire variables when
  2576. * they lose focus.
  2577. */
  2578. if (event.getTypeInt() == Event.ONMOUSEDOWN
  2579. || event.getTypeInt() == Event.ONTOUCHSTART) {
  2580. scrollBodyPanel.setFocus(true);
  2581. }
  2582. handleCaptionEvent(event);
  2583. boolean stopPropagation = true;
  2584. if (event.getTypeInt() == Event.ONCONTEXTMENU
  2585. && !client.hasEventListeners(VScrollTable.this,
  2586. TableConstants.HEADER_CLICK_EVENT_ID)) {
  2587. // Prevent showing the browser's context menu only when
  2588. // there is a header click listener.
  2589. stopPropagation = false;
  2590. }
  2591. if (stopPropagation) {
  2592. event.stopPropagation();
  2593. event.preventDefault();
  2594. }
  2595. }
  2596. }
  2597. }
  2598. private void createFloatingCopy() {
  2599. floatingCopyOfHeaderCell = DOM.createDiv();
  2600. DOM.setInnerHTML(floatingCopyOfHeaderCell, DOM.getInnerHTML(td));
  2601. floatingCopyOfHeaderCell = DOM
  2602. .getChild(floatingCopyOfHeaderCell, 2);
  2603. // #12714 the shown "ghost element" should be inside
  2604. // v-overlay-container, and it should contain the same styles as the
  2605. // table to enable theming (except v-table & v-widget).
  2606. String stylePrimaryName = VScrollTable.this.getStylePrimaryName();
  2607. StringBuilder sb = new StringBuilder();
  2608. for (String s : VScrollTable.this.getStyleName().split(" ")) {
  2609. if (!s.equals(StyleConstants.UI_WIDGET)) {
  2610. sb.append(s);
  2611. if (s.equals(stylePrimaryName)) {
  2612. sb.append("-header-drag ");
  2613. } else {
  2614. sb.append(" ");
  2615. }
  2616. }
  2617. }
  2618. floatingCopyOfHeaderCell.setClassName(sb.toString().trim());
  2619. // otherwise might wrap or be cut if narrow column
  2620. floatingCopyOfHeaderCell.getStyle().setProperty("width", "auto");
  2621. updateFloatingCopysPosition(DOM.getAbsoluteLeft(td),
  2622. DOM.getAbsoluteTop(td));
  2623. DOM.appendChild(VOverlay.getOverlayContainer(client),
  2624. floatingCopyOfHeaderCell);
  2625. }
  2626. private void updateFloatingCopysPosition(int x, int y) {
  2627. x -= DOM.getElementPropertyInt(floatingCopyOfHeaderCell,
  2628. "offsetWidth") / 2;
  2629. floatingCopyOfHeaderCell.getStyle().setLeft(x, Unit.PX);
  2630. if (y > 0) {
  2631. floatingCopyOfHeaderCell.getStyle().setTop(y + 7, Unit.PX);
  2632. }
  2633. }
  2634. private void hideFloatingCopy() {
  2635. floatingCopyOfHeaderCell.removeFromParent();
  2636. floatingCopyOfHeaderCell = null;
  2637. }
  2638. /**
  2639. * Fires a header click event after the user has clicked a column header
  2640. * cell
  2641. *
  2642. * @param event
  2643. * The click event
  2644. */
  2645. private void fireHeaderClickedEvent(Event event) {
  2646. if (client.hasEventListeners(VScrollTable.this,
  2647. TableConstants.HEADER_CLICK_EVENT_ID)) {
  2648. MouseEventDetails details = MouseEventDetailsBuilder
  2649. .buildMouseEventDetails(event);
  2650. client.updateVariable(paintableId, "headerClickEvent",
  2651. details.toString(), false);
  2652. client.updateVariable(paintableId, "headerClickCID", cid, true);
  2653. }
  2654. }
  2655. protected void handleCaptionEvent(Event event) {
  2656. switch (DOM.eventGetType(event)) {
  2657. case Event.ONTOUCHSTART:
  2658. case Event.ONMOUSEDOWN:
  2659. if (columnReordering
  2660. && Util.isTouchEventOrLeftMouseButton(event)) {
  2661. if (event.getTypeInt() == Event.ONTOUCHSTART) {
  2662. /*
  2663. * prevent using this event in e.g. scrolling
  2664. */
  2665. event.stopPropagation();
  2666. }
  2667. dragging = true;
  2668. moved = false;
  2669. colIndex = getColIndexByKey(cid);
  2670. DOM.setCapture(getElement());
  2671. headerX = tHead.getAbsoluteLeft();
  2672. event.preventDefault(); // prevent selecting text &&
  2673. // generated touch events
  2674. }
  2675. break;
  2676. case Event.ONMOUSEUP:
  2677. case Event.ONTOUCHEND:
  2678. case Event.ONTOUCHCANCEL:
  2679. if (columnReordering
  2680. && Util.isTouchEventOrLeftMouseButton(event)) {
  2681. dragging = false;
  2682. DOM.releaseCapture(getElement());
  2683. if (moved) {
  2684. hideFloatingCopy();
  2685. tHead.removeSlotFocus();
  2686. if (closestSlot != colIndex
  2687. && closestSlot != (colIndex + 1)) {
  2688. if (closestSlot > colIndex) {
  2689. reOrderColumn(cid, closestSlot - 1);
  2690. } else {
  2691. reOrderColumn(cid, closestSlot);
  2692. }
  2693. }
  2694. }
  2695. if (Util.isTouchEvent(event)) {
  2696. /*
  2697. * Prevent using in e.g. scrolling and prevent generated
  2698. * events.
  2699. */
  2700. event.preventDefault();
  2701. event.stopPropagation();
  2702. }
  2703. }
  2704. if (!moved) {
  2705. // mouse event was a click to header -> sort column
  2706. if (sortable && Util.isTouchEventOrLeftMouseButton(event)) {
  2707. if (sortColumn.equals(cid)) {
  2708. // just toggle order
  2709. client.updateVariable(paintableId, "sortascending",
  2710. !sortAscending, false);
  2711. } else {
  2712. // set table sorted by this column
  2713. client.updateVariable(paintableId, "sortcolumn",
  2714. cid, false);
  2715. }
  2716. // get also cache columns at the same request
  2717. scrollBodyPanel.setScrollPosition(0);
  2718. firstvisible = 0;
  2719. rowRequestHandler.setReqFirstRow(0);
  2720. rowRequestHandler.setReqRows((int) (2 * pageLength
  2721. * cache_rate + pageLength));
  2722. rowRequestHandler.deferRowFetch(); // some validation +
  2723. // defer 250ms
  2724. rowRequestHandler.cancel(); // instead of waiting
  2725. rowRequestHandler.run(); // run immediately
  2726. }
  2727. fireHeaderClickedEvent(event);
  2728. if (Util.isTouchEvent(event)) {
  2729. /*
  2730. * Prevent using in e.g. scrolling and prevent generated
  2731. * events.
  2732. */
  2733. event.preventDefault();
  2734. event.stopPropagation();
  2735. }
  2736. break;
  2737. }
  2738. break;
  2739. case Event.ONDBLCLICK:
  2740. fireHeaderClickedEvent(event);
  2741. break;
  2742. case Event.ONTOUCHMOVE:
  2743. case Event.ONMOUSEMOVE:
  2744. if (dragging && Util.isTouchEventOrLeftMouseButton(event)) {
  2745. if (event.getTypeInt() == Event.ONTOUCHMOVE) {
  2746. /*
  2747. * prevent using this event in e.g. scrolling
  2748. */
  2749. event.stopPropagation();
  2750. }
  2751. if (!moved) {
  2752. createFloatingCopy();
  2753. moved = true;
  2754. }
  2755. final int clientX = Util.getTouchOrMouseClientX(event);
  2756. final int x = clientX + tHead.hTableWrapper.getScrollLeft();
  2757. int slotX = headerX;
  2758. closestSlot = colIndex;
  2759. int closestDistance = -1;
  2760. int start = 0;
  2761. if (showRowHeaders) {
  2762. start++;
  2763. }
  2764. final int visibleCellCount = tHead.getVisibleCellCount();
  2765. for (int i = start; i <= visibleCellCount; i++) {
  2766. if (i > 0) {
  2767. final String colKey = getColKeyByIndex(i - 1);
  2768. // getColWidth only returns the internal width
  2769. // without padding, not the offset width of the
  2770. // whole td (#10890)
  2771. slotX += getColWidth(colKey)
  2772. + scrollBody.getCellExtraWidth();
  2773. }
  2774. final int dist = Math.abs(x - slotX);
  2775. if (closestDistance == -1 || dist < closestDistance) {
  2776. closestDistance = dist;
  2777. closestSlot = i;
  2778. }
  2779. }
  2780. tHead.focusSlot(closestSlot);
  2781. updateFloatingCopysPosition(clientX, -1);
  2782. }
  2783. break;
  2784. default:
  2785. break;
  2786. }
  2787. }
  2788. private void onResizeEvent(Event event) {
  2789. switch (DOM.eventGetType(event)) {
  2790. case Event.ONMOUSEDOWN:
  2791. if (!Util.isTouchEventOrLeftMouseButton(event)) {
  2792. return;
  2793. }
  2794. isResizing = true;
  2795. DOM.setCapture(getElement());
  2796. dragStartX = DOM.eventGetClientX(event);
  2797. colIndex = getColIndexByKey(cid);
  2798. originalWidth = getWidthWithIndent();
  2799. DOM.eventPreventDefault(event);
  2800. break;
  2801. case Event.ONMOUSEUP:
  2802. if (!Util.isTouchEventOrLeftMouseButton(event)) {
  2803. return;
  2804. }
  2805. isResizing = false;
  2806. DOM.releaseCapture(getElement());
  2807. tHead.disableAutoColumnWidthCalculation(this);
  2808. // Ensure last header cell is taking into account possible
  2809. // column selector
  2810. HeaderCell lastCell = tHead.getHeaderCell(tHead
  2811. .getVisibleCellCount() - 1);
  2812. tHead.resizeCaptionContainer(lastCell);
  2813. triggerLazyColumnAdjustment(true);
  2814. fireColumnResizeEvent(cid, originalWidth, getColWidth(cid));
  2815. break;
  2816. case Event.ONMOUSEMOVE:
  2817. if (!Util.isTouchEventOrLeftMouseButton(event)) {
  2818. return;
  2819. }
  2820. if (isResizing) {
  2821. final int deltaX = DOM.eventGetClientX(event) - dragStartX;
  2822. if (deltaX == 0) {
  2823. return;
  2824. }
  2825. tHead.disableAutoColumnWidthCalculation(this);
  2826. int newWidth = originalWidth + deltaX;
  2827. // get min width with indent, no padding
  2828. int minWidth = getMinWidth(true, false);
  2829. if (newWidth < minWidth) {
  2830. // already includes indent if any
  2831. newWidth = minWidth;
  2832. }
  2833. setColWidth(colIndex, newWidth, true);
  2834. triggerLazyColumnAdjustment(false);
  2835. forceRealignColumnHeaders();
  2836. }
  2837. break;
  2838. default:
  2839. break;
  2840. }
  2841. }
  2842. /**
  2843. * Returns the smallest possible cell width in pixels.
  2844. *
  2845. * @param includeIndent
  2846. * - width should include hierarchy column indent if
  2847. * applicable (VTreeTable only)
  2848. * @param includeCellExtraWidth
  2849. * - width should include paddings etc.
  2850. * @return
  2851. */
  2852. private int getMinWidth(boolean includeIndent,
  2853. boolean includeCellExtraWidth) {
  2854. int minWidth = sortIndicator.getOffsetWidth();
  2855. if (scrollBody != null) {
  2856. // check the need for indent before adding paddings etc.
  2857. if (includeIndent && isHierarchyColumn()) {
  2858. int maxIndent = scrollBody.getMaxIndent();
  2859. if (minWidth < maxIndent) {
  2860. minWidth = maxIndent;
  2861. }
  2862. }
  2863. if (includeCellExtraWidth) {
  2864. minWidth += scrollBody.getCellExtraWidth();
  2865. }
  2866. }
  2867. return minWidth;
  2868. }
  2869. public int getMinWidth() {
  2870. // get min width with padding, no indent
  2871. return getMinWidth(false, true);
  2872. }
  2873. public String getCaption() {
  2874. return DOM.getInnerText(captionContainer);
  2875. }
  2876. public boolean isEnabled() {
  2877. return getParent() != null;
  2878. }
  2879. public void setAlign(char c) {
  2880. align = c;
  2881. updateStyleNames(VScrollTable.this.getStylePrimaryName());
  2882. }
  2883. public char getAlign() {
  2884. return align;
  2885. }
  2886. /**
  2887. * Detects the natural minimum width for the column of this header cell.
  2888. * If column is resized by user or the width is defined by server the
  2889. * actual width is returned. Else the natural min width is returned.
  2890. *
  2891. * @param columnIndex
  2892. * column index hint, if -1 (unknown) it will be detected
  2893. *
  2894. * @return
  2895. */
  2896. public int getNaturalColumnWidth(int columnIndex) {
  2897. final int iw = columnIndex == getHierarchyColumnIndex() ? scrollBody
  2898. .getMaxIndent() : 0;
  2899. if (isDefinedWidth()) {
  2900. if (iw > width) {
  2901. return iw;
  2902. }
  2903. return width;
  2904. } else {
  2905. if (naturalWidth < 0) {
  2906. // This is recently revealed column. Try to detect a proper
  2907. // value (greater of header and data columns)
  2908. int hw = captionContainer.getOffsetWidth()
  2909. + getHeaderPadding();
  2910. if (BrowserInfo.get().isGecko()) {
  2911. hw += sortIndicator.getOffsetWidth();
  2912. }
  2913. if (columnIndex < 0) {
  2914. columnIndex = 0;
  2915. for (Iterator<Widget> it = tHead.iterator(); it
  2916. .hasNext(); columnIndex++) {
  2917. if (it.next() == this) {
  2918. break;
  2919. }
  2920. }
  2921. }
  2922. final int cw = scrollBody.getColWidth(columnIndex);
  2923. naturalWidth = (hw > cw ? hw : cw);
  2924. }
  2925. if (iw > naturalWidth) {
  2926. // indent is temporary value, naturalWidth shouldn't be
  2927. // updated
  2928. return iw;
  2929. } else {
  2930. return naturalWidth;
  2931. }
  2932. }
  2933. }
  2934. public void setExpandRatio(float floatAttribute) {
  2935. if (floatAttribute != expandRatio) {
  2936. triggerLazyColumnAdjustment(false);
  2937. }
  2938. expandRatio = floatAttribute;
  2939. }
  2940. public float getExpandRatio() {
  2941. return expandRatio;
  2942. }
  2943. public boolean isSorted() {
  2944. return sorted;
  2945. }
  2946. }
  2947. /**
  2948. * HeaderCell that is header cell for row headers.
  2949. *
  2950. * Reordering disabled and clicking on it resets sorting.
  2951. */
  2952. public class RowHeadersHeaderCell extends HeaderCell {
  2953. RowHeadersHeaderCell() {
  2954. super(ROW_HEADER_COLUMN_KEY, "");
  2955. updateStyleNames(VScrollTable.this.getStylePrimaryName());
  2956. }
  2957. @Override
  2958. protected void updateStyleNames(String primaryStyleName) {
  2959. super.updateStyleNames(primaryStyleName);
  2960. setStyleName(primaryStyleName + "-header-cell-rowheader");
  2961. }
  2962. @Override
  2963. protected void handleCaptionEvent(Event event) {
  2964. // NOP: RowHeaders cannot be reordered
  2965. // TODO It'd be nice to reset sorting here
  2966. }
  2967. }
  2968. public class TableHead extends Panel implements ActionOwner {
  2969. private static final int WRAPPER_WIDTH = 900000;
  2970. ArrayList<Widget> visibleCells = new ArrayList<Widget>();
  2971. HashMap<String, HeaderCell> availableCells = new HashMap<String, HeaderCell>();
  2972. Element div = DOM.createDiv();
  2973. Element hTableWrapper = DOM.createDiv();
  2974. Element hTableContainer = DOM.createDiv();
  2975. Element table = DOM.createTable();
  2976. Element headerTableBody = DOM.createTBody();
  2977. Element tr = DOM.createTR();
  2978. private final Element columnSelector = DOM.createDiv();
  2979. private int focusedSlot = -1;
  2980. public TableHead() {
  2981. if (BrowserInfo.get().isIE()) {
  2982. table.setPropertyInt("cellSpacing", 0);
  2983. }
  2984. hTableWrapper.getStyle().setOverflow(Overflow.HIDDEN);
  2985. columnSelector.getStyle().setDisplay(Display.NONE);
  2986. DOM.appendChild(table, headerTableBody);
  2987. DOM.appendChild(headerTableBody, tr);
  2988. DOM.appendChild(hTableContainer, table);
  2989. DOM.appendChild(hTableWrapper, hTableContainer);
  2990. DOM.appendChild(div, hTableWrapper);
  2991. DOM.appendChild(div, columnSelector);
  2992. setElement(div);
  2993. DOM.sinkEvents(columnSelector, Event.ONCLICK);
  2994. availableCells.put(ROW_HEADER_COLUMN_KEY,
  2995. new RowHeadersHeaderCell());
  2996. }
  2997. protected void updateStyleNames(String primaryStyleName) {
  2998. hTableWrapper.setClassName(primaryStyleName + "-header");
  2999. columnSelector.setClassName(primaryStyleName + "-column-selector");
  3000. setStyleName(primaryStyleName + "-header-wrap");
  3001. for (HeaderCell c : availableCells.values()) {
  3002. c.updateStyleNames(primaryStyleName);
  3003. }
  3004. }
  3005. public void resizeCaptionContainer(HeaderCell cell) {
  3006. HeaderCell lastcell = getHeaderCell(visibleCells.size() - 1);
  3007. int columnSelectorOffset = columnSelector.getOffsetWidth();
  3008. if (cell == lastcell && columnSelectorOffset > 0
  3009. && !hasVerticalScrollbar()) {
  3010. // Measure column widths
  3011. int columnTotalWidth = 0;
  3012. for (Widget w : visibleCells) {
  3013. int cellExtraWidth = w.getOffsetWidth();
  3014. if (scrollBody != null
  3015. && visibleCells.indexOf(w) == getHierarchyColumnIndex()
  3016. && cellExtraWidth < scrollBody.getMaxIndent()) {
  3017. // indent must be taken into consideration even if it
  3018. // hasn't been applied yet
  3019. columnTotalWidth += scrollBody.getMaxIndent();
  3020. } else {
  3021. columnTotalWidth += cellExtraWidth;
  3022. }
  3023. }
  3024. int divOffset = div.getOffsetWidth();
  3025. if (columnTotalWidth >= divOffset - columnSelectorOffset) {
  3026. /*
  3027. * Ensure column caption is visible when placed under the
  3028. * column selector widget by shifting and resizing the
  3029. * caption.
  3030. */
  3031. int offset = 0;
  3032. int diff = divOffset - columnTotalWidth;
  3033. if (diff < columnSelectorOffset && diff > 0) {
  3034. /*
  3035. * If the difference is less than the column selectors
  3036. * width then just offset by the difference
  3037. */
  3038. offset = columnSelectorOffset - diff;
  3039. } else {
  3040. // Else offset by the whole column selector
  3041. offset = columnSelectorOffset;
  3042. }
  3043. lastcell.resizeCaptionContainer(offset);
  3044. } else {
  3045. cell.resizeCaptionContainer(0);
  3046. }
  3047. } else {
  3048. cell.resizeCaptionContainer(0);
  3049. }
  3050. }
  3051. @Override
  3052. public void clear() {
  3053. for (String cid : availableCells.keySet()) {
  3054. removeCell(cid);
  3055. }
  3056. availableCells.clear();
  3057. availableCells.put(ROW_HEADER_COLUMN_KEY,
  3058. new RowHeadersHeaderCell());
  3059. }
  3060. public void updateCellsFromUIDL(UIDL uidl) {
  3061. Iterator<?> it = uidl.getChildIterator();
  3062. HashSet<String> updated = new HashSet<String>();
  3063. boolean refreshContentWidths = initializedAndAttached
  3064. && hadScrollBars != willHaveScrollbars();
  3065. while (it.hasNext()) {
  3066. final UIDL col = (UIDL) it.next();
  3067. final String cid = col.getStringAttribute("cid");
  3068. updated.add(cid);
  3069. String caption = buildCaptionHtmlSnippet(col);
  3070. HeaderCell c = getHeaderCell(cid);
  3071. if (c == null) {
  3072. c = new HeaderCell(cid, caption);
  3073. availableCells.put(cid, c);
  3074. if (initializedAndAttached) {
  3075. // we will need a column width recalculation
  3076. initializedAndAttached = false;
  3077. initialContentReceived = false;
  3078. isNewBody = true;
  3079. }
  3080. } else {
  3081. c.setText(caption);
  3082. }
  3083. if (col.hasAttribute("sortable")) {
  3084. c.setSortable(true);
  3085. c.setSorted(false);
  3086. } else {
  3087. c.setSortable(false);
  3088. }
  3089. if (col.hasAttribute("align")) {
  3090. c.setAlign(col.getStringAttribute("align").charAt(0));
  3091. } else {
  3092. c.setAlign(ALIGN_LEFT);
  3093. }
  3094. if (col.hasAttribute("width") && !c.isResizing) {
  3095. // Make sure to accomodate for the sort indicator if
  3096. // necessary.
  3097. int width = col.getIntAttribute("width");
  3098. int widthWithoutAddedIndent = width;
  3099. // get min width with indent, no padding
  3100. int minWidth = c.getMinWidth(true, false);
  3101. if (width < minWidth) {
  3102. width = minWidth;
  3103. }
  3104. if (scrollBody != null && width != c.getWidthWithIndent()) {
  3105. // Do a more thorough update if a column is resized from
  3106. // the server *after* the header has been properly
  3107. // initialized
  3108. final int colIx = getColIndexByKey(c.cid);
  3109. final int newWidth = width;
  3110. Scheduler.get().scheduleDeferred(
  3111. new ScheduledCommand() {
  3112. @Override
  3113. public void execute() {
  3114. setColWidth(colIx, newWidth, true);
  3115. }
  3116. });
  3117. refreshContentWidths = true;
  3118. } else {
  3119. // get min width with no indent or padding
  3120. minWidth = c.getMinWidth(false, false);
  3121. if (widthWithoutAddedIndent < minWidth) {
  3122. widthWithoutAddedIndent = minWidth;
  3123. }
  3124. // save min width without indent
  3125. c.setWidth(widthWithoutAddedIndent, true);
  3126. }
  3127. } else if (col.hasAttribute("er")) {
  3128. c.setExpandRatio(col.getFloatAttribute("er"));
  3129. } else if (recalcWidths) {
  3130. c.setUndefinedWidth();
  3131. } else {
  3132. boolean hadExpandRatio = c.getExpandRatio() > 0;
  3133. boolean hadDefinedWidth = c.isDefinedWidth();
  3134. if (hadExpandRatio || hadDefinedWidth) {
  3135. // Someone has removed a expand width or the defined
  3136. // width on the server side (setting it to -1), make the
  3137. // column undefined again and measure columns again.
  3138. c.setUndefinedWidth();
  3139. c.setExpandRatio(0);
  3140. refreshContentWidths = true;
  3141. }
  3142. }
  3143. if (col.hasAttribute("collapsed")) {
  3144. // ensure header is properly removed from parent (case when
  3145. // collapsing happens via servers side api)
  3146. if (c.isAttached()) {
  3147. c.removeFromParent();
  3148. headerChangedDuringUpdate = true;
  3149. }
  3150. }
  3151. }
  3152. if (refreshContentWidths) {
  3153. // Recalculate the column sizings if any column has changed
  3154. Scheduler.get().scheduleDeferred(new ScheduledCommand() {
  3155. @Override
  3156. public void execute() {
  3157. triggerLazyColumnAdjustment(true);
  3158. }
  3159. });
  3160. }
  3161. // check for orphaned header cells
  3162. for (Iterator<String> cit = availableCells.keySet().iterator(); cit
  3163. .hasNext();) {
  3164. String cid = cit.next();
  3165. if (!updated.contains(cid)) {
  3166. removeCell(cid);
  3167. cit.remove();
  3168. // we will need a column width recalculation, since columns
  3169. // with expand ratios should expand to fill the void.
  3170. initializedAndAttached = false;
  3171. initialContentReceived = false;
  3172. isNewBody = true;
  3173. }
  3174. }
  3175. }
  3176. public void enableColumn(String cid, int index) {
  3177. final HeaderCell c = getHeaderCell(cid);
  3178. if (!c.isEnabled() || getHeaderCell(index) != c) {
  3179. setHeaderCell(index, c);
  3180. if (initializedAndAttached) {
  3181. headerChangedDuringUpdate = true;
  3182. }
  3183. }
  3184. }
  3185. public int getVisibleCellCount() {
  3186. return visibleCells.size();
  3187. }
  3188. public void setHorizontalScrollPosition(int scrollLeft) {
  3189. hTableWrapper.setScrollLeft(scrollLeft);
  3190. }
  3191. public void setColumnCollapsingAllowed(boolean cc) {
  3192. if (cc) {
  3193. columnSelector.getStyle().setDisplay(Display.BLOCK);
  3194. } else {
  3195. columnSelector.getStyle().setDisplay(Display.NONE);
  3196. }
  3197. }
  3198. public void disableBrowserIntelligence() {
  3199. hTableContainer.getStyle().setWidth(WRAPPER_WIDTH, Unit.PX);
  3200. }
  3201. public void enableBrowserIntelligence() {
  3202. hTableContainer.getStyle().clearWidth();
  3203. }
  3204. public void setHeaderCell(int index, HeaderCell cell) {
  3205. if (cell.isEnabled()) {
  3206. // we're moving the cell
  3207. DOM.removeChild(tr, cell.getElement());
  3208. orphan(cell);
  3209. visibleCells.remove(cell);
  3210. }
  3211. if (index < visibleCells.size()) {
  3212. // insert to right slot
  3213. DOM.insertChild(tr, cell.getElement(), index);
  3214. adopt(cell);
  3215. visibleCells.add(index, cell);
  3216. } else if (index == visibleCells.size()) {
  3217. // simply append
  3218. DOM.appendChild(tr, cell.getElement());
  3219. adopt(cell);
  3220. visibleCells.add(cell);
  3221. } else {
  3222. throw new RuntimeException(
  3223. "Header cells must be appended in order");
  3224. }
  3225. }
  3226. public HeaderCell getHeaderCell(int index) {
  3227. if (index >= 0 && index < visibleCells.size()) {
  3228. return (HeaderCell) visibleCells.get(index);
  3229. } else {
  3230. return null;
  3231. }
  3232. }
  3233. /**
  3234. * Get's HeaderCell by it's column Key.
  3235. *
  3236. * Note that this returns HeaderCell even if it is currently collapsed.
  3237. *
  3238. * @param cid
  3239. * Column key of accessed HeaderCell
  3240. * @return HeaderCell
  3241. */
  3242. public HeaderCell getHeaderCell(String cid) {
  3243. return availableCells.get(cid);
  3244. }
  3245. public void moveCell(int oldIndex, int newIndex) {
  3246. final HeaderCell hCell = getHeaderCell(oldIndex);
  3247. final Element cell = hCell.getElement();
  3248. visibleCells.remove(oldIndex);
  3249. DOM.removeChild(tr, cell);
  3250. DOM.insertChild(tr, cell, newIndex);
  3251. visibleCells.add(newIndex, hCell);
  3252. }
  3253. @Override
  3254. public Iterator<Widget> iterator() {
  3255. return visibleCells.iterator();
  3256. }
  3257. @Override
  3258. public boolean remove(Widget w) {
  3259. if (visibleCells.contains(w)) {
  3260. visibleCells.remove(w);
  3261. orphan(w);
  3262. DOM.removeChild(DOM.getParent(w.getElement()), w.getElement());
  3263. return true;
  3264. }
  3265. return false;
  3266. }
  3267. public void removeCell(String colKey) {
  3268. final HeaderCell c = getHeaderCell(colKey);
  3269. remove(c);
  3270. }
  3271. private void focusSlot(int index) {
  3272. removeSlotFocus();
  3273. if (index > 0) {
  3274. Element child = tr.getChild(index - 1).getFirstChild().cast();
  3275. child.setClassName(VScrollTable.this.getStylePrimaryName()
  3276. + "-resizer");
  3277. child.addClassName(VScrollTable.this.getStylePrimaryName()
  3278. + "-focus-slot-right");
  3279. } else {
  3280. Element child = tr.getChild(index).getFirstChild().cast();
  3281. child.setClassName(VScrollTable.this.getStylePrimaryName()
  3282. + "-resizer");
  3283. child.addClassName(VScrollTable.this.getStylePrimaryName()
  3284. + "-focus-slot-left");
  3285. }
  3286. focusedSlot = index;
  3287. }
  3288. private void removeSlotFocus() {
  3289. if (focusedSlot < 0) {
  3290. return;
  3291. }
  3292. if (focusedSlot == 0) {
  3293. Element child = tr.getChild(focusedSlot).getFirstChild().cast();
  3294. child.setClassName(VScrollTable.this.getStylePrimaryName()
  3295. + "-resizer");
  3296. } else if (focusedSlot > 0) {
  3297. Element child = tr.getChild(focusedSlot - 1).getFirstChild()
  3298. .cast();
  3299. child.setClassName(VScrollTable.this.getStylePrimaryName()
  3300. + "-resizer");
  3301. }
  3302. focusedSlot = -1;
  3303. }
  3304. @Override
  3305. public void onBrowserEvent(Event event) {
  3306. if (enabled) {
  3307. if (event.getEventTarget().cast() == columnSelector) {
  3308. final int left = DOM.getAbsoluteLeft(columnSelector);
  3309. final int top = DOM.getAbsoluteTop(columnSelector)
  3310. + DOM.getElementPropertyInt(columnSelector,
  3311. "offsetHeight");
  3312. client.getContextMenu().showAt(this, left, top);
  3313. }
  3314. }
  3315. }
  3316. @Override
  3317. protected void onDetach() {
  3318. super.onDetach();
  3319. if (client != null) {
  3320. client.getContextMenu().ensureHidden(this);
  3321. }
  3322. }
  3323. class VisibleColumnAction extends Action {
  3324. String colKey;
  3325. private boolean collapsed;
  3326. private boolean noncollapsible = false;
  3327. private VScrollTableRow currentlyFocusedRow;
  3328. public VisibleColumnAction(String colKey) {
  3329. super(VScrollTable.TableHead.this);
  3330. this.colKey = colKey;
  3331. caption = tHead.getHeaderCell(colKey).getCaption();
  3332. currentlyFocusedRow = focusedRow;
  3333. }
  3334. @Override
  3335. public void execute() {
  3336. if (noncollapsible) {
  3337. return;
  3338. }
  3339. client.getContextMenu().hide();
  3340. // toggle selected column
  3341. if (collapsedColumns.contains(colKey)) {
  3342. collapsedColumns.remove(colKey);
  3343. } else {
  3344. tHead.removeCell(colKey);
  3345. collapsedColumns.add(colKey);
  3346. triggerLazyColumnAdjustment(true);
  3347. }
  3348. // update variable to server
  3349. client.updateVariable(paintableId, "collapsedcolumns",
  3350. collapsedColumns.toArray(new String[collapsedColumns
  3351. .size()]), false);
  3352. // let rowRequestHandler determine proper rows
  3353. rowRequestHandler.refreshContent();
  3354. lazyRevertFocusToRow(currentlyFocusedRow);
  3355. }
  3356. public void setCollapsed(boolean b) {
  3357. collapsed = b;
  3358. }
  3359. public void setNoncollapsible(boolean b) {
  3360. noncollapsible = b;
  3361. }
  3362. /**
  3363. * Override default method to distinguish on/off columns
  3364. */
  3365. @Override
  3366. public String getHTML() {
  3367. final StringBuffer buf = new StringBuffer();
  3368. buf.append("<span class=\"");
  3369. if (collapsed) {
  3370. buf.append("v-off");
  3371. } else {
  3372. buf.append("v-on");
  3373. }
  3374. if (noncollapsible) {
  3375. buf.append(" v-disabled");
  3376. }
  3377. buf.append("\">");
  3378. buf.append(super.getHTML());
  3379. buf.append("</span>");
  3380. return buf.toString();
  3381. }
  3382. }
  3383. /*
  3384. * Returns columns as Action array for column select popup
  3385. */
  3386. @Override
  3387. public Action[] getActions() {
  3388. Object[] cols;
  3389. if (columnReordering && columnOrder != null) {
  3390. cols = columnOrder;
  3391. } else {
  3392. // if columnReordering is disabled, we need different way to get
  3393. // all available columns
  3394. cols = visibleColOrder;
  3395. cols = new Object[visibleColOrder.length
  3396. + collapsedColumns.size()];
  3397. int i;
  3398. for (i = 0; i < visibleColOrder.length; i++) {
  3399. cols[i] = visibleColOrder[i];
  3400. }
  3401. for (final Iterator<String> it = collapsedColumns.iterator(); it
  3402. .hasNext();) {
  3403. cols[i++] = it.next();
  3404. }
  3405. }
  3406. final Action[] actions = new Action[cols.length];
  3407. for (int i = 0; i < cols.length; i++) {
  3408. final String cid = (String) cols[i];
  3409. final HeaderCell c = getHeaderCell(cid);
  3410. final VisibleColumnAction a = new VisibleColumnAction(
  3411. c.getColKey());
  3412. a.setCaption(c.getCaption());
  3413. if (!c.isEnabled()) {
  3414. a.setCollapsed(true);
  3415. }
  3416. if (noncollapsibleColumns.contains(cid)) {
  3417. a.setNoncollapsible(true);
  3418. }
  3419. actions[i] = a;
  3420. }
  3421. return actions;
  3422. }
  3423. @Override
  3424. public ApplicationConnection getClient() {
  3425. return client;
  3426. }
  3427. @Override
  3428. public String getPaintableId() {
  3429. return paintableId;
  3430. }
  3431. /**
  3432. * Returns column alignments for visible columns
  3433. */
  3434. public char[] getColumnAlignments() {
  3435. final Iterator<Widget> it = visibleCells.iterator();
  3436. final char[] aligns = new char[visibleCells.size()];
  3437. int colIndex = 0;
  3438. while (it.hasNext()) {
  3439. aligns[colIndex++] = ((HeaderCell) it.next()).getAlign();
  3440. }
  3441. return aligns;
  3442. }
  3443. /**
  3444. * Disables the automatic calculation of all column widths by forcing
  3445. * the widths to be "defined" thus turning off expand ratios and such.
  3446. */
  3447. public void disableAutoColumnWidthCalculation(HeaderCell source) {
  3448. for (HeaderCell cell : availableCells.values()) {
  3449. cell.disableAutoWidthCalculation();
  3450. }
  3451. // fire column resize events for all columns but the source of the
  3452. // resize action, since an event will fire separately for this.
  3453. ArrayList<HeaderCell> columns = new ArrayList<HeaderCell>(
  3454. availableCells.values());
  3455. columns.remove(source);
  3456. sendColumnWidthUpdates(columns);
  3457. forceRealignColumnHeaders();
  3458. }
  3459. }
  3460. /**
  3461. * A cell in the footer
  3462. */
  3463. public class FooterCell extends Widget {
  3464. private final Element td = DOM.createTD();
  3465. private final Element captionContainer = DOM.createDiv();
  3466. private char align = ALIGN_LEFT;
  3467. private int width = -1;
  3468. private float expandRatio = 0;
  3469. private final String cid;
  3470. boolean definedWidth = false;
  3471. private int naturalWidth = -1;
  3472. public FooterCell(String colId, String headerText) {
  3473. cid = colId;
  3474. setText(headerText);
  3475. // ensure no clipping initially (problem on column additions)
  3476. captionContainer.getStyle().setOverflow(Overflow.VISIBLE);
  3477. DOM.sinkEvents(captionContainer, Event.MOUSEEVENTS);
  3478. DOM.appendChild(td, captionContainer);
  3479. DOM.sinkEvents(td, Event.MOUSEEVENTS | Event.ONDBLCLICK
  3480. | Event.ONCONTEXTMENU);
  3481. setElement(td);
  3482. updateStyleNames(VScrollTable.this.getStylePrimaryName());
  3483. }
  3484. protected void updateStyleNames(String primaryStyleName) {
  3485. captionContainer.setClassName(primaryStyleName
  3486. + "-footer-container");
  3487. }
  3488. /**
  3489. * Sets the text of the footer
  3490. *
  3491. * @param footerText
  3492. * The text in the footer
  3493. */
  3494. public void setText(String footerText) {
  3495. if (footerText == null || footerText.equals("")) {
  3496. footerText = "&nbsp;";
  3497. }
  3498. DOM.setInnerHTML(captionContainer, footerText);
  3499. }
  3500. /**
  3501. * Set alignment of the text in the cell
  3502. *
  3503. * @param c
  3504. * The alignment which can be ALIGN_CENTER, ALIGN_LEFT,
  3505. * ALIGN_RIGHT
  3506. */
  3507. public void setAlign(char c) {
  3508. if (align != c) {
  3509. switch (c) {
  3510. case ALIGN_CENTER:
  3511. captionContainer.getStyle().setTextAlign(TextAlign.CENTER);
  3512. break;
  3513. case ALIGN_RIGHT:
  3514. captionContainer.getStyle().setTextAlign(TextAlign.RIGHT);
  3515. break;
  3516. default:
  3517. captionContainer.getStyle().setTextAlign(TextAlign.LEFT);
  3518. break;
  3519. }
  3520. }
  3521. align = c;
  3522. }
  3523. /**
  3524. * Get the alignment of the text int the cell
  3525. *
  3526. * @return Returns either ALIGN_CENTER, ALIGN_LEFT or ALIGN_RIGHT
  3527. */
  3528. public char getAlign() {
  3529. return align;
  3530. }
  3531. /**
  3532. * Sets the width of the cell. This width should not include any
  3533. * possible indent modifications that are present in
  3534. * {@link VScrollTableBody#getMaxIndent()}.
  3535. *
  3536. * @param w
  3537. * The width of the cell
  3538. * @param ensureDefinedWidth
  3539. * Ensures that the given width is not recalculated
  3540. */
  3541. public void setWidth(int w, boolean ensureDefinedWidth) {
  3542. if (ensureDefinedWidth) {
  3543. definedWidth = true;
  3544. // on column resize expand ratio becomes zero
  3545. expandRatio = 0;
  3546. }
  3547. if (width == w) {
  3548. return;
  3549. }
  3550. if (width == -1) {
  3551. // go to default mode, clip content if necessary
  3552. captionContainer.getStyle().clearOverflow();
  3553. }
  3554. width = w;
  3555. if (w == -1) {
  3556. captionContainer.getStyle().clearWidth();
  3557. setWidth("");
  3558. } else {
  3559. /*
  3560. * Reduce width with one pixel for the right border since the
  3561. * footers does not have any spacers between them.
  3562. */
  3563. final int borderWidths = 1;
  3564. // Set the container width (check for negative value)
  3565. captionContainer.getStyle().setPropertyPx("width",
  3566. Math.max(w - borderWidths, 0));
  3567. /*
  3568. * if we already have tBody, set the header width properly, if
  3569. * not defer it. IE will fail with complex float in table header
  3570. * unless TD width is not explicitly set.
  3571. */
  3572. if (scrollBody != null) {
  3573. int maxIndent = scrollBody.getMaxIndent();
  3574. if (w < maxIndent
  3575. && tFoot.visibleCells.indexOf(this) == getHierarchyColumnIndex()) {
  3576. // ensure there's room for the indent
  3577. w = maxIndent;
  3578. }
  3579. int tdWidth = w + scrollBody.getCellExtraWidth()
  3580. - borderWidths;
  3581. setWidth(Math.max(tdWidth, 0) + "px");
  3582. } else {
  3583. Scheduler.get().scheduleDeferred(new Command() {
  3584. @Override
  3585. public void execute() {
  3586. int tdWidth = width;
  3587. int maxIndent = scrollBody.getMaxIndent();
  3588. if (tdWidth < maxIndent
  3589. && tFoot.visibleCells.indexOf(this) == getHierarchyColumnIndex()) {
  3590. // ensure there's room for the indent
  3591. tdWidth = maxIndent;
  3592. }
  3593. tdWidth += scrollBody.getCellExtraWidth()
  3594. - borderWidths;
  3595. setWidth(Math.max(tdWidth, 0) + "px");
  3596. }
  3597. });
  3598. }
  3599. }
  3600. }
  3601. /**
  3602. * Sets the width to undefined
  3603. */
  3604. public void setUndefinedWidth() {
  3605. definedWidth = false;
  3606. setWidth(-1, false);
  3607. }
  3608. /**
  3609. * Detects if width is fixed by developer on server side or resized to
  3610. * current width by user.
  3611. *
  3612. * @return true if defined, false if "natural" width
  3613. */
  3614. public boolean isDefinedWidth() {
  3615. return definedWidth && width >= 0;
  3616. }
  3617. /**
  3618. * Returns the pixels width of the footer cell.
  3619. *
  3620. * @return The width in pixels
  3621. */
  3622. public int getWidth() {
  3623. return width;
  3624. }
  3625. /**
  3626. * Sets the expand ratio of the cell
  3627. *
  3628. * @param floatAttribute
  3629. * The expand ratio
  3630. */
  3631. public void setExpandRatio(float floatAttribute) {
  3632. expandRatio = floatAttribute;
  3633. }
  3634. /**
  3635. * Returns the expand ration of the cell
  3636. *
  3637. * @return The expand ratio
  3638. */
  3639. public float getExpandRatio() {
  3640. return expandRatio;
  3641. }
  3642. /**
  3643. * Is the cell enabled?
  3644. *
  3645. * @return True if enabled else False
  3646. */
  3647. public boolean isEnabled() {
  3648. return getParent() != null;
  3649. }
  3650. /**
  3651. * Handle column clicking
  3652. */
  3653. @Override
  3654. public void onBrowserEvent(Event event) {
  3655. if (enabled && event != null) {
  3656. handleCaptionEvent(event);
  3657. if (DOM.eventGetType(event) == Event.ONMOUSEUP) {
  3658. scrollBodyPanel.setFocus(true);
  3659. }
  3660. boolean stopPropagation = true;
  3661. if (event.getTypeInt() == Event.ONCONTEXTMENU
  3662. && !client.hasEventListeners(VScrollTable.this,
  3663. TableConstants.FOOTER_CLICK_EVENT_ID)) {
  3664. // Show browser context menu if a footer click listener is
  3665. // not present
  3666. stopPropagation = false;
  3667. }
  3668. if (stopPropagation) {
  3669. event.stopPropagation();
  3670. event.preventDefault();
  3671. }
  3672. }
  3673. }
  3674. /**
  3675. * Handles a event on the captions
  3676. *
  3677. * @param event
  3678. * The event to handle
  3679. */
  3680. protected void handleCaptionEvent(Event event) {
  3681. if (event.getTypeInt() == Event.ONMOUSEUP
  3682. || event.getTypeInt() == Event.ONDBLCLICK) {
  3683. fireFooterClickedEvent(event);
  3684. }
  3685. }
  3686. /**
  3687. * Fires a footer click event after the user has clicked a column footer
  3688. * cell
  3689. *
  3690. * @param event
  3691. * The click event
  3692. */
  3693. private void fireFooterClickedEvent(Event event) {
  3694. if (client.hasEventListeners(VScrollTable.this,
  3695. TableConstants.FOOTER_CLICK_EVENT_ID)) {
  3696. MouseEventDetails details = MouseEventDetailsBuilder
  3697. .buildMouseEventDetails(event);
  3698. client.updateVariable(paintableId, "footerClickEvent",
  3699. details.toString(), false);
  3700. client.updateVariable(paintableId, "footerClickCID", cid, true);
  3701. }
  3702. }
  3703. /**
  3704. * Returns the column key of the column
  3705. *
  3706. * @return The column key
  3707. */
  3708. public String getColKey() {
  3709. return cid;
  3710. }
  3711. /**
  3712. * Detects the natural minimum width for the column of this header cell.
  3713. * If column is resized by user or the width is defined by server the
  3714. * actual width is returned. Else the natural min width is returned.
  3715. *
  3716. * @param columnIndex
  3717. * column index hint, if -1 (unknown) it will be detected
  3718. *
  3719. * @return
  3720. */
  3721. public int getNaturalColumnWidth(int columnIndex) {
  3722. final int iw = columnIndex == getHierarchyColumnIndex() ? scrollBody
  3723. .getMaxIndent() : 0;
  3724. if (isDefinedWidth()) {
  3725. if (iw > width) {
  3726. return iw;
  3727. }
  3728. return width;
  3729. } else {
  3730. if (naturalWidth < 0) {
  3731. // This is recently revealed column. Try to detect a proper
  3732. // value (greater of header and data
  3733. // cols)
  3734. final int hw = ((Element) getElement().getLastChild())
  3735. .getOffsetWidth() + getHeaderPadding();
  3736. if (columnIndex < 0) {
  3737. columnIndex = 0;
  3738. for (Iterator<Widget> it = tHead.iterator(); it
  3739. .hasNext(); columnIndex++) {
  3740. if (it.next() == this) {
  3741. break;
  3742. }
  3743. }
  3744. }
  3745. final int cw = scrollBody.getColWidth(columnIndex);
  3746. naturalWidth = (hw > cw ? hw : cw);
  3747. }
  3748. if (iw > naturalWidth) {
  3749. return iw;
  3750. } else {
  3751. return naturalWidth;
  3752. }
  3753. }
  3754. }
  3755. public void setNaturalMinimumColumnWidth(int w) {
  3756. naturalWidth = w;
  3757. }
  3758. }
  3759. /**
  3760. * HeaderCell that is header cell for row headers.
  3761. *
  3762. * Reordering disabled and clicking on it resets sorting.
  3763. */
  3764. public class RowHeadersFooterCell extends FooterCell {
  3765. RowHeadersFooterCell() {
  3766. super(ROW_HEADER_COLUMN_KEY, "");
  3767. }
  3768. @Override
  3769. protected void handleCaptionEvent(Event event) {
  3770. // NOP: RowHeaders cannot be reordered
  3771. // TODO It'd be nice to reset sorting here
  3772. }
  3773. }
  3774. /**
  3775. * The footer of the table which can be seen in the bottom of the Table.
  3776. */
  3777. public class TableFooter extends Panel {
  3778. private static final int WRAPPER_WIDTH = 900000;
  3779. ArrayList<Widget> visibleCells = new ArrayList<Widget>();
  3780. HashMap<String, FooterCell> availableCells = new HashMap<String, FooterCell>();
  3781. Element div = DOM.createDiv();
  3782. Element hTableWrapper = DOM.createDiv();
  3783. Element hTableContainer = DOM.createDiv();
  3784. Element table = DOM.createTable();
  3785. Element headerTableBody = DOM.createTBody();
  3786. Element tr = DOM.createTR();
  3787. public TableFooter() {
  3788. hTableWrapper.getStyle().setOverflow(Overflow.HIDDEN);
  3789. DOM.appendChild(table, headerTableBody);
  3790. DOM.appendChild(headerTableBody, tr);
  3791. DOM.appendChild(hTableContainer, table);
  3792. DOM.appendChild(hTableWrapper, hTableContainer);
  3793. DOM.appendChild(div, hTableWrapper);
  3794. setElement(div);
  3795. availableCells.put(ROW_HEADER_COLUMN_KEY,
  3796. new RowHeadersFooterCell());
  3797. updateStyleNames(VScrollTable.this.getStylePrimaryName());
  3798. }
  3799. protected void updateStyleNames(String primaryStyleName) {
  3800. hTableWrapper.setClassName(primaryStyleName + "-footer");
  3801. setStyleName(primaryStyleName + "-footer-wrap");
  3802. for (FooterCell c : availableCells.values()) {
  3803. c.updateStyleNames(primaryStyleName);
  3804. }
  3805. }
  3806. @Override
  3807. public void clear() {
  3808. for (String cid : availableCells.keySet()) {
  3809. removeCell(cid);
  3810. }
  3811. availableCells.clear();
  3812. availableCells.put(ROW_HEADER_COLUMN_KEY,
  3813. new RowHeadersFooterCell());
  3814. }
  3815. /*
  3816. * (non-Javadoc)
  3817. *
  3818. * @see
  3819. * com.google.gwt.user.client.ui.Panel#remove(com.google.gwt.user.client
  3820. * .ui.Widget)
  3821. */
  3822. @Override
  3823. public boolean remove(Widget w) {
  3824. if (visibleCells.contains(w)) {
  3825. visibleCells.remove(w);
  3826. orphan(w);
  3827. DOM.removeChild(DOM.getParent(w.getElement()), w.getElement());
  3828. return true;
  3829. }
  3830. return false;
  3831. }
  3832. /*
  3833. * (non-Javadoc)
  3834. *
  3835. * @see com.google.gwt.user.client.ui.HasWidgets#iterator()
  3836. */
  3837. @Override
  3838. public Iterator<Widget> iterator() {
  3839. return visibleCells.iterator();
  3840. }
  3841. /**
  3842. * Gets a footer cell which represents the given columnId
  3843. *
  3844. * @param cid
  3845. * The columnId
  3846. *
  3847. * @return The cell
  3848. */
  3849. public FooterCell getFooterCell(String cid) {
  3850. return availableCells.get(cid);
  3851. }
  3852. /**
  3853. * Gets a footer cell by using a column index
  3854. *
  3855. * @param index
  3856. * The index of the column
  3857. * @return The Cell
  3858. */
  3859. public FooterCell getFooterCell(int index) {
  3860. if (index < visibleCells.size()) {
  3861. return (FooterCell) visibleCells.get(index);
  3862. } else {
  3863. return null;
  3864. }
  3865. }
  3866. /**
  3867. * Updates the cells contents when updateUIDL request is received
  3868. *
  3869. * @param uidl
  3870. * The UIDL
  3871. */
  3872. public void updateCellsFromUIDL(UIDL uidl) {
  3873. Iterator<?> columnIterator = uidl.getChildIterator();
  3874. HashSet<String> updated = new HashSet<String>();
  3875. while (columnIterator.hasNext()) {
  3876. final UIDL col = (UIDL) columnIterator.next();
  3877. final String cid = col.getStringAttribute("cid");
  3878. updated.add(cid);
  3879. String caption = col.hasAttribute("fcaption") ? col
  3880. .getStringAttribute("fcaption") : "";
  3881. FooterCell c = getFooterCell(cid);
  3882. if (c == null) {
  3883. c = new FooterCell(cid, caption);
  3884. availableCells.put(cid, c);
  3885. if (initializedAndAttached) {
  3886. // we will need a column width recalculation
  3887. initializedAndAttached = false;
  3888. initialContentReceived = false;
  3889. isNewBody = true;
  3890. }
  3891. } else {
  3892. c.setText(caption);
  3893. }
  3894. if (col.hasAttribute("align")) {
  3895. c.setAlign(col.getStringAttribute("align").charAt(0));
  3896. } else {
  3897. c.setAlign(ALIGN_LEFT);
  3898. }
  3899. if (col.hasAttribute("width")) {
  3900. if (scrollBody == null || isNewBody) {
  3901. // Already updated by setColWidth called from
  3902. // TableHeads.updateCellsFromUIDL in case of a server
  3903. // side resize
  3904. final int width = col.getIntAttribute("width");
  3905. c.setWidth(width, true);
  3906. }
  3907. } else if (recalcWidths) {
  3908. c.setUndefinedWidth();
  3909. }
  3910. if (col.hasAttribute("er")) {
  3911. c.setExpandRatio(col.getFloatAttribute("er"));
  3912. }
  3913. if (col.hasAttribute("collapsed")) {
  3914. // ensure header is properly removed from parent (case when
  3915. // collapsing happens via servers side api)
  3916. if (c.isAttached()) {
  3917. c.removeFromParent();
  3918. headerChangedDuringUpdate = true;
  3919. }
  3920. }
  3921. }
  3922. // check for orphaned header cells
  3923. for (Iterator<String> cit = availableCells.keySet().iterator(); cit
  3924. .hasNext();) {
  3925. String cid = cit.next();
  3926. if (!updated.contains(cid)) {
  3927. removeCell(cid);
  3928. cit.remove();
  3929. }
  3930. }
  3931. }
  3932. /**
  3933. * Set a footer cell for a specified column index
  3934. *
  3935. * @param index
  3936. * The index
  3937. * @param cell
  3938. * The footer cell
  3939. */
  3940. public void setFooterCell(int index, FooterCell cell) {
  3941. if (cell.isEnabled()) {
  3942. // we're moving the cell
  3943. DOM.removeChild(tr, cell.getElement());
  3944. orphan(cell);
  3945. visibleCells.remove(cell);
  3946. }
  3947. if (index < visibleCells.size()) {
  3948. // insert to right slot
  3949. DOM.insertChild(tr, cell.getElement(), index);
  3950. adopt(cell);
  3951. visibleCells.add(index, cell);
  3952. } else if (index == visibleCells.size()) {
  3953. // simply append
  3954. DOM.appendChild(tr, cell.getElement());
  3955. adopt(cell);
  3956. visibleCells.add(cell);
  3957. } else {
  3958. throw new RuntimeException(
  3959. "Header cells must be appended in order");
  3960. }
  3961. }
  3962. /**
  3963. * Remove a cell by using the columnId
  3964. *
  3965. * @param colKey
  3966. * The columnId to remove
  3967. */
  3968. public void removeCell(String colKey) {
  3969. final FooterCell c = getFooterCell(colKey);
  3970. remove(c);
  3971. }
  3972. /**
  3973. * Enable a column (Sets the footer cell)
  3974. *
  3975. * @param cid
  3976. * The columnId
  3977. * @param index
  3978. * The index of the column
  3979. */
  3980. public void enableColumn(String cid, int index) {
  3981. final FooterCell c = getFooterCell(cid);
  3982. if (!c.isEnabled() || getFooterCell(index) != c) {
  3983. setFooterCell(index, c);
  3984. if (initializedAndAttached) {
  3985. headerChangedDuringUpdate = true;
  3986. }
  3987. }
  3988. }
  3989. /**
  3990. * Disable browser measurement of the table width
  3991. */
  3992. public void disableBrowserIntelligence() {
  3993. hTableContainer.getStyle().setWidth(WRAPPER_WIDTH, Unit.PX);
  3994. }
  3995. /**
  3996. * Enable browser measurement of the table width
  3997. */
  3998. public void enableBrowserIntelligence() {
  3999. hTableContainer.getStyle().clearWidth();
  4000. }
  4001. /**
  4002. * Set the horizontal position in the cell in the footer. This is done
  4003. * when a horizontal scrollbar is present.
  4004. *
  4005. * @param scrollLeft
  4006. * The value of the leftScroll
  4007. */
  4008. public void setHorizontalScrollPosition(int scrollLeft) {
  4009. hTableWrapper.setScrollLeft(scrollLeft);
  4010. }
  4011. /**
  4012. * Swap cells when the column are dragged
  4013. *
  4014. * @param oldIndex
  4015. * The old index of the cell
  4016. * @param newIndex
  4017. * The new index of the cell
  4018. */
  4019. public void moveCell(int oldIndex, int newIndex) {
  4020. final FooterCell hCell = getFooterCell(oldIndex);
  4021. final Element cell = hCell.getElement();
  4022. visibleCells.remove(oldIndex);
  4023. DOM.removeChild(tr, cell);
  4024. DOM.insertChild(tr, cell, newIndex);
  4025. visibleCells.add(newIndex, hCell);
  4026. }
  4027. }
  4028. /**
  4029. * This Panel can only contain VScrollTableRow type of widgets. This
  4030. * "simulates" very large table, keeping spacers which take room of
  4031. * unrendered rows.
  4032. *
  4033. */
  4034. public class VScrollTableBody extends Panel {
  4035. public static final int DEFAULT_ROW_HEIGHT = 24;
  4036. private double rowHeight = -1;
  4037. private final LinkedList<Widget> renderedRows = new LinkedList<Widget>();
  4038. /**
  4039. * Due some optimizations row height measuring is deferred and initial
  4040. * set of rows is rendered detached. Flag set on when table body has
  4041. * been attached in dom and rowheight has been measured.
  4042. */
  4043. private boolean tBodyMeasurementsDone = false;
  4044. Element preSpacer = DOM.createDiv();
  4045. Element postSpacer = DOM.createDiv();
  4046. Element container = DOM.createDiv();
  4047. TableSectionElement tBodyElement = Document.get().createTBodyElement();
  4048. Element table = DOM.createTable();
  4049. private int firstRendered;
  4050. private int lastRendered;
  4051. private char[] aligns;
  4052. protected VScrollTableBody() {
  4053. constructDOM();
  4054. setElement(container);
  4055. }
  4056. public void setLastRendered(int lastRendered) {
  4057. if (totalRows >= 0 && lastRendered > totalRows) {
  4058. VConsole.log("setLastRendered: " + this.lastRendered + " -> "
  4059. + lastRendered);
  4060. this.lastRendered = totalRows - 1;
  4061. } else {
  4062. this.lastRendered = lastRendered;
  4063. }
  4064. }
  4065. public int getLastRendered() {
  4066. return lastRendered;
  4067. }
  4068. public int getFirstRendered() {
  4069. return firstRendered;
  4070. }
  4071. public VScrollTableRow getRowByRowIndex(int indexInTable) {
  4072. int internalIndex = indexInTable - firstRendered;
  4073. if (internalIndex >= 0 && internalIndex < renderedRows.size()) {
  4074. return (VScrollTableRow) renderedRows.get(internalIndex);
  4075. } else {
  4076. return null;
  4077. }
  4078. }
  4079. /**
  4080. * @return the height of scrollable body, subpixels ceiled.
  4081. */
  4082. public int getRequiredHeight() {
  4083. return preSpacer.getOffsetHeight() + postSpacer.getOffsetHeight()
  4084. + Util.getRequiredHeight(table);
  4085. }
  4086. private void constructDOM() {
  4087. if (BrowserInfo.get().isIE()) {
  4088. table.setPropertyInt("cellSpacing", 0);
  4089. }
  4090. table.appendChild(tBodyElement);
  4091. DOM.appendChild(container, preSpacer);
  4092. DOM.appendChild(container, table);
  4093. DOM.appendChild(container, postSpacer);
  4094. if (BrowserInfo.get().requiresTouchScrollDelegate()) {
  4095. NodeList<Node> childNodes = container.getChildNodes();
  4096. for (int i = 0; i < childNodes.getLength(); i++) {
  4097. Element item = (Element) childNodes.getItem(i);
  4098. item.getStyle().setProperty("webkitTransform",
  4099. "translate3d(0,0,0)");
  4100. }
  4101. }
  4102. updateStyleNames(VScrollTable.this.getStylePrimaryName());
  4103. }
  4104. protected void updateStyleNames(String primaryStyleName) {
  4105. table.setClassName(primaryStyleName + "-table");
  4106. preSpacer.setClassName(primaryStyleName + "-row-spacer");
  4107. postSpacer.setClassName(primaryStyleName + "-row-spacer");
  4108. for (Widget w : renderedRows) {
  4109. VScrollTableRow row = (VScrollTableRow) w;
  4110. row.updateStyleNames(primaryStyleName);
  4111. }
  4112. }
  4113. public int getAvailableWidth() {
  4114. int availW = scrollBodyPanel.getOffsetWidth() - getBorderWidth();
  4115. return availW;
  4116. }
  4117. public void renderInitialRows(UIDL rowData, int firstIndex, int rows) {
  4118. firstRendered = firstIndex;
  4119. setLastRendered(firstIndex + rows - 1);
  4120. final Iterator<?> it = rowData.getChildIterator();
  4121. aligns = tHead.getColumnAlignments();
  4122. while (it.hasNext()) {
  4123. final VScrollTableRow row = createRow((UIDL) it.next(), aligns);
  4124. addRow(row);
  4125. }
  4126. if (isAttached()) {
  4127. fixSpacers();
  4128. }
  4129. }
  4130. public void renderRows(UIDL rowData, int firstIndex, int rows) {
  4131. // FIXME REVIEW
  4132. aligns = tHead.getColumnAlignments();
  4133. final Iterator<?> it = rowData.getChildIterator();
  4134. if (firstIndex == lastRendered + 1) {
  4135. while (it.hasNext()) {
  4136. final VScrollTableRow row = prepareRow((UIDL) it.next());
  4137. addRow(row);
  4138. setLastRendered(lastRendered + 1);
  4139. }
  4140. fixSpacers();
  4141. } else if (firstIndex + rows == firstRendered) {
  4142. final VScrollTableRow[] rowArray = new VScrollTableRow[rows];
  4143. int i = rows;
  4144. while (it.hasNext()) {
  4145. i--;
  4146. rowArray[i] = prepareRow((UIDL) it.next());
  4147. }
  4148. for (i = 0; i < rows; i++) {
  4149. addRowBeforeFirstRendered(rowArray[i]);
  4150. firstRendered--;
  4151. }
  4152. } else {
  4153. // completely new set of rows
  4154. // there can't be sanity checks for last rendered within this
  4155. // while loop regardless of what has been set previously, so
  4156. // change it temporarily to true and then return the original
  4157. // value
  4158. boolean temp = postponeSanityCheckForLastRendered;
  4159. postponeSanityCheckForLastRendered = true;
  4160. while (lastRendered + 1 > firstRendered) {
  4161. unlinkRow(false);
  4162. }
  4163. postponeSanityCheckForLastRendered = temp;
  4164. VScrollTableRow row = prepareRow((UIDL) it.next());
  4165. firstRendered = firstIndex;
  4166. setLastRendered(firstIndex - 1);
  4167. addRow(row);
  4168. setLastRendered(lastRendered + 1);
  4169. setContainerHeight();
  4170. fixSpacers();
  4171. while (it.hasNext()) {
  4172. addRow(prepareRow((UIDL) it.next()));
  4173. setLastRendered(lastRendered + 1);
  4174. }
  4175. fixSpacers();
  4176. }
  4177. // this may be a new set of rows due content change,
  4178. // ensure we have proper cache rows
  4179. ensureCacheFilled();
  4180. }
  4181. /**
  4182. * Ensure we have the correct set of rows on client side, e.g. if the
  4183. * content on the server side has changed, or the client scroll position
  4184. * has changed since the last request.
  4185. */
  4186. protected void ensureCacheFilled() {
  4187. int reactFirstRow = (int) (firstRowInViewPort - pageLength
  4188. * cache_react_rate);
  4189. int reactLastRow = (int) (firstRowInViewPort + pageLength + pageLength
  4190. * cache_react_rate);
  4191. if (reactFirstRow < 0) {
  4192. reactFirstRow = 0;
  4193. }
  4194. if (reactLastRow >= totalRows) {
  4195. reactLastRow = totalRows - 1;
  4196. }
  4197. if (lastRendered < reactFirstRow || firstRendered > reactLastRow) {
  4198. /*
  4199. * #8040 - scroll position is completely changed since the
  4200. * latest request, so request a new set of rows.
  4201. *
  4202. * TODO: We should probably check whether the fetched rows match
  4203. * the current scroll position right when they arrive, so as to
  4204. * not waste time rendering a set of rows that will never be
  4205. * visible...
  4206. */
  4207. rowRequestHandler.triggerRowFetch(reactFirstRow, reactLastRow
  4208. - reactFirstRow + 1, 1);
  4209. } else if (lastRendered < reactLastRow) {
  4210. // get some cache rows below visible area
  4211. rowRequestHandler.triggerRowFetch(lastRendered + 1,
  4212. reactLastRow - lastRendered, 1);
  4213. } else if (firstRendered > reactFirstRow) {
  4214. /*
  4215. * Branch for fetching cache above visible area.
  4216. *
  4217. * If cache needed for both before and after visible area, this
  4218. * will be rendered after-cache is received and rendered. So in
  4219. * some rare situations the table may make two cache visits to
  4220. * server.
  4221. */
  4222. rowRequestHandler.triggerRowFetch(reactFirstRow, firstRendered
  4223. - reactFirstRow, 1);
  4224. }
  4225. }
  4226. /**
  4227. * Inserts rows as provided in the rowData starting at firstIndex.
  4228. *
  4229. * @param rowData
  4230. * @param firstIndex
  4231. * @param rows
  4232. * the number of rows
  4233. * @return a list of the rows added.
  4234. */
  4235. protected List<VScrollTableRow> insertRows(UIDL rowData,
  4236. int firstIndex, int rows) {
  4237. aligns = tHead.getColumnAlignments();
  4238. final Iterator<?> it = rowData.getChildIterator();
  4239. List<VScrollTableRow> insertedRows = new ArrayList<VScrollTableRow>();
  4240. if (firstIndex == lastRendered + 1) {
  4241. while (it.hasNext()) {
  4242. final VScrollTableRow row = prepareRow((UIDL) it.next());
  4243. addRow(row);
  4244. insertedRows.add(row);
  4245. if (postponeSanityCheckForLastRendered) {
  4246. lastRendered++;
  4247. } else {
  4248. setLastRendered(lastRendered + 1);
  4249. }
  4250. }
  4251. fixSpacers();
  4252. } else if (firstIndex + rows == firstRendered) {
  4253. final VScrollTableRow[] rowArray = new VScrollTableRow[rows];
  4254. int i = rows;
  4255. while (it.hasNext()) {
  4256. i--;
  4257. rowArray[i] = prepareRow((UIDL) it.next());
  4258. }
  4259. for (i = 0; i < rows; i++) {
  4260. addRowBeforeFirstRendered(rowArray[i]);
  4261. insertedRows.add(rowArray[i]);
  4262. firstRendered--;
  4263. }
  4264. } else {
  4265. // insert in the middle
  4266. int ix = firstIndex;
  4267. while (it.hasNext()) {
  4268. VScrollTableRow row = prepareRow((UIDL) it.next());
  4269. insertRowAt(row, ix);
  4270. insertedRows.add(row);
  4271. if (postponeSanityCheckForLastRendered) {
  4272. lastRendered++;
  4273. } else {
  4274. setLastRendered(lastRendered + 1);
  4275. }
  4276. ix++;
  4277. }
  4278. fixSpacers();
  4279. }
  4280. return insertedRows;
  4281. }
  4282. protected List<VScrollTableRow> insertAndReindexRows(UIDL rowData,
  4283. int firstIndex, int rows) {
  4284. List<VScrollTableRow> inserted = insertRows(rowData, firstIndex,
  4285. rows);
  4286. int actualIxOfFirstRowAfterInserted = firstIndex + rows
  4287. - firstRendered;
  4288. for (int ix = actualIxOfFirstRowAfterInserted; ix < renderedRows
  4289. .size(); ix++) {
  4290. VScrollTableRow r = (VScrollTableRow) renderedRows.get(ix);
  4291. r.setIndex(r.getIndex() + rows);
  4292. }
  4293. setContainerHeight();
  4294. return inserted;
  4295. }
  4296. protected void insertRowsDeleteBelow(UIDL rowData, int firstIndex,
  4297. int rows) {
  4298. unlinkAllRowsStartingAt(firstIndex);
  4299. insertRows(rowData, firstIndex, rows);
  4300. setContainerHeight();
  4301. }
  4302. /**
  4303. * This method is used to instantiate new rows for this table. It
  4304. * automatically sets correct widths to rows cells and assigns correct
  4305. * client reference for child widgets.
  4306. *
  4307. * This method can be called only after table has been initialized
  4308. *
  4309. * @param uidl
  4310. */
  4311. private VScrollTableRow prepareRow(UIDL uidl) {
  4312. final VScrollTableRow row = createRow(uidl, aligns);
  4313. row.initCellWidths();
  4314. return row;
  4315. }
  4316. protected VScrollTableRow createRow(UIDL uidl, char[] aligns2) {
  4317. if (uidl.hasAttribute("gen_html")) {
  4318. // This is a generated row.
  4319. return new VScrollTableGeneratedRow(uidl, aligns2);
  4320. }
  4321. return new VScrollTableRow(uidl, aligns2);
  4322. }
  4323. private void addRowBeforeFirstRendered(VScrollTableRow row) {
  4324. row.setIndex(firstRendered - 1);
  4325. if (row.isSelected()) {
  4326. row.addStyleName("v-selected");
  4327. }
  4328. tBodyElement.insertBefore(row.getElement(),
  4329. tBodyElement.getFirstChild());
  4330. adopt(row);
  4331. renderedRows.add(0, row);
  4332. }
  4333. private void addRow(VScrollTableRow row) {
  4334. row.setIndex(firstRendered + renderedRows.size());
  4335. if (row.isSelected()) {
  4336. row.addStyleName("v-selected");
  4337. }
  4338. tBodyElement.appendChild(row.getElement());
  4339. // Add to renderedRows before adopt so iterator() will return also
  4340. // this row if called in an attach handler (#9264)
  4341. renderedRows.add(row);
  4342. adopt(row);
  4343. }
  4344. private void insertRowAt(VScrollTableRow row, int index) {
  4345. row.setIndex(index);
  4346. if (row.isSelected()) {
  4347. row.addStyleName("v-selected");
  4348. }
  4349. if (index > 0) {
  4350. VScrollTableRow sibling = getRowByRowIndex(index - 1);
  4351. tBodyElement
  4352. .insertAfter(row.getElement(), sibling.getElement());
  4353. } else {
  4354. VScrollTableRow sibling = getRowByRowIndex(index);
  4355. tBodyElement.insertBefore(row.getElement(),
  4356. sibling.getElement());
  4357. }
  4358. adopt(row);
  4359. int actualIx = index - firstRendered;
  4360. renderedRows.add(actualIx, row);
  4361. }
  4362. @Override
  4363. public Iterator<Widget> iterator() {
  4364. return renderedRows.iterator();
  4365. }
  4366. /**
  4367. * @return false if couldn't remove row
  4368. */
  4369. protected boolean unlinkRow(boolean fromBeginning) {
  4370. if (lastRendered - firstRendered < 0) {
  4371. return false;
  4372. }
  4373. int actualIx;
  4374. if (fromBeginning) {
  4375. actualIx = 0;
  4376. firstRendered++;
  4377. } else {
  4378. actualIx = renderedRows.size() - 1;
  4379. if (postponeSanityCheckForLastRendered) {
  4380. --lastRendered;
  4381. } else {
  4382. setLastRendered(lastRendered - 1);
  4383. }
  4384. }
  4385. if (actualIx >= 0) {
  4386. unlinkRowAtActualIndex(actualIx);
  4387. fixSpacers();
  4388. return true;
  4389. }
  4390. return false;
  4391. }
  4392. protected void unlinkRows(int firstIndex, int count) {
  4393. if (count < 1) {
  4394. return;
  4395. }
  4396. if (firstRendered > firstIndex
  4397. && firstRendered < firstIndex + count) {
  4398. count = count - (firstRendered - firstIndex);
  4399. firstIndex = firstRendered;
  4400. }
  4401. int lastIndex = firstIndex + count - 1;
  4402. if (lastRendered < lastIndex) {
  4403. lastIndex = lastRendered;
  4404. }
  4405. for (int ix = lastIndex; ix >= firstIndex; ix--) {
  4406. unlinkRowAtActualIndex(actualIndex(ix));
  4407. if (postponeSanityCheckForLastRendered) {
  4408. // partialUpdate handles sanity check later
  4409. lastRendered--;
  4410. } else {
  4411. setLastRendered(lastRendered - 1);
  4412. }
  4413. }
  4414. fixSpacers();
  4415. }
  4416. protected void unlinkAndReindexRows(int firstIndex, int count) {
  4417. unlinkRows(firstIndex, count);
  4418. int actualFirstIx = firstIndex - firstRendered;
  4419. for (int ix = actualFirstIx; ix < renderedRows.size(); ix++) {
  4420. VScrollTableRow r = (VScrollTableRow) renderedRows.get(ix);
  4421. r.setIndex(r.getIndex() - count);
  4422. }
  4423. setContainerHeight();
  4424. }
  4425. protected void unlinkAllRowsStartingAt(int index) {
  4426. if (firstRendered > index) {
  4427. index = firstRendered;
  4428. }
  4429. for (int ix = renderedRows.size() - 1; ix >= index; ix--) {
  4430. unlinkRowAtActualIndex(actualIndex(ix));
  4431. setLastRendered(lastRendered - 1);
  4432. }
  4433. fixSpacers();
  4434. }
  4435. private int actualIndex(int index) {
  4436. return index - firstRendered;
  4437. }
  4438. private void unlinkRowAtActualIndex(int index) {
  4439. final VScrollTableRow toBeRemoved = (VScrollTableRow) renderedRows
  4440. .get(index);
  4441. tBodyElement.removeChild(toBeRemoved.getElement());
  4442. orphan(toBeRemoved);
  4443. renderedRows.remove(index);
  4444. }
  4445. @Override
  4446. public boolean remove(Widget w) {
  4447. throw new UnsupportedOperationException();
  4448. }
  4449. /**
  4450. * Fix container blocks height according to totalRows to avoid
  4451. * "bouncing" when scrolling
  4452. */
  4453. private void setContainerHeight() {
  4454. fixSpacers();
  4455. container.getStyle().setHeight(measureRowHeightOffset(totalRows),
  4456. Unit.PX);
  4457. }
  4458. private void fixSpacers() {
  4459. int prepx = measureRowHeightOffset(firstRendered);
  4460. if (prepx < 0) {
  4461. prepx = 0;
  4462. }
  4463. preSpacer.getStyle().setPropertyPx("height", prepx);
  4464. int postpx;
  4465. if (pageLength == 0 && totalRows == pageLength) {
  4466. /*
  4467. * TreeTable depends on having lastRendered out of sync in some
  4468. * situations, which makes this method miss the special
  4469. * situation in which one row worth of post spacer to be added
  4470. * if there are no rows in the table. #9203
  4471. */
  4472. postpx = measureRowHeightOffset(1);
  4473. } else {
  4474. postpx = measureRowHeightOffset(totalRows - 1)
  4475. - measureRowHeightOffset(lastRendered);
  4476. }
  4477. if (postpx < 0) {
  4478. postpx = 0;
  4479. }
  4480. postSpacer.getStyle().setPropertyPx("height", postpx);
  4481. }
  4482. public double getRowHeight() {
  4483. return getRowHeight(false);
  4484. }
  4485. public double getRowHeight(boolean forceUpdate) {
  4486. if (tBodyMeasurementsDone && !forceUpdate) {
  4487. return rowHeight;
  4488. } else {
  4489. if (tBodyElement.getRows().getLength() > 0) {
  4490. int tableHeight = getTableHeight();
  4491. int rowCount = tBodyElement.getRows().getLength();
  4492. rowHeight = tableHeight / (double) rowCount;
  4493. } else {
  4494. // Special cases if we can't just measure the current rows
  4495. if (!Double.isNaN(lastKnownRowHeight)) {
  4496. // Use previous value if available
  4497. if (BrowserInfo.get().isIE()) {
  4498. /*
  4499. * IE needs to reflow the table element at this
  4500. * point to work correctly (e.g.
  4501. * com.vaadin.tests.components.table.
  4502. * ContainerSizeChange) - the other code paths
  4503. * already trigger reflows, but here it must be done
  4504. * explicitly.
  4505. */
  4506. getTableHeight();
  4507. }
  4508. rowHeight = lastKnownRowHeight;
  4509. } else if (isAttached()) {
  4510. // measure row height by adding a dummy row
  4511. VScrollTableRow scrollTableRow = new VScrollTableRow();
  4512. tBodyElement.appendChild(scrollTableRow.getElement());
  4513. getRowHeight(forceUpdate);
  4514. tBodyElement.removeChild(scrollTableRow.getElement());
  4515. } else {
  4516. // TODO investigate if this can never happen anymore
  4517. return DEFAULT_ROW_HEIGHT;
  4518. }
  4519. }
  4520. lastKnownRowHeight = rowHeight;
  4521. tBodyMeasurementsDone = true;
  4522. return rowHeight;
  4523. }
  4524. }
  4525. public int getTableHeight() {
  4526. return table.getOffsetHeight();
  4527. }
  4528. /**
  4529. * Returns the width available for column content.
  4530. *
  4531. * @param columnIndex
  4532. * @return
  4533. */
  4534. public int getColWidth(int columnIndex) {
  4535. if (tBodyMeasurementsDone) {
  4536. if (renderedRows.isEmpty()) {
  4537. // no rows yet rendered
  4538. return 0;
  4539. }
  4540. for (Widget row : renderedRows) {
  4541. if (!(row instanceof VScrollTableGeneratedRow)) {
  4542. TableRowElement tr = row.getElement().cast();
  4543. Element wrapperdiv = tr.getCells().getItem(columnIndex)
  4544. .getFirstChildElement().cast();
  4545. return wrapperdiv.getOffsetWidth();
  4546. }
  4547. }
  4548. return 0;
  4549. } else {
  4550. return 0;
  4551. }
  4552. }
  4553. /**
  4554. * Sets the content width of a column.
  4555. *
  4556. * Due IE limitation, we must set the width to a wrapper elements inside
  4557. * table cells (with overflow hidden, which does not work on td
  4558. * elements).
  4559. *
  4560. * To get this work properly crossplatform, we will also set the width
  4561. * of td.
  4562. *
  4563. * @param colIndex
  4564. * @param w
  4565. */
  4566. public void setColWidth(int colIndex, int w) {
  4567. for (Widget row : renderedRows) {
  4568. ((VScrollTableRow) row).setCellWidth(colIndex, w);
  4569. }
  4570. }
  4571. private int cellExtraWidth = -1;
  4572. /**
  4573. * Method to return the space used for cell paddings + border.
  4574. */
  4575. private int getCellExtraWidth() {
  4576. if (cellExtraWidth < 0) {
  4577. detectExtrawidth();
  4578. }
  4579. return cellExtraWidth;
  4580. }
  4581. /**
  4582. * This method exists for the needs of {@link VTreeTable} only. May be
  4583. * removed or replaced in the future.</br> </br> Returns the maximum
  4584. * indent of the hierarcyColumn, if applicable.
  4585. *
  4586. * @see {@link VScrollTable#getHierarchyColumnIndex()}
  4587. *
  4588. * @return maximum indent in pixels
  4589. */
  4590. protected int getMaxIndent() {
  4591. return 0;
  4592. }
  4593. /**
  4594. * This method exists for the needs of {@link VTreeTable} only. May be
  4595. * removed or replaced in the future.</br> </br> Calculates the maximum
  4596. * indent of the hierarcyColumn, if applicable.
  4597. */
  4598. protected void calculateMaxIndent() {
  4599. // NOP
  4600. }
  4601. private void detectExtrawidth() {
  4602. NodeList<TableRowElement> rows = tBodyElement.getRows();
  4603. if (rows.getLength() == 0) {
  4604. /* need to temporary add empty row and detect */
  4605. VScrollTableRow scrollTableRow = new VScrollTableRow();
  4606. scrollTableRow.updateStyleNames(VScrollTable.this
  4607. .getStylePrimaryName());
  4608. tBodyElement.appendChild(scrollTableRow.getElement());
  4609. detectExtrawidth();
  4610. tBodyElement.removeChild(scrollTableRow.getElement());
  4611. } else {
  4612. boolean noCells = false;
  4613. TableRowElement item = rows.getItem(0);
  4614. TableCellElement firstTD = item.getCells().getItem(0);
  4615. if (firstTD == null) {
  4616. // content is currently empty, we need to add a fake cell
  4617. // for measuring
  4618. noCells = true;
  4619. VScrollTableRow next = (VScrollTableRow) iterator().next();
  4620. boolean sorted = tHead.getHeaderCell(0) != null ? tHead
  4621. .getHeaderCell(0).isSorted() : false;
  4622. next.addCell(null, "", ALIGN_LEFT, "", true, sorted);
  4623. firstTD = item.getCells().getItem(0);
  4624. }
  4625. com.google.gwt.dom.client.Element wrapper = firstTD
  4626. .getFirstChildElement();
  4627. cellExtraWidth = firstTD.getOffsetWidth()
  4628. - wrapper.getOffsetWidth();
  4629. if (noCells) {
  4630. firstTD.getParentElement().removeChild(firstTD);
  4631. }
  4632. }
  4633. }
  4634. public void moveCol(int oldIndex, int newIndex) {
  4635. // loop all rows and move given index to its new place
  4636. final Iterator<?> rows = iterator();
  4637. while (rows.hasNext()) {
  4638. final VScrollTableRow row = (VScrollTableRow) rows.next();
  4639. final Element td = DOM.getChild(row.getElement(), oldIndex);
  4640. if (td != null) {
  4641. DOM.removeChild(row.getElement(), td);
  4642. DOM.insertChild(row.getElement(), td, newIndex);
  4643. }
  4644. }
  4645. }
  4646. /**
  4647. * Restore row visibility which is set to "none" when the row is
  4648. * rendered (due a performance optimization).
  4649. */
  4650. private void restoreRowVisibility() {
  4651. for (Widget row : renderedRows) {
  4652. row.getElement().getStyle().setProperty("visibility", "");
  4653. }
  4654. }
  4655. public int indexOf(Widget row) {
  4656. int relIx = -1;
  4657. for (int ix = 0; ix < renderedRows.size(); ix++) {
  4658. if (renderedRows.get(ix) == row) {
  4659. relIx = ix;
  4660. break;
  4661. }
  4662. }
  4663. if (relIx >= 0) {
  4664. return firstRendered + relIx;
  4665. }
  4666. return -1;
  4667. }
  4668. public class VScrollTableRow extends Panel implements ActionOwner,
  4669. ContextMenuOwner {
  4670. private static final int TOUCHSCROLL_TIMEOUT = 100;
  4671. private static final int DRAGMODE_MULTIROW = 2;
  4672. protected ArrayList<Widget> childWidgets = new ArrayList<Widget>();
  4673. private boolean selected = false;
  4674. protected final int rowKey;
  4675. private String[] actionKeys = null;
  4676. private final TableRowElement rowElement;
  4677. private int index;
  4678. private Event touchStart;
  4679. private Timer dragTouchTimeout;
  4680. private int touchStartY;
  4681. private int touchStartX;
  4682. private TouchContextProvider touchContextProvider = new TouchContextProvider(
  4683. this);
  4684. private TooltipInfo tooltipInfo = null;
  4685. private Map<TableCellElement, TooltipInfo> cellToolTips = new HashMap<TableCellElement, TooltipInfo>();
  4686. private boolean isDragging = false;
  4687. private String rowStyle = null;
  4688. private VScrollTableRow(int rowKey) {
  4689. this.rowKey = rowKey;
  4690. rowElement = Document.get().createTRElement();
  4691. setElement(rowElement);
  4692. DOM.sinkEvents(getElement(), Event.MOUSEEVENTS
  4693. | Event.TOUCHEVENTS | Event.ONDBLCLICK
  4694. | Event.ONCONTEXTMENU | VTooltip.TOOLTIP_EVENTS);
  4695. }
  4696. public VScrollTableRow(UIDL uidl, char[] aligns) {
  4697. this(uidl.getIntAttribute("key"));
  4698. /*
  4699. * Rendering the rows as hidden improves Firefox and Safari
  4700. * performance drastically.
  4701. */
  4702. getElement().getStyle().setProperty("visibility", "hidden");
  4703. rowStyle = uidl.getStringAttribute("rowstyle");
  4704. updateStyleNames(VScrollTable.this.getStylePrimaryName());
  4705. String rowDescription = uidl.getStringAttribute("rowdescr");
  4706. if (rowDescription != null && !rowDescription.equals("")) {
  4707. tooltipInfo = new TooltipInfo(rowDescription, null, this);
  4708. } else {
  4709. tooltipInfo = null;
  4710. }
  4711. tHead.getColumnAlignments();
  4712. int col = 0;
  4713. int visibleColumnIndex = -1;
  4714. // row header
  4715. if (showRowHeaders) {
  4716. boolean sorted = tHead.getHeaderCell(col).isSorted();
  4717. addCell(uidl, buildCaptionHtmlSnippet(uidl), aligns[col++],
  4718. "rowheader", true, sorted);
  4719. visibleColumnIndex++;
  4720. }
  4721. if (uidl.hasAttribute("al")) {
  4722. actionKeys = uidl.getStringArrayAttribute("al");
  4723. }
  4724. addCellsFromUIDL(uidl, aligns, col, visibleColumnIndex);
  4725. if (uidl.hasAttribute("selected") && !isSelected()) {
  4726. toggleSelection();
  4727. }
  4728. }
  4729. protected void updateStyleNames(String primaryStyleName) {
  4730. if (getStylePrimaryName().contains("odd")) {
  4731. setStyleName(primaryStyleName + "-row-odd");
  4732. } else {
  4733. setStyleName(primaryStyleName + "-row");
  4734. }
  4735. if (rowStyle != null) {
  4736. addStyleName(primaryStyleName + "-row-" + rowStyle);
  4737. }
  4738. for (int i = 0; i < rowElement.getChildCount(); i++) {
  4739. TableCellElement cell = (TableCellElement) rowElement
  4740. .getChild(i);
  4741. updateCellStyleNames(cell, primaryStyleName);
  4742. }
  4743. }
  4744. public TooltipInfo getTooltipInfo() {
  4745. return tooltipInfo;
  4746. }
  4747. /**
  4748. * Add a dummy row, used for measurements if Table is empty.
  4749. */
  4750. public VScrollTableRow() {
  4751. this(0);
  4752. addCell(null, "_", 'b', "", true, false);
  4753. }
  4754. protected void initCellWidths() {
  4755. final int cells = tHead.getVisibleCellCount();
  4756. for (int i = 0; i < cells; i++) {
  4757. int w = VScrollTable.this.getColWidth(getColKeyByIndex(i));
  4758. if (w < 0) {
  4759. w = 0;
  4760. }
  4761. setCellWidth(i, w);
  4762. }
  4763. }
  4764. protected void setCellWidth(int cellIx, int width) {
  4765. final Element cell = DOM.getChild(getElement(), cellIx);
  4766. Style wrapperStyle = cell.getFirstChildElement().getStyle();
  4767. int wrapperWidth = width;
  4768. if (BrowserInfo.get().isWebkit()
  4769. || BrowserInfo.get().isOpera10()) {
  4770. /*
  4771. * Some versions of Webkit and Opera ignore the width
  4772. * definition of zero width table cells. Instead, use 1px
  4773. * and compensate with a negative margin.
  4774. */
  4775. if (width == 0) {
  4776. wrapperWidth = 1;
  4777. wrapperStyle.setMarginRight(-1, Unit.PX);
  4778. } else {
  4779. wrapperStyle.clearMarginRight();
  4780. }
  4781. }
  4782. wrapperStyle.setPropertyPx("width", wrapperWidth);
  4783. cell.getStyle().setPropertyPx("width", width);
  4784. }
  4785. protected void addCellsFromUIDL(UIDL uidl, char[] aligns, int col,
  4786. int visibleColumnIndex) {
  4787. final Iterator<?> cells = uidl.getChildIterator();
  4788. while (cells.hasNext()) {
  4789. final Object cell = cells.next();
  4790. visibleColumnIndex++;
  4791. String columnId = visibleColOrder[visibleColumnIndex];
  4792. String style = "";
  4793. if (uidl.hasAttribute("style-" + columnId)) {
  4794. style = uidl.getStringAttribute("style-" + columnId);
  4795. }
  4796. String description = null;
  4797. if (uidl.hasAttribute("descr-" + columnId)) {
  4798. description = uidl.getStringAttribute("descr-"
  4799. + columnId);
  4800. }
  4801. boolean sorted = tHead.getHeaderCell(col).isSorted();
  4802. if (cell instanceof String) {
  4803. addCell(uidl, cell.toString(), aligns[col++], style,
  4804. isRenderHtmlInCells(), sorted, description);
  4805. } else {
  4806. final ComponentConnector cellContent = client
  4807. .getPaintable((UIDL) cell);
  4808. addCell(uidl, cellContent.getWidget(), aligns[col++],
  4809. style, sorted, description);
  4810. }
  4811. }
  4812. }
  4813. /**
  4814. * Overriding this and returning true causes all text cells to be
  4815. * rendered as HTML.
  4816. *
  4817. * @return always returns false in the default implementation
  4818. */
  4819. protected boolean isRenderHtmlInCells() {
  4820. return false;
  4821. }
  4822. /**
  4823. * Detects whether row is visible in tables viewport.
  4824. *
  4825. * @return
  4826. */
  4827. public boolean isInViewPort() {
  4828. int absoluteTop = getAbsoluteTop();
  4829. int absoluteBottom = absoluteTop + getOffsetHeight();
  4830. int viewPortTop = scrollBodyPanel.getAbsoluteTop();
  4831. int viewPortBottom = viewPortTop
  4832. + scrollBodyPanel.getOffsetHeight();
  4833. return absoluteBottom > viewPortTop
  4834. && absoluteTop < viewPortBottom;
  4835. }
  4836. /**
  4837. * Makes a check based on indexes whether the row is before the
  4838. * compared row.
  4839. *
  4840. * @param row1
  4841. * @return true if this rows index is smaller than in the row1
  4842. */
  4843. public boolean isBefore(VScrollTableRow row1) {
  4844. return getIndex() < row1.getIndex();
  4845. }
  4846. /**
  4847. * Sets the index of the row in the whole table. Currently used just
  4848. * to set even/odd classname
  4849. *
  4850. * @param indexInWholeTable
  4851. */
  4852. private void setIndex(int indexInWholeTable) {
  4853. index = indexInWholeTable;
  4854. boolean isOdd = indexInWholeTable % 2 == 0;
  4855. // Inverted logic to be backwards compatible with earlier 6.4.
  4856. // It is very strange because rows 1,3,5 are considered "even"
  4857. // and 2,4,6 "odd".
  4858. //
  4859. // First remove any old styles so that both styles aren't
  4860. // applied when indexes are updated.
  4861. String primaryStyleName = getStylePrimaryName();
  4862. if (primaryStyleName != null && !primaryStyleName.equals("")) {
  4863. removeStyleName(getStylePrimaryName());
  4864. }
  4865. if (!isOdd) {
  4866. addStyleName(VScrollTable.this.getStylePrimaryName()
  4867. + "-row-odd");
  4868. } else {
  4869. addStyleName(VScrollTable.this.getStylePrimaryName()
  4870. + "-row");
  4871. }
  4872. }
  4873. public int getIndex() {
  4874. return index;
  4875. }
  4876. @Override
  4877. protected void onDetach() {
  4878. super.onDetach();
  4879. client.getContextMenu().ensureHidden(this);
  4880. }
  4881. public String getKey() {
  4882. return String.valueOf(rowKey);
  4883. }
  4884. public void addCell(UIDL rowUidl, String text, char align,
  4885. String style, boolean textIsHTML, boolean sorted) {
  4886. addCell(rowUidl, text, align, style, textIsHTML, sorted, null);
  4887. }
  4888. public void addCell(UIDL rowUidl, String text, char align,
  4889. String style, boolean textIsHTML, boolean sorted,
  4890. String description) {
  4891. // String only content is optimized by not using Label widget
  4892. final TableCellElement td = DOM.createTD().cast();
  4893. initCellWithText(text, align, style, textIsHTML, sorted,
  4894. description, td);
  4895. }
  4896. protected void initCellWithText(String text, char align,
  4897. String style, boolean textIsHTML, boolean sorted,
  4898. String description, final TableCellElement td) {
  4899. final Element container = DOM.createDiv();
  4900. container.setClassName(VScrollTable.this.getStylePrimaryName()
  4901. + "-cell-wrapper");
  4902. td.setClassName(VScrollTable.this.getStylePrimaryName()
  4903. + "-cell-content");
  4904. if (style != null && !style.equals("")) {
  4905. td.addClassName(VScrollTable.this.getStylePrimaryName()
  4906. + "-cell-content-" + style);
  4907. }
  4908. if (sorted) {
  4909. td.addClassName(VScrollTable.this.getStylePrimaryName()
  4910. + "-cell-content-sorted");
  4911. }
  4912. if (textIsHTML) {
  4913. container.setInnerHTML(text);
  4914. } else {
  4915. container.setInnerText(text);
  4916. }
  4917. setAlign(align, container);
  4918. setTooltip(td, description);
  4919. td.appendChild(container);
  4920. getElement().appendChild(td);
  4921. }
  4922. protected void updateCellStyleNames(TableCellElement td,
  4923. String primaryStyleName) {
  4924. Element container = td.getFirstChild().cast();
  4925. container.setClassName(primaryStyleName + "-cell-wrapper");
  4926. /*
  4927. * Replace old primary style name with new one
  4928. */
  4929. String className = td.getClassName();
  4930. String oldPrimaryName = className.split("-cell-content")[0];
  4931. td.setClassName(className.replaceAll(oldPrimaryName,
  4932. primaryStyleName));
  4933. }
  4934. public void addCell(UIDL rowUidl, Widget w, char align,
  4935. String style, boolean sorted, String description) {
  4936. final TableCellElement td = DOM.createTD().cast();
  4937. initCellWithWidget(w, align, style, sorted, td);
  4938. setTooltip(td, description);
  4939. }
  4940. private void setTooltip(TableCellElement td, String description) {
  4941. if (description != null && !description.equals("")) {
  4942. TooltipInfo info = new TooltipInfo(description, null, this);
  4943. cellToolTips.put(td, info);
  4944. } else {
  4945. cellToolTips.remove(td);
  4946. }
  4947. }
  4948. private void setAlign(char align, final Element container) {
  4949. switch (align) {
  4950. case ALIGN_CENTER:
  4951. container.getStyle().setProperty("textAlign", "center");
  4952. break;
  4953. case ALIGN_LEFT:
  4954. container.getStyle().setProperty("textAlign", "left");
  4955. break;
  4956. case ALIGN_RIGHT:
  4957. default:
  4958. container.getStyle().setProperty("textAlign", "right");
  4959. break;
  4960. }
  4961. }
  4962. protected void initCellWithWidget(Widget w, char align,
  4963. String style, boolean sorted, final TableCellElement td) {
  4964. final Element container = DOM.createDiv();
  4965. String className = VScrollTable.this.getStylePrimaryName()
  4966. + "-cell-content";
  4967. if (style != null && !style.equals("")) {
  4968. className += " " + VScrollTable.this.getStylePrimaryName()
  4969. + "-cell-content-" + style;
  4970. }
  4971. if (sorted) {
  4972. className += " " + VScrollTable.this.getStylePrimaryName()
  4973. + "-cell-content-sorted";
  4974. }
  4975. td.setClassName(className);
  4976. container.setClassName(VScrollTable.this.getStylePrimaryName()
  4977. + "-cell-wrapper");
  4978. setAlign(align, container);
  4979. td.appendChild(container);
  4980. getElement().appendChild(td);
  4981. // ensure widget not attached to another element (possible tBody
  4982. // change)
  4983. w.removeFromParent();
  4984. container.appendChild(w.getElement());
  4985. adopt(w);
  4986. childWidgets.add(w);
  4987. }
  4988. @Override
  4989. public Iterator<Widget> iterator() {
  4990. return childWidgets.iterator();
  4991. }
  4992. @Override
  4993. public boolean remove(Widget w) {
  4994. if (childWidgets.contains(w)) {
  4995. orphan(w);
  4996. DOM.removeChild(DOM.getParent(w.getElement()),
  4997. w.getElement());
  4998. childWidgets.remove(w);
  4999. return true;
  5000. } else {
  5001. return false;
  5002. }
  5003. }
  5004. /**
  5005. * If there are registered click listeners, sends a click event and
  5006. * returns true. Otherwise, does nothing and returns false.
  5007. *
  5008. * @param event
  5009. * @param targetTdOrTr
  5010. * @param immediate
  5011. * Whether the event is sent immediately
  5012. * @return Whether a click event was sent
  5013. */
  5014. private boolean handleClickEvent(Event event, Element targetTdOrTr,
  5015. boolean immediate) {
  5016. if (!client.hasEventListeners(VScrollTable.this,
  5017. TableConstants.ITEM_CLICK_EVENT_ID)) {
  5018. // Don't send an event if nobody is listening
  5019. return false;
  5020. }
  5021. // This row was clicked
  5022. client.updateVariable(paintableId, "clickedKey", "" + rowKey,
  5023. false);
  5024. if (getElement() == targetTdOrTr.getParentElement()) {
  5025. // A specific column was clicked
  5026. int childIndex = DOM.getChildIndex(getElement(),
  5027. targetTdOrTr);
  5028. String colKey = null;
  5029. colKey = tHead.getHeaderCell(childIndex).getColKey();
  5030. client.updateVariable(paintableId, "clickedColKey", colKey,
  5031. false);
  5032. }
  5033. MouseEventDetails details = MouseEventDetailsBuilder
  5034. .buildMouseEventDetails(event);
  5035. client.updateVariable(paintableId, "clickEvent",
  5036. details.toString(), immediate);
  5037. return true;
  5038. }
  5039. public TooltipInfo getTooltip(
  5040. com.google.gwt.dom.client.Element target) {
  5041. TooltipInfo info = null;
  5042. final Element targetTdOrTr = getTdOrTr(target);
  5043. if (targetTdOrTr != null
  5044. && "td".equals(targetTdOrTr.getTagName().toLowerCase())) {
  5045. TableCellElement td = (TableCellElement) targetTdOrTr
  5046. .cast();
  5047. info = cellToolTips.get(td);
  5048. }
  5049. if (info == null) {
  5050. info = tooltipInfo;
  5051. }
  5052. return info;
  5053. }
  5054. private Element getTdOrTr(Element target) {
  5055. Element thisTrElement = getElement();
  5056. if (target == thisTrElement) {
  5057. // This was a on the TR element
  5058. return target;
  5059. }
  5060. // Iterate upwards until we find the TR element
  5061. Element element = target;
  5062. while (element != null
  5063. && element.getParentElement() != thisTrElement) {
  5064. element = element.getParentElement();
  5065. }
  5066. return element;
  5067. }
  5068. /**
  5069. * Special handler for touch devices that support native scrolling
  5070. *
  5071. * @return Whether the event was handled by this method.
  5072. */
  5073. private boolean handleTouchEvent(final Event event) {
  5074. boolean touchEventHandled = false;
  5075. if (enabled && hasNativeTouchScrolling) {
  5076. touchContextProvider.handleTouchEvent(event);
  5077. final Element targetTdOrTr = getEventTargetTdOrTr(event);
  5078. final int type = event.getTypeInt();
  5079. switch (type) {
  5080. case Event.ONTOUCHSTART:
  5081. touchEventHandled = true;
  5082. touchStart = event;
  5083. isDragging = false;
  5084. Touch touch = event.getChangedTouches().get(0);
  5085. // save position to fields, touches in events are same
  5086. // instance during the operation.
  5087. touchStartX = touch.getClientX();
  5088. touchStartY = touch.getClientY();
  5089. if (dragmode != 0) {
  5090. if (dragTouchTimeout == null) {
  5091. dragTouchTimeout = new Timer() {
  5092. @Override
  5093. public void run() {
  5094. if (touchStart != null) {
  5095. // Start a drag if a finger is held
  5096. // in place long enough, then moved
  5097. isDragging = true;
  5098. }
  5099. }
  5100. };
  5101. }
  5102. dragTouchTimeout.schedule(TOUCHSCROLL_TIMEOUT);
  5103. }
  5104. break;
  5105. case Event.ONTOUCHMOVE:
  5106. touchEventHandled = true;
  5107. if (isSignificantMove(event)) {
  5108. if (!isDragging && dragTouchTimeout != null) {
  5109. // Moved finger before the drag timer expired,
  5110. // so let the browser handle this as a scroll.
  5111. dragTouchTimeout.cancel();
  5112. dragTouchTimeout = null;
  5113. }
  5114. if (dragmode != 0 && touchStart != null
  5115. && isDragging) {
  5116. event.preventDefault();
  5117. event.stopPropagation();
  5118. startRowDrag(touchStart, type, targetTdOrTr);
  5119. }
  5120. touchStart = null;
  5121. }
  5122. break;
  5123. case Event.ONTOUCHEND:
  5124. case Event.ONTOUCHCANCEL:
  5125. touchEventHandled = true;
  5126. if (dragTouchTimeout != null) {
  5127. dragTouchTimeout.cancel();
  5128. }
  5129. if (touchStart != null) {
  5130. if (!BrowserInfo.get().isAndroid()) {
  5131. event.preventDefault();
  5132. event.stopPropagation();
  5133. Util.simulateClickFromTouchEvent(touchStart,
  5134. this);
  5135. }
  5136. touchStart = null;
  5137. }
  5138. isDragging = false;
  5139. break;
  5140. }
  5141. }
  5142. return touchEventHandled;
  5143. }
  5144. /*
  5145. * React on click that occur on content cells only
  5146. */
  5147. @Override
  5148. public void onBrowserEvent(final Event event) {
  5149. final boolean touchEventHandled = handleTouchEvent(event);
  5150. if (enabled && !touchEventHandled) {
  5151. final int type = event.getTypeInt();
  5152. final Element targetTdOrTr = getEventTargetTdOrTr(event);
  5153. if (type == Event.ONCONTEXTMENU) {
  5154. showContextMenu(event);
  5155. if (enabled
  5156. && (actionKeys != null || client
  5157. .hasEventListeners(
  5158. VScrollTable.this,
  5159. TableConstants.ITEM_CLICK_EVENT_ID))) {
  5160. /*
  5161. * Prevent browser context menu only if there are
  5162. * action handlers or item click listeners
  5163. * registered
  5164. */
  5165. event.stopPropagation();
  5166. event.preventDefault();
  5167. }
  5168. return;
  5169. }
  5170. boolean targetCellOrRowFound = targetTdOrTr != null;
  5171. switch (type) {
  5172. case Event.ONDBLCLICK:
  5173. if (targetCellOrRowFound) {
  5174. handleClickEvent(event, targetTdOrTr, true);
  5175. }
  5176. break;
  5177. case Event.ONMOUSEUP:
  5178. if (targetCellOrRowFound) {
  5179. /*
  5180. * Queue here, send at the same time as the
  5181. * corresponding value change event - see #7127
  5182. */
  5183. boolean clickEventSent = handleClickEvent(event,
  5184. targetTdOrTr, false);
  5185. if (event.getButton() == Event.BUTTON_LEFT
  5186. && isSelectable()) {
  5187. // Ctrl+Shift click
  5188. if ((event.getCtrlKey() || event.getMetaKey())
  5189. && event.getShiftKey()
  5190. && isMultiSelectModeDefault()) {
  5191. toggleShiftSelection(false);
  5192. setRowFocus(this);
  5193. // Ctrl click
  5194. } else if ((event.getCtrlKey() || event
  5195. .getMetaKey())
  5196. && isMultiSelectModeDefault()) {
  5197. boolean wasSelected = isSelected();
  5198. toggleSelection();
  5199. setRowFocus(this);
  5200. /*
  5201. * next possible range select must start on
  5202. * this row
  5203. */
  5204. selectionRangeStart = this;
  5205. if (wasSelected) {
  5206. removeRowFromUnsentSelectionRanges(this);
  5207. }
  5208. } else if ((event.getCtrlKey() || event
  5209. .getMetaKey()) && isSingleSelectMode()) {
  5210. // Ctrl (or meta) click (Single selection)
  5211. if (!isSelected()
  5212. || (isSelected() && nullSelectionAllowed)) {
  5213. if (!isSelected()) {
  5214. deselectAll();
  5215. }
  5216. toggleSelection();
  5217. setRowFocus(this);
  5218. }
  5219. } else if (event.getShiftKey()
  5220. && isMultiSelectModeDefault()) {
  5221. // Shift click
  5222. toggleShiftSelection(true);
  5223. } else {
  5224. // click
  5225. boolean currentlyJustThisRowSelected = selectedRowKeys
  5226. .size() == 1
  5227. && selectedRowKeys
  5228. .contains(getKey());
  5229. if (!currentlyJustThisRowSelected) {
  5230. if (isSingleSelectMode()
  5231. || isMultiSelectModeDefault()) {
  5232. /*
  5233. * For default multi select mode
  5234. * (ctrl/shift) and for single
  5235. * select mode we need to clear the
  5236. * previous selection before
  5237. * selecting a new one when the user
  5238. * clicks on a row. Only in
  5239. * multiselect/simple mode the old
  5240. * selection should remain after a
  5241. * normal click.
  5242. */
  5243. deselectAll();
  5244. }
  5245. toggleSelection();
  5246. } else if ((isSingleSelectMode() || isMultiSelectModeSimple())
  5247. && nullSelectionAllowed) {
  5248. toggleSelection();
  5249. }/*
  5250. * else NOP to avoid excessive server
  5251. * visits (selection is removed with
  5252. * CTRL/META click)
  5253. */
  5254. selectionRangeStart = this;
  5255. setRowFocus(this);
  5256. }
  5257. // Remove IE text selection hack
  5258. if (BrowserInfo.get().isIE()) {
  5259. ((Element) event.getEventTarget().cast())
  5260. .setPropertyJSO("onselectstart",
  5261. null);
  5262. }
  5263. // Queue value change
  5264. sendSelectedRows(false);
  5265. }
  5266. /*
  5267. * Send queued click and value change events if any
  5268. * If a click event is sent, send value change with
  5269. * it regardless of the immediate flag, see #7127
  5270. */
  5271. if (immediate || clickEventSent) {
  5272. client.sendPendingVariableChanges();
  5273. }
  5274. }
  5275. break;
  5276. case Event.ONTOUCHEND:
  5277. case Event.ONTOUCHCANCEL:
  5278. if (touchStart != null) {
  5279. /*
  5280. * Touch has not been handled as neither context or
  5281. * drag start, handle it as a click.
  5282. */
  5283. Util.simulateClickFromTouchEvent(touchStart, this);
  5284. touchStart = null;
  5285. }
  5286. touchContextProvider.cancel();
  5287. break;
  5288. case Event.ONTOUCHMOVE:
  5289. if (isSignificantMove(event)) {
  5290. /*
  5291. * TODO figure out scroll delegate don't eat events
  5292. * if row is selected. Null check for active
  5293. * delegate is as a workaround.
  5294. */
  5295. if (dragmode != 0
  5296. && touchStart != null
  5297. && (TouchScrollDelegate
  5298. .getActiveScrollDelegate() == null)) {
  5299. startRowDrag(touchStart, type, targetTdOrTr);
  5300. }
  5301. touchContextProvider.cancel();
  5302. /*
  5303. * Avoid clicks and drags by clearing touch start
  5304. * flag.
  5305. */
  5306. touchStart = null;
  5307. }
  5308. break;
  5309. case Event.ONTOUCHSTART:
  5310. touchStart = event;
  5311. Touch touch = event.getChangedTouches().get(0);
  5312. // save position to fields, touches in events are same
  5313. // isntance during the operation.
  5314. touchStartX = touch.getClientX();
  5315. touchStartY = touch.getClientY();
  5316. /*
  5317. * Prevent simulated mouse events.
  5318. */
  5319. touchStart.preventDefault();
  5320. if (dragmode != 0 || actionKeys != null) {
  5321. new Timer() {
  5322. @Override
  5323. public void run() {
  5324. TouchScrollDelegate activeScrollDelegate = TouchScrollDelegate
  5325. .getActiveScrollDelegate();
  5326. /*
  5327. * If there's a scroll delegate, check if
  5328. * we're actually scrolling and handle it.
  5329. * If no delegate, do nothing here and let
  5330. * the row handle potential drag'n'drop or
  5331. * context menu.
  5332. */
  5333. if (activeScrollDelegate != null) {
  5334. if (activeScrollDelegate.isMoved()) {
  5335. /*
  5336. * Prevent the row from handling
  5337. * touch move/end events (the
  5338. * delegate handles those) and from
  5339. * doing drag'n'drop or opening a
  5340. * context menu.
  5341. */
  5342. touchStart = null;
  5343. } else {
  5344. /*
  5345. * Scrolling hasn't started, so
  5346. * cancel delegate and let the row
  5347. * handle potential drag'n'drop or
  5348. * context menu.
  5349. */
  5350. activeScrollDelegate
  5351. .stopScrolling();
  5352. }
  5353. }
  5354. }
  5355. }.schedule(TOUCHSCROLL_TIMEOUT);
  5356. }
  5357. break;
  5358. case Event.ONMOUSEDOWN:
  5359. if (targetCellOrRowFound) {
  5360. setRowFocus(this);
  5361. ensureFocus();
  5362. if (dragmode != 0
  5363. && (event.getButton() == NativeEvent.BUTTON_LEFT)) {
  5364. startRowDrag(event, type, targetTdOrTr);
  5365. } else if (event.getCtrlKey()
  5366. || event.getShiftKey()
  5367. || event.getMetaKey()
  5368. && isMultiSelectModeDefault()) {
  5369. // Prevent default text selection in Firefox
  5370. event.preventDefault();
  5371. // Prevent default text selection in IE
  5372. if (BrowserInfo.get().isIE()) {
  5373. ((Element) event.getEventTarget().cast())
  5374. .setPropertyJSO(
  5375. "onselectstart",
  5376. getPreventTextSelectionIEHack());
  5377. }
  5378. event.stopPropagation();
  5379. }
  5380. }
  5381. break;
  5382. case Event.ONMOUSEOUT:
  5383. break;
  5384. default:
  5385. break;
  5386. }
  5387. }
  5388. super.onBrowserEvent(event);
  5389. }
  5390. private boolean isSignificantMove(Event event) {
  5391. if (touchStart == null) {
  5392. // no touch start
  5393. return false;
  5394. }
  5395. /*
  5396. * TODO calculate based on real distance instead of separate
  5397. * axis checks
  5398. */
  5399. Touch touch = event.getChangedTouches().get(0);
  5400. if (Math.abs(touch.getClientX() - touchStartX) > TouchScrollDelegate.SIGNIFICANT_MOVE_THRESHOLD) {
  5401. return true;
  5402. }
  5403. if (Math.abs(touch.getClientY() - touchStartY) > TouchScrollDelegate.SIGNIFICANT_MOVE_THRESHOLD) {
  5404. return true;
  5405. }
  5406. return false;
  5407. }
  5408. /**
  5409. * Checks if the row represented by the row key has been selected
  5410. *
  5411. * @param key
  5412. * The generated row key
  5413. */
  5414. private boolean rowKeyIsSelected(int rowKey) {
  5415. // Check single selections
  5416. if (selectedRowKeys.contains("" + rowKey)) {
  5417. return true;
  5418. }
  5419. // Check range selections
  5420. for (SelectionRange r : selectedRowRanges) {
  5421. if (r.inRange(getRenderedRowByKey("" + rowKey))) {
  5422. return true;
  5423. }
  5424. }
  5425. return false;
  5426. }
  5427. protected void startRowDrag(Event event, final int type,
  5428. Element targetTdOrTr) {
  5429. VTransferable transferable = new VTransferable();
  5430. transferable.setDragSource(ConnectorMap.get(client)
  5431. .getConnector(VScrollTable.this));
  5432. transferable.setData("itemId", "" + rowKey);
  5433. NodeList<TableCellElement> cells = rowElement.getCells();
  5434. for (int i = 0; i < cells.getLength(); i++) {
  5435. if (cells.getItem(i).isOrHasChild(targetTdOrTr)) {
  5436. HeaderCell headerCell = tHead.getHeaderCell(i);
  5437. transferable.setData("propertyId", headerCell.cid);
  5438. break;
  5439. }
  5440. }
  5441. VDragEvent ev = VDragAndDropManager.get().startDrag(
  5442. transferable, event, true);
  5443. if (dragmode == DRAGMODE_MULTIROW && isMultiSelectModeAny()
  5444. && rowKeyIsSelected(rowKey)) {
  5445. // Create a drag image of ALL rows
  5446. ev.createDragImage(scrollBody.tBodyElement, true);
  5447. // Hide rows which are not selected
  5448. Element dragImage = ev.getDragImage();
  5449. int i = 0;
  5450. for (Iterator<Widget> iterator = scrollBody.iterator(); iterator
  5451. .hasNext();) {
  5452. VScrollTableRow next = (VScrollTableRow) iterator
  5453. .next();
  5454. Element child = (Element) dragImage.getChild(i++);
  5455. if (!rowKeyIsSelected(next.rowKey)) {
  5456. child.getStyle().setVisibility(Visibility.HIDDEN);
  5457. }
  5458. }
  5459. } else {
  5460. ev.createDragImage(getElement(), true);
  5461. }
  5462. if (type == Event.ONMOUSEDOWN) {
  5463. event.preventDefault();
  5464. }
  5465. event.stopPropagation();
  5466. }
  5467. /**
  5468. * Finds the TD that the event interacts with. Returns null if the
  5469. * target of the event should not be handled. If the event target is
  5470. * the row directly this method returns the TR element instead of
  5471. * the TD.
  5472. *
  5473. * @param event
  5474. * @return TD or TR element that the event targets (the actual event
  5475. * target is this element or a child of it)
  5476. */
  5477. private Element getEventTargetTdOrTr(Event event) {
  5478. final Element eventTarget = event.getEventTarget().cast();
  5479. Widget widget = Util.findWidget(eventTarget, null);
  5480. if (widget != this) {
  5481. /*
  5482. * This is a workaround to make Labels, read only TextFields
  5483. * and Embedded in a Table clickable (see #2688). It is
  5484. * really not a fix as it does not work with a custom read
  5485. * only components (not extending VLabel/VEmbedded).
  5486. */
  5487. while (widget != null && widget.getParent() != this) {
  5488. widget = widget.getParent();
  5489. }
  5490. if (!(widget instanceof VLabel)
  5491. && !(widget instanceof VEmbedded)
  5492. && !(widget instanceof VTextField && ((VTextField) widget)
  5493. .isReadOnly())) {
  5494. return null;
  5495. }
  5496. }
  5497. return getTdOrTr(eventTarget);
  5498. }
  5499. @Override
  5500. public void showContextMenu(Event event) {
  5501. if (enabled && actionKeys != null) {
  5502. // Show context menu if there are registered action handlers
  5503. int left = Util.getTouchOrMouseClientX(event)
  5504. + Window.getScrollLeft();
  5505. int top = Util.getTouchOrMouseClientY(event)
  5506. + Window.getScrollTop();
  5507. showContextMenu(left, top);
  5508. }
  5509. }
  5510. public void showContextMenu(int left, int top) {
  5511. VContextMenu menu = client.getContextMenu();
  5512. contextMenu = new ContextMenuDetails(menu, getKey(), left, top);
  5513. menu.showAt(this, left, top);
  5514. }
  5515. /**
  5516. * Has the row been selected?
  5517. *
  5518. * @return Returns true if selected, else false
  5519. */
  5520. public boolean isSelected() {
  5521. return selected;
  5522. }
  5523. /**
  5524. * Toggle the selection of the row
  5525. */
  5526. public void toggleSelection() {
  5527. selected = !selected;
  5528. selectionChanged = true;
  5529. if (selected) {
  5530. selectedRowKeys.add(String.valueOf(rowKey));
  5531. addStyleName("v-selected");
  5532. } else {
  5533. removeStyleName("v-selected");
  5534. selectedRowKeys.remove(String.valueOf(rowKey));
  5535. }
  5536. }
  5537. /**
  5538. * Is called when a user clicks an item when holding SHIFT key down.
  5539. * This will select a new range from the last focused row
  5540. *
  5541. * @param deselectPrevious
  5542. * Should the previous selected range be deselected
  5543. */
  5544. private void toggleShiftSelection(boolean deselectPrevious) {
  5545. /*
  5546. * Ensures that we are in multiselect mode and that we have a
  5547. * previous selection which was not a deselection
  5548. */
  5549. if (isSingleSelectMode()) {
  5550. // No previous selection found
  5551. deselectAll();
  5552. toggleSelection();
  5553. return;
  5554. }
  5555. // Set the selectable range
  5556. VScrollTableRow endRow = this;
  5557. VScrollTableRow startRow = selectionRangeStart;
  5558. if (startRow == null) {
  5559. startRow = focusedRow;
  5560. // If start row is null then we have a multipage selection
  5561. // from
  5562. // above
  5563. if (startRow == null) {
  5564. startRow = (VScrollTableRow) scrollBody.iterator()
  5565. .next();
  5566. setRowFocus(endRow);
  5567. }
  5568. } else if (!startRow.isSelected()) {
  5569. // The start row is no longer selected (probably removed)
  5570. // and so we select from above
  5571. startRow = (VScrollTableRow) scrollBody.iterator().next();
  5572. setRowFocus(endRow);
  5573. }
  5574. // Deselect previous items if so desired
  5575. if (deselectPrevious) {
  5576. deselectAll();
  5577. }
  5578. // we'll ensure GUI state from top down even though selection
  5579. // was the opposite way
  5580. if (!startRow.isBefore(endRow)) {
  5581. VScrollTableRow tmp = startRow;
  5582. startRow = endRow;
  5583. endRow = tmp;
  5584. }
  5585. SelectionRange range = new SelectionRange(startRow, endRow);
  5586. for (Widget w : scrollBody) {
  5587. VScrollTableRow row = (VScrollTableRow) w;
  5588. if (range.inRange(row)) {
  5589. if (!row.isSelected()) {
  5590. row.toggleSelection();
  5591. }
  5592. selectedRowKeys.add(row.getKey());
  5593. }
  5594. }
  5595. // Add range
  5596. if (startRow != endRow) {
  5597. selectedRowRanges.add(range);
  5598. }
  5599. }
  5600. /*
  5601. * (non-Javadoc)
  5602. *
  5603. * @see com.vaadin.client.ui.IActionOwner#getActions ()
  5604. */
  5605. @Override
  5606. public Action[] getActions() {
  5607. if (actionKeys == null) {
  5608. return new Action[] {};
  5609. }
  5610. final Action[] actions = new Action[actionKeys.length];
  5611. for (int i = 0; i < actions.length; i++) {
  5612. final String actionKey = actionKeys[i];
  5613. final TreeAction a = new TreeAction(this,
  5614. String.valueOf(rowKey), actionKey) {
  5615. @Override
  5616. public void execute() {
  5617. super.execute();
  5618. lazyRevertFocusToRow(VScrollTableRow.this);
  5619. }
  5620. };
  5621. a.setCaption(getActionCaption(actionKey));
  5622. a.setIconUrl(getActionIcon(actionKey));
  5623. actions[i] = a;
  5624. }
  5625. return actions;
  5626. }
  5627. @Override
  5628. public ApplicationConnection getClient() {
  5629. return client;
  5630. }
  5631. @Override
  5632. public String getPaintableId() {
  5633. return paintableId;
  5634. }
  5635. private int getColIndexOf(Widget child) {
  5636. com.google.gwt.dom.client.Element widgetCell = child
  5637. .getElement().getParentElement().getParentElement();
  5638. NodeList<TableCellElement> cells = rowElement.getCells();
  5639. for (int i = 0; i < cells.getLength(); i++) {
  5640. if (cells.getItem(i) == widgetCell) {
  5641. return i;
  5642. }
  5643. }
  5644. return -1;
  5645. }
  5646. public Widget getWidgetForPaintable() {
  5647. return this;
  5648. }
  5649. }
  5650. protected class VScrollTableGeneratedRow extends VScrollTableRow {
  5651. private boolean spanColumns;
  5652. private boolean htmlContentAllowed;
  5653. public VScrollTableGeneratedRow(UIDL uidl, char[] aligns) {
  5654. super(uidl, aligns);
  5655. addStyleName("v-table-generated-row");
  5656. }
  5657. public boolean isSpanColumns() {
  5658. return spanColumns;
  5659. }
  5660. @Override
  5661. protected void initCellWidths() {
  5662. if (spanColumns) {
  5663. setSpannedColumnWidthAfterDOMFullyInited();
  5664. } else {
  5665. super.initCellWidths();
  5666. }
  5667. }
  5668. private void setSpannedColumnWidthAfterDOMFullyInited() {
  5669. // Defer setting width on spanned columns to make sure that
  5670. // they are added to the DOM before trying to calculate
  5671. // widths.
  5672. Scheduler.get().scheduleDeferred(new ScheduledCommand() {
  5673. @Override
  5674. public void execute() {
  5675. if (showRowHeaders) {
  5676. setCellWidth(0, tHead.getHeaderCell(0)
  5677. .getWidthWithIndent());
  5678. calcAndSetSpanWidthOnCell(1);
  5679. } else {
  5680. calcAndSetSpanWidthOnCell(0);
  5681. }
  5682. }
  5683. });
  5684. }
  5685. @Override
  5686. protected boolean isRenderHtmlInCells() {
  5687. return htmlContentAllowed;
  5688. }
  5689. @Override
  5690. protected void addCellsFromUIDL(UIDL uidl, char[] aligns, int col,
  5691. int visibleColumnIndex) {
  5692. htmlContentAllowed = uidl.getBooleanAttribute("gen_html");
  5693. spanColumns = uidl.getBooleanAttribute("gen_span");
  5694. final Iterator<?> cells = uidl.getChildIterator();
  5695. if (spanColumns) {
  5696. int colCount = uidl.getChildCount();
  5697. if (cells.hasNext()) {
  5698. final Object cell = cells.next();
  5699. if (cell instanceof String) {
  5700. addSpannedCell(uidl, cell.toString(), aligns[0],
  5701. "", htmlContentAllowed, false, null,
  5702. colCount);
  5703. } else {
  5704. addSpannedCell(uidl, (Widget) cell, aligns[0], "",
  5705. false, colCount);
  5706. }
  5707. }
  5708. } else {
  5709. super.addCellsFromUIDL(uidl, aligns, col,
  5710. visibleColumnIndex);
  5711. }
  5712. }
  5713. private void addSpannedCell(UIDL rowUidl, Widget w, char align,
  5714. String style, boolean sorted, int colCount) {
  5715. TableCellElement td = DOM.createTD().cast();
  5716. td.setColSpan(colCount);
  5717. initCellWithWidget(w, align, style, sorted, td);
  5718. }
  5719. private void addSpannedCell(UIDL rowUidl, String text, char align,
  5720. String style, boolean textIsHTML, boolean sorted,
  5721. String description, int colCount) {
  5722. // String only content is optimized by not using Label widget
  5723. final TableCellElement td = DOM.createTD().cast();
  5724. td.setColSpan(colCount);
  5725. initCellWithText(text, align, style, textIsHTML, sorted,
  5726. description, td);
  5727. }
  5728. @Override
  5729. protected void setCellWidth(int cellIx, int width) {
  5730. if (isSpanColumns()) {
  5731. if (showRowHeaders) {
  5732. if (cellIx == 0) {
  5733. super.setCellWidth(0, width);
  5734. } else {
  5735. // We need to recalculate the spanning TDs width for
  5736. // every cellIx in order to support column resizing.
  5737. calcAndSetSpanWidthOnCell(1);
  5738. }
  5739. } else {
  5740. // Same as above.
  5741. calcAndSetSpanWidthOnCell(0);
  5742. }
  5743. } else {
  5744. super.setCellWidth(cellIx, width);
  5745. }
  5746. }
  5747. private void calcAndSetSpanWidthOnCell(final int cellIx) {
  5748. int spanWidth = 0;
  5749. for (int ix = (showRowHeaders ? 1 : 0); ix < tHead
  5750. .getVisibleCellCount(); ix++) {
  5751. spanWidth += tHead.getHeaderCell(ix).getOffsetWidth();
  5752. }
  5753. Util.setWidthExcludingPaddingAndBorder((Element) getElement()
  5754. .getChild(cellIx), spanWidth, 13, false);
  5755. }
  5756. }
  5757. /**
  5758. * Ensure the component has a focus.
  5759. *
  5760. * TODO the current implementation simply always calls focus for the
  5761. * component. In case the Table at some point implements focus/blur
  5762. * listeners, this method needs to be evolved to conditionally call
  5763. * focus only if not currently focused.
  5764. */
  5765. protected void ensureFocus() {
  5766. if (!hasFocus) {
  5767. scrollBodyPanel.setFocus(true);
  5768. }
  5769. }
  5770. }
  5771. /**
  5772. * Deselects all items
  5773. */
  5774. public void deselectAll() {
  5775. for (Widget w : scrollBody) {
  5776. VScrollTableRow row = (VScrollTableRow) w;
  5777. if (row.isSelected()) {
  5778. row.toggleSelection();
  5779. }
  5780. }
  5781. // still ensure all selects are removed from (not necessary rendered)
  5782. selectedRowKeys.clear();
  5783. selectedRowRanges.clear();
  5784. // also notify server that it clears all previous selections (the client
  5785. // side does not know about the invisible ones)
  5786. instructServerToForgetPreviousSelections();
  5787. }
  5788. /**
  5789. * Used in multiselect mode when the client side knows that all selections
  5790. * are in the next request.
  5791. */
  5792. private void instructServerToForgetPreviousSelections() {
  5793. client.updateVariable(paintableId, "clearSelections", true, false);
  5794. }
  5795. /**
  5796. * Determines the pagelength when the table height is fixed.
  5797. */
  5798. public void updatePageLength() {
  5799. // Only update if visible and enabled
  5800. if (!isVisible() || !enabled) {
  5801. return;
  5802. }
  5803. if (scrollBody == null) {
  5804. return;
  5805. }
  5806. if (isDynamicHeight()) {
  5807. return;
  5808. }
  5809. int rowHeight = (int) Math.round(scrollBody.getRowHeight());
  5810. int bodyH = scrollBodyPanel.getOffsetHeight();
  5811. int rowsAtOnce = bodyH / rowHeight;
  5812. boolean anotherPartlyVisible = ((bodyH % rowHeight) != 0);
  5813. if (anotherPartlyVisible) {
  5814. rowsAtOnce++;
  5815. }
  5816. if (pageLength != rowsAtOnce) {
  5817. pageLength = rowsAtOnce;
  5818. client.updateVariable(paintableId, "pagelength", pageLength, false);
  5819. if (!rendering) {
  5820. int currentlyVisible = scrollBody.getLastRendered()
  5821. - scrollBody.getFirstRendered();
  5822. if (currentlyVisible < pageLength
  5823. && currentlyVisible < totalRows) {
  5824. // shake scrollpanel to fill empty space
  5825. scrollBodyPanel.setScrollPosition(scrollTop + 1);
  5826. scrollBodyPanel.setScrollPosition(scrollTop - 1);
  5827. }
  5828. sizeNeedsInit = true;
  5829. }
  5830. }
  5831. }
  5832. /** For internal use only. May be removed or replaced in the future. */
  5833. public void updateWidth() {
  5834. if (!isVisible()) {
  5835. /*
  5836. * Do not update size when the table is hidden as all column widths
  5837. * will be set to zero and they won't be recalculated when the table
  5838. * is set visible again (until the size changes again)
  5839. */
  5840. return;
  5841. }
  5842. if (!isDynamicWidth()) {
  5843. int innerPixels = getOffsetWidth() - getBorderWidth();
  5844. if (innerPixels < 0) {
  5845. innerPixels = 0;
  5846. }
  5847. setContentWidth(innerPixels);
  5848. // readjust undefined width columns
  5849. triggerLazyColumnAdjustment(false);
  5850. } else {
  5851. sizeNeedsInit = true;
  5852. // readjust undefined width columns
  5853. triggerLazyColumnAdjustment(false);
  5854. }
  5855. /*
  5856. * setting width may affect wheter the component has scrollbars -> needs
  5857. * scrolling or not
  5858. */
  5859. setProperTabIndex();
  5860. }
  5861. private static final int LAZY_COLUMN_ADJUST_TIMEOUT = 300;
  5862. private final Timer lazyAdjustColumnWidths = new Timer() {
  5863. /**
  5864. * Check for column widths, and available width, to see if we can fix
  5865. * column widths "optimally". Doing this lazily to avoid expensive
  5866. * calculation when resizing is not yet finished.
  5867. */
  5868. @Override
  5869. public void run() {
  5870. if (scrollBody == null) {
  5871. // Try again later if we get here before scrollBody has been
  5872. // initalized
  5873. triggerLazyColumnAdjustment(false);
  5874. return;
  5875. }
  5876. Iterator<Widget> headCells = tHead.iterator();
  5877. int usedMinimumWidth = 0;
  5878. int totalExplicitColumnsWidths = 0;
  5879. float expandRatioDivider = 0;
  5880. int colIndex = 0;
  5881. int hierarchyColumnIndent = scrollBody.getMaxIndent();
  5882. int hierarchyColumnIndex = getHierarchyColumnIndex();
  5883. HeaderCell hierarchyHeaderInNeedOfFurtherHandling = null;
  5884. while (headCells.hasNext()) {
  5885. final HeaderCell hCell = (HeaderCell) headCells.next();
  5886. boolean hasIndent = hierarchyColumnIndent > 0
  5887. && hCell.isHierarchyColumn();
  5888. if (hCell.isDefinedWidth()) {
  5889. // get width without indent to find out whether adjustments
  5890. // are needed (requires special handling further ahead)
  5891. int w = hCell.getWidth();
  5892. if (hasIndent && w < hierarchyColumnIndent) {
  5893. // enforce indent if necessary
  5894. w = hierarchyColumnIndent;
  5895. hierarchyHeaderInNeedOfFurtherHandling = hCell;
  5896. }
  5897. totalExplicitColumnsWidths += w;
  5898. usedMinimumWidth += w;
  5899. } else {
  5900. // natural width already includes indent if any
  5901. int naturalColumnWidth = hCell
  5902. .getNaturalColumnWidth(colIndex);
  5903. usedMinimumWidth += naturalColumnWidth;
  5904. expandRatioDivider += hCell.getExpandRatio();
  5905. if (hasIndent) {
  5906. hierarchyHeaderInNeedOfFurtherHandling = hCell;
  5907. }
  5908. }
  5909. colIndex++;
  5910. }
  5911. int availW = scrollBody.getAvailableWidth();
  5912. // Hey IE, are you really sure about this?
  5913. availW = scrollBody.getAvailableWidth();
  5914. int visibleCellCount = tHead.getVisibleCellCount();
  5915. int totalExtraWidth = scrollBody.getCellExtraWidth()
  5916. * visibleCellCount;
  5917. if (willHaveScrollbars()) {
  5918. totalExtraWidth += Util.getNativeScrollbarSize();
  5919. }
  5920. availW -= totalExtraWidth;
  5921. int forceScrollBodyWidth = -1;
  5922. int extraSpace = availW - usedMinimumWidth;
  5923. if (extraSpace < 0) {
  5924. if (getTotalRows() == 0) {
  5925. /*
  5926. * Too wide header combined with no rows in the table.
  5927. *
  5928. * No horizontal scrollbars would be displayed because
  5929. * there's no rows that grows too wide causing the
  5930. * scrollBody container div to overflow. Must explicitely
  5931. * force a width to a scrollbar. (see #9187)
  5932. */
  5933. forceScrollBodyWidth = usedMinimumWidth + totalExtraWidth;
  5934. }
  5935. extraSpace = 0;
  5936. }
  5937. if (forceScrollBodyWidth > 0) {
  5938. scrollBody.container.getStyle().setWidth(forceScrollBodyWidth,
  5939. Unit.PX);
  5940. } else {
  5941. // Clear width that might have been set to force horizontal
  5942. // scrolling if there are no rows
  5943. scrollBody.container.getStyle().clearWidth();
  5944. }
  5945. int totalUndefinedNaturalWidths = usedMinimumWidth
  5946. - totalExplicitColumnsWidths;
  5947. if (hierarchyHeaderInNeedOfFurtherHandling != null
  5948. && !hierarchyHeaderInNeedOfFurtherHandling.isDefinedWidth()) {
  5949. // ensure the cell gets enough space for the indent
  5950. int w = hierarchyHeaderInNeedOfFurtherHandling
  5951. .getNaturalColumnWidth(hierarchyColumnIndex);
  5952. int newSpace = Math.round(w + (float) extraSpace * (float) w
  5953. / totalUndefinedNaturalWidths);
  5954. if (newSpace >= hierarchyColumnIndent) {
  5955. // no special handling required
  5956. hierarchyHeaderInNeedOfFurtherHandling = null;
  5957. } else {
  5958. // treat as a defined width column of indent's width
  5959. totalExplicitColumnsWidths += hierarchyColumnIndent;
  5960. usedMinimumWidth -= w - hierarchyColumnIndent;
  5961. totalUndefinedNaturalWidths = usedMinimumWidth
  5962. - totalExplicitColumnsWidths;
  5963. expandRatioDivider += hierarchyHeaderInNeedOfFurtherHandling
  5964. .getExpandRatio();
  5965. extraSpace = Math.max(availW - usedMinimumWidth, 0);
  5966. }
  5967. }
  5968. // we have some space that can be divided optimally
  5969. HeaderCell hCell;
  5970. colIndex = 0;
  5971. headCells = tHead.iterator();
  5972. int checksum = 0;
  5973. while (headCells.hasNext()) {
  5974. hCell = (HeaderCell) headCells.next();
  5975. if (hCell.isResizing) {
  5976. continue;
  5977. }
  5978. if (!hCell.isDefinedWidth()) {
  5979. int w = hCell.getNaturalColumnWidth(colIndex);
  5980. int newSpace;
  5981. if (expandRatioDivider > 0) {
  5982. // divide excess space by expand ratios
  5983. newSpace = Math.round((w + extraSpace
  5984. * hCell.getExpandRatio() / expandRatioDivider));
  5985. } else {
  5986. if (hierarchyHeaderInNeedOfFurtherHandling == hCell) {
  5987. // still exists, so needs exactly the indent's width
  5988. newSpace = hierarchyColumnIndent;
  5989. } else if (totalUndefinedNaturalWidths != 0) {
  5990. // divide relatively to natural column widths
  5991. newSpace = Math.round(w + (float) extraSpace
  5992. * (float) w / totalUndefinedNaturalWidths);
  5993. } else {
  5994. newSpace = w;
  5995. }
  5996. }
  5997. checksum += newSpace;
  5998. setColWidth(colIndex, newSpace, false);
  5999. } else {
  6000. if (hierarchyHeaderInNeedOfFurtherHandling == hCell) {
  6001. // defined with enforced into indent width
  6002. checksum += hierarchyColumnIndent;
  6003. setColWidth(colIndex, hierarchyColumnIndent, false);
  6004. } else {
  6005. int cellWidth = hCell.getWidthWithIndent();
  6006. checksum += cellWidth;
  6007. if (hCell.isHierarchyColumn()) {
  6008. // update in case the indent has changed
  6009. // (not detectable earlier)
  6010. setColWidth(colIndex, cellWidth, true);
  6011. }
  6012. }
  6013. }
  6014. colIndex++;
  6015. }
  6016. if (extraSpace > 0 && checksum != availW) {
  6017. /*
  6018. * There might be in some cases a rounding error of 1px when
  6019. * extra space is divided so if there is one then we give the
  6020. * first undefined column 1 more pixel
  6021. */
  6022. headCells = tHead.iterator();
  6023. colIndex = 0;
  6024. while (headCells.hasNext()) {
  6025. HeaderCell hc = (HeaderCell) headCells.next();
  6026. if (!hc.isResizing && !hc.isDefinedWidth()) {
  6027. setColWidth(colIndex, hc.getWidthWithIndent() + availW
  6028. - checksum, false);
  6029. break;
  6030. }
  6031. colIndex++;
  6032. }
  6033. }
  6034. if (isDynamicHeight() && totalRows == pageLength) {
  6035. // fix body height (may vary if lazy loading is offhorizontal
  6036. // scrollbar appears/disappears)
  6037. int bodyHeight = scrollBody.getRequiredHeight();
  6038. boolean needsSpaceForHorizontalScrollbar = (availW < usedMinimumWidth);
  6039. if (needsSpaceForHorizontalScrollbar) {
  6040. bodyHeight += Util.getNativeScrollbarSize();
  6041. }
  6042. int heightBefore = getOffsetHeight();
  6043. scrollBodyPanel.setHeight(bodyHeight + "px");
  6044. if (heightBefore != getOffsetHeight()) {
  6045. Util.notifyParentOfSizeChange(VScrollTable.this, rendering);
  6046. }
  6047. }
  6048. Util.runWebkitOverflowAutoFixDeferred(scrollBodyPanel.getElement());
  6049. forceRealignColumnHeaders();
  6050. }
  6051. };
  6052. private void forceRealignColumnHeaders() {
  6053. if (BrowserInfo.get().isIE()) {
  6054. /*
  6055. * IE does not fire onscroll event if scroll position is reverted to
  6056. * 0 due to the content element size growth. Ensure headers are in
  6057. * sync with content manually. Safe to use null event as we don't
  6058. * actually use the event object in listener.
  6059. */
  6060. onScroll(null);
  6061. }
  6062. }
  6063. /**
  6064. * helper to set pixel size of head and body part
  6065. *
  6066. * @param pixels
  6067. */
  6068. private void setContentWidth(int pixels) {
  6069. tHead.setWidth(pixels + "px");
  6070. scrollBodyPanel.setWidth(pixels + "px");
  6071. tFoot.setWidth(pixels + "px");
  6072. }
  6073. private int borderWidth = -1;
  6074. /**
  6075. * @return border left + border right
  6076. */
  6077. private int getBorderWidth() {
  6078. if (borderWidth < 0) {
  6079. borderWidth = Util.measureHorizontalPaddingAndBorder(
  6080. scrollBodyPanel.getElement(), 2);
  6081. if (borderWidth < 0) {
  6082. borderWidth = 0;
  6083. }
  6084. }
  6085. return borderWidth;
  6086. }
  6087. /**
  6088. * Ensures scrollable area is properly sized. This method is used when fixed
  6089. * size is used.
  6090. */
  6091. private int containerHeight;
  6092. private void setContainerHeight() {
  6093. if (!isDynamicHeight()) {
  6094. /*
  6095. * Android 2.3 cannot measure the height of the inline-block
  6096. * properly, and will return the wrong offset height. So for android
  6097. * 2.3 we set the element to a block element while measuring and
  6098. * then restore it which yields the correct result. #11331
  6099. */
  6100. if (BrowserInfo.get().isAndroid23()) {
  6101. getElement().getStyle().setDisplay(Display.BLOCK);
  6102. }
  6103. containerHeight = getOffsetHeight();
  6104. containerHeight -= showColHeaders ? tHead.getOffsetHeight() : 0;
  6105. containerHeight -= tFoot.getOffsetHeight();
  6106. containerHeight -= getContentAreaBorderHeight();
  6107. if (containerHeight < 0) {
  6108. containerHeight = 0;
  6109. }
  6110. scrollBodyPanel.setHeight(containerHeight + "px");
  6111. if (BrowserInfo.get().isAndroid23()) {
  6112. getElement().getStyle().clearDisplay();
  6113. }
  6114. }
  6115. }
  6116. private int contentAreaBorderHeight = -1;
  6117. private int scrollLeft;
  6118. private int scrollTop;
  6119. /** For internal use only. May be removed or replaced in the future. */
  6120. public VScrollTableDropHandler dropHandler;
  6121. private boolean navKeyDown;
  6122. /** For internal use only. May be removed or replaced in the future. */
  6123. public boolean multiselectPending;
  6124. /**
  6125. * @return border top + border bottom of the scrollable area of table
  6126. */
  6127. private int getContentAreaBorderHeight() {
  6128. if (contentAreaBorderHeight < 0) {
  6129. scrollBodyPanel.getElement().getStyle()
  6130. .setOverflow(Overflow.HIDDEN);
  6131. int oh = scrollBodyPanel.getOffsetHeight();
  6132. int ch = scrollBodyPanel.getElement()
  6133. .getPropertyInt("clientHeight");
  6134. contentAreaBorderHeight = oh - ch;
  6135. scrollBodyPanel.getElement().getStyle().setOverflow(Overflow.AUTO);
  6136. }
  6137. return contentAreaBorderHeight;
  6138. }
  6139. @Override
  6140. public void setHeight(String height) {
  6141. if (height.length() == 0
  6142. && getElement().getStyle().getHeight().length() != 0) {
  6143. /*
  6144. * Changing from defined to undefined size -> should do a size init
  6145. * to take page length into account again
  6146. */
  6147. sizeNeedsInit = true;
  6148. }
  6149. super.setHeight(height);
  6150. }
  6151. /** For internal use only. May be removed or replaced in the future. */
  6152. public void updateHeight() {
  6153. setContainerHeight();
  6154. if (initializedAndAttached) {
  6155. updatePageLength();
  6156. }
  6157. if (!rendering) {
  6158. // Webkit may sometimes get an odd rendering bug (white space
  6159. // between header and body), see bug #3875. Running
  6160. // overflow hack here to shake body element a bit.
  6161. // We must run the fix as a deferred command to prevent it from
  6162. // overwriting the scroll position with an outdated value, see
  6163. // #7607.
  6164. Util.runWebkitOverflowAutoFixDeferred(scrollBodyPanel.getElement());
  6165. }
  6166. triggerLazyColumnAdjustment(false);
  6167. /*
  6168. * setting height may affect wheter the component has scrollbars ->
  6169. * needs scrolling or not
  6170. */
  6171. setProperTabIndex();
  6172. }
  6173. /*
  6174. * Overridden due Table might not survive of visibility change (scroll pos
  6175. * lost). Example ITabPanel just set contained components invisible and back
  6176. * when changing tabs.
  6177. */
  6178. @Override
  6179. public void setVisible(boolean visible) {
  6180. if (isVisible() != visible) {
  6181. super.setVisible(visible);
  6182. if (initializedAndAttached) {
  6183. if (visible) {
  6184. Scheduler.get().scheduleDeferred(new Command() {
  6185. @Override
  6186. public void execute() {
  6187. scrollBodyPanel
  6188. .setScrollPosition(measureRowHeightOffset(firstRowInViewPort));
  6189. }
  6190. });
  6191. }
  6192. }
  6193. }
  6194. }
  6195. /**
  6196. * Helper function to build html snippet for column or row headers
  6197. *
  6198. * @param uidl
  6199. * possibly with values caption and icon
  6200. * @return html snippet containing possibly an icon + caption text
  6201. */
  6202. protected String buildCaptionHtmlSnippet(UIDL uidl) {
  6203. String s = uidl.hasAttribute("caption") ? uidl
  6204. .getStringAttribute("caption") : "";
  6205. if (uidl.hasAttribute("icon")) {
  6206. Icon icon = client.getIcon(uidl.getStringAttribute("icon"));
  6207. icon.setAlternateText("icon");
  6208. s = icon.getElement().getString() + s;
  6209. }
  6210. return s;
  6211. }
  6212. /**
  6213. * This method has logic which rows needs to be requested from server when
  6214. * user scrolls
  6215. */
  6216. @Override
  6217. public void onScroll(ScrollEvent event) {
  6218. // Do not handle scroll events while there is scroll initiated from
  6219. // server side which is not yet executed (#11454)
  6220. if (isLazyScrollerActive()) {
  6221. return;
  6222. }
  6223. scrollLeft = scrollBodyPanel.getElement().getScrollLeft();
  6224. scrollTop = scrollBodyPanel.getScrollPosition();
  6225. /*
  6226. * #6970 - IE sometimes fires scroll events for a detached table.
  6227. *
  6228. * FIXME initializedAndAttached should probably be renamed - its name
  6229. * doesn't seem to reflect its semantics. onDetach() doesn't set it to
  6230. * false, and changing that might break something else, so we need to
  6231. * check isAttached() separately.
  6232. */
  6233. if (!initializedAndAttached || !isAttached()) {
  6234. return;
  6235. }
  6236. if (!enabled) {
  6237. scrollBodyPanel
  6238. .setScrollPosition(measureRowHeightOffset(firstRowInViewPort));
  6239. return;
  6240. }
  6241. rowRequestHandler.cancel();
  6242. if (BrowserInfo.get().isSafari() && event != null && scrollTop == 0) {
  6243. // due to the webkitoverflowworkaround, top may sometimes report 0
  6244. // for webkit, although it really is not. Expecting to have the
  6245. // correct
  6246. // value available soon.
  6247. Scheduler.get().scheduleDeferred(new Command() {
  6248. @Override
  6249. public void execute() {
  6250. onScroll(null);
  6251. }
  6252. });
  6253. return;
  6254. }
  6255. // fix headers horizontal scrolling
  6256. tHead.setHorizontalScrollPosition(scrollLeft);
  6257. // fix footers horizontal scrolling
  6258. tFoot.setHorizontalScrollPosition(scrollLeft);
  6259. if (totalRows == 0) {
  6260. // No rows, no need to fetch new rows
  6261. return;
  6262. }
  6263. firstRowInViewPort = calcFirstRowInViewPort();
  6264. int maxFirstRow = totalRows - pageLength;
  6265. if (firstRowInViewPort > maxFirstRow && maxFirstRow >= 0) {
  6266. firstRowInViewPort = maxFirstRow;
  6267. }
  6268. int postLimit = (int) (firstRowInViewPort + (pageLength - 1) + pageLength
  6269. * cache_react_rate);
  6270. if (postLimit > totalRows - 1) {
  6271. postLimit = totalRows - 1;
  6272. }
  6273. int preLimit = (int) (firstRowInViewPort - pageLength
  6274. * cache_react_rate);
  6275. if (preLimit < 0) {
  6276. preLimit = 0;
  6277. }
  6278. final int lastRendered = scrollBody.getLastRendered();
  6279. final int firstRendered = scrollBody.getFirstRendered();
  6280. if (postLimit <= lastRendered && preLimit >= firstRendered) {
  6281. // we're within no-react area, no need to request more rows
  6282. // remember which firstvisible we requested, in case the server has
  6283. // a differing opinion
  6284. lastRequestedFirstvisible = firstRowInViewPort;
  6285. client.updateVariable(paintableId, "firstvisible",
  6286. firstRowInViewPort, false);
  6287. return;
  6288. }
  6289. if (firstRowInViewPort - pageLength * cache_rate > lastRendered
  6290. || firstRowInViewPort + pageLength + pageLength * cache_rate < firstRendered) {
  6291. // need a totally new set of rows
  6292. rowRequestHandler
  6293. .setReqFirstRow((firstRowInViewPort - (int) (pageLength * cache_rate)));
  6294. int last = firstRowInViewPort + (int) (cache_rate * pageLength)
  6295. + pageLength - 1;
  6296. if (last >= totalRows) {
  6297. last = totalRows - 1;
  6298. }
  6299. rowRequestHandler.setReqRows(last
  6300. - rowRequestHandler.getReqFirstRow() + 1);
  6301. rowRequestHandler.deferRowFetch();
  6302. return;
  6303. }
  6304. if (preLimit < firstRendered) {
  6305. // need some rows to the beginning of the rendered area
  6306. rowRequestHandler
  6307. .setReqFirstRow((int) (firstRowInViewPort - pageLength
  6308. * cache_rate));
  6309. rowRequestHandler.setReqRows(firstRendered
  6310. - rowRequestHandler.getReqFirstRow());
  6311. rowRequestHandler.deferRowFetch();
  6312. return;
  6313. }
  6314. if (postLimit > lastRendered) {
  6315. // need some rows to the end of the rendered area
  6316. int reqRows = (int) ((firstRowInViewPort + pageLength + pageLength
  6317. * cache_rate) - lastRendered);
  6318. rowRequestHandler.triggerRowFetch(lastRendered + 1, reqRows);
  6319. }
  6320. }
  6321. protected int calcFirstRowInViewPort() {
  6322. return (int) Math.ceil(scrollTop / scrollBody.getRowHeight());
  6323. }
  6324. @Override
  6325. public VScrollTableDropHandler getDropHandler() {
  6326. return dropHandler;
  6327. }
  6328. private static class TableDDDetails {
  6329. int overkey = -1;
  6330. VerticalDropLocation dropLocation;
  6331. String colkey;
  6332. @Override
  6333. public boolean equals(Object obj) {
  6334. if (obj instanceof TableDDDetails) {
  6335. TableDDDetails other = (TableDDDetails) obj;
  6336. return dropLocation == other.dropLocation
  6337. && overkey == other.overkey
  6338. && ((colkey != null && colkey.equals(other.colkey)) || (colkey == null && other.colkey == null));
  6339. }
  6340. return false;
  6341. }
  6342. //
  6343. // public int hashCode() {
  6344. // return overkey;
  6345. // }
  6346. }
  6347. public class VScrollTableDropHandler extends VAbstractDropHandler {
  6348. private static final String ROWSTYLEBASE = "v-table-row-drag-";
  6349. private TableDDDetails dropDetails;
  6350. private TableDDDetails lastEmphasized;
  6351. @Override
  6352. public void dragEnter(VDragEvent drag) {
  6353. updateDropDetails(drag);
  6354. super.dragEnter(drag);
  6355. }
  6356. private void updateDropDetails(VDragEvent drag) {
  6357. dropDetails = new TableDDDetails();
  6358. Element elementOver = drag.getElementOver();
  6359. Class<? extends Widget> clazz = getRowClass();
  6360. VScrollTableRow row = null;
  6361. if (clazz != null) {
  6362. row = Util.findWidget(elementOver, clazz);
  6363. }
  6364. if (row != null) {
  6365. dropDetails.overkey = row.rowKey;
  6366. Element tr = row.getElement();
  6367. Element element = elementOver;
  6368. while (element != null && element.getParentElement() != tr) {
  6369. element = element.getParentElement();
  6370. }
  6371. int childIndex = DOM.getChildIndex(tr, element);
  6372. dropDetails.colkey = tHead.getHeaderCell(childIndex)
  6373. .getColKey();
  6374. dropDetails.dropLocation = DDUtil.getVerticalDropLocation(
  6375. row.getElement(), drag.getCurrentGwtEvent(), 0.2);
  6376. }
  6377. drag.getDropDetails().put("itemIdOver", dropDetails.overkey + "");
  6378. drag.getDropDetails().put(
  6379. "detail",
  6380. dropDetails.dropLocation != null ? dropDetails.dropLocation
  6381. .toString() : null);
  6382. }
  6383. private Class<? extends Widget> getRowClass() {
  6384. // get the row type this way to make dd work in derived
  6385. // implementations
  6386. Iterator<Widget> iterator = scrollBody.iterator();
  6387. if (iterator.hasNext()) {
  6388. return iterator.next().getClass();
  6389. } else {
  6390. return null;
  6391. }
  6392. }
  6393. @Override
  6394. public void dragOver(VDragEvent drag) {
  6395. TableDDDetails oldDetails = dropDetails;
  6396. updateDropDetails(drag);
  6397. if (!oldDetails.equals(dropDetails)) {
  6398. deEmphasis();
  6399. final TableDDDetails newDetails = dropDetails;
  6400. VAcceptCallback cb = new VAcceptCallback() {
  6401. @Override
  6402. public void accepted(VDragEvent event) {
  6403. if (newDetails.equals(dropDetails)) {
  6404. dragAccepted(event);
  6405. }
  6406. /*
  6407. * Else new target slot already defined, ignore
  6408. */
  6409. }
  6410. };
  6411. validate(cb, drag);
  6412. }
  6413. }
  6414. @Override
  6415. public void dragLeave(VDragEvent drag) {
  6416. deEmphasis();
  6417. super.dragLeave(drag);
  6418. }
  6419. @Override
  6420. public boolean drop(VDragEvent drag) {
  6421. deEmphasis();
  6422. return super.drop(drag);
  6423. }
  6424. private void deEmphasis() {
  6425. UIObject.setStyleName(getElement(),
  6426. getStylePrimaryName() + "-drag", false);
  6427. if (lastEmphasized == null) {
  6428. return;
  6429. }
  6430. for (Widget w : scrollBody.renderedRows) {
  6431. VScrollTableRow row = (VScrollTableRow) w;
  6432. if (lastEmphasized != null
  6433. && row.rowKey == lastEmphasized.overkey) {
  6434. String stylename = ROWSTYLEBASE
  6435. + lastEmphasized.dropLocation.toString()
  6436. .toLowerCase();
  6437. VScrollTableRow.setStyleName(row.getElement(), stylename,
  6438. false);
  6439. lastEmphasized = null;
  6440. return;
  6441. }
  6442. }
  6443. }
  6444. /**
  6445. * TODO needs different drop modes ?? (on cells, on rows), now only
  6446. * supports rows
  6447. */
  6448. private void emphasis(TableDDDetails details) {
  6449. deEmphasis();
  6450. UIObject.setStyleName(getElement(),
  6451. getStylePrimaryName() + "-drag", true);
  6452. // iterate old and new emphasized row
  6453. for (Widget w : scrollBody.renderedRows) {
  6454. VScrollTableRow row = (VScrollTableRow) w;
  6455. if (details != null && details.overkey == row.rowKey) {
  6456. String stylename = ROWSTYLEBASE
  6457. + details.dropLocation.toString().toLowerCase();
  6458. VScrollTableRow.setStyleName(row.getElement(), stylename,
  6459. true);
  6460. lastEmphasized = details;
  6461. return;
  6462. }
  6463. }
  6464. }
  6465. @Override
  6466. protected void dragAccepted(VDragEvent drag) {
  6467. emphasis(dropDetails);
  6468. }
  6469. @Override
  6470. public ComponentConnector getConnector() {
  6471. return ConnectorMap.get(client).getConnector(VScrollTable.this);
  6472. }
  6473. @Override
  6474. public ApplicationConnection getApplicationConnection() {
  6475. return client;
  6476. }
  6477. }
  6478. protected VScrollTableRow getFocusedRow() {
  6479. return focusedRow;
  6480. }
  6481. /**
  6482. * Moves the selection head to a specific row
  6483. *
  6484. * @param row
  6485. * The row to where the selection head should move
  6486. * @return Returns true if focus was moved successfully, else false
  6487. */
  6488. public boolean setRowFocus(VScrollTableRow row) {
  6489. if (!isSelectable()) {
  6490. return false;
  6491. }
  6492. // Remove previous selection
  6493. if (focusedRow != null && focusedRow != row) {
  6494. focusedRow.removeStyleName(getStylePrimaryName() + "-focus");
  6495. }
  6496. if (row != null) {
  6497. // Apply focus style to new selection
  6498. row.addStyleName(getStylePrimaryName() + "-focus");
  6499. /*
  6500. * Trying to set focus on already focused row
  6501. */
  6502. if (row == focusedRow) {
  6503. return false;
  6504. }
  6505. // Set new focused row
  6506. focusedRow = row;
  6507. ensureRowIsVisible(row);
  6508. return true;
  6509. }
  6510. return false;
  6511. }
  6512. /**
  6513. * Ensures that the row is visible
  6514. *
  6515. * @param row
  6516. * The row to ensure is visible
  6517. */
  6518. private void ensureRowIsVisible(VScrollTableRow row) {
  6519. if (BrowserInfo.get().isTouchDevice()) {
  6520. // Skip due to android devices that have broken scrolltop will may
  6521. // get odd scrolling here.
  6522. return;
  6523. }
  6524. Util.scrollIntoViewVertically(row.getElement());
  6525. }
  6526. /**
  6527. * Handles the keyboard events handled by the table
  6528. *
  6529. * @param event
  6530. * The keyboard event received
  6531. * @return true iff the navigation event was handled
  6532. */
  6533. protected boolean handleNavigation(int keycode, boolean ctrl, boolean shift) {
  6534. if (keycode == KeyCodes.KEY_TAB || keycode == KeyCodes.KEY_SHIFT) {
  6535. // Do not handle tab key
  6536. return false;
  6537. }
  6538. // Down navigation
  6539. if (!isSelectable() && keycode == getNavigationDownKey()) {
  6540. scrollBodyPanel.setScrollPosition(scrollBodyPanel
  6541. .getScrollPosition() + scrollingVelocity);
  6542. return true;
  6543. } else if (keycode == getNavigationDownKey()) {
  6544. if (isMultiSelectModeAny() && moveFocusDown()) {
  6545. selectFocusedRow(ctrl, shift);
  6546. } else if (isSingleSelectMode() && !shift && moveFocusDown()) {
  6547. selectFocusedRow(ctrl, shift);
  6548. }
  6549. return true;
  6550. }
  6551. // Up navigation
  6552. if (!isSelectable() && keycode == getNavigationUpKey()) {
  6553. scrollBodyPanel.setScrollPosition(scrollBodyPanel
  6554. .getScrollPosition() - scrollingVelocity);
  6555. return true;
  6556. } else if (keycode == getNavigationUpKey()) {
  6557. if (isMultiSelectModeAny() && moveFocusUp()) {
  6558. selectFocusedRow(ctrl, shift);
  6559. } else if (isSingleSelectMode() && !shift && moveFocusUp()) {
  6560. selectFocusedRow(ctrl, shift);
  6561. }
  6562. return true;
  6563. }
  6564. if (keycode == getNavigationLeftKey()) {
  6565. // Left navigation
  6566. scrollBodyPanel.setHorizontalScrollPosition(scrollBodyPanel
  6567. .getHorizontalScrollPosition() - scrollingVelocity);
  6568. return true;
  6569. } else if (keycode == getNavigationRightKey()) {
  6570. // Right navigation
  6571. scrollBodyPanel.setHorizontalScrollPosition(scrollBodyPanel
  6572. .getHorizontalScrollPosition() + scrollingVelocity);
  6573. return true;
  6574. }
  6575. // Select navigation
  6576. if (isSelectable() && keycode == getNavigationSelectKey()) {
  6577. if (isSingleSelectMode()) {
  6578. boolean wasSelected = focusedRow.isSelected();
  6579. deselectAll();
  6580. if (!wasSelected || !nullSelectionAllowed) {
  6581. focusedRow.toggleSelection();
  6582. }
  6583. } else {
  6584. focusedRow.toggleSelection();
  6585. removeRowFromUnsentSelectionRanges(focusedRow);
  6586. }
  6587. sendSelectedRows();
  6588. return true;
  6589. }
  6590. // Page Down navigation
  6591. if (keycode == getNavigationPageDownKey()) {
  6592. if (isSelectable()) {
  6593. /*
  6594. * If selectable we plagiate MSW behaviour: first scroll to the
  6595. * end of current view. If at the end, scroll down one page
  6596. * length and keep the selected row in the bottom part of
  6597. * visible area.
  6598. */
  6599. if (!isFocusAtTheEndOfTable()) {
  6600. VScrollTableRow lastVisibleRowInViewPort = scrollBody
  6601. .getRowByRowIndex(firstRowInViewPort
  6602. + getFullyVisibleRowCount() - 1);
  6603. if (lastVisibleRowInViewPort != null
  6604. && lastVisibleRowInViewPort != focusedRow) {
  6605. // focused row is not at the end of the table, move
  6606. // focus and select the last visible row
  6607. setRowFocus(lastVisibleRowInViewPort);
  6608. selectFocusedRow(ctrl, shift);
  6609. sendSelectedRows();
  6610. } else {
  6611. int indexOfToBeFocused = focusedRow.getIndex()
  6612. + getFullyVisibleRowCount();
  6613. if (indexOfToBeFocused >= totalRows) {
  6614. indexOfToBeFocused = totalRows - 1;
  6615. }
  6616. VScrollTableRow toBeFocusedRow = scrollBody
  6617. .getRowByRowIndex(indexOfToBeFocused);
  6618. if (toBeFocusedRow != null) {
  6619. /*
  6620. * if the next focused row is rendered
  6621. */
  6622. setRowFocus(toBeFocusedRow);
  6623. selectFocusedRow(ctrl, shift);
  6624. // TODO needs scrollintoview ?
  6625. sendSelectedRows();
  6626. } else {
  6627. // scroll down by pixels and return, to wait for
  6628. // new rows, then select the last item in the
  6629. // viewport
  6630. selectLastItemInNextRender = true;
  6631. multiselectPending = shift;
  6632. scrollByPagelenght(1);
  6633. }
  6634. }
  6635. }
  6636. } else {
  6637. /* No selections, go page down by scrolling */
  6638. scrollByPagelenght(1);
  6639. }
  6640. return true;
  6641. }
  6642. // Page Up navigation
  6643. if (keycode == getNavigationPageUpKey()) {
  6644. if (isSelectable()) {
  6645. /*
  6646. * If selectable we plagiate MSW behaviour: first scroll to the
  6647. * end of current view. If at the end, scroll down one page
  6648. * length and keep the selected row in the bottom part of
  6649. * visible area.
  6650. */
  6651. if (!isFocusAtTheBeginningOfTable()) {
  6652. VScrollTableRow firstVisibleRowInViewPort = scrollBody
  6653. .getRowByRowIndex(firstRowInViewPort);
  6654. if (firstVisibleRowInViewPort != null
  6655. && firstVisibleRowInViewPort != focusedRow) {
  6656. // focus is not at the beginning of the table, move
  6657. // focus and select the first visible row
  6658. setRowFocus(firstVisibleRowInViewPort);
  6659. selectFocusedRow(ctrl, shift);
  6660. sendSelectedRows();
  6661. } else {
  6662. int indexOfToBeFocused = focusedRow.getIndex()
  6663. - getFullyVisibleRowCount();
  6664. if (indexOfToBeFocused < 0) {
  6665. indexOfToBeFocused = 0;
  6666. }
  6667. VScrollTableRow toBeFocusedRow = scrollBody
  6668. .getRowByRowIndex(indexOfToBeFocused);
  6669. if (toBeFocusedRow != null) { // if the next focused row
  6670. // is rendered
  6671. setRowFocus(toBeFocusedRow);
  6672. selectFocusedRow(ctrl, shift);
  6673. // TODO needs scrollintoview ?
  6674. sendSelectedRows();
  6675. } else {
  6676. // unless waiting for the next rowset already
  6677. // scroll down by pixels and return, to wait for
  6678. // new rows, then select the last item in the
  6679. // viewport
  6680. selectFirstItemInNextRender = true;
  6681. multiselectPending = shift;
  6682. scrollByPagelenght(-1);
  6683. }
  6684. }
  6685. }
  6686. } else {
  6687. /* No selections, go page up by scrolling */
  6688. scrollByPagelenght(-1);
  6689. }
  6690. return true;
  6691. }
  6692. // Goto start navigation
  6693. if (keycode == getNavigationStartKey()) {
  6694. scrollBodyPanel.setScrollPosition(0);
  6695. if (isSelectable()) {
  6696. if (focusedRow != null && focusedRow.getIndex() == 0) {
  6697. return false;
  6698. } else {
  6699. VScrollTableRow rowByRowIndex = (VScrollTableRow) scrollBody
  6700. .iterator().next();
  6701. if (rowByRowIndex.getIndex() == 0) {
  6702. setRowFocus(rowByRowIndex);
  6703. selectFocusedRow(ctrl, shift);
  6704. sendSelectedRows();
  6705. } else {
  6706. // first row of table will come in next row fetch
  6707. if (ctrl) {
  6708. focusFirstItemInNextRender = true;
  6709. } else {
  6710. selectFirstItemInNextRender = true;
  6711. multiselectPending = shift;
  6712. }
  6713. }
  6714. }
  6715. }
  6716. return true;
  6717. }
  6718. // Goto end navigation
  6719. if (keycode == getNavigationEndKey()) {
  6720. scrollBodyPanel.setScrollPosition(scrollBody.getOffsetHeight());
  6721. if (isSelectable()) {
  6722. final int lastRendered = scrollBody.getLastRendered();
  6723. if (lastRendered + 1 == totalRows) {
  6724. VScrollTableRow rowByRowIndex = scrollBody
  6725. .getRowByRowIndex(lastRendered);
  6726. if (focusedRow != rowByRowIndex) {
  6727. setRowFocus(rowByRowIndex);
  6728. selectFocusedRow(ctrl, shift);
  6729. sendSelectedRows();
  6730. }
  6731. } else {
  6732. if (ctrl) {
  6733. focusLastItemInNextRender = true;
  6734. } else {
  6735. selectLastItemInNextRender = true;
  6736. multiselectPending = shift;
  6737. }
  6738. }
  6739. }
  6740. return true;
  6741. }
  6742. return false;
  6743. }
  6744. private boolean isFocusAtTheBeginningOfTable() {
  6745. return focusedRow.getIndex() == 0;
  6746. }
  6747. private boolean isFocusAtTheEndOfTable() {
  6748. return focusedRow.getIndex() + 1 >= totalRows;
  6749. }
  6750. private int getFullyVisibleRowCount() {
  6751. return (int) (scrollBodyPanel.getOffsetHeight() / scrollBody
  6752. .getRowHeight());
  6753. }
  6754. private void scrollByPagelenght(int i) {
  6755. int pixels = i * scrollBodyPanel.getOffsetHeight();
  6756. int newPixels = scrollBodyPanel.getScrollPosition() + pixels;
  6757. if (newPixels < 0) {
  6758. newPixels = 0;
  6759. } // else if too high, NOP (all know browsers accept illegally big
  6760. // values here)
  6761. scrollBodyPanel.setScrollPosition(newPixels);
  6762. }
  6763. /*
  6764. * (non-Javadoc)
  6765. *
  6766. * @see
  6767. * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event
  6768. * .dom.client.FocusEvent)
  6769. */
  6770. @Override
  6771. public void onFocus(FocusEvent event) {
  6772. if (isFocusable()) {
  6773. hasFocus = true;
  6774. // Focus a row if no row is in focus
  6775. if (focusedRow == null) {
  6776. focusRowFromBody();
  6777. } else {
  6778. setRowFocus(focusedRow);
  6779. }
  6780. }
  6781. }
  6782. /*
  6783. * (non-Javadoc)
  6784. *
  6785. * @see
  6786. * com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event
  6787. * .dom.client.BlurEvent)
  6788. */
  6789. @Override
  6790. public void onBlur(BlurEvent event) {
  6791. hasFocus = false;
  6792. navKeyDown = false;
  6793. if (BrowserInfo.get().isIE()) {
  6794. /*
  6795. * IE sometimes moves focus to a clicked table cell... (#7965)
  6796. * ...and sometimes it sends blur events even though the focus
  6797. * handler is still active. (#10464)
  6798. */
  6799. Element focusedElement = Util.getIEFocusedElement();
  6800. if (Util.getConnectorForElement(client, getParent(), focusedElement) == this
  6801. && focusedElement != null
  6802. && focusedElement != scrollBodyPanel.getFocusElement()) {
  6803. /*
  6804. * Steal focus back to the focus handler if it was moved to some
  6805. * other part of the table. Avoid stealing focus in other cases.
  6806. */
  6807. focus();
  6808. return;
  6809. }
  6810. }
  6811. if (isFocusable()) {
  6812. // Unfocus any row
  6813. setRowFocus(null);
  6814. }
  6815. }
  6816. /**
  6817. * Removes a key from a range if the key is found in a selected range
  6818. *
  6819. * @param key
  6820. * The key to remove
  6821. */
  6822. private void removeRowFromUnsentSelectionRanges(VScrollTableRow row) {
  6823. Collection<SelectionRange> newRanges = null;
  6824. for (Iterator<SelectionRange> iterator = selectedRowRanges.iterator(); iterator
  6825. .hasNext();) {
  6826. SelectionRange range = iterator.next();
  6827. if (range.inRange(row)) {
  6828. // Split the range if given row is in range
  6829. Collection<SelectionRange> splitranges = range.split(row);
  6830. if (newRanges == null) {
  6831. newRanges = new ArrayList<SelectionRange>();
  6832. }
  6833. newRanges.addAll(splitranges);
  6834. iterator.remove();
  6835. }
  6836. }
  6837. if (newRanges != null) {
  6838. selectedRowRanges.addAll(newRanges);
  6839. }
  6840. }
  6841. /**
  6842. * Can the Table be focused?
  6843. *
  6844. * @return True if the table can be focused, else false
  6845. */
  6846. public boolean isFocusable() {
  6847. if (scrollBody != null && enabled) {
  6848. return !(!hasHorizontalScrollbar() && !hasVerticalScrollbar() && !isSelectable());
  6849. }
  6850. return false;
  6851. }
  6852. private boolean hasHorizontalScrollbar() {
  6853. return scrollBody.getOffsetWidth() > scrollBodyPanel.getOffsetWidth();
  6854. }
  6855. private boolean hasVerticalScrollbar() {
  6856. return scrollBody.getOffsetHeight() > scrollBodyPanel.getOffsetHeight();
  6857. }
  6858. /*
  6859. * (non-Javadoc)
  6860. *
  6861. * @see com.vaadin.client.Focusable#focus()
  6862. */
  6863. @Override
  6864. public void focus() {
  6865. if (isFocusable()) {
  6866. scrollBodyPanel.focus();
  6867. }
  6868. }
  6869. /**
  6870. * Sets the proper tabIndex for scrollBodyPanel (the focusable elemen in the
  6871. * component).
  6872. * <p>
  6873. * If the component has no explicit tabIndex a zero is given (default
  6874. * tabbing order based on dom hierarchy) or -1 if the component does not
  6875. * need to gain focus. The component needs no focus if it has no scrollabars
  6876. * (not scrollable) and not selectable. Note that in the future shortcut
  6877. * actions may need focus.
  6878. * <p>
  6879. * For internal use only. May be removed or replaced in the future.
  6880. */
  6881. public void setProperTabIndex() {
  6882. int storedScrollTop = 0;
  6883. int storedScrollLeft = 0;
  6884. if (BrowserInfo.get().getOperaVersion() >= 11) {
  6885. // Workaround for Opera scroll bug when changing tabIndex (#6222)
  6886. storedScrollTop = scrollBodyPanel.getScrollPosition();
  6887. storedScrollLeft = scrollBodyPanel.getHorizontalScrollPosition();
  6888. }
  6889. if (tabIndex == 0 && !isFocusable()) {
  6890. scrollBodyPanel.setTabIndex(-1);
  6891. } else {
  6892. scrollBodyPanel.setTabIndex(tabIndex);
  6893. }
  6894. if (BrowserInfo.get().getOperaVersion() >= 11) {
  6895. // Workaround for Opera scroll bug when changing tabIndex (#6222)
  6896. scrollBodyPanel.setScrollPosition(storedScrollTop);
  6897. scrollBodyPanel.setHorizontalScrollPosition(storedScrollLeft);
  6898. }
  6899. }
  6900. public void startScrollingVelocityTimer() {
  6901. if (scrollingVelocityTimer == null) {
  6902. scrollingVelocityTimer = new Timer() {
  6903. @Override
  6904. public void run() {
  6905. scrollingVelocity++;
  6906. }
  6907. };
  6908. scrollingVelocityTimer.scheduleRepeating(100);
  6909. }
  6910. }
  6911. public void cancelScrollingVelocityTimer() {
  6912. if (scrollingVelocityTimer != null) {
  6913. // Remove velocityTimer if it exists and the Table is disabled
  6914. scrollingVelocityTimer.cancel();
  6915. scrollingVelocityTimer = null;
  6916. scrollingVelocity = 10;
  6917. }
  6918. }
  6919. /**
  6920. *
  6921. * @param keyCode
  6922. * @return true if the given keyCode is used by the table for navigation
  6923. */
  6924. private boolean isNavigationKey(int keyCode) {
  6925. return keyCode == getNavigationUpKey()
  6926. || keyCode == getNavigationLeftKey()
  6927. || keyCode == getNavigationRightKey()
  6928. || keyCode == getNavigationDownKey()
  6929. || keyCode == getNavigationPageUpKey()
  6930. || keyCode == getNavigationPageDownKey()
  6931. || keyCode == getNavigationEndKey()
  6932. || keyCode == getNavigationStartKey();
  6933. }
  6934. public void lazyRevertFocusToRow(final VScrollTableRow currentlyFocusedRow) {
  6935. Scheduler.get().scheduleFinally(new ScheduledCommand() {
  6936. @Override
  6937. public void execute() {
  6938. if (currentlyFocusedRow != null) {
  6939. setRowFocus(currentlyFocusedRow);
  6940. } else {
  6941. VConsole.log("no row?");
  6942. focusRowFromBody();
  6943. }
  6944. scrollBody.ensureFocus();
  6945. }
  6946. });
  6947. }
  6948. @Override
  6949. public Action[] getActions() {
  6950. if (bodyActionKeys == null) {
  6951. return new Action[] {};
  6952. }
  6953. final Action[] actions = new Action[bodyActionKeys.length];
  6954. for (int i = 0; i < actions.length; i++) {
  6955. final String actionKey = bodyActionKeys[i];
  6956. Action bodyAction = new TreeAction(this, null, actionKey);
  6957. bodyAction.setCaption(getActionCaption(actionKey));
  6958. bodyAction.setIconUrl(getActionIcon(actionKey));
  6959. actions[i] = bodyAction;
  6960. }
  6961. return actions;
  6962. }
  6963. @Override
  6964. public ApplicationConnection getClient() {
  6965. return client;
  6966. }
  6967. @Override
  6968. public String getPaintableId() {
  6969. return paintableId;
  6970. }
  6971. /**
  6972. * Add this to the element mouse down event by using element.setPropertyJSO
  6973. * ("onselectstart",applyDisableTextSelectionIEHack()); Remove it then again
  6974. * when the mouse is depressed in the mouse up event.
  6975. *
  6976. * @return Returns the JSO preventing text selection
  6977. */
  6978. private static native JavaScriptObject getPreventTextSelectionIEHack()
  6979. /*-{
  6980. return function(){ return false; };
  6981. }-*/;
  6982. public void triggerLazyColumnAdjustment(boolean now) {
  6983. lazyAdjustColumnWidths.cancel();
  6984. if (now) {
  6985. lazyAdjustColumnWidths.run();
  6986. } else {
  6987. lazyAdjustColumnWidths.schedule(LAZY_COLUMN_ADJUST_TIMEOUT);
  6988. }
  6989. }
  6990. private boolean isDynamicWidth() {
  6991. ComponentConnector paintable = ConnectorMap.get(client).getConnector(
  6992. this);
  6993. return paintable.isUndefinedWidth();
  6994. }
  6995. private boolean isDynamicHeight() {
  6996. ComponentConnector paintable = ConnectorMap.get(client).getConnector(
  6997. this);
  6998. if (paintable == null) {
  6999. // This should be refactored. As isDynamicHeight can be called from
  7000. // a timer it is possible that the connector has been unregistered
  7001. // when this method is called, causing getConnector to return null.
  7002. return false;
  7003. }
  7004. return paintable.isUndefinedHeight();
  7005. }
  7006. private void debug(String msg) {
  7007. if (enableDebug) {
  7008. VConsole.error(msg);
  7009. }
  7010. }
  7011. public Widget getWidgetForPaintable() {
  7012. return this;
  7013. }
  7014. private static final String SUBPART_HEADER = "header";
  7015. private static final String SUBPART_FOOTER = "footer";
  7016. private static final String SUBPART_ROW = "row";
  7017. private static final String SUBPART_COL = "col";
  7018. /**
  7019. * Matches header[ix] - used for extracting the index of the targeted header
  7020. * cell
  7021. */
  7022. private static final RegExp SUBPART_HEADER_REGEXP = RegExp
  7023. .compile(SUBPART_HEADER + "\\[(\\d+)\\]");
  7024. /**
  7025. * Matches footer[ix] - used for extracting the index of the targeted footer
  7026. * cell
  7027. */
  7028. private static final RegExp SUBPART_FOOTER_REGEXP = RegExp
  7029. .compile(SUBPART_FOOTER + "\\[(\\d+)\\]");
  7030. /** Matches row[ix] - used for extracting the index of the targeted row */
  7031. private static final RegExp SUBPART_ROW_REGEXP = RegExp.compile(SUBPART_ROW
  7032. + "\\[(\\d+)]");
  7033. /** Matches col[ix] - used for extracting the index of the targeted column */
  7034. private static final RegExp SUBPART_ROW_COL_REGEXP = RegExp
  7035. .compile(SUBPART_ROW + "\\[(\\d+)\\]/" + SUBPART_COL
  7036. + "\\[(\\d+)\\]");
  7037. @Override
  7038. public com.google.gwt.user.client.Element getSubPartElement(String subPart) {
  7039. if (SUBPART_ROW_COL_REGEXP.test(subPart)) {
  7040. MatchResult result = SUBPART_ROW_COL_REGEXP.exec(subPart);
  7041. int rowIx = Integer.valueOf(result.getGroup(1));
  7042. int colIx = Integer.valueOf(result.getGroup(2));
  7043. VScrollTableRow row = scrollBody.getRowByRowIndex(rowIx);
  7044. if (row != null) {
  7045. Element rowElement = row.getElement();
  7046. if (colIx < rowElement.getChildCount()) {
  7047. return rowElement.getChild(colIx).getFirstChild().cast();
  7048. }
  7049. }
  7050. } else if (SUBPART_ROW_REGEXP.test(subPart)) {
  7051. MatchResult result = SUBPART_ROW_REGEXP.exec(subPart);
  7052. int rowIx = Integer.valueOf(result.getGroup(1));
  7053. VScrollTableRow row = scrollBody.getRowByRowIndex(rowIx);
  7054. if (row != null) {
  7055. return row.getElement();
  7056. }
  7057. } else if (SUBPART_HEADER_REGEXP.test(subPart)) {
  7058. MatchResult result = SUBPART_HEADER_REGEXP.exec(subPart);
  7059. int headerIx = Integer.valueOf(result.getGroup(1));
  7060. HeaderCell headerCell = tHead.getHeaderCell(headerIx);
  7061. if (headerCell != null) {
  7062. return headerCell.getElement();
  7063. }
  7064. } else if (SUBPART_FOOTER_REGEXP.test(subPart)) {
  7065. MatchResult result = SUBPART_FOOTER_REGEXP.exec(subPart);
  7066. int footerIx = Integer.valueOf(result.getGroup(1));
  7067. FooterCell footerCell = tFoot.getFooterCell(footerIx);
  7068. if (footerCell != null) {
  7069. return footerCell.getElement();
  7070. }
  7071. }
  7072. // Nothing found.
  7073. return null;
  7074. }
  7075. @Override
  7076. public String getSubPartName(com.google.gwt.user.client.Element subElement) {
  7077. Widget widget = Util.findWidget(subElement, null);
  7078. if (widget instanceof HeaderCell) {
  7079. return SUBPART_HEADER + "[" + tHead.visibleCells.indexOf(widget)
  7080. + "]";
  7081. } else if (widget instanceof FooterCell) {
  7082. return SUBPART_FOOTER + "[" + tFoot.visibleCells.indexOf(widget)
  7083. + "]";
  7084. } else if (widget instanceof VScrollTableRow) {
  7085. // a cell in a row
  7086. VScrollTableRow row = (VScrollTableRow) widget;
  7087. int rowIx = scrollBody.indexOf(row);
  7088. if (rowIx >= 0) {
  7089. int colIx = -1;
  7090. for (int ix = 0; ix < row.getElement().getChildCount(); ix++) {
  7091. if (row.getElement().getChild(ix).isOrHasChild(subElement)) {
  7092. colIx = ix;
  7093. break;
  7094. }
  7095. }
  7096. if (colIx >= 0) {
  7097. return SUBPART_ROW + "[" + rowIx + "]/" + SUBPART_COL + "["
  7098. + colIx + "]";
  7099. }
  7100. return SUBPART_ROW + "[" + rowIx + "]";
  7101. }
  7102. }
  7103. // Nothing found.
  7104. return null;
  7105. }
  7106. /**
  7107. * @since
  7108. */
  7109. public void onUnregister() {
  7110. if (addCloseHandler != null) {
  7111. addCloseHandler.removeHandler();
  7112. }
  7113. }
  7114. public void totalRowsHaveChanged() {
  7115. firstvisibleOnLastPage = -1;
  7116. }
  7117. }